@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/reconcile.js
CHANGED
|
@@ -493,11 +493,12 @@ var require_ignore = __commonJS({
|
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
495
|
import { existsSync as existsSync7 } from "fs";
|
|
496
|
-
import { join as
|
|
496
|
+
import { join as join13 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/attached-derived.ts
|
|
499
499
|
import { rmSync } from "fs";
|
|
500
500
|
import { join } from "path";
|
|
501
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
501
502
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
502
503
|
var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
503
504
|
|
|
@@ -598,6 +599,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
598
599
|
{
|
|
599
600
|
tag: "0022_audit_inspection_ms",
|
|
600
601
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
tag: "0023_secret_vault_user_authorized",
|
|
605
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
609
|
+
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`);"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
613
|
+
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;"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
617
|
+
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;"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
tag: "0027_audit_llm_usage_index",
|
|
621
|
+
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;"
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
tag: "0028_activity_session_probe_indexes",
|
|
625
|
+
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"
|
|
601
626
|
}
|
|
602
627
|
];
|
|
603
628
|
|
|
@@ -22140,6 +22165,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22140
22165
|
AttachTokenExpired,
|
|
22141
22166
|
external_exports.object({ status: printable(64) })
|
|
22142
22167
|
]);
|
|
22168
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22169
|
+
var DeviceCommand = external_exports.object({
|
|
22170
|
+
id: printable(128).min(1),
|
|
22171
|
+
kind: DeviceCommandKind,
|
|
22172
|
+
issuedAt: printable(64).min(1),
|
|
22173
|
+
expiresAt: printable(64).min(1)
|
|
22174
|
+
}).strict();
|
|
22175
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22176
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22177
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22178
|
+
external_exports.object({
|
|
22179
|
+
outcome: external_exports.literal("reported"),
|
|
22180
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22181
|
+
}).strict(),
|
|
22182
|
+
external_exports.object({
|
|
22183
|
+
outcome: external_exports.literal("failed"),
|
|
22184
|
+
reason: DeviceCommandFailureReason,
|
|
22185
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22186
|
+
}).strict()
|
|
22187
|
+
]);
|
|
22143
22188
|
|
|
22144
22189
|
// ../../packages/schema/src/zod/registry.ts
|
|
22145
22190
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22306,7 +22351,7 @@ var PackManifest = external_exports.object({
|
|
|
22306
22351
|
}).meta({ id: "PackManifest" });
|
|
22307
22352
|
|
|
22308
22353
|
// ../../packages/schema/src/zod/detection.ts
|
|
22309
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22354
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22310
22355
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22311
22356
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22312
22357
|
var DetectionCounts = external_exports.object({
|
|
@@ -22443,14 +22488,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22443
22488
|
function isStringArray(value) {
|
|
22444
22489
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22445
22490
|
}
|
|
22491
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22492
|
+
function resolveOrigin(origin) {
|
|
22493
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22494
|
+
}
|
|
22446
22495
|
function summaryToDetectionListItem(s) {
|
|
22447
22496
|
return {
|
|
22448
22497
|
id: `${s.namespace}/${s.packId}`,
|
|
22449
22498
|
name: s.name,
|
|
22450
22499
|
version: s.version,
|
|
22451
22500
|
enabled: s.enabled,
|
|
22452
|
-
origin:
|
|
22453
|
-
// v1: every installed pack is library origin
|
|
22501
|
+
origin: resolveOrigin(s.origin),
|
|
22454
22502
|
namespace: s.namespace,
|
|
22455
22503
|
packId: s.packId,
|
|
22456
22504
|
ruleCount: s.ruleCount,
|
|
@@ -22502,7 +22550,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22502
22550
|
name: row.name,
|
|
22503
22551
|
version: row.version,
|
|
22504
22552
|
enabled: row.enabled,
|
|
22505
|
-
origin:
|
|
22553
|
+
origin: resolveOrigin(row.origin),
|
|
22506
22554
|
namespace: row.namespace,
|
|
22507
22555
|
packId: row.packId,
|
|
22508
22556
|
ruleCount: row.rules.length,
|
|
@@ -22522,16 +22570,20 @@ function splitDetectionId(id) {
|
|
|
22522
22570
|
}
|
|
22523
22571
|
function buildDetectionsList(summaries, query) {
|
|
22524
22572
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22573
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22525
22574
|
const counts = {
|
|
22526
22575
|
all: summaries.length,
|
|
22527
|
-
library: summaries.length,
|
|
22528
|
-
|
|
22529
|
-
|
|
22576
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22577
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22578
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22579
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22580
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22581
|
+
// OriginEnum.
|
|
22530
22582
|
customized: 0,
|
|
22531
22583
|
updates: withUpdate.length
|
|
22532
22584
|
};
|
|
22533
22585
|
const filter = query.filter;
|
|
22534
|
-
let filtered = filter === "custom"
|
|
22586
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22535
22587
|
if (query.q) {
|
|
22536
22588
|
const q = query.q.toLowerCase();
|
|
22537
22589
|
filtered = filtered.filter(
|
|
@@ -22611,8 +22663,9 @@ var Event = external_exports.object({
|
|
|
22611
22663
|
metadata: EventMetadata.optional()
|
|
22612
22664
|
}).meta({ id: "Event" });
|
|
22613
22665
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22666
|
+
var INGEST_BATCH_MAX = 100;
|
|
22614
22667
|
var IngestBatch = external_exports.object({
|
|
22615
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22668
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22616
22669
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22617
22670
|
// additionally rejects any event whose contentHash the store has already
|
|
22618
22671
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23168,382 +23221,11 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23168
23221
|
message: "At least one field must be provided"
|
|
23169
23222
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23170
23223
|
|
|
23171
|
-
// ../../packages/schema/src/zod/vault.ts
|
|
23172
|
-
var POINTER_FORMAT_VERSION = 2;
|
|
23173
|
-
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23174
|
-
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23175
|
-
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23176
|
-
);
|
|
23177
|
-
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23178
|
-
function pointerTokenScanner() {
|
|
23179
|
-
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23180
|
-
}
|
|
23181
|
-
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23182
|
-
var ParsedPointer = external_exports.object({
|
|
23183
|
-
category: DetectionCategory,
|
|
23184
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23185
|
-
pointerId: external_exports.string(),
|
|
23186
|
-
tag: external_exports.string()
|
|
23187
|
-
});
|
|
23188
|
-
var VaultEntry = external_exports.object({
|
|
23189
|
-
pointerId: external_exports.string(),
|
|
23190
|
-
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23191
|
-
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23192
|
-
// independently of the vault encryption key below.
|
|
23193
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23194
|
-
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23195
|
-
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23196
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23197
|
-
// Fixed at first mint and never updated: the same value detected later under a
|
|
23198
|
-
// different rule's category keeps the category it was minted with, so one
|
|
23199
|
-
// value always produces exactly one wire token.
|
|
23200
|
-
category: DetectionCategory,
|
|
23201
|
-
ruleId: external_exports.string(),
|
|
23202
|
-
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23203
|
-
maskedMatch: external_exports.string(),
|
|
23204
|
-
provider: external_exports.string().optional(),
|
|
23205
|
-
ciphertext: external_exports.string(),
|
|
23206
|
-
nonce: external_exports.string(),
|
|
23207
|
-
authTag: external_exports.string(),
|
|
23208
|
-
// How many times this value has been detected on this machine — the reuse
|
|
23209
|
-
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23210
|
-
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23211
|
-
firstSeen: external_exports.string(),
|
|
23212
|
-
lastSeen: external_exports.string()
|
|
23213
|
-
});
|
|
23214
|
-
var PointerDescriptor = external_exports.object({
|
|
23215
|
-
category: DetectionCategory,
|
|
23216
|
-
provider: external_exports.string().optional(),
|
|
23217
|
-
maskedMatch: external_exports.string(),
|
|
23218
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23219
|
-
firstSeen: external_exports.string(),
|
|
23220
|
-
lastSeen: external_exports.string()
|
|
23221
|
-
});
|
|
23222
|
-
var PointerIdentity = external_exports.object({
|
|
23223
|
-
ruleId: external_exports.string(),
|
|
23224
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23225
|
-
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23226
|
-
});
|
|
23227
|
-
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23228
|
-
var VaultDerefReason = external_exports.enum([
|
|
23229
|
-
"display",
|
|
23230
|
-
"explicit-reveal",
|
|
23231
|
-
"view-render",
|
|
23232
|
-
"model-input",
|
|
23233
|
-
"remediation",
|
|
23234
|
-
"purge"
|
|
23235
|
-
]);
|
|
23236
|
-
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23237
|
-
var BATCHED_DEREF_REASONS = ["display", "view-render"];
|
|
23238
|
-
function isBatchedDerefReason(reason) {
|
|
23239
|
-
return BATCHED_DEREF_REASONS.includes(reason);
|
|
23240
|
-
}
|
|
23241
|
-
var VaultDeref = external_exports.object({
|
|
23242
|
-
id: external_exports.guid(),
|
|
23243
|
-
pointerId: external_exports.string(),
|
|
23244
|
-
at: external_exports.string(),
|
|
23245
|
-
target: DetokenizeTarget,
|
|
23246
|
-
reason: VaultDerefReason,
|
|
23247
|
-
outcome: VaultDerefOutcome,
|
|
23248
|
-
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23249
|
-
grantId: external_exports.string().optional(),
|
|
23250
|
-
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23251
|
-
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23252
|
-
pointerCount: external_exports.number().int().positive().default(1)
|
|
23253
|
-
});
|
|
23254
|
-
var VaultSightingKind = external_exports.enum([
|
|
23255
|
-
"prompt",
|
|
23256
|
-
"tool-input",
|
|
23257
|
-
"tool-output",
|
|
23258
|
-
"file",
|
|
23259
|
-
"transcript"
|
|
23260
|
-
]);
|
|
23261
|
-
var VaultSighting = external_exports.object({
|
|
23262
|
-
location: external_exports.string(),
|
|
23263
|
-
kind: VaultSightingKind,
|
|
23264
|
-
firstSeen: external_exports.string(),
|
|
23265
|
-
lastSeen: external_exports.string()
|
|
23266
|
-
});
|
|
23267
|
-
var VaultInventoryEntry = external_exports.object({
|
|
23268
|
-
pointerId: external_exports.string(),
|
|
23269
|
-
category: DetectionCategory,
|
|
23270
|
-
provider: external_exports.string().optional(),
|
|
23271
|
-
maskedMatch: external_exports.string(),
|
|
23272
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23273
|
-
firstSeen: external_exports.string(),
|
|
23274
|
-
lastSeen: external_exports.string(),
|
|
23275
|
-
// The active reveal-to-model grant covering this value, when one exists —
|
|
23276
|
-
// the inventory badges it, the row links to revocation.
|
|
23277
|
-
revealGrantId: external_exports.string().nullable(),
|
|
23278
|
-
sightings: external_exports.array(VaultSighting)
|
|
23279
|
-
});
|
|
23280
|
-
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23281
|
-
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23282
|
-
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23283
|
-
var ListVaultInventoryQuery = external_exports.object({
|
|
23284
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23285
|
-
// Opaque; names the last row of the page just served.
|
|
23286
|
-
cursor: external_exports.string().optional()
|
|
23287
|
-
});
|
|
23288
|
-
var ListVaultInventoryResponse = external_exports.object({
|
|
23289
|
-
// Vaulted values across the whole store, not just this page — cursor-
|
|
23290
|
-
// independent, so paging never changes what the count claims.
|
|
23291
|
-
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23292
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23293
|
-
// `null` once the last page is reached.
|
|
23294
|
-
nextCursor: external_exports.string().nullable()
|
|
23295
|
-
});
|
|
23296
|
-
var ListVaultReuseQuery = external_exports.object({
|
|
23297
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23298
|
-
cursor: external_exports.string().optional()
|
|
23299
|
-
});
|
|
23300
|
-
var ListVaultReuseResponse = external_exports.object({
|
|
23301
|
-
// Reused values across the whole store — the number the section's claim
|
|
23302
|
-
// ("values detected in more than one place") is about.
|
|
23303
|
-
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23304
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23305
|
-
nextCursor: external_exports.string().nullable()
|
|
23306
|
-
});
|
|
23307
|
-
var ListVaultDerefsQuery = external_exports.object({
|
|
23308
|
-
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23309
|
-
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23310
|
-
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23311
|
-
// over a Server Action, which preserves the type, never as a URL param.
|
|
23312
|
-
includeBatched: external_exports.boolean().optional(),
|
|
23313
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23314
|
-
cursor: external_exports.string().optional()
|
|
23315
|
-
});
|
|
23316
|
-
var ListVaultDerefsResponse = external_exports.object({
|
|
23317
|
-
items: external_exports.array(VaultDeref),
|
|
23318
|
-
nextCursor: external_exports.string().nullable(),
|
|
23319
|
-
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23320
|
-
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23321
|
-
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23322
|
-
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23323
|
-
});
|
|
23324
|
-
var VaultKeyCustody = external_exports.string();
|
|
23325
|
-
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23326
|
-
var VAULT_CONSENT_VERSION = 1;
|
|
23327
|
-
var VaultConsent = external_exports.object({
|
|
23328
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23329
|
-
version: external_exports.number().int().positive()
|
|
23330
|
-
});
|
|
23331
|
-
function isVaultConsentValid(consent) {
|
|
23332
|
-
return consent?.version === VAULT_CONSENT_VERSION;
|
|
23333
|
-
}
|
|
23334
|
-
|
|
23335
|
-
// ../../packages/schema/src/zod/local.ts
|
|
23336
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
|
|
23337
|
-
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23338
|
-
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23339
|
-
var ControlPlaneConnection = external_exports.object({
|
|
23340
|
-
endpoint: external_exports.string().min(1),
|
|
23341
|
-
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23342
|
-
label: external_exports.string().min(1).optional(),
|
|
23343
|
-
attachedAt: external_exports.iso.datetime()
|
|
23344
|
-
}).meta({ id: "ControlPlaneConnection" });
|
|
23345
|
-
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23346
|
-
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23347
|
-
var ModelJudgeConsent = external_exports.object({
|
|
23348
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23349
|
-
payloadVersion: external_exports.number().int().positive()
|
|
23350
|
-
});
|
|
23351
|
-
var HistorySyncConsent = external_exports.object({
|
|
23352
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23353
|
-
payloadVersion: external_exports.number().int().positive(),
|
|
23354
|
-
endpoint: external_exports.string()
|
|
23355
|
-
});
|
|
23356
|
-
var WorkspaceSettings = external_exports.object({
|
|
23357
|
-
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23358
|
-
runMode: RunMode.default("standalone"),
|
|
23359
|
-
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23360
|
-
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23361
|
-
controlPlane: ControlPlaneConnection.optional(),
|
|
23362
|
-
policy: SimpleDetectionPolicy.default("redact"),
|
|
23363
|
-
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23364
|
-
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23365
|
-
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23366
|
-
// Shares writes.
|
|
23367
|
-
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23368
|
-
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23369
|
-
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23370
|
-
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23371
|
-
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23372
|
-
// purging the vault is the eraser.
|
|
23373
|
-
vaultConsent: VaultConsent.optional(),
|
|
23374
|
-
// Where the vault master key lives.
|
|
23375
|
-
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23376
|
-
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23377
|
-
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23378
|
-
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23379
|
-
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23380
|
-
// Records that the user consented to sending findings to the model API for
|
|
23381
|
-
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23382
|
-
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23383
|
-
// covers the current payload and must be re-granted.
|
|
23384
|
-
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23385
|
-
// Records that the user consented to sending the activity already recorded on
|
|
23386
|
-
// this machine to the deployment it is attached to, along with the payload
|
|
23387
|
-
// shape and the endpoint they agreed to. Absent until granted, and a grant for
|
|
23388
|
-
// a different endpoint or an older payload no longer counts.
|
|
23389
|
-
historySyncConsent: HistorySyncConsent.optional()
|
|
23390
|
-
});
|
|
23391
|
-
function defaultWorkspaceSettings() {
|
|
23392
|
-
return WorkspaceSettings.parse({});
|
|
23393
|
-
}
|
|
23394
|
-
function isAttached(settings) {
|
|
23395
|
-
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23396
|
-
}
|
|
23397
|
-
function toInventoryRow(input2, id, now) {
|
|
23398
|
-
return {
|
|
23399
|
-
id,
|
|
23400
|
-
objectType: input2.objectType,
|
|
23401
|
-
location: input2.location ?? null,
|
|
23402
|
-
title: input2.title ?? null,
|
|
23403
|
-
hostId: input2.hostId ?? null,
|
|
23404
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23405
|
-
firstSeen: now,
|
|
23406
|
-
lastSeen: now
|
|
23407
|
-
};
|
|
23408
|
-
}
|
|
23409
|
-
function toSourceProjectRow(input2, id, now) {
|
|
23410
|
-
return {
|
|
23411
|
-
id,
|
|
23412
|
-
url: input2.url,
|
|
23413
|
-
name: input2.name ?? null,
|
|
23414
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23415
|
-
firstSeen: now,
|
|
23416
|
-
lastSeen: now
|
|
23417
|
-
};
|
|
23418
|
-
}
|
|
23419
|
-
function toAuditEventRow(input2) {
|
|
23420
|
-
return {
|
|
23421
|
-
id: input2.id,
|
|
23422
|
-
parentId: input2.parentId ?? null,
|
|
23423
|
-
rootSessionId: input2.rootSessionId ?? null,
|
|
23424
|
-
eventType: input2.eventType,
|
|
23425
|
-
hostId: input2.hostId ?? null,
|
|
23426
|
-
harnessId: input2.harnessId ?? null,
|
|
23427
|
-
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23428
|
-
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23429
|
-
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23430
|
-
severity: input2.severity ?? null,
|
|
23431
|
-
priority: input2.priority ?? null,
|
|
23432
|
-
content: input2.content ?? null,
|
|
23433
|
-
contentHash: input2.contentHash ?? null,
|
|
23434
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23435
|
-
};
|
|
23436
|
-
}
|
|
23437
|
-
function toClassifiedDataRow(input2, id) {
|
|
23438
|
-
return {
|
|
23439
|
-
id,
|
|
23440
|
-
class: input2.class,
|
|
23441
|
-
label: input2.label ?? null,
|
|
23442
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23443
|
-
};
|
|
23444
|
-
}
|
|
23445
|
-
function toInspectionDefinitionRow(input2, id) {
|
|
23446
|
-
return {
|
|
23447
|
-
id,
|
|
23448
|
-
ruleId: input2.ruleId,
|
|
23449
|
-
name: input2.name,
|
|
23450
|
-
category: input2.category,
|
|
23451
|
-
severity: input2.severity,
|
|
23452
|
-
definition: input2.definition,
|
|
23453
|
-
version: input2.version
|
|
23454
|
-
};
|
|
23455
|
-
}
|
|
23456
|
-
function toInspectionFindingRow(input2) {
|
|
23457
|
-
return {
|
|
23458
|
-
id: input2.id,
|
|
23459
|
-
auditEventId: input2.auditEventId,
|
|
23460
|
-
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23461
|
-
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23462
|
-
spanStart: input2.span.start,
|
|
23463
|
-
spanEnd: input2.span.end,
|
|
23464
|
-
maskedMatch: input2.maskedMatch,
|
|
23465
|
-
actionTaken: input2.actionTaken,
|
|
23466
|
-
confidence: input2.confidence,
|
|
23467
|
-
findingKey: input2.findingKey ?? null,
|
|
23468
|
-
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23469
|
-
};
|
|
23470
|
-
}
|
|
23471
|
-
function toCaptureAttributes(event) {
|
|
23472
|
-
const metadata = event.metadata;
|
|
23473
|
-
return {
|
|
23474
|
-
source_tool: event.sourceTool,
|
|
23475
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23476
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23477
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23478
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23479
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23480
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23481
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23482
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23483
|
-
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23484
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23485
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
23486
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23487
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
23488
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23489
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23490
|
-
};
|
|
23491
|
-
}
|
|
23492
|
-
function captureDefinitionVersion(finding) {
|
|
23493
|
-
return `capture/${finding.category}/${finding.severity}`;
|
|
23494
|
-
}
|
|
23495
|
-
function toCaptureDefinitionInput(finding) {
|
|
23496
|
-
return {
|
|
23497
|
-
ruleId: finding.ruleId,
|
|
23498
|
-
version: captureDefinitionVersion(finding),
|
|
23499
|
-
name: finding.ruleId,
|
|
23500
|
-
category: finding.category,
|
|
23501
|
-
severity: finding.severity,
|
|
23502
|
-
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
23503
|
-
};
|
|
23504
|
-
}
|
|
23505
|
-
|
|
23506
|
-
// ../../packages/schema/src/zod/managed.ts
|
|
23507
|
-
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23508
|
-
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23509
|
-
var ManagedSettingKey = external_exports.enum([
|
|
23510
|
-
"runMode",
|
|
23511
|
-
"historicalAccess",
|
|
23512
|
-
"vaultConsent",
|
|
23513
|
-
"vaultKeyCustody",
|
|
23514
|
-
"vaultInlineReveal",
|
|
23515
|
-
"modelJudgeConsent",
|
|
23516
|
-
"dataSharesInPlace"
|
|
23517
|
-
]).meta({ id: "ManagedSettingKey" });
|
|
23518
|
-
var ManagedSettingsValues = external_exports.object({
|
|
23519
|
-
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23520
|
-
controlPlane: external_exports.object({
|
|
23521
|
-
endpoint: external_exports.string().min(1),
|
|
23522
|
-
label: external_exports.string().min(1).optional()
|
|
23523
|
-
}).optional(),
|
|
23524
|
-
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23525
|
-
vaultConsent: external_exports.boolean().optional(),
|
|
23526
|
-
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23527
|
-
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23528
|
-
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23529
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23530
|
-
}).meta({ id: "ManagedSettingsValues" });
|
|
23531
|
-
var ManagedSettings = external_exports.object({
|
|
23532
|
-
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23533
|
-
// Shown on every locked control, so the user can tell an administrative
|
|
23534
|
-
// decision from a bug. Absent renders as a generic "your organization".
|
|
23535
|
-
organization: external_exports.string().min(1).optional(),
|
|
23536
|
-
// What the administrator pinned.
|
|
23537
|
-
values: ManagedSettingsValues.default({}),
|
|
23538
|
-
// Which of those the user may not change. A key here with no matching value
|
|
23539
|
-
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23540
|
-
// the user may still override. The two are separable on purpose.
|
|
23541
|
-
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23542
|
-
}).meta({ id: "ManagedSettings" });
|
|
23543
|
-
|
|
23544
23224
|
// ../../packages/schema/src/zod/policy.ts
|
|
23545
23225
|
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23546
23226
|
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23227
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23228
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23547
23229
|
var Policy = external_exports.object({
|
|
23548
23230
|
id: external_exports.guid(),
|
|
23549
23231
|
scope: PolicyScope,
|
|
@@ -23553,7 +23235,27 @@ var Policy = external_exports.object({
|
|
|
23553
23235
|
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23554
23236
|
// Display name — optional so older policy rows without name still parse.
|
|
23555
23237
|
// Added for the findings API (policy.name column migration).
|
|
23556
|
-
name: external_exports.string().optional()
|
|
23238
|
+
name: external_exports.string().optional(),
|
|
23239
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23240
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23241
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23242
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23243
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23244
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23245
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23246
|
+
//
|
|
23247
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23248
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23249
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23250
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23251
|
+
// — the CRUD routes neither accept nor set it.
|
|
23252
|
+
//
|
|
23253
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23254
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23255
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23256
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23257
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23258
|
+
provenance: PolicyProvenance.optional()
|
|
23557
23259
|
}).meta({ id: "Policy" });
|
|
23558
23260
|
var PolicyBundle = external_exports.object({
|
|
23559
23261
|
version: external_exports.string(),
|
|
@@ -23605,6 +23307,12 @@ var PolicyBundle = external_exports.object({
|
|
|
23605
23307
|
customKeywords: external_exports.array(external_exports.string()),
|
|
23606
23308
|
fetchedAt: external_exports.iso.datetime()
|
|
23607
23309
|
}).meta({ id: "PolicyBundle" });
|
|
23310
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23311
|
+
...Object.keys(PolicyBundle.shape),
|
|
23312
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23313
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23314
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23315
|
+
].sort().join(",");
|
|
23608
23316
|
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23609
23317
|
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23610
23318
|
var CATEGORY_PEAK_SEVERITY = {
|
|
@@ -23625,9 +23333,11 @@ function severityFloorPolicy(category) {
|
|
|
23625
23333
|
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23626
23334
|
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23627
23335
|
}
|
|
23628
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23629
23336
|
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23630
23337
|
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23338
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23339
|
+
id: "RedactFallback"
|
|
23340
|
+
});
|
|
23631
23341
|
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23632
23342
|
var BUILTIN_POLICY_SPECS = {
|
|
23633
23343
|
monitor: {
|
|
@@ -23664,6 +23374,42 @@ var BUILTIN_POLICY_SPECS = {
|
|
|
23664
23374
|
function builtinPolicyToAction(id) {
|
|
23665
23375
|
return BUILTIN_POLICY_SPECS[id].action;
|
|
23666
23376
|
}
|
|
23377
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23378
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23379
|
+
];
|
|
23380
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23381
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23382
|
+
);
|
|
23383
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23384
|
+
...BELOW_PALETTE,
|
|
23385
|
+
...PALETTE_WEAKEST_FIRST
|
|
23386
|
+
];
|
|
23387
|
+
function actionRank(action) {
|
|
23388
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23389
|
+
}
|
|
23390
|
+
function isActionAtLeast(action, floor) {
|
|
23391
|
+
return actionRank(action) >= actionRank(floor);
|
|
23392
|
+
}
|
|
23393
|
+
function strongerAction(a, b) {
|
|
23394
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23395
|
+
}
|
|
23396
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23397
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23398
|
+
}
|
|
23399
|
+
var PackPolicyFloor = external_exports.object({
|
|
23400
|
+
/**
|
|
23401
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23402
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23403
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23404
|
+
*/
|
|
23405
|
+
floor: BuiltinPolicyId,
|
|
23406
|
+
/**
|
|
23407
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23408
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23409
|
+
* re-assignable locally in either direction.
|
|
23410
|
+
*/
|
|
23411
|
+
locked: external_exports.boolean()
|
|
23412
|
+
}).describe("PackPolicyFloor");
|
|
23667
23413
|
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23668
23414
|
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23669
23415
|
);
|
|
@@ -23721,6 +23467,404 @@ var PolicyStatsResponse = external_exports.object({
|
|
|
23721
23467
|
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23722
23468
|
}).meta({ id: "PolicyStatsResponse" });
|
|
23723
23469
|
|
|
23470
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
23471
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
23472
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23473
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23474
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23475
|
+
);
|
|
23476
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23477
|
+
function pointerTokenScanner() {
|
|
23478
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23479
|
+
}
|
|
23480
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23481
|
+
var ParsedPointer = external_exports.object({
|
|
23482
|
+
category: DetectionCategory,
|
|
23483
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23484
|
+
pointerId: external_exports.string(),
|
|
23485
|
+
tag: external_exports.string()
|
|
23486
|
+
});
|
|
23487
|
+
var VaultEntry = external_exports.object({
|
|
23488
|
+
pointerId: external_exports.string(),
|
|
23489
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23490
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23491
|
+
// independently of the vault encryption key below.
|
|
23492
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23493
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23494
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23495
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23496
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
23497
|
+
// different rule's category keeps the category it was minted with, so one
|
|
23498
|
+
// value always produces exactly one wire token.
|
|
23499
|
+
category: DetectionCategory,
|
|
23500
|
+
ruleId: external_exports.string(),
|
|
23501
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23502
|
+
maskedMatch: external_exports.string(),
|
|
23503
|
+
provider: external_exports.string().optional(),
|
|
23504
|
+
ciphertext: external_exports.string(),
|
|
23505
|
+
nonce: external_exports.string(),
|
|
23506
|
+
authTag: external_exports.string(),
|
|
23507
|
+
// How many times this value has been detected on this machine — the reuse
|
|
23508
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23509
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23510
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23511
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23512
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23513
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23514
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23515
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23516
|
+
// the value does not expire.
|
|
23517
|
+
userAuthorized: external_exports.boolean(),
|
|
23518
|
+
firstSeen: external_exports.string(),
|
|
23519
|
+
lastSeen: external_exports.string()
|
|
23520
|
+
});
|
|
23521
|
+
var PointerDescriptor = external_exports.object({
|
|
23522
|
+
category: DetectionCategory,
|
|
23523
|
+
provider: external_exports.string().optional(),
|
|
23524
|
+
maskedMatch: external_exports.string(),
|
|
23525
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23526
|
+
firstSeen: external_exports.string(),
|
|
23527
|
+
lastSeen: external_exports.string()
|
|
23528
|
+
});
|
|
23529
|
+
var PointerIdentity = external_exports.object({
|
|
23530
|
+
ruleId: external_exports.string(),
|
|
23531
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23532
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23533
|
+
});
|
|
23534
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23535
|
+
var VaultDerefReason = external_exports.enum([
|
|
23536
|
+
"display",
|
|
23537
|
+
"explicit-reveal",
|
|
23538
|
+
"view-render",
|
|
23539
|
+
"model-input",
|
|
23540
|
+
"remediation",
|
|
23541
|
+
"purge"
|
|
23542
|
+
]);
|
|
23543
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23544
|
+
var BATCHED_DEREF_REASONS = ["display", "view-render"];
|
|
23545
|
+
function isBatchedDerefReason(reason) {
|
|
23546
|
+
return BATCHED_DEREF_REASONS.includes(reason);
|
|
23547
|
+
}
|
|
23548
|
+
var VaultDeref = external_exports.object({
|
|
23549
|
+
id: external_exports.guid(),
|
|
23550
|
+
pointerId: external_exports.string(),
|
|
23551
|
+
at: external_exports.string(),
|
|
23552
|
+
target: DetokenizeTarget,
|
|
23553
|
+
reason: VaultDerefReason,
|
|
23554
|
+
outcome: VaultDerefOutcome,
|
|
23555
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23556
|
+
grantId: external_exports.string().optional(),
|
|
23557
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23558
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23559
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
23560
|
+
});
|
|
23561
|
+
var VaultSightingKind = external_exports.enum([
|
|
23562
|
+
"prompt",
|
|
23563
|
+
"tool-input",
|
|
23564
|
+
"tool-output",
|
|
23565
|
+
"file",
|
|
23566
|
+
"transcript"
|
|
23567
|
+
]);
|
|
23568
|
+
var VaultSighting = external_exports.object({
|
|
23569
|
+
location: external_exports.string(),
|
|
23570
|
+
kind: VaultSightingKind,
|
|
23571
|
+
firstSeen: external_exports.string(),
|
|
23572
|
+
lastSeen: external_exports.string()
|
|
23573
|
+
});
|
|
23574
|
+
var VaultInventoryEntry = external_exports.object({
|
|
23575
|
+
pointerId: external_exports.string(),
|
|
23576
|
+
category: DetectionCategory,
|
|
23577
|
+
provider: external_exports.string().optional(),
|
|
23578
|
+
maskedMatch: external_exports.string(),
|
|
23579
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23580
|
+
firstSeen: external_exports.string(),
|
|
23581
|
+
lastSeen: external_exports.string(),
|
|
23582
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
23583
|
+
// the inventory badges it, the row links to revocation.
|
|
23584
|
+
revealGrantId: external_exports.string().nullable(),
|
|
23585
|
+
sightings: external_exports.array(VaultSighting)
|
|
23586
|
+
});
|
|
23587
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23588
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23589
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23590
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
23591
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23592
|
+
// Opaque; names the last row of the page just served.
|
|
23593
|
+
cursor: external_exports.string().optional()
|
|
23594
|
+
});
|
|
23595
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
23596
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
23597
|
+
// independent, so paging never changes what the count claims.
|
|
23598
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23599
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23600
|
+
// `null` once the last page is reached.
|
|
23601
|
+
nextCursor: external_exports.string().nullable()
|
|
23602
|
+
});
|
|
23603
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
23604
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23605
|
+
cursor: external_exports.string().optional()
|
|
23606
|
+
});
|
|
23607
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
23608
|
+
// Reused values across the whole store — the number the section's claim
|
|
23609
|
+
// ("values detected in more than one place") is about.
|
|
23610
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23611
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23612
|
+
nextCursor: external_exports.string().nullable()
|
|
23613
|
+
});
|
|
23614
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
23615
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23616
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23617
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23618
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
23619
|
+
includeBatched: external_exports.boolean().optional(),
|
|
23620
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23621
|
+
cursor: external_exports.string().optional()
|
|
23622
|
+
});
|
|
23623
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
23624
|
+
items: external_exports.array(VaultDeref),
|
|
23625
|
+
nextCursor: external_exports.string().nullable(),
|
|
23626
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23627
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23628
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23629
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23630
|
+
});
|
|
23631
|
+
var VaultKeyCustody = external_exports.string();
|
|
23632
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23633
|
+
var VAULT_CONSENT_VERSION = 1;
|
|
23634
|
+
var VaultConsent = external_exports.object({
|
|
23635
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23636
|
+
version: external_exports.number().int().positive()
|
|
23637
|
+
});
|
|
23638
|
+
function isVaultConsentValid(consent) {
|
|
23639
|
+
return consent?.version === VAULT_CONSENT_VERSION;
|
|
23640
|
+
}
|
|
23641
|
+
|
|
23642
|
+
// ../../packages/schema/src/zod/local.ts
|
|
23643
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23644
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23645
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23646
|
+
var ControlPlaneConnection = external_exports.object({
|
|
23647
|
+
endpoint: external_exports.string().min(1),
|
|
23648
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23649
|
+
label: external_exports.string().min(1).optional(),
|
|
23650
|
+
attachedAt: external_exports.iso.datetime()
|
|
23651
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
23652
|
+
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23653
|
+
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23654
|
+
var ModelJudgeConsent = external_exports.object({
|
|
23655
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23656
|
+
payloadVersion: external_exports.number().int().positive()
|
|
23657
|
+
});
|
|
23658
|
+
var HistorySyncConsent = external_exports.object({
|
|
23659
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23660
|
+
payloadVersion: external_exports.number().int().positive(),
|
|
23661
|
+
endpoint: external_exports.string()
|
|
23662
|
+
});
|
|
23663
|
+
var WorkspaceSettings = external_exports.object({
|
|
23664
|
+
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23665
|
+
runMode: RunMode.default("standalone"),
|
|
23666
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23667
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23668
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
23669
|
+
policy: SimpleDetectionPolicy.default("redact"),
|
|
23670
|
+
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23671
|
+
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23672
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23673
|
+
// Shares writes.
|
|
23674
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23675
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23676
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23677
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23678
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23679
|
+
// purging the vault is the eraser.
|
|
23680
|
+
vaultConsent: VaultConsent.optional(),
|
|
23681
|
+
// Where the vault master key lives.
|
|
23682
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23683
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23684
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23685
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23686
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23687
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23688
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23689
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23690
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23691
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23692
|
+
//
|
|
23693
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23694
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23695
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23696
|
+
// word and stays out of the stored value.
|
|
23697
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23698
|
+
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23699
|
+
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23700
|
+
// Records that the user consented to sending findings to the model API for
|
|
23701
|
+
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23702
|
+
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23703
|
+
// covers the current payload and must be re-granted.
|
|
23704
|
+
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23705
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23706
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23707
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23708
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23709
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23710
|
+
// payload no longer counts.
|
|
23711
|
+
historySyncConsent: HistorySyncConsent.optional()
|
|
23712
|
+
});
|
|
23713
|
+
function defaultWorkspaceSettings() {
|
|
23714
|
+
return WorkspaceSettings.parse({});
|
|
23715
|
+
}
|
|
23716
|
+
function isAttached(settings) {
|
|
23717
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23718
|
+
}
|
|
23719
|
+
function toInventoryRow(input2, id, now) {
|
|
23720
|
+
return {
|
|
23721
|
+
id,
|
|
23722
|
+
objectType: input2.objectType,
|
|
23723
|
+
location: input2.location ?? null,
|
|
23724
|
+
title: input2.title ?? null,
|
|
23725
|
+
hostId: input2.hostId ?? null,
|
|
23726
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23727
|
+
firstSeen: now,
|
|
23728
|
+
lastSeen: now
|
|
23729
|
+
};
|
|
23730
|
+
}
|
|
23731
|
+
function toSourceProjectRow(input2, id, now) {
|
|
23732
|
+
return {
|
|
23733
|
+
id,
|
|
23734
|
+
url: input2.url,
|
|
23735
|
+
name: input2.name ?? null,
|
|
23736
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23737
|
+
firstSeen: now,
|
|
23738
|
+
lastSeen: now
|
|
23739
|
+
};
|
|
23740
|
+
}
|
|
23741
|
+
function toAuditEventRow(input2) {
|
|
23742
|
+
return {
|
|
23743
|
+
id: input2.id,
|
|
23744
|
+
parentId: input2.parentId ?? null,
|
|
23745
|
+
rootSessionId: input2.rootSessionId ?? null,
|
|
23746
|
+
eventType: input2.eventType,
|
|
23747
|
+
hostId: input2.hostId ?? null,
|
|
23748
|
+
harnessId: input2.harnessId ?? null,
|
|
23749
|
+
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23750
|
+
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23751
|
+
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23752
|
+
severity: input2.severity ?? null,
|
|
23753
|
+
priority: input2.priority ?? null,
|
|
23754
|
+
content: input2.content ?? null,
|
|
23755
|
+
contentHash: input2.contentHash ?? null,
|
|
23756
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23757
|
+
};
|
|
23758
|
+
}
|
|
23759
|
+
function toClassifiedDataRow(input2, id) {
|
|
23760
|
+
return {
|
|
23761
|
+
id,
|
|
23762
|
+
class: input2.class,
|
|
23763
|
+
label: input2.label ?? null,
|
|
23764
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23765
|
+
};
|
|
23766
|
+
}
|
|
23767
|
+
function toInspectionDefinitionRow(input2, id) {
|
|
23768
|
+
return {
|
|
23769
|
+
id,
|
|
23770
|
+
ruleId: input2.ruleId,
|
|
23771
|
+
name: input2.name,
|
|
23772
|
+
category: input2.category,
|
|
23773
|
+
severity: input2.severity,
|
|
23774
|
+
definition: input2.definition,
|
|
23775
|
+
version: input2.version
|
|
23776
|
+
};
|
|
23777
|
+
}
|
|
23778
|
+
function toInspectionFindingRow(input2) {
|
|
23779
|
+
return {
|
|
23780
|
+
id: input2.id,
|
|
23781
|
+
auditEventId: input2.auditEventId,
|
|
23782
|
+
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23783
|
+
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23784
|
+
spanStart: input2.span.start,
|
|
23785
|
+
spanEnd: input2.span.end,
|
|
23786
|
+
maskedMatch: input2.maskedMatch,
|
|
23787
|
+
actionTaken: input2.actionTaken,
|
|
23788
|
+
confidence: input2.confidence,
|
|
23789
|
+
findingKey: input2.findingKey ?? null,
|
|
23790
|
+
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23791
|
+
};
|
|
23792
|
+
}
|
|
23793
|
+
function toCaptureAttributes(event) {
|
|
23794
|
+
const metadata = event.metadata;
|
|
23795
|
+
return {
|
|
23796
|
+
source_tool: event.sourceTool,
|
|
23797
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23798
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23799
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23800
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23801
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23802
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23803
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23804
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23805
|
+
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23806
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23807
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
23808
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23809
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
23810
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23811
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23812
|
+
};
|
|
23813
|
+
}
|
|
23814
|
+
function captureDefinitionVersion(finding) {
|
|
23815
|
+
return `capture/${finding.category}/${finding.severity}`;
|
|
23816
|
+
}
|
|
23817
|
+
function toCaptureDefinitionInput(finding) {
|
|
23818
|
+
return {
|
|
23819
|
+
ruleId: finding.ruleId,
|
|
23820
|
+
version: captureDefinitionVersion(finding),
|
|
23821
|
+
name: finding.ruleId,
|
|
23822
|
+
category: finding.category,
|
|
23823
|
+
severity: finding.severity,
|
|
23824
|
+
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
23825
|
+
};
|
|
23826
|
+
}
|
|
23827
|
+
|
|
23828
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
23829
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23830
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23831
|
+
var ManagedSettingKey = external_exports.enum([
|
|
23832
|
+
"runMode",
|
|
23833
|
+
"historicalAccess",
|
|
23834
|
+
"vaultConsent",
|
|
23835
|
+
"vaultKeyCustody",
|
|
23836
|
+
"vaultInlineReveal",
|
|
23837
|
+
"modelJudgeConsent",
|
|
23838
|
+
"dataSharesInPlace",
|
|
23839
|
+
"redactFallback"
|
|
23840
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
23841
|
+
var ManagedSettingsValues = external_exports.object({
|
|
23842
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23843
|
+
controlPlane: external_exports.object({
|
|
23844
|
+
endpoint: external_exports.string().min(1),
|
|
23845
|
+
label: external_exports.string().min(1).optional()
|
|
23846
|
+
}).optional(),
|
|
23847
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23848
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
23849
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23850
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23851
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23852
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23853
|
+
redactFallback: RedactFallback.optional()
|
|
23854
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
23855
|
+
var ManagedSettings = external_exports.object({
|
|
23856
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23857
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
23858
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
23859
|
+
organization: external_exports.string().min(1).optional(),
|
|
23860
|
+
// What the administrator pinned.
|
|
23861
|
+
values: ManagedSettingsValues.default({}),
|
|
23862
|
+
// Which of those the user may not change. A key here with no matching value
|
|
23863
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23864
|
+
// the user may still override. The two are separable on purpose.
|
|
23865
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23866
|
+
}).meta({ id: "ManagedSettings" });
|
|
23867
|
+
|
|
23724
23868
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23725
23869
|
var ProjectFileInput = external_exports.object({
|
|
23726
23870
|
path: external_exports.string().min(1),
|
|
@@ -23966,10 +24110,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
23966
24110
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
23967
24111
|
|
|
23968
24112
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24113
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24114
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
23969
24115
|
var SaveSettingsInput = external_exports.object({
|
|
23970
24116
|
historicalAccess: external_exports.string(),
|
|
23971
|
-
modelJudgeConsent:
|
|
23972
|
-
historySyncConsent:
|
|
24117
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24118
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
23973
24119
|
vaultConsent: external_exports.string(),
|
|
23974
24120
|
vaultInlineReveal: external_exports.string()
|
|
23975
24121
|
});
|
|
@@ -24119,9 +24265,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24119
24265
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24120
24266
|
return reasons;
|
|
24121
24267
|
}
|
|
24122
|
-
function buildReviewInfo(trust, transports) {
|
|
24268
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24123
24269
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24124
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24270
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24125
24271
|
}
|
|
24126
24272
|
function distinctTransports(transports) {
|
|
24127
24273
|
return Array.from(new Set(transports));
|
|
@@ -24339,8 +24485,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
|
|
|
24339
24485
|
}
|
|
24340
24486
|
|
|
24341
24487
|
// ../../packages/persistence/src/database.ts
|
|
24342
|
-
import { randomUUID as
|
|
24343
|
-
import { join as
|
|
24488
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24489
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24344
24490
|
import { DatabaseSync } from "node:sqlite";
|
|
24345
24491
|
|
|
24346
24492
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24595,6 +24741,10 @@ function allRows(stmt, params) {
|
|
|
24595
24741
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24596
24742
|
return stmt.all(params);
|
|
24597
24743
|
}
|
|
24744
|
+
function* iterateRows(stmt, params) {
|
|
24745
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24746
|
+
for (const row of rows) yield row;
|
|
24747
|
+
}
|
|
24598
24748
|
function getRow(stmt, params) {
|
|
24599
24749
|
if (params === void 0) return stmt.get();
|
|
24600
24750
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -25063,10 +25213,17 @@ function ensureSyncedAtColumn(db, table) {
|
|
|
25063
25213
|
if (!columns.includes("sync_claimed_at")) {
|
|
25064
25214
|
db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
|
|
25065
25215
|
}
|
|
25216
|
+
if (!columns.includes("outbox_owed")) {
|
|
25217
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
|
|
25218
|
+
}
|
|
25066
25219
|
db.exec(
|
|
25067
25220
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
25068
25221
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
25069
25222
|
);
|
|
25223
|
+
db.exec(
|
|
25224
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25225
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25226
|
+
);
|
|
25070
25227
|
db.exec(
|
|
25071
25228
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
25072
25229
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25171,7 +25328,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25171
25328
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25172
25329
|
var DAY_MS = 864e5;
|
|
25173
25330
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25174
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25175
25331
|
function defaultTimeZone() {
|
|
25176
25332
|
try {
|
|
25177
25333
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25226,6 +25382,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25226
25382
|
error: "error",
|
|
25227
25383
|
active: "active"
|
|
25228
25384
|
};
|
|
25385
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25229
25386
|
function safeParseStringArray(raw) {
|
|
25230
25387
|
if (!raw) return [];
|
|
25231
25388
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25299,6 +25456,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25299
25456
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25300
25457
|
json_extract(attributes, '$.internal') AS internal,
|
|
25301
25458
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25459
|
+
var LLM_USAGE_SELECT = `
|
|
25460
|
+
SELECT root_session_id AS sessionId,
|
|
25461
|
+
provider,
|
|
25462
|
+
model,
|
|
25463
|
+
service_tier AS serviceTier,
|
|
25464
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25465
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25466
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25467
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25468
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25469
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25470
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25471
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25472
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25473
|
+
function usageLeaves(rows) {
|
|
25474
|
+
return rows.map((row) => {
|
|
25475
|
+
const attributes = {
|
|
25476
|
+
input_tokens: row.inputTokens,
|
|
25477
|
+
output_tokens: row.outputTokens,
|
|
25478
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25479
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25480
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25481
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25482
|
+
web_search_requests: row.webSearchRequests
|
|
25483
|
+
};
|
|
25484
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25485
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25486
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25487
|
+
return { sessionId: row.sessionId, attributes };
|
|
25488
|
+
});
|
|
25489
|
+
}
|
|
25302
25490
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25303
25491
|
var HAS_ACTIVITY = `EXISTS (
|
|
25304
25492
|
SELECT 1 FROM audit_events c
|
|
@@ -25324,16 +25512,17 @@ var SqliteActivityRepository = class {
|
|
|
25324
25512
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25325
25513
|
const liveNow = countScalar(
|
|
25326
25514
|
this.db,
|
|
25327
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25515
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25328
25516
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25329
|
-
AND
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
25517
|
+
AND s.id IN (
|
|
25518
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25519
|
+
UNION
|
|
25520
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25521
|
+
WHERE started_at >= ?
|
|
25522
|
+
UNION
|
|
25523
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25524
|
+
WHERE ended_at >= ?)`,
|
|
25525
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25337
25526
|
);
|
|
25338
25527
|
const toolCallsToday = countScalar(
|
|
25339
25528
|
this.db,
|
|
@@ -25463,7 +25652,7 @@ var SqliteActivityRepository = class {
|
|
|
25463
25652
|
this.db.prepare(
|
|
25464
25653
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25465
25654
|
FROM audit_events
|
|
25466
|
-
WHERE id = ? OR root_session_id = ?
|
|
25655
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25467
25656
|
ORDER BY started_at ASC, id ASC`
|
|
25468
25657
|
),
|
|
25469
25658
|
[sessionId, sessionId]
|
|
@@ -25476,14 +25665,14 @@ var SqliteActivityRepository = class {
|
|
|
25476
25665
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25477
25666
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25478
25667
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25479
|
-
FROM audit_events
|
|
25668
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25480
25669
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25481
25670
|
),
|
|
25482
25671
|
[sessionId]
|
|
25483
25672
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25484
25673
|
const primaryModel = getRow(
|
|
25485
25674
|
this.db.prepare(
|
|
25486
|
-
`SELECT model, provider FROM audit_events
|
|
25675
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25487
25676
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25488
25677
|
ORDER BY started_at ASC, id ASC
|
|
25489
25678
|
LIMIT 1`
|
|
@@ -25494,7 +25683,7 @@ var SqliteActivityRepository = class {
|
|
|
25494
25683
|
this.db.prepare(
|
|
25495
25684
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25496
25685
|
count(*) AS n
|
|
25497
|
-
FROM audit_events
|
|
25686
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25498
25687
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25499
25688
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25500
25689
|
),
|
|
@@ -25502,7 +25691,7 @@ var SqliteActivityRepository = class {
|
|
|
25502
25691
|
);
|
|
25503
25692
|
const modelRows = allRows(
|
|
25504
25693
|
this.db.prepare(
|
|
25505
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25694
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25506
25695
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25507
25696
|
ORDER BY model`
|
|
25508
25697
|
),
|
|
@@ -25511,7 +25700,7 @@ var SqliteActivityRepository = class {
|
|
|
25511
25700
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25512
25701
|
const commits = countScalar(
|
|
25513
25702
|
this.db,
|
|
25514
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25703
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25515
25704
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25516
25705
|
[sessionId]
|
|
25517
25706
|
);
|
|
@@ -25547,25 +25736,57 @@ var SqliteActivityRepository = class {
|
|
|
25547
25736
|
return Promise.resolve(session);
|
|
25548
25737
|
}
|
|
25549
25738
|
/**
|
|
25550
|
-
* Cross-session token report — every `llm_call`
|
|
25551
|
-
* `started_at >= fromMs`
|
|
25552
|
-
* USD cost DERIVED at read time via the shared
|
|
25553
|
-
*
|
|
25554
|
-
*
|
|
25555
|
-
*
|
|
25739
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25740
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25741
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25742
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25743
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25744
|
+
*
|
|
25745
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25746
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25747
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25748
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25749
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25750
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25751
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25752
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25753
|
+
* recompute the columns it could have read. The index is one every open
|
|
25754
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25755
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25756
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25757
|
+
* per call, no bag parsed.
|
|
25556
25758
|
*/
|
|
25557
25759
|
tokenReports(fromMs) {
|
|
25558
|
-
const
|
|
25559
|
-
|
|
25760
|
+
const rows = allRows(
|
|
25761
|
+
this.db.prepare(
|
|
25762
|
+
`${LLM_USAGE_SELECT}
|
|
25763
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25764
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25765
|
+
${LLM_USAGE_GROUP}`
|
|
25766
|
+
),
|
|
25767
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25768
|
+
);
|
|
25769
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25560
25770
|
}
|
|
25561
25771
|
/**
|
|
25562
|
-
* One session's token report — its `llm_call`
|
|
25563
|
-
* model) with derived cost, or `null` when the session made no
|
|
25564
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25565
|
-
* breakdown + estimated cost.
|
|
25772
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25773
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25774
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25775
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25776
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25777
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25566
25778
|
*/
|
|
25567
25779
|
tokenReportForSession(sessionId) {
|
|
25568
|
-
const
|
|
25780
|
+
const rows = allRows(
|
|
25781
|
+
this.db.prepare(
|
|
25782
|
+
`${LLM_USAGE_SELECT}
|
|
25783
|
+
FROM audit_events
|
|
25784
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25785
|
+
${LLM_USAGE_GROUP}`
|
|
25786
|
+
),
|
|
25787
|
+
[sessionId]
|
|
25788
|
+
);
|
|
25789
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25569
25790
|
return Promise.resolve(reports[0] ?? null);
|
|
25570
25791
|
}
|
|
25571
25792
|
/**
|
|
@@ -25589,42 +25810,6 @@ var SqliteActivityRepository = class {
|
|
|
25589
25810
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25590
25811
|
return Promise.resolve([...seen]);
|
|
25591
25812
|
}
|
|
25592
|
-
/**
|
|
25593
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25594
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25595
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25596
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25597
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25598
|
-
*/
|
|
25599
|
-
readLlmCallLeaves(opts = {}) {
|
|
25600
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25601
|
-
const params = [];
|
|
25602
|
-
if (opts.sessionId !== void 0) {
|
|
25603
|
-
conditions.push("root_session_id = ?");
|
|
25604
|
-
params.push(opts.sessionId);
|
|
25605
|
-
}
|
|
25606
|
-
if (opts.fromMs !== void 0) {
|
|
25607
|
-
conditions.push("started_at >= ?");
|
|
25608
|
-
params.push(opts.fromMs);
|
|
25609
|
-
}
|
|
25610
|
-
const rows = allRows(
|
|
25611
|
-
this.db.prepare(
|
|
25612
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25613
|
-
FROM audit_events
|
|
25614
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25615
|
-
),
|
|
25616
|
-
params
|
|
25617
|
-
);
|
|
25618
|
-
return mapRowsTolerant(
|
|
25619
|
-
rows.filter(
|
|
25620
|
-
(row) => row.sessionId !== null
|
|
25621
|
-
),
|
|
25622
|
-
(row) => ({
|
|
25623
|
-
sessionId: row.sessionId,
|
|
25624
|
-
attributes: JSON.parse(row.attributes)
|
|
25625
|
-
})
|
|
25626
|
-
);
|
|
25627
|
-
}
|
|
25628
25813
|
/**
|
|
25629
25814
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25630
25815
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25639,20 +25824,23 @@ var SqliteActivityRepository = class {
|
|
|
25639
25824
|
const inClause = placeholders(sessionIds.length);
|
|
25640
25825
|
const lastActivityRows = allRows(
|
|
25641
25826
|
this.db.prepare(
|
|
25642
|
-
`SELECT
|
|
25643
|
-
|
|
25644
|
-
|
|
25827
|
+
`SELECT ids.value AS id,
|
|
25828
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25829
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25830
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25831
|
+
FROM json_each(?) AS ids`
|
|
25645
25832
|
),
|
|
25646
|
-
sessionIds
|
|
25833
|
+
[JSON.stringify(sessionIds)]
|
|
25647
25834
|
);
|
|
25648
25835
|
for (const row of lastActivityRows) {
|
|
25649
|
-
if (row.id === null) continue;
|
|
25650
25836
|
const entry = result.get(row.id);
|
|
25651
|
-
|
|
25837
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25838
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25652
25839
|
}
|
|
25653
25840
|
const turnsRows = allRows(
|
|
25654
25841
|
this.db.prepare(
|
|
25655
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25842
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25843
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25656
25844
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25657
25845
|
GROUP BY root_session_id`
|
|
25658
25846
|
),
|
|
@@ -25667,7 +25855,7 @@ var SqliteActivityRepository = class {
|
|
|
25667
25855
|
this.db.prepare(
|
|
25668
25856
|
`SELECT root_session_id AS id,
|
|
25669
25857
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25670
|
-
FROM audit_events
|
|
25858
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25671
25859
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25672
25860
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25673
25861
|
GROUP BY root_session_id`
|
|
@@ -25697,7 +25885,7 @@ var SqliteActivityRepository = class {
|
|
|
25697
25885
|
this.db.prepare(
|
|
25698
25886
|
`SELECT root_session_id AS id,
|
|
25699
25887
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25700
|
-
FROM audit_events
|
|
25888
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25701
25889
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25702
25890
|
GROUP BY root_session_id`
|
|
25703
25891
|
),
|
|
@@ -26726,7 +26914,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26726
26914
|
|
|
26727
26915
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26728
26916
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26729
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26730
26917
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26731
26918
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26732
26919
|
function compareLocationOrder(a, b) {
|
|
@@ -26755,6 +26942,25 @@ function deriveInstanceStatus(row) {
|
|
|
26755
26942
|
latestResolutionStatus: row.latest_status
|
|
26756
26943
|
});
|
|
26757
26944
|
}
|
|
26945
|
+
function toFlatFindingRow(r) {
|
|
26946
|
+
return {
|
|
26947
|
+
id: r.id,
|
|
26948
|
+
ruleId: r.rule_id,
|
|
26949
|
+
category: r.category,
|
|
26950
|
+
severity: r.severity,
|
|
26951
|
+
maskedMatch: r.masked_match,
|
|
26952
|
+
actionTaken: r.action_taken,
|
|
26953
|
+
confidence: r.confidence,
|
|
26954
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26955
|
+
sourceTool: r.source_tool,
|
|
26956
|
+
repo: r.repo ?? "",
|
|
26957
|
+
file: r.file ?? "",
|
|
26958
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26959
|
+
eventId: r.event_id,
|
|
26960
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26961
|
+
status: deriveInstanceStatus(r)
|
|
26962
|
+
};
|
|
26963
|
+
}
|
|
26758
26964
|
function encodeGroupCursor(group) {
|
|
26759
26965
|
const payload = {
|
|
26760
26966
|
sev: group.severity,
|
|
@@ -26830,7 +27036,7 @@ var SqliteFindingsRepository = class {
|
|
|
26830
27036
|
this.db.prepare(
|
|
26831
27037
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26832
27038
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26833
|
-
|
|
27039
|
+
e.source_tool AS source_tool,
|
|
26834
27040
|
e.event_type AS kind
|
|
26835
27041
|
FROM audit_events e
|
|
26836
27042
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26938,56 +27144,11 @@ var SqliteFindingsRepository = class {
|
|
|
26938
27144
|
predicate,
|
|
26939
27145
|
params: sessionParams
|
|
26940
27146
|
});
|
|
26941
|
-
const rows =
|
|
26942
|
-
|
|
26943
|
-
|
|
26944
|
-
|
|
26945
|
-
|
|
26946
|
-
FROM (
|
|
26947
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26948
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26949
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26950
|
-
e.started_at AS occurred_at,
|
|
26951
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26952
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26953
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26954
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26955
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26956
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26957
|
-
latest.status AS latest_status,
|
|
26958
|
-
ROW_NUMBER() OVER (
|
|
26959
|
-
PARTITION BY d.rule_id
|
|
26960
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26961
|
-
) AS rn
|
|
26962
|
-
FROM inspection_findings f
|
|
26963
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26964
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26965
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26966
|
-
ON latest.finding_key = f.finding_key
|
|
26967
|
-
${predicate}
|
|
26968
|
-
)
|
|
26969
|
-
WHERE rn <= :cap
|
|
26970
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26971
|
-
),
|
|
26972
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
26973
|
-
);
|
|
26974
|
-
const groupable = rows.map((r) => ({
|
|
26975
|
-
id: r.id,
|
|
26976
|
-
ruleId: r.rule_id,
|
|
26977
|
-
category: r.category,
|
|
26978
|
-
severity: r.severity,
|
|
26979
|
-
maskedMatch: r.masked_match,
|
|
26980
|
-
actionTaken: r.action_taken,
|
|
26981
|
-
confidence: r.confidence,
|
|
26982
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26983
|
-
sourceTool: r.source_tool,
|
|
26984
|
-
repo: r.repo ?? "",
|
|
26985
|
-
file: r.file ?? "",
|
|
26986
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26987
|
-
eventId: r.event_id,
|
|
26988
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26989
|
-
status: deriveInstanceStatus(r)
|
|
26990
|
-
}));
|
|
27147
|
+
const rows = this.previewRows(aggregates, {
|
|
27148
|
+
sessionId: query.sessionId,
|
|
27149
|
+
from: query.from
|
|
27150
|
+
});
|
|
27151
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
26991
27152
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
26992
27153
|
const filterOpts = {
|
|
26993
27154
|
severity: query.severity,
|
|
@@ -27073,8 +27234,10 @@ var SqliteFindingsRepository = class {
|
|
|
27073
27234
|
*
|
|
27074
27235
|
* The scan runs from the top of the scope on every request, not from the
|
|
27075
27236
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
27076
|
-
* move as the caller pages. Rows
|
|
27077
|
-
*
|
|
27237
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27238
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27239
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27240
|
+
* fetched batches; only the page itself is retained.
|
|
27078
27241
|
*/
|
|
27079
27242
|
listFindingInstances(query) {
|
|
27080
27243
|
const opts = {
|
|
@@ -27090,6 +27253,10 @@ var SqliteFindingsRepository = class {
|
|
|
27090
27253
|
};
|
|
27091
27254
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27092
27255
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27256
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27257
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27258
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27259
|
+
};
|
|
27093
27260
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27094
27261
|
const items = [];
|
|
27095
27262
|
let total = 0;
|
|
@@ -27102,6 +27269,7 @@ var SqliteFindingsRepository = class {
|
|
|
27102
27269
|
accumulator.add(row);
|
|
27103
27270
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27104
27271
|
total += 1;
|
|
27272
|
+
if (!isPastCursor(row)) continue;
|
|
27105
27273
|
if (items.length < limit) {
|
|
27106
27274
|
items.push(toInstanceDetail(row));
|
|
27107
27275
|
last = row;
|
|
@@ -27110,15 +27278,6 @@ var SqliteFindingsRepository = class {
|
|
|
27110
27278
|
}
|
|
27111
27279
|
}
|
|
27112
27280
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27113
|
-
if (cursor !== null) {
|
|
27114
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27115
|
-
return Promise.resolve({
|
|
27116
|
-
totals: { findings: total },
|
|
27117
|
-
facets: accumulator.facets(),
|
|
27118
|
-
items: resumed.items,
|
|
27119
|
-
nextCursor: resumed.nextCursor
|
|
27120
|
-
});
|
|
27121
|
-
}
|
|
27122
27281
|
return Promise.resolve({
|
|
27123
27282
|
totals: { findings: total },
|
|
27124
27283
|
facets: accumulator.facets(),
|
|
@@ -27126,35 +27285,6 @@ var SqliteFindingsRepository = class {
|
|
|
27126
27285
|
nextCursor
|
|
27127
27286
|
});
|
|
27128
27287
|
}
|
|
27129
|
-
/**
|
|
27130
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27131
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27132
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27133
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27134
|
-
*/
|
|
27135
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27136
|
-
const items = [];
|
|
27137
|
-
let last;
|
|
27138
|
-
let hasMore = false;
|
|
27139
|
-
for (const row of this.scanFindingRows({
|
|
27140
|
-
sessionId: query.sessionId,
|
|
27141
|
-
from: query.from,
|
|
27142
|
-
after: cursor
|
|
27143
|
-
})) {
|
|
27144
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27145
|
-
if (items.length < limit) {
|
|
27146
|
-
items.push(toInstanceDetail(row));
|
|
27147
|
-
last = row;
|
|
27148
|
-
} else {
|
|
27149
|
-
hasMore = true;
|
|
27150
|
-
break;
|
|
27151
|
-
}
|
|
27152
|
-
}
|
|
27153
|
-
return {
|
|
27154
|
-
items,
|
|
27155
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27156
|
-
};
|
|
27157
|
-
}
|
|
27158
27288
|
/**
|
|
27159
27289
|
* The same findings folded by location: repository, then file within it.
|
|
27160
27290
|
*
|
|
@@ -27237,25 +27367,111 @@ var SqliteFindingsRepository = class {
|
|
|
27237
27367
|
});
|
|
27238
27368
|
}
|
|
27239
27369
|
/**
|
|
27240
|
-
*
|
|
27370
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27371
|
+
*
|
|
27372
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27373
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27374
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27375
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27376
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27377
|
+
* store while the answer does not.
|
|
27378
|
+
*
|
|
27379
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27380
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27381
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27382
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27383
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27384
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27385
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27386
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27387
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27388
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27389
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27390
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27391
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27392
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27393
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27394
|
+
* sort of the scope, which is still that floor.
|
|
27395
|
+
*
|
|
27396
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27397
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27398
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27399
|
+
* built from.
|
|
27400
|
+
*/
|
|
27401
|
+
previewRows(aggregates, scope) {
|
|
27402
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27403
|
+
let remaining = 0;
|
|
27404
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27405
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27406
|
+
wanted.set(ruleId, n);
|
|
27407
|
+
remaining += n;
|
|
27408
|
+
}
|
|
27409
|
+
const rows = [];
|
|
27410
|
+
if (remaining === 0) return rows;
|
|
27411
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27412
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27413
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27414
|
+
const want = wanted.get(r.rule_id);
|
|
27415
|
+
if (want === void 0) continue;
|
|
27416
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27417
|
+
if (have >= want) continue;
|
|
27418
|
+
taken.set(r.rule_id, have + 1);
|
|
27419
|
+
rows.push(r);
|
|
27420
|
+
remaining -= 1;
|
|
27421
|
+
if (remaining === 0) break;
|
|
27422
|
+
}
|
|
27423
|
+
return rows;
|
|
27424
|
+
}
|
|
27425
|
+
/**
|
|
27426
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27241
27427
|
*
|
|
27242
27428
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27243
27429
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27244
|
-
* store is far more rows than any page.
|
|
27245
|
-
*
|
|
27246
|
-
*
|
|
27430
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27431
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27432
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27433
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27434
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27435
|
+
* batch and cost the square of the scope.
|
|
27247
27436
|
*
|
|
27248
|
-
*
|
|
27249
|
-
*
|
|
27250
|
-
*
|
|
27251
|
-
*
|
|
27252
|
-
*
|
|
27253
|
-
*
|
|
27254
|
-
*
|
|
27255
|
-
* dimension — see listFindingInstances.
|
|
27437
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27438
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27439
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27440
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27441
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27442
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27443
|
+
* page-2+ request would otherwise re-seek for.
|
|
27256
27444
|
*/
|
|
27257
27445
|
*scanFindingRows(scope) {
|
|
27258
|
-
const
|
|
27446
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27447
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27448
|
+
yield toFlatFindingRow(r);
|
|
27449
|
+
}
|
|
27450
|
+
}
|
|
27451
|
+
/**
|
|
27452
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27453
|
+
* joined to its event and definition, newest first.
|
|
27454
|
+
*
|
|
27455
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27456
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27457
|
+
*
|
|
27458
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27459
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27460
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27461
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27462
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27463
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27464
|
+
* ordered within the session — and the order falls out of the index.
|
|
27465
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27466
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27467
|
+
*
|
|
27468
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27469
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27470
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27471
|
+
* table would be materialized before the first row streamed.
|
|
27472
|
+
*/
|
|
27473
|
+
findingScanSql(scope) {
|
|
27474
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27259
27475
|
const params = [];
|
|
27260
27476
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27261
27477
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27269,58 +27485,24 @@ var SqliteFindingsRepository = class {
|
|
|
27269
27485
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27270
27486
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27271
27487
|
e.started_at AS occurred_at,
|
|
27272
|
-
|
|
27273
|
-
|
|
27274
|
-
|
|
27275
|
-
|
|
27488
|
+
e.source_tool AS source_tool,
|
|
27489
|
+
e.repo AS repo,
|
|
27490
|
+
e.file_path AS file,
|
|
27491
|
+
e.tool_name AS tool_name,
|
|
27276
27492
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27277
27493
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27278
27494
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27279
|
-
FROM
|
|
27280
|
-
JOIN
|
|
27281
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27495
|
+
FROM audit_events e
|
|
27496
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27497
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27282
27498
|
WHERE ${conditions.join(" AND ")}
|
|
27283
|
-
|
|
27284
|
-
|
|
27285
|
-
LIMIT ?`;
|
|
27286
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27287
|
-
for (; ; ) {
|
|
27288
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27289
|
-
...params,
|
|
27290
|
-
after.startedAtMs,
|
|
27291
|
-
after.startedAtMs,
|
|
27292
|
-
after.id,
|
|
27293
|
-
SCAN_BATCH_ROWS
|
|
27294
|
-
]);
|
|
27295
|
-
for (const r of rows) {
|
|
27296
|
-
yield {
|
|
27297
|
-
id: r.id,
|
|
27298
|
-
ruleId: r.rule_id,
|
|
27299
|
-
category: r.category,
|
|
27300
|
-
severity: r.severity,
|
|
27301
|
-
maskedMatch: r.masked_match,
|
|
27302
|
-
actionTaken: r.action_taken,
|
|
27303
|
-
confidence: r.confidence,
|
|
27304
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27305
|
-
sourceTool: r.source_tool,
|
|
27306
|
-
repo: r.repo ?? "",
|
|
27307
|
-
file: r.file ?? "",
|
|
27308
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27309
|
-
eventId: r.event_id,
|
|
27310
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27311
|
-
status: deriveInstanceStatus(r)
|
|
27312
|
-
};
|
|
27313
|
-
}
|
|
27314
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27315
|
-
const lastRow = rows[rows.length - 1];
|
|
27316
|
-
if (lastRow === void 0) return;
|
|
27317
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27318
|
-
}
|
|
27499
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27500
|
+
return { sql, params };
|
|
27319
27501
|
}
|
|
27320
27502
|
groupAggregates(withSearchText, scope) {
|
|
27321
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27322
|
-
group_concat(DISTINCT
|
|
27323
|
-
group_concat(DISTINCT 'via ' ||
|
|
27503
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27504
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27505
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27324
27506
|
const rows = this.db.prepare(
|
|
27325
27507
|
`SELECT rule_id,
|
|
27326
27508
|
sum(tuple_count) AS instance_count,
|
|
@@ -27338,7 +27520,7 @@ var SqliteFindingsRepository = class {
|
|
|
27338
27520
|
coalesce(latest.status, '') AS status_tuple,
|
|
27339
27521
|
count(*) AS tuple_count,
|
|
27340
27522
|
max(e.started_at) AS latest_at,
|
|
27341
|
-
group_concat(DISTINCT
|
|
27523
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27342
27524
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27343
27525
|
${innerSearchColumns}
|
|
27344
27526
|
FROM inspection_findings f
|
|
@@ -27469,6 +27651,8 @@ function isoDay(ms) {
|
|
|
27469
27651
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27470
27652
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27471
27653
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27654
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27655
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27472
27656
|
var SKIPPED = -1;
|
|
27473
27657
|
var ROW_COLUMNS = `id,
|
|
27474
27658
|
parent_id AS parentId,
|
|
@@ -27508,6 +27692,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27508
27692
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27509
27693
|
LIMIT :limit`
|
|
27510
27694
|
);
|
|
27695
|
+
this.captureRowsStmt = db.prepare(
|
|
27696
|
+
`SELECT ${ROW_COLUMNS}
|
|
27697
|
+
FROM audit_events
|
|
27698
|
+
WHERE synced_at IS NULL
|
|
27699
|
+
AND sync_claimed_at IS NULL
|
|
27700
|
+
AND outbox_owed = 1
|
|
27701
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27702
|
+
AND started_at < :before
|
|
27703
|
+
ORDER BY started_at
|
|
27704
|
+
LIMIT :limit`
|
|
27705
|
+
);
|
|
27706
|
+
this.markOwedStmt = db.prepare(
|
|
27707
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27708
|
+
);
|
|
27511
27709
|
this.stampStmt = db.prepare(
|
|
27512
27710
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27513
27711
|
);
|
|
@@ -27539,6 +27737,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27539
27737
|
FROM audit_events
|
|
27540
27738
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27541
27739
|
);
|
|
27740
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27741
|
+
`SELECT COUNT(*) AS skipped
|
|
27742
|
+
FROM audit_events
|
|
27743
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27744
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27745
|
+
);
|
|
27542
27746
|
this.fingerprintStmt = db.prepare(
|
|
27543
27747
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27544
27748
|
FROM history_sync WHERE id = 1`
|
|
@@ -27548,6 +27752,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27548
27752
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27549
27753
|
WHERE id = 1`
|
|
27550
27754
|
);
|
|
27755
|
+
this.disownCapturesStmt = db.prepare(
|
|
27756
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27757
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27758
|
+
);
|
|
27551
27759
|
this.rearmStmt = db.prepare(
|
|
27552
27760
|
`UPDATE audit_events SET synced_at = NULL
|
|
27553
27761
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27620,6 +27828,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27620
27828
|
closeWindowStmt;
|
|
27621
27829
|
releaseBoundaryStmt;
|
|
27622
27830
|
freezeBoundaryStmt;
|
|
27831
|
+
captureRowsStmt;
|
|
27832
|
+
markOwedStmt;
|
|
27833
|
+
captureSkipCountStmt;
|
|
27834
|
+
disownCapturesStmt;
|
|
27623
27835
|
partitionStmt;
|
|
27624
27836
|
claimRowStmt;
|
|
27625
27837
|
releaseRowStmt;
|
|
@@ -27653,6 +27865,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27653
27865
|
pendingRows(sessionId, limit, before) {
|
|
27654
27866
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27655
27867
|
}
|
|
27868
|
+
/**
|
|
27869
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27870
|
+
*
|
|
27871
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27872
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27873
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27874
|
+
* the live path.
|
|
27875
|
+
*/
|
|
27876
|
+
pendingCaptureRows(limit, before) {
|
|
27877
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27878
|
+
}
|
|
27879
|
+
/**
|
|
27880
|
+
* Record that a capture is OWED to the deployment.
|
|
27881
|
+
*
|
|
27882
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27883
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27884
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27885
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27886
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27887
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27888
|
+
* offered to anyone.
|
|
27889
|
+
*
|
|
27890
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27891
|
+
* out of the drain's read.
|
|
27892
|
+
*/
|
|
27893
|
+
markCaptureOwed(id) {
|
|
27894
|
+
this.markOwedStmt.run({ id });
|
|
27895
|
+
}
|
|
27656
27896
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27657
27897
|
markSynced(ids, atMs) {
|
|
27658
27898
|
this.stampAll(ids, atMs);
|
|
@@ -27736,10 +27976,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27736
27976
|
this.countsStmt,
|
|
27737
27977
|
{ before }
|
|
27738
27978
|
);
|
|
27979
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27739
27980
|
return {
|
|
27740
27981
|
pending: row?.pending ?? 0,
|
|
27741
27982
|
sent: row?.sent ?? 0,
|
|
27742
|
-
skipped: row?.skipped ?? 0
|
|
27983
|
+
skipped: row?.skipped ?? 0,
|
|
27984
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27743
27985
|
};
|
|
27744
27986
|
}
|
|
27745
27987
|
/**
|
|
@@ -27780,7 +28022,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27780
28022
|
withTransaction(
|
|
27781
28023
|
this.db,
|
|
27782
28024
|
() => {
|
|
28025
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27783
28026
|
this.rearmStmt.run();
|
|
28027
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28028
|
+
this.disownCapturesStmt.run();
|
|
28029
|
+
}
|
|
27784
28030
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27785
28031
|
},
|
|
27786
28032
|
"IMMEDIATE"
|
|
@@ -27977,7 +28223,259 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
27977
28223
|
};
|
|
27978
28224
|
|
|
27979
28225
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
27980
|
-
import { createHash as createHash2, randomUUID as
|
|
28226
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28227
|
+
|
|
28228
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28229
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28230
|
+
import { join as join6 } from "path";
|
|
28231
|
+
|
|
28232
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28233
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28234
|
+
import { mkdir } from "fs/promises";
|
|
28235
|
+
import { homedir } from "os";
|
|
28236
|
+
import { join as join4 } from "path";
|
|
28237
|
+
function defaultDataDir() {
|
|
28238
|
+
return join4(homedir(), ".aka");
|
|
28239
|
+
}
|
|
28240
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28241
|
+
return join4(base, "settings");
|
|
28242
|
+
}
|
|
28243
|
+
function dataDir(base = defaultDataDir()) {
|
|
28244
|
+
return join4(base, "data");
|
|
28245
|
+
}
|
|
28246
|
+
function dbPath(base = defaultDataDir()) {
|
|
28247
|
+
return join4(dataDir(base), "aka.db");
|
|
28248
|
+
}
|
|
28249
|
+
function keysDir(base = defaultDataDir()) {
|
|
28250
|
+
return join4(base, "keys");
|
|
28251
|
+
}
|
|
28252
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
28253
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
28254
|
+
tightenDir(dir);
|
|
28255
|
+
}
|
|
28256
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28257
|
+
ensureDataDirSync(dir);
|
|
28258
|
+
}
|
|
28259
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28260
|
+
const moves = [
|
|
28261
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28262
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28263
|
+
];
|
|
28264
|
+
for (const { name, dest } of moves) {
|
|
28265
|
+
try {
|
|
28266
|
+
ensureDataDirSync(dest);
|
|
28267
|
+
const moved = join4(dest, name);
|
|
28268
|
+
renameSync3(join4(base, name), moved);
|
|
28269
|
+
tightenFile(moved);
|
|
28270
|
+
} catch {
|
|
28271
|
+
}
|
|
28272
|
+
}
|
|
28273
|
+
}
|
|
28274
|
+
|
|
28275
|
+
// ../../packages/persistence/src/settings.ts
|
|
28276
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28277
|
+
import { join as join5 } from "path";
|
|
28278
|
+
|
|
28279
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28280
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28281
|
+
import {
|
|
28282
|
+
closeSync,
|
|
28283
|
+
existsSync as existsSync2,
|
|
28284
|
+
openSync,
|
|
28285
|
+
readFileSync as readFileSync2,
|
|
28286
|
+
rmSync as rmSync5,
|
|
28287
|
+
statSync as statSync3,
|
|
28288
|
+
writeFileSync as writeFileSync2
|
|
28289
|
+
} from "fs";
|
|
28290
|
+
import { hostname as hostname3 } from "os";
|
|
28291
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28292
|
+
|
|
28293
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28294
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28295
|
+
import { posix, win32 } from "path";
|
|
28296
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28297
|
+
if (platform2 === "darwin") {
|
|
28298
|
+
return [
|
|
28299
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28300
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28301
|
+
];
|
|
28302
|
+
}
|
|
28303
|
+
if (platform2 === "win32") {
|
|
28304
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28305
|
+
}
|
|
28306
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28307
|
+
}
|
|
28308
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28309
|
+
for (const path of paths) {
|
|
28310
|
+
let text;
|
|
28311
|
+
try {
|
|
28312
|
+
text = readFileSync3(path, "utf8");
|
|
28313
|
+
} catch {
|
|
28314
|
+
continue;
|
|
28315
|
+
}
|
|
28316
|
+
const record2 = parseJsonObject(text);
|
|
28317
|
+
if (!record2) continue;
|
|
28318
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28319
|
+
if (parsed2.success) return parsed2.data;
|
|
28320
|
+
}
|
|
28321
|
+
return null;
|
|
28322
|
+
}
|
|
28323
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28324
|
+
if (!managed) return settings;
|
|
28325
|
+
const { values } = managed;
|
|
28326
|
+
const merged = { ...settings };
|
|
28327
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28328
|
+
if (values.controlPlane !== void 0) {
|
|
28329
|
+
merged.controlPlane = {
|
|
28330
|
+
...values.controlPlane,
|
|
28331
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28332
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28333
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28334
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28335
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28336
|
+
};
|
|
28337
|
+
}
|
|
28338
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28339
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28340
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28341
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28342
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28343
|
+
if (values.vaultConsent !== void 0) {
|
|
28344
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28345
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28346
|
+
// at the current version otherwise.
|
|
28347
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28348
|
+
) : void 0;
|
|
28349
|
+
}
|
|
28350
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28351
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28352
|
+
acknowledgedAt: now().toISOString(),
|
|
28353
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28354
|
+
} : void 0;
|
|
28355
|
+
}
|
|
28356
|
+
return merged;
|
|
28357
|
+
}
|
|
28358
|
+
|
|
28359
|
+
// ../../packages/persistence/src/settings.ts
|
|
28360
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28361
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28362
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28363
|
+
}
|
|
28364
|
+
function readUserSettings(base) {
|
|
28365
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28366
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28367
|
+
try {
|
|
28368
|
+
return WorkspaceSettings.parse(record2);
|
|
28369
|
+
} catch {
|
|
28370
|
+
return defaultWorkspaceSettings();
|
|
28371
|
+
}
|
|
28372
|
+
}
|
|
28373
|
+
function readJson(file2) {
|
|
28374
|
+
let text;
|
|
28375
|
+
try {
|
|
28376
|
+
text = readFileSync4(file2, "utf8");
|
|
28377
|
+
} catch {
|
|
28378
|
+
return null;
|
|
28379
|
+
}
|
|
28380
|
+
return parseJsonObject(text) ?? null;
|
|
28381
|
+
}
|
|
28382
|
+
|
|
28383
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28384
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28385
|
+
switch (refusal) {
|
|
28386
|
+
case "lock":
|
|
28387
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28388
|
+
case "disable":
|
|
28389
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28390
|
+
case "floor":
|
|
28391
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28392
|
+
}
|
|
28393
|
+
}
|
|
28394
|
+
var PolicyFloorError = class extends Error {
|
|
28395
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28396
|
+
pack;
|
|
28397
|
+
/**
|
|
28398
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28399
|
+
* clearing the assignment, or switching the detection off.
|
|
28400
|
+
*/
|
|
28401
|
+
attempted;
|
|
28402
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28403
|
+
floor;
|
|
28404
|
+
refusal;
|
|
28405
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28406
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28407
|
+
this.name = "PolicyFloorError";
|
|
28408
|
+
this.pack = pack;
|
|
28409
|
+
this.attempted = attempted;
|
|
28410
|
+
this.floor = floor;
|
|
28411
|
+
this.refusal = refusal;
|
|
28412
|
+
}
|
|
28413
|
+
};
|
|
28414
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28415
|
+
try {
|
|
28416
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28417
|
+
const parsed2 = JSON.parse(raw);
|
|
28418
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28419
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28420
|
+
} catch {
|
|
28421
|
+
return null;
|
|
28422
|
+
}
|
|
28423
|
+
}
|
|
28424
|
+
function indexEnabled(policies) {
|
|
28425
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28426
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28427
|
+
for (const policy of policies) {
|
|
28428
|
+
if (!policy.enabled) continue;
|
|
28429
|
+
if ("ruleId" in policy.target) {
|
|
28430
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28431
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28432
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28433
|
+
}
|
|
28434
|
+
}
|
|
28435
|
+
return { byRuleId, byCategory };
|
|
28436
|
+
}
|
|
28437
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28438
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28439
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28440
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28441
|
+
return policies.some((policy) => {
|
|
28442
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28443
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28444
|
+
});
|
|
28445
|
+
}
|
|
28446
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28447
|
+
const floors = openControlPlaneFloors(base);
|
|
28448
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28449
|
+
}
|
|
28450
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28451
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28452
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28453
|
+
if (bundle === null) return null;
|
|
28454
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28455
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28456
|
+
}
|
|
28457
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28458
|
+
let action = null;
|
|
28459
|
+
for (const rule of rules) {
|
|
28460
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28461
|
+
if (resolved === void 0) continue;
|
|
28462
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28463
|
+
}
|
|
28464
|
+
if (action === null) return null;
|
|
28465
|
+
return {
|
|
28466
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28467
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28468
|
+
};
|
|
28469
|
+
}
|
|
28470
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28471
|
+
if (floor.locked) return "lock";
|
|
28472
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28473
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28474
|
+
}
|
|
28475
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28476
|
+
if (floor === null || enabled) return null;
|
|
28477
|
+
return "disable";
|
|
28478
|
+
}
|
|
27981
28479
|
|
|
27982
28480
|
// ../../packages/persistence/src/semver.ts
|
|
27983
28481
|
function parse3(version2) {
|
|
@@ -28071,8 +28569,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
28071
28569
|
return ids;
|
|
28072
28570
|
}
|
|
28073
28571
|
var SqliteInstalledPacksRepository = class {
|
|
28074
|
-
|
|
28572
|
+
/**
|
|
28573
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28574
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28575
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28576
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28577
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28578
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28579
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28580
|
+
* real `~/.aka`.
|
|
28581
|
+
*/
|
|
28582
|
+
constructor(db, baseDir) {
|
|
28075
28583
|
this.db = db;
|
|
28584
|
+
this.baseDir = baseDir;
|
|
28076
28585
|
this.insertMissingStmt = db.prepare(
|
|
28077
28586
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
28078
28587
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28094,11 +28603,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28094
28603
|
this.signatureStmt = db.prepare(
|
|
28095
28604
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28096
28605
|
);
|
|
28606
|
+
this.packRulesStmt = db.prepare(
|
|
28607
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28608
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28609
|
+
);
|
|
28097
28610
|
}
|
|
28098
28611
|
db;
|
|
28612
|
+
baseDir;
|
|
28099
28613
|
insertMissingStmt;
|
|
28100
28614
|
upsertAvailableStmt;
|
|
28101
28615
|
signatureStmt;
|
|
28616
|
+
packRulesStmt;
|
|
28102
28617
|
/**
|
|
28103
28618
|
* Record the running binary's detection inventory. Refreshes the
|
|
28104
28619
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28140,7 +28655,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28140
28655
|
let behind = false;
|
|
28141
28656
|
for (const row of rows) {
|
|
28142
28657
|
const params = {
|
|
28143
|
-
id:
|
|
28658
|
+
id: randomUUID4(),
|
|
28144
28659
|
namespace: row.namespace,
|
|
28145
28660
|
packId: row.packId,
|
|
28146
28661
|
version: row.version,
|
|
@@ -28152,7 +28667,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28152
28667
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28153
28668
|
this.upsertAvailableStmt.run({
|
|
28154
28669
|
...params,
|
|
28155
|
-
id:
|
|
28670
|
+
id: randomUUID4(),
|
|
28156
28671
|
recordedBy: meta4?.recordedBy ?? null
|
|
28157
28672
|
});
|
|
28158
28673
|
} else {
|
|
@@ -28398,9 +28913,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28398
28913
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28399
28914
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28400
28915
|
// caller can tell an edit from a no-such-detection.
|
|
28916
|
+
/**
|
|
28917
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
28918
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
28919
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
28920
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
28921
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
28922
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
28923
|
+
*/
|
|
28924
|
+
packFloorRules(namespace, packId) {
|
|
28925
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
28926
|
+
if (!row) return [];
|
|
28927
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
28928
|
+
}
|
|
28929
|
+
/**
|
|
28930
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
28931
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
28932
|
+
* repository constructed without a layout base).
|
|
28933
|
+
*
|
|
28934
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
28935
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
28936
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
28937
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
28938
|
+
*/
|
|
28939
|
+
policyFloor(namespace, packId) {
|
|
28940
|
+
if (this.baseDir === void 0) return null;
|
|
28941
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
28942
|
+
}
|
|
28943
|
+
/**
|
|
28944
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
28945
|
+
* entry only for a pack the control plane actually governs.
|
|
28946
|
+
*
|
|
28947
|
+
* A surface listing every detection asks per pack, and asking through
|
|
28948
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
28949
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
28950
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
28951
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
28952
|
+
* exactly as the single-pack read returns null for them.
|
|
28953
|
+
*/
|
|
28954
|
+
policyFloors(packs2) {
|
|
28955
|
+
const floors = /* @__PURE__ */ new Map();
|
|
28956
|
+
if (this.baseDir === void 0) return floors;
|
|
28957
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
28958
|
+
if (source === null) return floors;
|
|
28959
|
+
for (const pack of packs2) {
|
|
28960
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
28961
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
28962
|
+
}
|
|
28963
|
+
return floors;
|
|
28964
|
+
}
|
|
28401
28965
|
/**
|
|
28402
28966
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28403
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
28967
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
28968
|
+
*
|
|
28969
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
28970
|
+
* write below, and a detection the organization has authored a policy for is
|
|
28971
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
28972
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
28973
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
28974
|
+
* surface that offers the choice.
|
|
28404
28975
|
*/
|
|
28405
28976
|
setPolicy(namespace, packId, policyId) {
|
|
28406
28977
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28408,14 +28979,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28408
28979
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28409
28980
|
);
|
|
28410
28981
|
}
|
|
28982
|
+
const requested = policyId;
|
|
28983
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28984
|
+
if (floor !== null) {
|
|
28985
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
28986
|
+
if (refusal !== null) {
|
|
28987
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
28988
|
+
}
|
|
28989
|
+
}
|
|
28411
28990
|
const res = this.db.prepare(
|
|
28412
28991
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28413
28992
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28414
28993
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28415
28994
|
return Number(res.changes) > 0;
|
|
28416
28995
|
}
|
|
28417
|
-
/**
|
|
28996
|
+
/**
|
|
28997
|
+
* Enable or disable one installed pack.
|
|
28998
|
+
*
|
|
28999
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29000
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29001
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29002
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29003
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29004
|
+
* that lived in a page would leave the CLI free.
|
|
29005
|
+
*/
|
|
28418
29006
|
setEnabled(namespace, packId, enabled) {
|
|
29007
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29008
|
+
if (floor !== null) {
|
|
29009
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29010
|
+
if (refusal !== null) {
|
|
29011
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29012
|
+
}
|
|
29013
|
+
}
|
|
28419
29014
|
const res = this.db.prepare(
|
|
28420
29015
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28421
29016
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28501,7 +29096,7 @@ var SqliteInventoryRepository = class {
|
|
|
28501
29096
|
};
|
|
28502
29097
|
|
|
28503
29098
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28504
|
-
import { randomUUID as
|
|
29099
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28505
29100
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28506
29101
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28507
29102
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -28990,7 +29585,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28990
29585
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
28991
29586
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
28992
29587
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
28993
|
-
).run({ id:
|
|
29588
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
28994
29589
|
}
|
|
28995
29590
|
return true;
|
|
28996
29591
|
}
|
|
@@ -29011,7 +29606,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29011
29606
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
29012
29607
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
29013
29608
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
29014
|
-
).run({ id:
|
|
29609
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
29015
29610
|
}
|
|
29016
29611
|
this.configRowsCache = void 0;
|
|
29017
29612
|
return "ok";
|
|
@@ -29308,7 +29903,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29308
29903
|
};
|
|
29309
29904
|
|
|
29310
29905
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29311
|
-
import { randomUUID as
|
|
29906
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29312
29907
|
var SqlitePoliciesRepository = class {
|
|
29313
29908
|
constructor(db) {
|
|
29314
29909
|
this.db = db;
|
|
@@ -29343,7 +29938,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29343
29938
|
failOpenTransaction(this.db, () => {
|
|
29344
29939
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29345
29940
|
stmt.run({
|
|
29346
|
-
id:
|
|
29941
|
+
id: randomUUID6(),
|
|
29347
29942
|
target: JSON.stringify({ category }),
|
|
29348
29943
|
action,
|
|
29349
29944
|
now: Date.now()
|
|
@@ -29363,7 +29958,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29363
29958
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29364
29959
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29365
29960
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29366
|
-
).run({ id:
|
|
29961
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29367
29962
|
}
|
|
29368
29963
|
// Caps every global per-category policy currently set to block/redact down
|
|
29369
29964
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29431,7 +30026,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29431
30026
|
};
|
|
29432
30027
|
|
|
29433
30028
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29434
|
-
import { randomUUID as
|
|
30029
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29435
30030
|
var SqliteProjectFilesRepository = class {
|
|
29436
30031
|
constructor(db) {
|
|
29437
30032
|
this.db = db;
|
|
@@ -29463,7 +30058,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29463
30058
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29464
30059
|
for (const file2 of scan2.files) {
|
|
29465
30060
|
this.upsertStmt.run({
|
|
29466
|
-
id:
|
|
30061
|
+
id: randomUUID7(),
|
|
29467
30062
|
projectId,
|
|
29468
30063
|
path: file2.path,
|
|
29469
30064
|
name: file2.name,
|
|
@@ -29477,9 +30072,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29477
30072
|
};
|
|
29478
30073
|
|
|
29479
30074
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29480
|
-
import { randomUUID as
|
|
30075
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29481
30076
|
var SqliteResolutionsRepository = class {
|
|
29482
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30077
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29483
30078
|
this.db = db;
|
|
29484
30079
|
this.now = now;
|
|
29485
30080
|
this.newId = newId;
|
|
@@ -29692,7 +30287,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29692
30287
|
};
|
|
29693
30288
|
|
|
29694
30289
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29695
|
-
import { randomUUID as
|
|
30290
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29696
30291
|
function pageLimit(requested, fallback) {
|
|
29697
30292
|
if (requested === void 0) return fallback;
|
|
29698
30293
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29738,12 +30333,14 @@ var SELECT_COLUMNS = `
|
|
|
29738
30333
|
ciphertext,
|
|
29739
30334
|
nonce,
|
|
29740
30335
|
auth_tag AS authTag,
|
|
30336
|
+
user_authorized AS userAuthorized,
|
|
29741
30337
|
occurrence_count AS occurrenceCount,
|
|
29742
30338
|
first_seen AS firstSeen,
|
|
29743
30339
|
last_seen AS lastSeen`;
|
|
29744
30340
|
function toRow(raw) {
|
|
29745
|
-
const { provider, ...rest } = raw;
|
|
29746
|
-
|
|
30341
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30342
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30343
|
+
return provider === null ? row : { ...row, provider };
|
|
29747
30344
|
}
|
|
29748
30345
|
var SqliteSecretVaultRepository = class {
|
|
29749
30346
|
constructor(db) {
|
|
@@ -29753,17 +30350,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29753
30350
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29754
30351
|
format_version, category, rule_id, masked_match, provider,
|
|
29755
30352
|
ciphertext, nonce, auth_tag,
|
|
29756
|
-
occurrence_count, first_seen, last_seen
|
|
30353
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29757
30354
|
) VALUES (
|
|
29758
30355
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29759
30356
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29760
30357
|
:ciphertext, :nonce, :authTag,
|
|
29761
|
-
1, :now, :now
|
|
30358
|
+
:userAuthorized, 1, :now, :now
|
|
29762
30359
|
)`
|
|
29763
30360
|
);
|
|
29764
30361
|
this.bumpStmt = db.prepare(
|
|
29765
30362
|
`UPDATE secret_vault
|
|
29766
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30363
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30364
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29767
30365
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29768
30366
|
);
|
|
29769
30367
|
this.byPointerStmt = db.prepare(
|
|
@@ -29783,6 +30381,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29783
30381
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29784
30382
|
WHERE pointer_id = :pointerId`
|
|
29785
30383
|
);
|
|
30384
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29786
30385
|
this.derefStmt = db.prepare(
|
|
29787
30386
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29788
30387
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29796,6 +30395,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29796
30395
|
listStmt;
|
|
29797
30396
|
replaceCiphertextStmt;
|
|
29798
30397
|
refreshFingerprintStmt;
|
|
30398
|
+
deleteByPointerStmt;
|
|
29799
30399
|
derefStmt;
|
|
29800
30400
|
/**
|
|
29801
30401
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29804,6 +30404,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29804
30404
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29805
30405
|
* wire token. `minted` is true only when this call created the row.
|
|
29806
30406
|
*
|
|
30407
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30408
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30409
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30410
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30411
|
+
*
|
|
29807
30412
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29808
30413
|
* writers cannot both decide they are minting.
|
|
29809
30414
|
*/
|
|
@@ -29830,13 +30435,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29830
30435
|
ciphertext: input2.ciphertext,
|
|
29831
30436
|
nonce: input2.nonce,
|
|
29832
30437
|
authTag: input2.authTag,
|
|
30438
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29833
30439
|
now
|
|
29834
30440
|
})
|
|
29835
30441
|
);
|
|
29836
30442
|
minted = true;
|
|
29837
30443
|
return;
|
|
29838
30444
|
}
|
|
29839
|
-
this.bumpStmt.run({
|
|
30445
|
+
this.bumpStmt.run({
|
|
30446
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30447
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30448
|
+
now
|
|
30449
|
+
});
|
|
29840
30450
|
},
|
|
29841
30451
|
"IMMEDIATE"
|
|
29842
30452
|
);
|
|
@@ -29896,6 +30506,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29896
30506
|
);
|
|
29897
30507
|
return destroyed;
|
|
29898
30508
|
}
|
|
30509
|
+
/**
|
|
30510
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30511
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30512
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30513
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30514
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30515
|
+
* it.
|
|
30516
|
+
*
|
|
30517
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30518
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30519
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30520
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30521
|
+
* entry still sitting in the vault.
|
|
30522
|
+
*
|
|
30523
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30524
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30525
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30526
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30527
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30528
|
+
*/
|
|
30529
|
+
deleteByPointerIds(pointerIds) {
|
|
30530
|
+
if (pointerIds.length === 0) return [];
|
|
30531
|
+
const deleted = [];
|
|
30532
|
+
withTransaction(
|
|
30533
|
+
this.db,
|
|
30534
|
+
() => {
|
|
30535
|
+
for (const pointerId of pointerIds) {
|
|
30536
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30537
|
+
deleted.push(pointerId);
|
|
30538
|
+
}
|
|
30539
|
+
}
|
|
30540
|
+
},
|
|
30541
|
+
"IMMEDIATE"
|
|
30542
|
+
);
|
|
30543
|
+
return deleted;
|
|
30544
|
+
}
|
|
29899
30545
|
/**
|
|
29900
30546
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29901
30547
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29908,7 +30554,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29908
30554
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29909
30555
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29910
30556
|
).run({
|
|
29911
|
-
id:
|
|
30557
|
+
id: randomUUID9(),
|
|
29912
30558
|
pointerId: entry.pointerId,
|
|
29913
30559
|
location: entry.location,
|
|
29914
30560
|
kind: entry.kind,
|
|
@@ -30421,15 +31067,15 @@ var SqliteSecurityRepository = class {
|
|
|
30421
31067
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30422
31068
|
const rows = allRows(
|
|
30423
31069
|
this.db.prepare(
|
|
30424
|
-
`SELECT
|
|
31070
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30425
31071
|
FROM inspection_findings f
|
|
30426
31072
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30427
31073
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30428
31074
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30429
|
-
AND
|
|
30430
|
-
AND
|
|
30431
|
-
GROUP BY repo
|
|
30432
|
-
ORDER BY c DESC, repo
|
|
31075
|
+
AND e.repo IS NOT NULL
|
|
31076
|
+
AND e.repo != ''
|
|
31077
|
+
GROUP BY e.repo
|
|
31078
|
+
ORDER BY c DESC, e.repo
|
|
30433
31079
|
LIMIT :limit`
|
|
30434
31080
|
),
|
|
30435
31081
|
{ from, to: now, limit }
|
|
@@ -30491,7 +31137,7 @@ var SqliteSecurityRepository = class {
|
|
|
30491
31137
|
`SELECT f.finding_key AS finding_key,
|
|
30492
31138
|
d.rule_id AS rule_id,
|
|
30493
31139
|
d.severity AS severity,
|
|
30494
|
-
|
|
31140
|
+
e.file_path AS path,
|
|
30495
31141
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30496
31142
|
latest.resolved_at AS latest_resolved_at
|
|
30497
31143
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30544,7 +31190,7 @@ var SqliteSecurityRepository = class {
|
|
|
30544
31190
|
};
|
|
30545
31191
|
|
|
30546
31192
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30547
|
-
import { randomUUID as
|
|
31193
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30548
31194
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30549
31195
|
var IN_CHUNK = 500;
|
|
30550
31196
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30632,7 +31278,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30632
31278
|
callSiteCount,
|
|
30633
31279
|
transports: distinctTransports(transports),
|
|
30634
31280
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30635
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31281
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30636
31282
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30637
31283
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30638
31284
|
};
|
|
@@ -30659,7 +31305,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30659
31305
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30660
31306
|
transports: distinctTransports(transports),
|
|
30661
31307
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30662
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31308
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30663
31309
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30664
31310
|
note: dest.note,
|
|
30665
31311
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30688,7 +31334,11 @@ var SqliteSharesRepository = class {
|
|
|
30688
31334
|
FROM share_destination d
|
|
30689
31335
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30690
31336
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30691
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31337
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31338
|
+
AND NOT EXISTS (
|
|
31339
|
+
SELECT 1 FROM egress_decision_override o
|
|
31340
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31341
|
+
)`
|
|
30692
31342
|
);
|
|
30693
31343
|
const kindCounts = countBy(
|
|
30694
31344
|
this.db,
|
|
@@ -30800,7 +31450,7 @@ var SqliteSharesRepository = class {
|
|
|
30800
31450
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30801
31451
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30802
31452
|
).run({
|
|
30803
|
-
id:
|
|
31453
|
+
id: randomUUID10(),
|
|
30804
31454
|
destinationId,
|
|
30805
31455
|
host: dest.host,
|
|
30806
31456
|
decision,
|
|
@@ -30949,7 +31599,7 @@ var SqliteSharesRepository = class {
|
|
|
30949
31599
|
let destinationId = destIds.get(hit.host);
|
|
30950
31600
|
if (destinationId === void 0) {
|
|
30951
31601
|
destStmt.run({
|
|
30952
|
-
id:
|
|
31602
|
+
id: randomUUID10(),
|
|
30953
31603
|
kind: hit.kind,
|
|
30954
31604
|
name: hit.name,
|
|
30955
31605
|
host: hit.host,
|
|
@@ -30965,7 +31615,7 @@ var SqliteSharesRepository = class {
|
|
|
30965
31615
|
let endpointId = endpointIds.get(endpointKey);
|
|
30966
31616
|
if (endpointId === void 0) {
|
|
30967
31617
|
endpointStmt.run({
|
|
30968
|
-
id:
|
|
31618
|
+
id: randomUUID10(),
|
|
30969
31619
|
destinationId,
|
|
30970
31620
|
method: hit.method,
|
|
30971
31621
|
transport: hit.transport,
|
|
@@ -30978,7 +31628,7 @@ var SqliteSharesRepository = class {
|
|
|
30978
31628
|
endpointIds.set(endpointKey, endpointId);
|
|
30979
31629
|
}
|
|
30980
31630
|
siteStmt.run({
|
|
30981
|
-
id:
|
|
31631
|
+
id: randomUUID10(),
|
|
30982
31632
|
endpointId,
|
|
30983
31633
|
project: input2.project,
|
|
30984
31634
|
projectKey: input2.projectKey,
|
|
@@ -31343,6 +31993,7 @@ function purgeSampleData(db) {
|
|
|
31343
31993
|
}
|
|
31344
31994
|
|
|
31345
31995
|
// ../../packages/persistence/src/database.ts
|
|
31996
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31346
31997
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31347
31998
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31348
31999
|
);
|
|
@@ -31390,7 +32041,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31390
32041
|
discardStore(file2, backup);
|
|
31391
32042
|
return backup;
|
|
31392
32043
|
}
|
|
31393
|
-
function openAndInitialize(file2) {
|
|
32044
|
+
function openAndInitialize(file2, base) {
|
|
31394
32045
|
let db = openWithPragmas(file2);
|
|
31395
32046
|
try {
|
|
31396
32047
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31403,7 +32054,7 @@ function openAndInitialize(file2) {
|
|
|
31403
32054
|
applyMigrations(db, file2);
|
|
31404
32055
|
tightenPerms(file2);
|
|
31405
32056
|
const policies = new SqlitePoliciesRepository(db);
|
|
31406
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32057
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31407
32058
|
const repositories = {
|
|
31408
32059
|
events: new SqliteEventsRepository(db),
|
|
31409
32060
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31439,7 +32090,7 @@ function openAndInitialize(file2) {
|
|
|
31439
32090
|
}
|
|
31440
32091
|
function openLocalDatabase(dir) {
|
|
31441
32092
|
ensureDataDirSync(dir);
|
|
31442
|
-
const file2 =
|
|
32093
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31443
32094
|
reapStalePartials(file2);
|
|
31444
32095
|
const {
|
|
31445
32096
|
db,
|
|
@@ -31467,7 +32118,13 @@ function openLocalDatabase(dir) {
|
|
|
31467
32118
|
inspectionDefinitions,
|
|
31468
32119
|
inspectionFindings,
|
|
31469
32120
|
configInventory
|
|
31470
|
-
} = openAndInitialize(
|
|
32121
|
+
} = openAndInitialize(
|
|
32122
|
+
file2,
|
|
32123
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32124
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32125
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32126
|
+
dirname2(dir)
|
|
32127
|
+
);
|
|
31471
32128
|
function captureRowId(event) {
|
|
31472
32129
|
return captureId(
|
|
31473
32130
|
event.metadata?.sessionId ?? null,
|
|
@@ -31480,6 +32137,21 @@ function openLocalDatabase(dir) {
|
|
|
31480
32137
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31481
32138
|
});
|
|
31482
32139
|
}
|
|
32140
|
+
function markCaptureOwed(event) {
|
|
32141
|
+
failOpenTransaction(db, () => {
|
|
32142
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32143
|
+
});
|
|
32144
|
+
}
|
|
32145
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32146
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32147
|
+
if (stampable.length === 0) return;
|
|
32148
|
+
failOpenTransaction(db, () => {
|
|
32149
|
+
historySync.markSynced(
|
|
32150
|
+
stampable.map((event) => event.id),
|
|
32151
|
+
atMs
|
|
32152
|
+
);
|
|
32153
|
+
});
|
|
32154
|
+
}
|
|
31483
32155
|
function recordCapture(event, detected) {
|
|
31484
32156
|
failOpenTransaction(db, () => {
|
|
31485
32157
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31566,7 +32238,7 @@ function openLocalDatabase(dir) {
|
|
|
31566
32238
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31567
32239
|
if (!definitionId) continue;
|
|
31568
32240
|
inspectionFindings.insertFinding({
|
|
31569
|
-
id:
|
|
32241
|
+
id: randomUUID11(),
|
|
31570
32242
|
auditEventId: record2.scanEvent.id,
|
|
31571
32243
|
inspectionDefinitionId: definitionId,
|
|
31572
32244
|
span: finding.span,
|
|
@@ -31662,6 +32334,8 @@ function openLocalDatabase(dir) {
|
|
|
31662
32334
|
inspectionFindings,
|
|
31663
32335
|
recordCapture,
|
|
31664
32336
|
markCaptureDelivered,
|
|
32337
|
+
markCaptureOwed,
|
|
32338
|
+
markAuditEventsDelivered,
|
|
31665
32339
|
ensureInventory,
|
|
31666
32340
|
recordConfigScan,
|
|
31667
32341
|
recordProjectFiles,
|
|
@@ -31700,32 +32374,18 @@ var UserGrantPolicyProvider = class {
|
|
|
31700
32374
|
}
|
|
31701
32375
|
};
|
|
31702
32376
|
|
|
31703
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31704
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31705
|
-
import {
|
|
31706
|
-
closeSync,
|
|
31707
|
-
existsSync as existsSync2,
|
|
31708
|
-
openSync,
|
|
31709
|
-
readFileSync as readFileSync2,
|
|
31710
|
-
rmSync as rmSync5,
|
|
31711
|
-
statSync as statSync3,
|
|
31712
|
-
writeFileSync as writeFileSync2
|
|
31713
|
-
} from "fs";
|
|
31714
|
-
import { hostname as hostname3 } from "os";
|
|
31715
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31716
|
-
|
|
31717
32377
|
// ../../packages/persistence/src/finding-key.ts
|
|
31718
32378
|
import { createHash as createHash3 } from "crypto";
|
|
31719
32379
|
|
|
31720
32380
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31721
32381
|
import { createHmac, randomBytes } from "crypto";
|
|
31722
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31723
|
-
import { join as
|
|
32382
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32383
|
+
import { join as join8 } from "path";
|
|
31724
32384
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31725
32385
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31726
32386
|
var KEY_MATERIAL_BYTES = 32;
|
|
31727
32387
|
function keyFilePath(dataDir2) {
|
|
31728
|
-
return
|
|
32388
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31729
32389
|
}
|
|
31730
32390
|
function parseKeyFile(raw) {
|
|
31731
32391
|
const parsed2 = JSON.parse(raw);
|
|
@@ -31763,7 +32423,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
31763
32423
|
}
|
|
31764
32424
|
};
|
|
31765
32425
|
function storedKeyVersionFloor(dataDir2) {
|
|
31766
|
-
const file2 =
|
|
32426
|
+
const file2 = join8(dataDir2, DB_FILENAME);
|
|
31767
32427
|
if (!existsSync3(file2)) return 0;
|
|
31768
32428
|
let db;
|
|
31769
32429
|
try {
|
|
@@ -31818,7 +32478,7 @@ function occupantMessage(file2, kind) {
|
|
|
31818
32478
|
function readFingerprintKey(dataDir2) {
|
|
31819
32479
|
let raw;
|
|
31820
32480
|
try {
|
|
31821
|
-
raw =
|
|
32481
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31822
32482
|
} catch (err) {
|
|
31823
32483
|
if (err.code === "ENOENT") return null;
|
|
31824
32484
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31842,146 +32502,12 @@ function fingerprintValue(key, raw) {
|
|
|
31842
32502
|
|
|
31843
32503
|
// ../../packages/persistence/src/history-preview.ts
|
|
31844
32504
|
import { existsSync as existsSync4 } from "fs";
|
|
31845
|
-
import { join as
|
|
32505
|
+
import { join as join9 } from "path";
|
|
31846
32506
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31847
32507
|
|
|
31848
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31849
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31850
|
-
import { mkdir } from "fs/promises";
|
|
31851
|
-
import { homedir } from "os";
|
|
31852
|
-
import { join as join7 } from "path";
|
|
31853
|
-
function defaultDataDir() {
|
|
31854
|
-
return join7(homedir(), ".aka");
|
|
31855
|
-
}
|
|
31856
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31857
|
-
return join7(base, "settings");
|
|
31858
|
-
}
|
|
31859
|
-
function dataDir(base = defaultDataDir()) {
|
|
31860
|
-
return join7(base, "data");
|
|
31861
|
-
}
|
|
31862
|
-
function dbPath(base = defaultDataDir()) {
|
|
31863
|
-
return join7(dataDir(base), "aka.db");
|
|
31864
|
-
}
|
|
31865
|
-
function keysDir(base = defaultDataDir()) {
|
|
31866
|
-
return join7(base, "keys");
|
|
31867
|
-
}
|
|
31868
|
-
async function ensureDataDir(dir = defaultDataDir()) {
|
|
31869
|
-
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
31870
|
-
tightenDir(dir);
|
|
31871
|
-
}
|
|
31872
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31873
|
-
ensureDataDirSync(dir);
|
|
31874
|
-
}
|
|
31875
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31876
|
-
const moves = [
|
|
31877
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31878
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31879
|
-
];
|
|
31880
|
-
for (const { name, dest } of moves) {
|
|
31881
|
-
try {
|
|
31882
|
-
ensureDataDirSync(dest);
|
|
31883
|
-
const moved = join7(dest, name);
|
|
31884
|
-
renameSync3(join7(base, name), moved);
|
|
31885
|
-
tightenFile(moved);
|
|
31886
|
-
} catch {
|
|
31887
|
-
}
|
|
31888
|
-
}
|
|
31889
|
-
}
|
|
31890
|
-
|
|
31891
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
31892
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
31893
|
-
import { posix, win32 } from "path";
|
|
31894
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
31895
|
-
if (platform2 === "darwin") {
|
|
31896
|
-
return [
|
|
31897
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31898
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31899
|
-
];
|
|
31900
|
-
}
|
|
31901
|
-
if (platform2 === "win32") {
|
|
31902
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31903
|
-
}
|
|
31904
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31905
|
-
}
|
|
31906
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
31907
|
-
for (const path of paths) {
|
|
31908
|
-
let text;
|
|
31909
|
-
try {
|
|
31910
|
-
text = readFileSync4(path, "utf8");
|
|
31911
|
-
} catch {
|
|
31912
|
-
continue;
|
|
31913
|
-
}
|
|
31914
|
-
const record2 = parseJsonObject(text);
|
|
31915
|
-
if (!record2) continue;
|
|
31916
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
31917
|
-
if (parsed2.success) return parsed2.data;
|
|
31918
|
-
}
|
|
31919
|
-
return null;
|
|
31920
|
-
}
|
|
31921
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31922
|
-
if (!managed) return settings;
|
|
31923
|
-
const { values } = managed;
|
|
31924
|
-
const merged = { ...settings };
|
|
31925
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31926
|
-
if (values.controlPlane !== void 0) {
|
|
31927
|
-
merged.controlPlane = {
|
|
31928
|
-
...values.controlPlane,
|
|
31929
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31930
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31931
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31932
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31933
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31934
|
-
};
|
|
31935
|
-
}
|
|
31936
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31937
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31938
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31939
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31940
|
-
if (values.vaultConsent !== void 0) {
|
|
31941
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31942
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31943
|
-
// at the current version otherwise.
|
|
31944
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31945
|
-
) : void 0;
|
|
31946
|
-
}
|
|
31947
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31948
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31949
|
-
acknowledgedAt: now().toISOString(),
|
|
31950
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31951
|
-
} : void 0;
|
|
31952
|
-
}
|
|
31953
|
-
return merged;
|
|
31954
|
-
}
|
|
31955
|
-
|
|
31956
|
-
// ../../packages/persistence/src/settings.ts
|
|
31957
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31958
|
-
import { join as join8 } from "path";
|
|
31959
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31960
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31961
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31962
|
-
}
|
|
31963
|
-
function readUserSettings(base) {
|
|
31964
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31965
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31966
|
-
try {
|
|
31967
|
-
return WorkspaceSettings.parse(record2);
|
|
31968
|
-
} catch {
|
|
31969
|
-
return defaultWorkspaceSettings();
|
|
31970
|
-
}
|
|
31971
|
-
}
|
|
31972
|
-
function readJson(file2) {
|
|
31973
|
-
let text;
|
|
31974
|
-
try {
|
|
31975
|
-
text = readFileSync5(file2, "utf8");
|
|
31976
|
-
} catch {
|
|
31977
|
-
return null;
|
|
31978
|
-
}
|
|
31979
|
-
return parseJsonObject(text) ?? null;
|
|
31980
|
-
}
|
|
31981
|
-
|
|
31982
32508
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31983
32509
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31984
|
-
import { dirname as
|
|
32510
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31985
32511
|
|
|
31986
32512
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
31987
32513
|
import {
|
|
@@ -32094,8 +32620,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
32094
32620
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32095
32621
|
import { execFileSync } from "child_process";
|
|
32096
32622
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32097
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32098
|
-
import { join as
|
|
32623
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32624
|
+
import { join as join11 } from "path";
|
|
32099
32625
|
var VAULT_OCCUPANT_REASON = {
|
|
32100
32626
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
32101
32627
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -32194,7 +32720,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32194
32720
|
throw asError(err);
|
|
32195
32721
|
}
|
|
32196
32722
|
try {
|
|
32197
|
-
writeFileSync3(
|
|
32723
|
+
writeFileSync3(join11(lock, LOCK_OWNER_FILE), `${owner}
|
|
32198
32724
|
`, { mode: DATA_FILE_MODE });
|
|
32199
32725
|
return true;
|
|
32200
32726
|
} catch (err) {
|
|
@@ -32203,7 +32729,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32203
32729
|
}
|
|
32204
32730
|
}
|
|
32205
32731
|
function acquireRotationLock(keysDir2) {
|
|
32206
|
-
const lock =
|
|
32732
|
+
const lock = join11(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
32207
32733
|
const owner = randomBytes2(16).toString("hex");
|
|
32208
32734
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
32209
32735
|
let held;
|
|
@@ -32230,7 +32756,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
32230
32756
|
}
|
|
32231
32757
|
function releaseRotationLock(lease) {
|
|
32232
32758
|
try {
|
|
32233
|
-
if (
|
|
32759
|
+
if (readFileSync7(join11(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
32234
32760
|
} catch {
|
|
32235
32761
|
return;
|
|
32236
32762
|
}
|
|
@@ -32251,7 +32777,7 @@ var FileKeyProvider = class {
|
|
|
32251
32777
|
this.#keysDir = keysDir2;
|
|
32252
32778
|
}
|
|
32253
32779
|
get filePath() {
|
|
32254
|
-
return
|
|
32780
|
+
return join11(this.#keysDir, VAULT_KEY_FILENAME);
|
|
32255
32781
|
}
|
|
32256
32782
|
loadOrCreate() {
|
|
32257
32783
|
return asAsync(() => {
|
|
@@ -32281,7 +32807,7 @@ var FileKeyProvider = class {
|
|
|
32281
32807
|
#read() {
|
|
32282
32808
|
let raw;
|
|
32283
32809
|
try {
|
|
32284
|
-
raw =
|
|
32810
|
+
raw = readFileSync7(this.filePath, "utf8");
|
|
32285
32811
|
} catch (err) {
|
|
32286
32812
|
if (err.code === "ENOENT") return null;
|
|
32287
32813
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -32568,7 +33094,14 @@ var SecretVault = class {
|
|
|
32568
33094
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
32569
33095
|
const now = this.#now();
|
|
32570
33096
|
if (existing) {
|
|
32571
|
-
this.#repo.upsert(
|
|
33097
|
+
this.#repo.upsert(
|
|
33098
|
+
{
|
|
33099
|
+
...existing,
|
|
33100
|
+
provider: existing.provider ?? void 0,
|
|
33101
|
+
userAuthorized: meta4.userAuthorized === true
|
|
33102
|
+
},
|
|
33103
|
+
now
|
|
33104
|
+
);
|
|
32572
33105
|
return await this.#emitToken(existing.keyVersion, existing.pointerId, existing.category);
|
|
32573
33106
|
}
|
|
32574
33107
|
const { material, version: version2 } = await this.#keys.loadOrCreate();
|
|
@@ -32590,6 +33123,7 @@ var SecretVault = class {
|
|
|
32590
33123
|
ruleId: meta4.ruleId,
|
|
32591
33124
|
maskedMatch: meta4.maskedMatch,
|
|
32592
33125
|
provider: meta4.provider,
|
|
33126
|
+
userAuthorized: meta4.userAuthorized === true,
|
|
32593
33127
|
ciphertext: sealed.ciphertext.toString("base64"),
|
|
32594
33128
|
nonce: sealed.nonce.toString("base64"),
|
|
32595
33129
|
authTag: sealed.authTag.toString("base64")
|
|
@@ -32909,11 +33443,11 @@ var SecretVault = class {
|
|
|
32909
33443
|
|
|
32910
33444
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32911
33445
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32912
|
-
import { join as
|
|
33446
|
+
import { join as join12 } from "path";
|
|
32913
33447
|
var MARKER = "warn-era-capped";
|
|
32914
33448
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32915
33449
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32916
|
-
const marker =
|
|
33450
|
+
const marker = join12(dataDir2, MARKER);
|
|
32917
33451
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
32918
33452
|
const capped = db.policies.capCategoryActions();
|
|
32919
33453
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -32983,7 +33517,7 @@ function providerFromModelId(modelId) {
|
|
|
32983
33517
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32984
33518
|
try {
|
|
32985
33519
|
ensureLayoutDirSync(base);
|
|
32986
|
-
const settingsFile =
|
|
33520
|
+
const settingsFile = join13(settingsDir(base), "settings.json");
|
|
32987
33521
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32988
33522
|
} catch {
|
|
32989
33523
|
}
|
|
@@ -33007,9 +33541,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
33007
33541
|
}
|
|
33008
33542
|
|
|
33009
33543
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
33010
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
33544
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
33011
33545
|
import { homedir as homedir2 } from "os";
|
|
33012
|
-
import { basename as basename3, join as
|
|
33546
|
+
import { basename as basename3, join as join15 } from "path";
|
|
33013
33547
|
|
|
33014
33548
|
// ../../packages/detections/src/egress/registry.ts
|
|
33015
33549
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -36053,8 +36587,8 @@ function scanText(text, ruleVersions) {
|
|
|
36053
36587
|
}
|
|
36054
36588
|
|
|
36055
36589
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
36056
|
-
import { existsSync as existsSync8, readFileSync as
|
|
36057
|
-
import { basename as basename2, dirname as
|
|
36590
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
36591
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join14, sep as sep2 } from "path";
|
|
36058
36592
|
function resolveRepoIdentity(cwd) {
|
|
36059
36593
|
try {
|
|
36060
36594
|
const root = findGitRoot(cwd);
|
|
@@ -36087,36 +36621,36 @@ function resolveRepoNwo(cwd) {
|
|
|
36087
36621
|
function findGitRoot(start) {
|
|
36088
36622
|
let dir = start;
|
|
36089
36623
|
for (; ; ) {
|
|
36090
|
-
if (existsSync8(
|
|
36091
|
-
const parent =
|
|
36624
|
+
if (existsSync8(join14(dir, ".git"))) return dir;
|
|
36625
|
+
const parent = dirname4(dir);
|
|
36092
36626
|
if (parent === dir) return void 0;
|
|
36093
36627
|
dir = parent;
|
|
36094
36628
|
}
|
|
36095
36629
|
}
|
|
36096
36630
|
function resolveGitContext(root) {
|
|
36097
|
-
const dotGit =
|
|
36631
|
+
const dotGit = join14(root, ".git");
|
|
36098
36632
|
try {
|
|
36099
36633
|
if (statSync6(dotGit).isDirectory()) {
|
|
36100
|
-
return { configPath:
|
|
36634
|
+
return { configPath: join14(dotGit, "config"), headRoot: root };
|
|
36101
36635
|
}
|
|
36102
36636
|
} catch {
|
|
36103
36637
|
return void 0;
|
|
36104
36638
|
}
|
|
36105
36639
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
36106
36640
|
if (!target) return void 0;
|
|
36107
|
-
const gitdir = isAbsolute(target) ? target :
|
|
36108
|
-
if (existsSync8(
|
|
36109
|
-
return { configPath:
|
|
36641
|
+
const gitdir = isAbsolute(target) ? target : join14(root, target);
|
|
36642
|
+
if (existsSync8(join14(gitdir, "config"))) {
|
|
36643
|
+
return { configPath: join14(gitdir, "config"), headRoot: root };
|
|
36110
36644
|
}
|
|
36111
|
-
const commonRaw = safeRead(
|
|
36645
|
+
const commonRaw = safeRead(join14(gitdir, "commondir"))?.trim();
|
|
36112
36646
|
if (!commonRaw) return void 0;
|
|
36113
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
36114
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
36115
|
-
return { configPath:
|
|
36647
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join14(gitdir, commonRaw);
|
|
36648
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
|
|
36649
|
+
return { configPath: join14(commonGitDir, "config"), headRoot };
|
|
36116
36650
|
}
|
|
36117
36651
|
function safeRead(path) {
|
|
36118
36652
|
try {
|
|
36119
|
-
return
|
|
36653
|
+
return readFileSync8(path, "utf8");
|
|
36120
36654
|
} catch {
|
|
36121
36655
|
return void 0;
|
|
36122
36656
|
}
|
|
@@ -36176,8 +36710,8 @@ import { Worker } from "worker_threads";
|
|
|
36176
36710
|
|
|
36177
36711
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
36178
36712
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
36179
|
-
import { readFileSync as
|
|
36180
|
-
import { join as
|
|
36713
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
36714
|
+
import { join as join16 } from "path";
|
|
36181
36715
|
|
|
36182
36716
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
36183
36717
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -36214,24 +36748,59 @@ import {
|
|
|
36214
36748
|
fstatSync,
|
|
36215
36749
|
mkdirSync as mkdirSync2,
|
|
36216
36750
|
openSync as openSync2,
|
|
36217
|
-
readFileSync as
|
|
36751
|
+
readFileSync as readFileSync11,
|
|
36218
36752
|
readSync,
|
|
36219
36753
|
writeFileSync as writeFileSync5
|
|
36220
36754
|
} from "fs";
|
|
36221
|
-
import { join as
|
|
36755
|
+
import { join as join17 } from "path";
|
|
36222
36756
|
var TAIL_BYTES = 256 * 1024;
|
|
36223
36757
|
|
|
36224
36758
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
36225
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
36226
|
-
import { join as
|
|
36759
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
36760
|
+
import { join as join18 } from "path";
|
|
36227
36761
|
|
|
36228
36762
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
36229
36763
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
36230
|
-
import { basename as basename4, dirname as
|
|
36764
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
36765
|
+
|
|
36766
|
+
// ../../packages/plugin-sdk/src/policy-resolver.ts
|
|
36767
|
+
function createPolicyResolver(bundle) {
|
|
36768
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
36769
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
36770
|
+
let reversible = /* @__PURE__ */ new Set();
|
|
36771
|
+
try {
|
|
36772
|
+
for (const policy of bundle.policies) {
|
|
36773
|
+
if (!policy.enabled) continue;
|
|
36774
|
+
if ("ruleId" in policy.target) {
|
|
36775
|
+
if (!byRule.has(policy.target.ruleId)) byRule.set(policy.target.ruleId, policy.action);
|
|
36776
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
36777
|
+
byCategory.set(policy.target.category, policy.action);
|
|
36778
|
+
}
|
|
36779
|
+
}
|
|
36780
|
+
reversible = new Set(bundle.reversibleRuleIds ?? []);
|
|
36781
|
+
} catch {
|
|
36782
|
+
byRule.clear();
|
|
36783
|
+
byCategory.clear();
|
|
36784
|
+
reversible = /* @__PURE__ */ new Set();
|
|
36785
|
+
}
|
|
36786
|
+
return {
|
|
36787
|
+
actionFor(ruleId, category) {
|
|
36788
|
+
const byRuleAction = byRule.get(ruleId);
|
|
36789
|
+
if (byRuleAction !== void 0) return byRuleAction;
|
|
36790
|
+
const byCategoryAction = byCategory.get(category);
|
|
36791
|
+
if (byCategoryAction !== void 0) return byCategoryAction;
|
|
36792
|
+
const fallback = DEFAULT_ACTIONS[category];
|
|
36793
|
+
return fallback ?? "log";
|
|
36794
|
+
},
|
|
36795
|
+
isReversible(ruleId) {
|
|
36796
|
+
return reversible.has(ruleId);
|
|
36797
|
+
}
|
|
36798
|
+
};
|
|
36799
|
+
}
|
|
36231
36800
|
|
|
36232
36801
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
36233
36802
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
36234
|
-
import { basename as basename5, join as
|
|
36803
|
+
import { basename as basename5, join as join19 } from "path";
|
|
36235
36804
|
|
|
36236
36805
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
36237
36806
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -36267,7 +36836,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
36267
36836
|
|
|
36268
36837
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
36269
36838
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
36270
|
-
import { join as
|
|
36839
|
+
import { join as join20 } from "path";
|
|
36271
36840
|
|
|
36272
36841
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
36273
36842
|
function redactedPlaceholder(category) {
|
|
@@ -36329,14 +36898,26 @@ var SecretVaultGlue = class {
|
|
|
36329
36898
|
}
|
|
36330
36899
|
async tokenizeText(text, opts) {
|
|
36331
36900
|
try {
|
|
36332
|
-
const
|
|
36333
|
-
const
|
|
36334
|
-
const
|
|
36335
|
-
if (
|
|
36336
|
-
|
|
36901
|
+
const supplied = opts?.findings;
|
|
36902
|
+
const resolver = opts?.resolver;
|
|
36903
|
+
const scanned = supplied ?? this.#selfScan(text);
|
|
36904
|
+
if (scanned === null) {
|
|
36905
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
36906
|
+
}
|
|
36907
|
+
const findings = supplied === void 0 && resolver !== void 0 ? scanned.filter(
|
|
36908
|
+
(f) => isActionAtLeast(resolver.actionFor(f.ruleId, f.category), "redact")
|
|
36909
|
+
) : scanned;
|
|
36910
|
+
let reversible = opts?.reversible;
|
|
36911
|
+
if (resolver !== void 0 && reversible === void 0) {
|
|
36912
|
+
reversible = new Set(findings.filter((f) => resolver.isReversible(f.ruleId)));
|
|
36913
|
+
}
|
|
36914
|
+
const reversibleSet = reversible;
|
|
36915
|
+
const keeps = (finding) => reversibleSet === void 0 || reversibleSet.has(finding);
|
|
36916
|
+
if (findings.length === 0) return { text, pointers: [], degraded: [], redacted: [] };
|
|
36337
36917
|
const groups = groupSpans(text, findings);
|
|
36338
36918
|
const pointers = [];
|
|
36339
36919
|
const degraded = [];
|
|
36920
|
+
const redacted = [];
|
|
36340
36921
|
let out = text;
|
|
36341
36922
|
for (const group of [...groups].reverse()) {
|
|
36342
36923
|
const original = text.slice(group.start, group.end);
|
|
@@ -36350,6 +36931,7 @@ var SecretVaultGlue = class {
|
|
|
36350
36931
|
degraded.unshift({ category: group.category });
|
|
36351
36932
|
} else if (!keeps(finding)) {
|
|
36352
36933
|
replacement = redactedPlaceholder(finding.category);
|
|
36934
|
+
redacted.unshift({ category: finding.category });
|
|
36353
36935
|
} else {
|
|
36354
36936
|
replacement = await this.tokenizeValue(finding.rawMatch, {
|
|
36355
36937
|
ruleId: finding.ruleId,
|
|
@@ -36370,9 +36952,9 @@ var SecretVaultGlue = class {
|
|
|
36370
36952
|
}
|
|
36371
36953
|
}
|
|
36372
36954
|
}
|
|
36373
|
-
return { text: out, pointers, degraded };
|
|
36955
|
+
return { text: out, pointers, degraded, redacted };
|
|
36374
36956
|
} catch {
|
|
36375
|
-
return { text: "[REDACTED]", pointers: [], degraded: [] };
|
|
36957
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
36376
36958
|
}
|
|
36377
36959
|
}
|
|
36378
36960
|
async detokenizeText(text, opts) {
|
|
@@ -36606,6 +37188,307 @@ function toEgressIngestRequest(input2) {
|
|
|
36606
37188
|
};
|
|
36607
37189
|
}
|
|
36608
37190
|
|
|
37191
|
+
// ../../packages/remote/src/http.ts
|
|
37192
|
+
import { request as httpRequest } from "http";
|
|
37193
|
+
import { request as httpsRequest } from "https";
|
|
37194
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
37195
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
37196
|
+
var RemoteRequestError = class extends Error {
|
|
37197
|
+
constructor(status) {
|
|
37198
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
37199
|
+
this.status = status;
|
|
37200
|
+
this.name = "RemoteRequestError";
|
|
37201
|
+
}
|
|
37202
|
+
status;
|
|
37203
|
+
};
|
|
37204
|
+
var RemoteRouteAbsent = class extends Error {
|
|
37205
|
+
constructor(route) {
|
|
37206
|
+
super(`control plane does not serve ${route}`);
|
|
37207
|
+
this.route = route;
|
|
37208
|
+
this.name = "RemoteRouteAbsent";
|
|
37209
|
+
}
|
|
37210
|
+
route;
|
|
37211
|
+
};
|
|
37212
|
+
var RemoteRequestInvalid = class extends Error {
|
|
37213
|
+
constructor(route, cause) {
|
|
37214
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
37215
|
+
this.cause = cause;
|
|
37216
|
+
this.name = "RemoteRequestInvalid";
|
|
37217
|
+
}
|
|
37218
|
+
cause;
|
|
37219
|
+
};
|
|
37220
|
+
var RemoteResponseInvalid = class extends Error {
|
|
37221
|
+
constructor(route, detail) {
|
|
37222
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
37223
|
+
this.name = "RemoteResponseInvalid";
|
|
37224
|
+
}
|
|
37225
|
+
};
|
|
37226
|
+
var RemoteTransportError = class extends Error {
|
|
37227
|
+
/**
|
|
37228
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
37229
|
+
* refused.
|
|
37230
|
+
*
|
|
37231
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
37232
|
+
* all. It exists because two paths reject after a status has already been
|
|
37233
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
37234
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
37235
|
+
* outage, which sends the reader to look at their network instead of their
|
|
37236
|
+
* credential.
|
|
37237
|
+
*/
|
|
37238
|
+
constructor(reason, status) {
|
|
37239
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
37240
|
+
this.status = status;
|
|
37241
|
+
this.name = "RemoteTransportError";
|
|
37242
|
+
}
|
|
37243
|
+
status;
|
|
37244
|
+
};
|
|
37245
|
+
async function send(options) {
|
|
37246
|
+
const url2 = new URL(options.url);
|
|
37247
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
37248
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
37249
|
+
const requestOptions = {
|
|
37250
|
+
method: options.method,
|
|
37251
|
+
headers: {
|
|
37252
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
37253
|
+
// last they win, and two of the values below are ones no caller may
|
|
37254
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
37255
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
37256
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
37257
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
37258
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
37259
|
+
// conditional GET — is untouched by this order.
|
|
37260
|
+
...options.headers,
|
|
37261
|
+
// The credential. One header, matching what the deployment authenticates
|
|
37262
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
37263
|
+
// it can be logged by an intermediary for no gain.
|
|
37264
|
+
//
|
|
37265
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
37266
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
37267
|
+
// key differently from an absent one, and "the header is not there" is
|
|
37268
|
+
// the property the attach flow needs.
|
|
37269
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
37270
|
+
accept: "application/json",
|
|
37271
|
+
...options.body === void 0 ? {} : {
|
|
37272
|
+
"content-type": "application/json",
|
|
37273
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
37274
|
+
// character count is truncated by the receiver.
|
|
37275
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
37276
|
+
}
|
|
37277
|
+
}
|
|
37278
|
+
};
|
|
37279
|
+
return new Promise((resolve3, reject) => {
|
|
37280
|
+
let settled = false;
|
|
37281
|
+
const fail = (reason, status) => {
|
|
37282
|
+
if (settled) return;
|
|
37283
|
+
settled = true;
|
|
37284
|
+
reject(new RemoteTransportError(reason, status));
|
|
37285
|
+
};
|
|
37286
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
37287
|
+
const chunks = [];
|
|
37288
|
+
let size = 0;
|
|
37289
|
+
res.on("data", (chunk) => {
|
|
37290
|
+
size += chunk.length;
|
|
37291
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
37292
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
37293
|
+
res.destroy();
|
|
37294
|
+
req.destroy();
|
|
37295
|
+
return;
|
|
37296
|
+
}
|
|
37297
|
+
chunks.push(chunk);
|
|
37298
|
+
});
|
|
37299
|
+
res.on("aborted", () => {
|
|
37300
|
+
fail("the response was aborted", res.statusCode);
|
|
37301
|
+
});
|
|
37302
|
+
res.on("end", () => {
|
|
37303
|
+
if (settled) return;
|
|
37304
|
+
settled = true;
|
|
37305
|
+
resolve3({
|
|
37306
|
+
status: res.statusCode ?? 0,
|
|
37307
|
+
headers: res.headers,
|
|
37308
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
37309
|
+
});
|
|
37310
|
+
});
|
|
37311
|
+
});
|
|
37312
|
+
const deadline = setTimeout(() => {
|
|
37313
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
37314
|
+
req.destroy();
|
|
37315
|
+
}, timeoutMs);
|
|
37316
|
+
deadline.unref();
|
|
37317
|
+
req.on("upgrade", (_res, socket) => {
|
|
37318
|
+
fail("the deployment answered with a protocol upgrade");
|
|
37319
|
+
socket.destroy();
|
|
37320
|
+
});
|
|
37321
|
+
req.on("close", () => {
|
|
37322
|
+
fail("the connection closed before a response was read");
|
|
37323
|
+
clearTimeout(deadline);
|
|
37324
|
+
});
|
|
37325
|
+
req.on("error", (err) => {
|
|
37326
|
+
fail(err.message);
|
|
37327
|
+
});
|
|
37328
|
+
if (options.body !== void 0) req.write(options.body);
|
|
37329
|
+
req.end();
|
|
37330
|
+
});
|
|
37331
|
+
}
|
|
37332
|
+
|
|
37333
|
+
// ../../packages/remote/src/client.ts
|
|
37334
|
+
var ROUTES = {
|
|
37335
|
+
events: "/v1/events",
|
|
37336
|
+
auditEvents: "/v1/audit-events",
|
|
37337
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
37338
|
+
inventory: "/v1/inventory",
|
|
37339
|
+
storePosture: "/v1/store-posture",
|
|
37340
|
+
policyBundle: "/v1/policy-bundle",
|
|
37341
|
+
whoami: "/v1/plugin/whoami",
|
|
37342
|
+
shares: "/v1/shares",
|
|
37343
|
+
commands: "/v1/plugin/commands"
|
|
37344
|
+
};
|
|
37345
|
+
function ackRoute(id) {
|
|
37346
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
37347
|
+
}
|
|
37348
|
+
function headerValue(response, name) {
|
|
37349
|
+
const raw = response.headers[name];
|
|
37350
|
+
if (raw === void 0) return void 0;
|
|
37351
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
37352
|
+
}
|
|
37353
|
+
function okBody(response) {
|
|
37354
|
+
if (response.status < 200 || response.status >= 300) {
|
|
37355
|
+
throw new RemoteRequestError(response.status);
|
|
37356
|
+
}
|
|
37357
|
+
return response.body;
|
|
37358
|
+
}
|
|
37359
|
+
function parsed(schema, body, route) {
|
|
37360
|
+
let json2;
|
|
37361
|
+
try {
|
|
37362
|
+
json2 = JSON.parse(body);
|
|
37363
|
+
} catch {
|
|
37364
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
37365
|
+
}
|
|
37366
|
+
const result = schema.safeParse(json2);
|
|
37367
|
+
if (!result.success) {
|
|
37368
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
37369
|
+
}
|
|
37370
|
+
return result.data;
|
|
37371
|
+
}
|
|
37372
|
+
function withoutTrailingSlashes(endpoint) {
|
|
37373
|
+
let end = endpoint.length;
|
|
37374
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
37375
|
+
return endpoint.slice(0, end);
|
|
37376
|
+
}
|
|
37377
|
+
var SLASH = "/".charCodeAt(0);
|
|
37378
|
+
function createRemoteClient(options) {
|
|
37379
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
37380
|
+
const url2 = (route) => `${base}${route}`;
|
|
37381
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
37382
|
+
const sendOne = async (event) => {
|
|
37383
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
37384
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
37385
|
+
const response = await send({
|
|
37386
|
+
...common,
|
|
37387
|
+
method: "POST",
|
|
37388
|
+
url: url2(ROUTES.auditEvents),
|
|
37389
|
+
body: JSON.stringify(validated.data)
|
|
37390
|
+
});
|
|
37391
|
+
okBody(response);
|
|
37392
|
+
};
|
|
37393
|
+
return {
|
|
37394
|
+
async ingestEvents(batch) {
|
|
37395
|
+
const response = await send({
|
|
37396
|
+
...common,
|
|
37397
|
+
method: "POST",
|
|
37398
|
+
url: url2(ROUTES.events),
|
|
37399
|
+
body: JSON.stringify(batch)
|
|
37400
|
+
});
|
|
37401
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
37402
|
+
},
|
|
37403
|
+
async ingestInventory(context) {
|
|
37404
|
+
const response = await send({
|
|
37405
|
+
...common,
|
|
37406
|
+
method: "POST",
|
|
37407
|
+
url: url2(ROUTES.inventory),
|
|
37408
|
+
body: JSON.stringify(context)
|
|
37409
|
+
});
|
|
37410
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
37411
|
+
},
|
|
37412
|
+
async recordAuditEvent(event) {
|
|
37413
|
+
await sendOne(event);
|
|
37414
|
+
},
|
|
37415
|
+
async recordAuditEvents(events, opts) {
|
|
37416
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
37417
|
+
if (!validated.success) {
|
|
37418
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
37419
|
+
}
|
|
37420
|
+
const response = await send({
|
|
37421
|
+
...common,
|
|
37422
|
+
method: "POST",
|
|
37423
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
37424
|
+
body: JSON.stringify(validated.data)
|
|
37425
|
+
});
|
|
37426
|
+
if (response.status === 404) {
|
|
37427
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
37428
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
37429
|
+
}
|
|
37430
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
37431
|
+
return { accepted: validated.data.events.length };
|
|
37432
|
+
}
|
|
37433
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
37434
|
+
},
|
|
37435
|
+
async reportStorePosture(snapshot) {
|
|
37436
|
+
const response = await send({
|
|
37437
|
+
...common,
|
|
37438
|
+
method: "POST",
|
|
37439
|
+
url: url2(ROUTES.storePosture),
|
|
37440
|
+
body: JSON.stringify(snapshot)
|
|
37441
|
+
});
|
|
37442
|
+
okBody(response);
|
|
37443
|
+
},
|
|
37444
|
+
async getPolicyBundle(etag) {
|
|
37445
|
+
const response = await send({
|
|
37446
|
+
...common,
|
|
37447
|
+
method: "GET",
|
|
37448
|
+
url: url2(ROUTES.policyBundle),
|
|
37449
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
37450
|
+
});
|
|
37451
|
+
if (response.status === 304) {
|
|
37452
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
37453
|
+
}
|
|
37454
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
37455
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
37456
|
+
},
|
|
37457
|
+
async whoami() {
|
|
37458
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
37459
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
37460
|
+
},
|
|
37461
|
+
async recordProjectEgress(request) {
|
|
37462
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
37463
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
37464
|
+
const response = await send({
|
|
37465
|
+
...common,
|
|
37466
|
+
method: "POST",
|
|
37467
|
+
url: url2(ROUTES.shares),
|
|
37468
|
+
body: JSON.stringify(validated.data)
|
|
37469
|
+
});
|
|
37470
|
+
okBody(response);
|
|
37471
|
+
},
|
|
37472
|
+
async pollCommand() {
|
|
37473
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
37474
|
+
if (response.status === 404) return null;
|
|
37475
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
37476
|
+
},
|
|
37477
|
+
async ackCommand(id, body) {
|
|
37478
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
37479
|
+
const route = ackRoute(id);
|
|
37480
|
+
if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
|
|
37481
|
+
const response = await send({
|
|
37482
|
+
...common,
|
|
37483
|
+
method: "POST",
|
|
37484
|
+
url: url2(route),
|
|
37485
|
+
body: JSON.stringify(validated.data)
|
|
37486
|
+
});
|
|
37487
|
+
okBody(response);
|
|
37488
|
+
}
|
|
37489
|
+
};
|
|
37490
|
+
}
|
|
37491
|
+
|
|
36609
37492
|
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
36610
37493
|
function statusOf(err) {
|
|
36611
37494
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
@@ -36624,12 +37507,27 @@ function classifyFailure(err) {
|
|
|
36624
37507
|
}
|
|
36625
37508
|
}
|
|
36626
37509
|
|
|
37510
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
37511
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
37512
|
+
function withTimeout(promise2, ms) {
|
|
37513
|
+
let timer;
|
|
37514
|
+
const timeout = new Promise((_, reject) => {
|
|
37515
|
+
timer = setTimeout(() => {
|
|
37516
|
+
reject(new Error("attached gateway request timed out"));
|
|
37517
|
+
}, ms);
|
|
37518
|
+
});
|
|
37519
|
+
promise2.catch(() => void 0);
|
|
37520
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
37521
|
+
clearTimeout(timer);
|
|
37522
|
+
});
|
|
37523
|
+
}
|
|
37524
|
+
|
|
36627
37525
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
36628
|
-
import { readFileSync as
|
|
36629
|
-
import { join as
|
|
37526
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
37527
|
+
import { join as join21 } from "path";
|
|
36630
37528
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
36631
37529
|
function forwardDropsPath(dataDir2) {
|
|
36632
|
-
return
|
|
37530
|
+
return join21(dataDir2, FORWARD_DROPS_FILENAME);
|
|
36633
37531
|
}
|
|
36634
37532
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
36635
37533
|
if (count <= 0) return;
|
|
@@ -36647,7 +37545,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
36647
37545
|
}
|
|
36648
37546
|
function readForwardDrops(dataDir2) {
|
|
36649
37547
|
try {
|
|
36650
|
-
const parsed2 = JSON.parse(
|
|
37548
|
+
const parsed2 = JSON.parse(readFileSync13(forwardDropsPath(dataDir2), "utf8"));
|
|
36651
37549
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
36652
37550
|
const record2 = parsed2;
|
|
36653
37551
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -36665,29 +37563,19 @@ function readForwardDrops(dataDir2) {
|
|
|
36665
37563
|
|
|
36666
37564
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
36667
37565
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
36668
|
-
import { readFileSync as
|
|
37566
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
36669
37567
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
36670
|
-
import { join as
|
|
36671
|
-
|
|
36672
|
-
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
36673
|
-
var REQUEST_TIMEOUT_MS = 2e3;
|
|
36674
|
-
function withTimeout(promise2, ms) {
|
|
36675
|
-
let timer;
|
|
36676
|
-
const timeout = new Promise((_, reject) => {
|
|
36677
|
-
timer = setTimeout(() => {
|
|
36678
|
-
reject(new Error("attached gateway request timed out"));
|
|
36679
|
-
}, ms);
|
|
36680
|
-
});
|
|
36681
|
-
promise2.catch(() => void 0);
|
|
36682
|
-
return Promise.race([promise2, timeout]).finally(() => {
|
|
36683
|
-
clearTimeout(timer);
|
|
36684
|
-
});
|
|
36685
|
-
}
|
|
36686
|
-
|
|
36687
|
-
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
37568
|
+
import { join as join22 } from "path";
|
|
36688
37569
|
function isInvalidRequest(err) {
|
|
36689
37570
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
36690
37571
|
}
|
|
37572
|
+
function isRouteAbsent(err) {
|
|
37573
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
|
|
37574
|
+
}
|
|
37575
|
+
function isServerRejection(err) {
|
|
37576
|
+
const status = statusOf(err);
|
|
37577
|
+
return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
|
|
37578
|
+
}
|
|
36691
37579
|
var FORWARD_BUDGET_MS = 1500;
|
|
36692
37580
|
var DECISION_PATH_BUDGET_MS = 800;
|
|
36693
37581
|
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
@@ -36715,7 +37603,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
36715
37603
|
}
|
|
36716
37604
|
function createForwardPolicy(deps) {
|
|
36717
37605
|
const now = deps.now ?? (() => Date.now());
|
|
36718
|
-
const file2 =
|
|
37606
|
+
const file2 = join22(deps.dir, STATE_FILENAME);
|
|
36719
37607
|
let state = null;
|
|
36720
37608
|
let loading = null;
|
|
36721
37609
|
async function readState() {
|
|
@@ -36755,6 +37643,20 @@ function createForwardPolicy(deps) {
|
|
|
36755
37643
|
} catch {
|
|
36756
37644
|
current = { ...CLOSED };
|
|
36757
37645
|
}
|
|
37646
|
+
const restoreOpenedAtMs = (openedAtMs) => persist({
|
|
37647
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
37648
|
+
openedAtMs,
|
|
37649
|
+
lastFailure: current.lastFailure
|
|
37650
|
+
});
|
|
37651
|
+
const recordFailure = (cause) => {
|
|
37652
|
+
const failures = current.consecutiveFailures + 1;
|
|
37653
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
37654
|
+
return persist({
|
|
37655
|
+
consecutiveFailures: failures,
|
|
37656
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
37657
|
+
lastFailure: cause
|
|
37658
|
+
});
|
|
37659
|
+
};
|
|
36758
37660
|
const at = now();
|
|
36759
37661
|
if (current.openedAtMs !== null) {
|
|
36760
37662
|
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
@@ -36773,15 +37675,20 @@ function createForwardPolicy(deps) {
|
|
|
36773
37675
|
}
|
|
36774
37676
|
return { ok: true, value };
|
|
36775
37677
|
} catch (err) {
|
|
36776
|
-
if (isInvalidRequest(err))
|
|
37678
|
+
if (isInvalidRequest(err)) {
|
|
37679
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
|
|
37680
|
+
return { ok: false, reason: "invalid-request" };
|
|
37681
|
+
}
|
|
37682
|
+
if (isRouteAbsent(err)) {
|
|
37683
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
|
|
37684
|
+
return { ok: false, reason: "route-absent" };
|
|
37685
|
+
}
|
|
37686
|
+
if (isServerRejection(err)) {
|
|
37687
|
+
await recordFailure("unreachable");
|
|
37688
|
+
return { ok: false, reason: "rejected" };
|
|
37689
|
+
}
|
|
36777
37690
|
const reason = classifyFailure(err);
|
|
36778
|
-
|
|
36779
|
-
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
36780
|
-
await persist({
|
|
36781
|
-
consecutiveFailures: failures,
|
|
36782
|
-
openedAtMs: shouldOpen ? now() : null,
|
|
36783
|
-
lastFailure: reason
|
|
36784
|
-
});
|
|
37691
|
+
await recordFailure(reason);
|
|
36785
37692
|
return { ok: false, reason };
|
|
36786
37693
|
}
|
|
36787
37694
|
}
|
|
@@ -36789,13 +37696,11 @@ function createForwardPolicy(deps) {
|
|
|
36789
37696
|
}
|
|
36790
37697
|
|
|
36791
37698
|
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
36792
|
-
|
|
36793
|
-
|
|
36794
|
-
|
|
36795
|
-
|
|
36796
|
-
|
|
36797
|
-
block: 4
|
|
36798
|
-
};
|
|
37699
|
+
function strongerOf(a, b) {
|
|
37700
|
+
if (a === null) return b;
|
|
37701
|
+
if (b === null) return a;
|
|
37702
|
+
return strongerAction(a, b);
|
|
37703
|
+
}
|
|
36799
37704
|
function ruleCategoryMap(wireRules, localRules) {
|
|
36800
37705
|
const map2 = /* @__PURE__ */ new Map();
|
|
36801
37706
|
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
@@ -36805,11 +37710,6 @@ function ruleCategoryMap(wireRules, localRules) {
|
|
|
36805
37710
|
}
|
|
36806
37711
|
return map2;
|
|
36807
37712
|
}
|
|
36808
|
-
function strongerOf(a, b) {
|
|
36809
|
-
if (a === null) return b;
|
|
36810
|
-
if (b === null) return a;
|
|
36811
|
-
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
36812
|
-
}
|
|
36813
37713
|
function policyKey(policy) {
|
|
36814
37714
|
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
36815
37715
|
}
|
|
@@ -36828,7 +37728,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
36828
37728
|
const floor = floorFor(policy, categoryByRuleId);
|
|
36829
37729
|
remoteCategoryAction.set(
|
|
36830
37730
|
policy.target.category,
|
|
36831
|
-
floor !== null &&
|
|
37731
|
+
floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
|
|
36832
37732
|
);
|
|
36833
37733
|
}
|
|
36834
37734
|
for (const policy of localPolicies) {
|
|
@@ -36845,7 +37745,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
36845
37745
|
}
|
|
36846
37746
|
merged.set(
|
|
36847
37747
|
key,
|
|
36848
|
-
remoteFloor !== null &&
|
|
37748
|
+
remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
|
|
36849
37749
|
);
|
|
36850
37750
|
}
|
|
36851
37751
|
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
@@ -36865,13 +37765,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
36865
37765
|
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
36866
37766
|
}
|
|
36867
37767
|
const effectiveFloor = strongerOf(floor, localFloor);
|
|
36868
|
-
const clamped = effectiveFloor !== null &&
|
|
37768
|
+
const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
|
|
36869
37769
|
const existing = merged.get(key);
|
|
36870
37770
|
if (existing === void 0) {
|
|
36871
37771
|
merged.set(key, clamped);
|
|
36872
37772
|
continue;
|
|
36873
37773
|
}
|
|
36874
|
-
if (
|
|
37774
|
+
if (actionRank(clamped.action) > actionRank(existing.action)) {
|
|
36875
37775
|
merged.set(key, clamped);
|
|
36876
37776
|
}
|
|
36877
37777
|
}
|
|
@@ -36904,6 +37804,8 @@ var AttachedDataGateway = class {
|
|
|
36904
37804
|
);
|
|
36905
37805
|
if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
|
|
36906
37806
|
this.deps.local.markCaptureDelivered(record2.event, Date.now());
|
|
37807
|
+
} else {
|
|
37808
|
+
this.deps.local.markCaptureOwed(record2.event);
|
|
36907
37809
|
}
|
|
36908
37810
|
}
|
|
36909
37811
|
async ensureInventory(ctx) {
|
|
@@ -36940,9 +37842,10 @@ var AttachedDataGateway = class {
|
|
|
36940
37842
|
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
36941
37843
|
async recordAuditEvent(event) {
|
|
36942
37844
|
await this.deps.local.recordAuditEvent(event);
|
|
36943
|
-
await this.deps.forward.run(
|
|
37845
|
+
const forwarded = await this.deps.forward.run(
|
|
36944
37846
|
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
36945
37847
|
);
|
|
37848
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
36946
37849
|
}
|
|
36947
37850
|
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
36948
37851
|
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
@@ -36951,44 +37854,170 @@ var AttachedDataGateway = class {
|
|
|
36951
37854
|
// which would write the event to the local store a second time.
|
|
36952
37855
|
async recordLlmCall(input2) {
|
|
36953
37856
|
await this.deps.local.recordLlmCall(input2);
|
|
36954
|
-
|
|
36955
|
-
|
|
36956
|
-
|
|
36957
|
-
)
|
|
37857
|
+
const event = llmAuditEvent(input2);
|
|
37858
|
+
const forwarded = await this.deps.forward.run(
|
|
37859
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
36958
37860
|
);
|
|
37861
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
36959
37862
|
}
|
|
36960
37863
|
/**
|
|
36961
|
-
* Forward one batch
|
|
37864
|
+
* Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
|
|
37865
|
+
*
|
|
37866
|
+
* This used to send one HTTP request per event, which is what made the batch
|
|
37867
|
+
* budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
|
|
37868
|
+
* away everything after them. The same rows now cross 50 at a time over
|
|
37869
|
+
* `POST /v1/audit-events/batch` — the route the attach-time drain has always
|
|
37870
|
+
* used — so the same budget admits ~750. The wire cap is the server's own
|
|
37871
|
+
* constant, sized against server cost, and the client REFUSES a longer array
|
|
37872
|
+
* client-side, so the chunking here is not a convention.
|
|
37873
|
+
*
|
|
37874
|
+
* Still serial, and still for the original reason: firing N requests at once
|
|
37875
|
+
* would trade a latency problem for a burst the plane's per-key rate limiting
|
|
37876
|
+
* answers with the refusals the breaker then counts. Fewer, fuller requests is
|
|
37877
|
+
* the fix; more concurrent ones is not.
|
|
37878
|
+
*
|
|
37879
|
+
* When the deadline passes the remainder is dropped rather than sent: the
|
|
37880
|
+
* local write has already succeeded, so every caller has a correct result to
|
|
37881
|
+
* return. What is dropped is COUNTED, everywhere it can happen — this path
|
|
37882
|
+
* returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
|
|
37883
|
+
* `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
|
|
37884
|
+
* the breaker closed, renders a healthy block, and discards the tail of every
|
|
37885
|
+
* batch indefinitely. The SAME tally also covers a single that fails inside
|
|
37886
|
+
* the per-item retry below — the breaker opening mid-retry is a failure the
|
|
37887
|
+
* breaker's own state DOES capture, but the events still in this chunk once
|
|
37888
|
+
* that happens are neither delivered nor otherwise counted anywhere, which is
|
|
37889
|
+
* the same invisibility with a different cause.
|
|
36962
37890
|
*
|
|
36963
|
-
*
|
|
36964
|
-
*
|
|
36965
|
-
*
|
|
36966
|
-
*
|
|
36967
|
-
*
|
|
37891
|
+
* `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
|
|
37892
|
+
* single-event ack and at fifty times the blast radius here:
|
|
37893
|
+
* `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
|
|
37894
|
+
* not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
|
|
37895
|
+
* fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
|
|
37896
|
+
* delivered and never re-offer the twenty the plane did not take. So success
|
|
37897
|
+
* is checked against `chunk.length`; anything short of it falls into the same
|
|
37898
|
+
* per-item pass as a refused chunk, which is the only way to recover the
|
|
37899
|
+
* rows that did not land, since the ack carries no per-row verdict to
|
|
37900
|
+
* resend by.
|
|
36968
37901
|
*
|
|
36969
|
-
*
|
|
36970
|
-
*
|
|
36971
|
-
*
|
|
37902
|
+
* That fallback ASSUMES a re-send of an already-landed row is a harmless
|
|
37903
|
+
* no-op rather than a second cost — an assumption this file cannot verify.
|
|
37904
|
+
* `AuditEventBatchAck` carries only `accepted`, unlike its sibling
|
|
37905
|
+
* `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
|
|
37906
|
+
* the batch size as the invariant `recordCapture` reads), so whether a
|
|
37907
|
+
* duplicate counts toward THIS route's `accepted` is not expressed
|
|
37908
|
+
* anywhere in this repo. If it follows its sibling's convention and does
|
|
37909
|
+
* NOT, a chunk containing even one already-delivered row — the ordinary
|
|
37910
|
+
* consequence of a lost stamp, which this file already treats as cheap —
|
|
37911
|
+
* answers short forever and enters the per-item pass on every pass it is
|
|
37912
|
+
* offered again. The cost of that is bounded rather than silent: the
|
|
37913
|
+
* pass converges (every row lands and stamps), so it is one wasted round
|
|
37914
|
+
* of singles rather than a stall, and it errs toward an extra resend
|
|
37915
|
+
* rather than toward the lost row the alternative risks.
|
|
36972
37916
|
*
|
|
36973
|
-
*
|
|
36974
|
-
*
|
|
36975
|
-
*
|
|
36976
|
-
*
|
|
36977
|
-
*
|
|
36978
|
-
*
|
|
37917
|
+
* BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
|
|
37918
|
+
* one transaction, so a full 2xx settles every event in it and a non-2xx
|
|
37919
|
+
* settles none — which is why the whole chunk is stamped together on a FULL
|
|
37920
|
+
* accept and none of it otherwise. THREE reasons do not deserve whole-chunk
|
|
37921
|
+
* treatment, alongside a short accept, and all are re-sent one event at a
|
|
37922
|
+
* time:
|
|
37923
|
+
*
|
|
37924
|
+
* `invalid-request` a chunk the client refused to send at all. One malformed
|
|
37925
|
+
* event would otherwise cost the 49 good ones beside it —
|
|
37926
|
+
* a new way to lose data introduced by the very change
|
|
37927
|
+
* meant to stop losing it.
|
|
37928
|
+
* `route-absent` a deployment that predates the batch route. The
|
|
37929
|
+
* single-event route is the one it serves, and re-sending
|
|
37930
|
+
* here rather than inside the client is what gives each
|
|
37931
|
+
* request its own budget instead of 50 inside one.
|
|
37932
|
+
* `rejected` the deployment's SERVER-side twin of `invalid-request` —
|
|
37933
|
+
* a 4xx body refusal from schema drift on the other side
|
|
37934
|
+
* of the wire. Settlement is batch-atomic on this reason
|
|
37935
|
+
* exactly as on the others, so leaving it out would cost
|
|
37936
|
+
* the whole chunk for one event the DEPLOYMENT considers
|
|
37937
|
+
* malformed, where the per-item form cost only that one.
|
|
37938
|
+
*
|
|
37939
|
+
* Every other reason (breaker-open, a refusal, a timeout) applies to the whole
|
|
37940
|
+
* chunk, and re-sending it item by item would just spend the budget failing 50
|
|
37941
|
+
* more times — for those, the blast radius stays exactly what it was before
|
|
37942
|
+
* batching.
|
|
36979
37943
|
*/
|
|
36980
37944
|
async forwardBatch(inputs, toEvent) {
|
|
36981
37945
|
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
36982
|
-
|
|
36983
|
-
|
|
36984
|
-
|
|
36985
|
-
|
|
36986
|
-
|
|
37946
|
+
const delivered = [];
|
|
37947
|
+
try {
|
|
37948
|
+
for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
|
|
37949
|
+
const now = Date.now();
|
|
37950
|
+
if (now >= deadline) {
|
|
37951
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
37952
|
+
return;
|
|
37953
|
+
}
|
|
37954
|
+
const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
|
|
37955
|
+
const forwarded = await this.deps.forward.run(
|
|
37956
|
+
() => this.deps.client.recordAuditEvents(
|
|
37957
|
+
chunk.map((event) => reKeyForForward(event, this.remoteInventory))
|
|
37958
|
+
)
|
|
37959
|
+
);
|
|
37960
|
+
if (forwarded.ok) {
|
|
37961
|
+
if (forwarded.value.accepted === chunk.length) {
|
|
37962
|
+
delivered.push(...chunk);
|
|
37963
|
+
continue;
|
|
37964
|
+
}
|
|
37965
|
+
} else if (
|
|
37966
|
+
// THREE reasons are worth a second pass, one at a time, and they are
|
|
37967
|
+
// the three settled BEFORE the control plane refused anything, or
|
|
37968
|
+
// (for `rejected`) refused the BODY rather than the connection.
|
|
37969
|
+
//
|
|
37970
|
+
// `invalid-request` — the CLIENT refused the body before any request
|
|
37971
|
+
// went out: a defect in one event, not an outage. Re-sending singly
|
|
37972
|
+
// isolates the bad one instead of charging its 49 neighbours for it.
|
|
37973
|
+
//
|
|
37974
|
+
// `route-absent` — the deployment predates the batch route and serves
|
|
37975
|
+
// only the single-event one. The retry IS the compatibility path, and
|
|
37976
|
+
// it has to live HERE rather than inside the client: each single gets
|
|
37977
|
+
// its own FORWARD_BUDGET_MS through `run`, whereas the client's own
|
|
37978
|
+
// fallback would spend 50 sequential round trips inside the ONE
|
|
37979
|
+
// budget wrapping this call — turning a working older deployment into
|
|
37980
|
+
// a timeout, three of those into an open breaker, and every row into
|
|
37981
|
+
// a silent drop while the status surface called an answering
|
|
37982
|
+
// deployment down.
|
|
37983
|
+
//
|
|
37984
|
+
// `rejected` — the deployment's own 4xx refusal of the body, the
|
|
37985
|
+
// server-side twin of `invalid-request`: isolating it the same way
|
|
37986
|
+
// costs one event instead of the whole chunk for a defect the
|
|
37987
|
+
// deployment considers local to one row.
|
|
37988
|
+
//
|
|
37989
|
+
// Every other reason (breaker-open, a refusal, a timeout) applies to
|
|
37990
|
+
// the whole chunk; re-sending it item by item would just spend the
|
|
37991
|
+
// budget failing 50 more times.
|
|
37992
|
+
forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
|
|
37993
|
+
) {
|
|
37994
|
+
continue;
|
|
37995
|
+
}
|
|
37996
|
+
for (const [j, event] of chunk.entries()) {
|
|
37997
|
+
const at = Date.now();
|
|
37998
|
+
if (at >= deadline) {
|
|
37999
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
38000
|
+
return;
|
|
38001
|
+
}
|
|
38002
|
+
const single = await this.deps.forward.run(
|
|
38003
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
38004
|
+
);
|
|
38005
|
+
if (single.ok) {
|
|
38006
|
+
delivered.push(event);
|
|
38007
|
+
continue;
|
|
38008
|
+
}
|
|
38009
|
+
if (single.reason === "breaker-open") {
|
|
38010
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
38011
|
+
return;
|
|
38012
|
+
}
|
|
38013
|
+
recordForwardDrops(this.deps.dataDir, 1, at);
|
|
38014
|
+
}
|
|
38015
|
+
}
|
|
38016
|
+
} finally {
|
|
38017
|
+
try {
|
|
38018
|
+
this.deps.local.markAuditEventsDelivered(delivered, Date.now());
|
|
38019
|
+
} catch {
|
|
36987
38020
|
}
|
|
36988
|
-
const input2 = inputs[i];
|
|
36989
|
-
await this.deps.forward.run(
|
|
36990
|
-
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
|
|
36991
|
-
);
|
|
36992
38021
|
}
|
|
36993
38022
|
}
|
|
36994
38023
|
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
@@ -37031,9 +38060,10 @@ var AttachedDataGateway = class {
|
|
|
37031
38060
|
// local store.
|
|
37032
38061
|
async recordConfigScan(record2) {
|
|
37033
38062
|
await this.deps.local.recordConfigScan(record2);
|
|
37034
|
-
await this.deps.forward.run(
|
|
38063
|
+
const forwarded = await this.deps.forward.run(
|
|
37035
38064
|
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
37036
38065
|
);
|
|
38066
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
|
|
37037
38067
|
}
|
|
37038
38068
|
async recordBlockedDetection(entry) {
|
|
37039
38069
|
return this.deps.local.recordBlockedDetection(entry);
|
|
@@ -37167,6 +38197,18 @@ var AttachedDataGateway = class {
|
|
|
37167
38197
|
// exactly what it did, leaving the whole control inert on every device
|
|
37168
38198
|
// while every test around it stayed green.
|
|
37169
38199
|
prohibitedModels: cached2.prohibitedModels
|
|
38200
|
+
// ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
|
|
38201
|
+
// merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
|
|
38202
|
+
// it emits, so an 'authored' policy arriving from the control plane
|
|
38203
|
+
// keeps that marker even where the clamp rebuilds it with a stronger
|
|
38204
|
+
// action. The device reads it in exactly one direction — the rules such a
|
|
38205
|
+
// policy targets are not locally re-assignable — so it sits on the
|
|
38206
|
+
// `prohibitedModels` side of the line for the same reason that field
|
|
38207
|
+
// does: it can only ever ADD a refusal, never relax one, and an unsigned
|
|
38208
|
+
// cache therefore has no relaxation to grant by carrying it. Dropping it
|
|
38209
|
+
// would be the silent failure rather than the safe one — the action would
|
|
38210
|
+
// still be enforced while the local override the organization authored
|
|
38211
|
+
// away quietly came back.
|
|
37170
38212
|
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
37171
38213
|
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
37172
38214
|
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
@@ -37206,10 +38248,10 @@ var AttachedDataGateway = class {
|
|
|
37206
38248
|
//
|
|
37207
38249
|
// Implementing these is what actually closes the skipped-local-maintenance
|
|
37208
38250
|
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
37209
|
-
// any object carrying all
|
|
38251
|
+
// any object carrying them all, so the composite qualifies and SessionStart
|
|
37210
38252
|
// runs maintenance on the device's real store.
|
|
37211
38253
|
//
|
|
37212
|
-
// ⚠
|
|
38254
|
+
// ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
37213
38255
|
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
37214
38256
|
// return value directly; declaring them `async` here would hand those call
|
|
37215
38257
|
// sites a Promise and silently break both.
|
|
@@ -37232,9 +38274,15 @@ var AttachedDataGateway = class {
|
|
|
37232
38274
|
// Delegated like the rest, and SYNCHRONOUS for the reason the note above
|
|
37233
38275
|
// gives: `recordCapture` calls it after the forward has already settled, on a
|
|
37234
38276
|
// path that has nothing left to await.
|
|
38277
|
+
markCaptureOwed(event) {
|
|
38278
|
+
this.deps.local.markCaptureOwed(event);
|
|
38279
|
+
}
|
|
37235
38280
|
markCaptureDelivered(event, atMs) {
|
|
37236
38281
|
this.deps.local.markCaptureDelivered(event, atMs);
|
|
37237
38282
|
}
|
|
38283
|
+
markAuditEventsDelivered(events, atMs) {
|
|
38284
|
+
this.deps.local.markAuditEventsDelivered(events, atMs);
|
|
38285
|
+
}
|
|
37238
38286
|
};
|
|
37239
38287
|
function reKeyForForward(event, remote) {
|
|
37240
38288
|
if (remote === null) {
|
|
@@ -37277,281 +38325,17 @@ function toolAuditEvent(input2) {
|
|
|
37277
38325
|
}
|
|
37278
38326
|
|
|
37279
38327
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
37280
|
-
import { readFileSync as
|
|
37281
|
-
import { join as
|
|
38328
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
38329
|
+
import { join as join23 } from "path";
|
|
37282
38330
|
|
|
37283
38331
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
37284
38332
|
import { createHash as createHash6 } from "crypto";
|
|
37285
38333
|
import { hostname as hostname5 } from "os";
|
|
37286
38334
|
|
|
37287
|
-
// ../../packages/
|
|
37288
|
-
|
|
37289
|
-
|
|
37290
|
-
var
|
|
37291
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
37292
|
-
var RemoteRequestError = class extends Error {
|
|
37293
|
-
constructor(status) {
|
|
37294
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
37295
|
-
this.status = status;
|
|
37296
|
-
this.name = "RemoteRequestError";
|
|
37297
|
-
}
|
|
37298
|
-
status;
|
|
37299
|
-
};
|
|
37300
|
-
var RemoteRequestInvalid = class extends Error {
|
|
37301
|
-
constructor(route, cause) {
|
|
37302
|
-
super(`refusing to send a malformed body to ${route}`);
|
|
37303
|
-
this.cause = cause;
|
|
37304
|
-
this.name = "RemoteRequestInvalid";
|
|
37305
|
-
}
|
|
37306
|
-
cause;
|
|
37307
|
-
};
|
|
37308
|
-
var RemoteResponseInvalid = class extends Error {
|
|
37309
|
-
constructor(route, detail) {
|
|
37310
|
-
super(`control plane answered ${route} with ${detail}`);
|
|
37311
|
-
this.name = "RemoteResponseInvalid";
|
|
37312
|
-
}
|
|
37313
|
-
};
|
|
37314
|
-
var RemoteTransportError = class extends Error {
|
|
37315
|
-
/**
|
|
37316
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
37317
|
-
* refused.
|
|
37318
|
-
*
|
|
37319
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
37320
|
-
* all. It exists because two paths reject after a status has already been
|
|
37321
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
37322
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
37323
|
-
* outage, which sends the reader to look at their network instead of their
|
|
37324
|
-
* credential.
|
|
37325
|
-
*/
|
|
37326
|
-
constructor(reason, status) {
|
|
37327
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
37328
|
-
this.status = status;
|
|
37329
|
-
this.name = "RemoteTransportError";
|
|
37330
|
-
}
|
|
37331
|
-
status;
|
|
37332
|
-
};
|
|
37333
|
-
async function send(options) {
|
|
37334
|
-
const url2 = new URL(options.url);
|
|
37335
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
37336
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
37337
|
-
const requestOptions = {
|
|
37338
|
-
method: options.method,
|
|
37339
|
-
headers: {
|
|
37340
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
37341
|
-
// last they win, and two of the values below are ones no caller may
|
|
37342
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
37343
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
37344
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
37345
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
37346
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
37347
|
-
// conditional GET — is untouched by this order.
|
|
37348
|
-
...options.headers,
|
|
37349
|
-
// The credential. One header, matching what the deployment authenticates
|
|
37350
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
37351
|
-
// it can be logged by an intermediary for no gain.
|
|
37352
|
-
//
|
|
37353
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
37354
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
37355
|
-
// key differently from an absent one, and "the header is not there" is
|
|
37356
|
-
// the property the attach flow needs.
|
|
37357
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
37358
|
-
accept: "application/json",
|
|
37359
|
-
...options.body === void 0 ? {} : {
|
|
37360
|
-
"content-type": "application/json",
|
|
37361
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
37362
|
-
// character count is truncated by the receiver.
|
|
37363
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
37364
|
-
}
|
|
37365
|
-
}
|
|
37366
|
-
};
|
|
37367
|
-
return new Promise((resolve3, reject) => {
|
|
37368
|
-
let settled = false;
|
|
37369
|
-
const fail = (reason, status) => {
|
|
37370
|
-
if (settled) return;
|
|
37371
|
-
settled = true;
|
|
37372
|
-
reject(new RemoteTransportError(reason, status));
|
|
37373
|
-
};
|
|
37374
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
37375
|
-
const chunks = [];
|
|
37376
|
-
let size = 0;
|
|
37377
|
-
res.on("data", (chunk) => {
|
|
37378
|
-
size += chunk.length;
|
|
37379
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
37380
|
-
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
37381
|
-
res.destroy();
|
|
37382
|
-
req.destroy();
|
|
37383
|
-
return;
|
|
37384
|
-
}
|
|
37385
|
-
chunks.push(chunk);
|
|
37386
|
-
});
|
|
37387
|
-
res.on("aborted", () => {
|
|
37388
|
-
fail("the response was aborted", res.statusCode);
|
|
37389
|
-
});
|
|
37390
|
-
res.on("end", () => {
|
|
37391
|
-
if (settled) return;
|
|
37392
|
-
settled = true;
|
|
37393
|
-
resolve3({
|
|
37394
|
-
status: res.statusCode ?? 0,
|
|
37395
|
-
headers: res.headers,
|
|
37396
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
37397
|
-
});
|
|
37398
|
-
});
|
|
37399
|
-
});
|
|
37400
|
-
const deadline = setTimeout(() => {
|
|
37401
|
-
fail(`no response within ${String(timeoutMs)}ms`);
|
|
37402
|
-
req.destroy();
|
|
37403
|
-
}, timeoutMs);
|
|
37404
|
-
deadline.unref();
|
|
37405
|
-
req.on("upgrade", (_res, socket) => {
|
|
37406
|
-
fail("the deployment answered with a protocol upgrade");
|
|
37407
|
-
socket.destroy();
|
|
37408
|
-
});
|
|
37409
|
-
req.on("close", () => {
|
|
37410
|
-
fail("the connection closed before a response was read");
|
|
37411
|
-
clearTimeout(deadline);
|
|
37412
|
-
});
|
|
37413
|
-
req.on("error", (err) => {
|
|
37414
|
-
fail(err.message);
|
|
37415
|
-
});
|
|
37416
|
-
if (options.body !== void 0) req.write(options.body);
|
|
37417
|
-
req.end();
|
|
37418
|
-
});
|
|
37419
|
-
}
|
|
37420
|
-
|
|
37421
|
-
// ../../packages/remote/src/client.ts
|
|
37422
|
-
var ROUTES = {
|
|
37423
|
-
events: "/v1/events",
|
|
37424
|
-
auditEvents: "/v1/audit-events",
|
|
37425
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
37426
|
-
inventory: "/v1/inventory",
|
|
37427
|
-
storePosture: "/v1/store-posture",
|
|
37428
|
-
policyBundle: "/v1/policy-bundle",
|
|
37429
|
-
whoami: "/v1/plugin/whoami",
|
|
37430
|
-
shares: "/v1/shares"
|
|
37431
|
-
};
|
|
37432
|
-
function headerValue(response, name) {
|
|
37433
|
-
const raw = response.headers[name];
|
|
37434
|
-
if (raw === void 0) return void 0;
|
|
37435
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
37436
|
-
}
|
|
37437
|
-
function okBody(response) {
|
|
37438
|
-
if (response.status < 200 || response.status >= 300) {
|
|
37439
|
-
throw new RemoteRequestError(response.status);
|
|
37440
|
-
}
|
|
37441
|
-
return response.body;
|
|
37442
|
-
}
|
|
37443
|
-
function parsed(schema, body, route) {
|
|
37444
|
-
let json2;
|
|
37445
|
-
try {
|
|
37446
|
-
json2 = JSON.parse(body);
|
|
37447
|
-
} catch {
|
|
37448
|
-
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
37449
|
-
}
|
|
37450
|
-
const result = schema.safeParse(json2);
|
|
37451
|
-
if (!result.success) {
|
|
37452
|
-
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
37453
|
-
}
|
|
37454
|
-
return result.data;
|
|
37455
|
-
}
|
|
37456
|
-
function withoutTrailingSlashes(endpoint) {
|
|
37457
|
-
let end = endpoint.length;
|
|
37458
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
37459
|
-
return endpoint.slice(0, end);
|
|
37460
|
-
}
|
|
37461
|
-
var SLASH = "/".charCodeAt(0);
|
|
37462
|
-
function createRemoteClient(options) {
|
|
37463
|
-
const base = withoutTrailingSlashes(options.endpoint);
|
|
37464
|
-
const url2 = (route) => `${base}${route}`;
|
|
37465
|
-
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
37466
|
-
const sendOne = async (event) => {
|
|
37467
|
-
const validated = RecordAuditEventRequest.safeParse(event);
|
|
37468
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
37469
|
-
const response = await send({
|
|
37470
|
-
...common,
|
|
37471
|
-
method: "POST",
|
|
37472
|
-
url: url2(ROUTES.auditEvents),
|
|
37473
|
-
body: JSON.stringify(validated.data)
|
|
37474
|
-
});
|
|
37475
|
-
okBody(response);
|
|
37476
|
-
};
|
|
37477
|
-
return {
|
|
37478
|
-
async ingestEvents(batch) {
|
|
37479
|
-
const response = await send({
|
|
37480
|
-
...common,
|
|
37481
|
-
method: "POST",
|
|
37482
|
-
url: url2(ROUTES.events),
|
|
37483
|
-
body: JSON.stringify(batch)
|
|
37484
|
-
});
|
|
37485
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
37486
|
-
},
|
|
37487
|
-
async ingestInventory(context) {
|
|
37488
|
-
const response = await send({
|
|
37489
|
-
...common,
|
|
37490
|
-
method: "POST",
|
|
37491
|
-
url: url2(ROUTES.inventory),
|
|
37492
|
-
body: JSON.stringify(context)
|
|
37493
|
-
});
|
|
37494
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
37495
|
-
},
|
|
37496
|
-
async recordAuditEvent(event) {
|
|
37497
|
-
await sendOne(event);
|
|
37498
|
-
},
|
|
37499
|
-
async recordAuditEvents(events) {
|
|
37500
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
37501
|
-
if (!validated.success) {
|
|
37502
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
37503
|
-
}
|
|
37504
|
-
const response = await send({
|
|
37505
|
-
...common,
|
|
37506
|
-
method: "POST",
|
|
37507
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
37508
|
-
body: JSON.stringify(validated.data)
|
|
37509
|
-
});
|
|
37510
|
-
if (response.status === 404) {
|
|
37511
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
37512
|
-
return { accepted: validated.data.events.length };
|
|
37513
|
-
}
|
|
37514
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
37515
|
-
},
|
|
37516
|
-
async reportStorePosture(snapshot) {
|
|
37517
|
-
const response = await send({
|
|
37518
|
-
...common,
|
|
37519
|
-
method: "POST",
|
|
37520
|
-
url: url2(ROUTES.storePosture),
|
|
37521
|
-
body: JSON.stringify(snapshot)
|
|
37522
|
-
});
|
|
37523
|
-
okBody(response);
|
|
37524
|
-
},
|
|
37525
|
-
async getPolicyBundle(etag) {
|
|
37526
|
-
const response = await send({
|
|
37527
|
-
...common,
|
|
37528
|
-
method: "GET",
|
|
37529
|
-
url: url2(ROUTES.policyBundle),
|
|
37530
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
37531
|
-
});
|
|
37532
|
-
if (response.status === 304) {
|
|
37533
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
37534
|
-
}
|
|
37535
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
37536
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
37537
|
-
},
|
|
37538
|
-
async whoami() {
|
|
37539
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
37540
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
37541
|
-
},
|
|
37542
|
-
async recordProjectEgress(request) {
|
|
37543
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
37544
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
37545
|
-
const response = await send({
|
|
37546
|
-
...common,
|
|
37547
|
-
method: "POST",
|
|
37548
|
-
url: url2(ROUTES.shares),
|
|
37549
|
-
body: JSON.stringify(validated.data)
|
|
37550
|
-
});
|
|
37551
|
-
okBody(response);
|
|
37552
|
-
}
|
|
37553
|
-
};
|
|
37554
|
-
}
|
|
38335
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
38336
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
38337
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
38338
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
37555
38339
|
|
|
37556
38340
|
// ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
|
|
37557
38341
|
import { spawn } from "child_process";
|
|
@@ -37559,14 +38343,14 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
37559
38343
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
37560
38344
|
|
|
37561
38345
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
37562
|
-
import { readFileSync as
|
|
38346
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
37563
38347
|
var manifestBuildCache = /* @__PURE__ */ new Map();
|
|
37564
38348
|
function readManifestBuild(manifestUrl, packageName) {
|
|
37565
38349
|
const key = manifestUrl.href;
|
|
37566
38350
|
if (!manifestBuildCache.has(key)) {
|
|
37567
38351
|
let build;
|
|
37568
38352
|
try {
|
|
37569
|
-
const manifest = JSON.parse(
|
|
38353
|
+
const manifest = JSON.parse(readFileSync16(manifestUrl, "utf8"));
|
|
37570
38354
|
build = typeof manifest.version === "string" && manifest.version.length > 0 ? { package: packageName, version: manifest.version } : void 0;
|
|
37571
38355
|
} catch {
|
|
37572
38356
|
build = void 0;
|
|
@@ -37593,7 +38377,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
37593
38377
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
37594
38378
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
37595
38379
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
37596
|
-
import { join as
|
|
38380
|
+
import { join as join24 } from "path";
|
|
37597
38381
|
|
|
37598
38382
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
37599
38383
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -37617,7 +38401,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
37617
38401
|
|
|
37618
38402
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
37619
38403
|
function createPolicyStore(dir = dataDir()) {
|
|
37620
|
-
const file2 =
|
|
38404
|
+
const file2 = join24(dir, "policy-cache.json");
|
|
37621
38405
|
async function read() {
|
|
37622
38406
|
try {
|
|
37623
38407
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -37626,22 +38410,32 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
37626
38410
|
const record2 = parsed2;
|
|
37627
38411
|
const bundle = PolicyBundle.parse(record2.bundle);
|
|
37628
38412
|
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
37629
|
-
const
|
|
38413
|
+
const stored = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
38414
|
+
const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
|
|
38415
|
+
const etag = replayable ? stored : void 0;
|
|
37630
38416
|
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
37631
38417
|
} catch {
|
|
37632
38418
|
return null;
|
|
37633
38419
|
}
|
|
37634
38420
|
}
|
|
37635
|
-
|
|
37636
|
-
|
|
37637
|
-
const
|
|
37638
|
-
|
|
37639
|
-
|
|
37640
|
-
|
|
37641
|
-
|
|
38421
|
+
function knowsMoreThanThisBuild(shapeId) {
|
|
38422
|
+
if (typeof shapeId !== "string" || shapeId === "") return false;
|
|
38423
|
+
const theirs = new Set(shapeId.split(","));
|
|
38424
|
+
const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
|
|
38425
|
+
return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
|
|
38426
|
+
}
|
|
38427
|
+
async function priorRecord() {
|
|
38428
|
+
try {
|
|
38429
|
+
const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
|
|
38430
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
38431
|
+
} catch {
|
|
38432
|
+
return null;
|
|
38433
|
+
}
|
|
38434
|
+
}
|
|
38435
|
+
async function publishRecord(record2) {
|
|
37642
38436
|
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
37643
38437
|
try {
|
|
37644
|
-
await writeFile2(tmp, JSON.stringify(
|
|
38438
|
+
await writeFile2(tmp, JSON.stringify(record2), {
|
|
37645
38439
|
encoding: "utf8",
|
|
37646
38440
|
mode: DATA_FILE_MODE,
|
|
37647
38441
|
flag: "wx"
|
|
@@ -37652,6 +38446,27 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
37652
38446
|
throw err;
|
|
37653
38447
|
}
|
|
37654
38448
|
}
|
|
38449
|
+
async function write(bundle, etag) {
|
|
38450
|
+
await ensureDataDir(dir);
|
|
38451
|
+
const prior = await priorRecord();
|
|
38452
|
+
const priorVersion = prior?.bundle?.version;
|
|
38453
|
+
if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
|
|
38454
|
+
await publishRecord({
|
|
38455
|
+
...prior,
|
|
38456
|
+
fetchedAtMs: Date.now()
|
|
38457
|
+
});
|
|
38458
|
+
return;
|
|
38459
|
+
}
|
|
38460
|
+
await publishRecord({
|
|
38461
|
+
bundle,
|
|
38462
|
+
fetchedAtMs: Date.now(),
|
|
38463
|
+
// Stamped on EVERY write, the 304 arm's included: that arm hands back the
|
|
38464
|
+
// bundle it already holds, and the point of the stamp is to describe the
|
|
38465
|
+
// build that last narrowed those bytes, which is this one.
|
|
38466
|
+
shapeId: POLICY_BUNDLE_SHAPE_ID,
|
|
38467
|
+
...etag === void 0 ? {} : { etag }
|
|
38468
|
+
});
|
|
38469
|
+
}
|
|
37655
38470
|
return { read, write, file: file2 };
|
|
37656
38471
|
}
|
|
37657
38472
|
|
|
@@ -37817,11 +38632,11 @@ function readStorePosture(dbPath2) {
|
|
|
37817
38632
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
37818
38633
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
37819
38634
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
37820
|
-
import { join as
|
|
38635
|
+
import { join as join25 } from "path";
|
|
37821
38636
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
37822
38637
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
37823
|
-
const file2 =
|
|
37824
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
38638
|
+
const file2 = join25(dir, "posture-state.json");
|
|
38639
|
+
const legacyFile = legacyDir === void 0 ? null : join25(legacyDir, "posture-state.json");
|
|
37825
38640
|
async function persist(state) {
|
|
37826
38641
|
await ensureDataDir(dir);
|
|
37827
38642
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -37889,8 +38704,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
37889
38704
|
}
|
|
37890
38705
|
|
|
37891
38706
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
37892
|
-
import { readFileSync as
|
|
37893
|
-
import { join as
|
|
38707
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
38708
|
+
import { join as join26 } from "path";
|
|
37894
38709
|
|
|
37895
38710
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
37896
38711
|
var REFUSAL_LINES = {
|
|
@@ -38208,9 +39023,21 @@ var StandaloneDataGateway = class {
|
|
|
38208
39023
|
// for the whole of it, so a member that threw would make that answer a lie
|
|
38209
39024
|
// the moment a composite delegated to it. A store-level no-op is the honest
|
|
38210
39025
|
// shape — a standalone machine has nothing delivered to record.
|
|
39026
|
+
markCaptureOwed(event) {
|
|
39027
|
+
this.db.markCaptureOwed(event);
|
|
39028
|
+
}
|
|
38211
39029
|
markCaptureDelivered(event, atMs) {
|
|
38212
39030
|
this.db.markCaptureDelivered(event, atMs);
|
|
38213
39031
|
}
|
|
39032
|
+
// Implemented, not stubbed, for the same reason its sibling above is: the
|
|
39033
|
+
// attached gateway is a DECORATOR over an instance of this class
|
|
39034
|
+
// (`attached/factory.ts` builds one and passes it as `deps.local`), so every
|
|
39035
|
+
// stamp the live forward makes lands here with a non-empty array. This is the
|
|
39036
|
+
// production write path for that feature, not a shape-satisfying no-op — a
|
|
39037
|
+
// machine that is merely standalone simply never calls it.
|
|
39038
|
+
markAuditEventsDelivered(events, atMs) {
|
|
39039
|
+
this.db.markAuditEventsDelivered(events, atMs);
|
|
39040
|
+
}
|
|
38214
39041
|
staleBinaryNotice(currentVersion) {
|
|
38215
39042
|
try {
|
|
38216
39043
|
const newest = this.db.installedPacks.newestRecordedBinary();
|
|
@@ -38356,15 +39183,24 @@ function pluginBuild() {
|
|
|
38356
39183
|
}
|
|
38357
39184
|
|
|
38358
39185
|
// src/remediation/redact.ts
|
|
38359
|
-
import {
|
|
38360
|
-
|
|
39186
|
+
import {
|
|
39187
|
+
lstatSync as lstatSync4,
|
|
39188
|
+
readdirSync as readdirSync6,
|
|
39189
|
+
readFileSync as readFileSync19,
|
|
39190
|
+
realpathSync as realpathSync4,
|
|
39191
|
+
renameSync as renameSync5,
|
|
39192
|
+
rmSync as rmSync7,
|
|
39193
|
+
statSync as statSync10,
|
|
39194
|
+
writeFileSync as writeFileSync8
|
|
39195
|
+
} from "fs";
|
|
39196
|
+
import { basename as basename6, dirname as dirname6, isAbsolute as isAbsolute2, join as join28, relative, resolve as resolve2 } from "path";
|
|
38361
39197
|
|
|
38362
39198
|
// src/history/transcripts.ts
|
|
38363
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
39199
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync18 } from "fs";
|
|
38364
39200
|
import { homedir as homedir3 } from "os";
|
|
38365
|
-
import { join as
|
|
39201
|
+
import { join as join27 } from "path";
|
|
38366
39202
|
function transcriptsDir(home) {
|
|
38367
|
-
return
|
|
39203
|
+
return join27(home ?? homedir3(), ".claude", "projects");
|
|
38368
39204
|
}
|
|
38369
39205
|
function isRecord(value) {
|
|
38370
39206
|
return typeof value === "object" && value !== null;
|
|
@@ -38597,13 +39433,13 @@ import {
|
|
|
38597
39433
|
fstatSync as fstatSync2,
|
|
38598
39434
|
mkdirSync as mkdirSync5,
|
|
38599
39435
|
openSync as openSync3,
|
|
38600
|
-
readFileSync as
|
|
39436
|
+
readFileSync as readFileSync20,
|
|
38601
39437
|
readSync as readSync2,
|
|
38602
39438
|
writeFileSync as writeFileSync9
|
|
38603
39439
|
} from "fs";
|
|
38604
|
-
import { join as
|
|
39440
|
+
import { join as join29 } from "path";
|
|
38605
39441
|
function offsetsDir(dataDir2) {
|
|
38606
|
-
return
|
|
39442
|
+
return join29(dataDir2, "usage-offsets");
|
|
38607
39443
|
}
|
|
38608
39444
|
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
|
|
38609
39445
|
function safeSessionId(sessionId) {
|
|
@@ -38613,11 +39449,11 @@ function safeSessionId(sessionId) {
|
|
|
38613
39449
|
return createHash7("sha256").update(sessionId).digest("hex");
|
|
38614
39450
|
}
|
|
38615
39451
|
function offsetPath(dataDir2, sessionId) {
|
|
38616
|
-
return
|
|
39452
|
+
return join29(offsetsDir(dataDir2), safeSessionId(sessionId));
|
|
38617
39453
|
}
|
|
38618
39454
|
function readOffset(dataDir2, sessionId) {
|
|
38619
39455
|
try {
|
|
38620
|
-
const raw =
|
|
39456
|
+
const raw = readFileSync20(offsetPath(dataDir2, sessionId), "utf8");
|
|
38621
39457
|
const parsed2 = JSON.parse(raw);
|
|
38622
39458
|
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
38623
39459
|
const rec = parsed2;
|
|
@@ -38675,22 +39511,24 @@ function readTail(transcriptPath, startOffset) {
|
|
|
38675
39511
|
}
|
|
38676
39512
|
|
|
38677
39513
|
// src/history/tail-scrub.ts
|
|
38678
|
-
import { readFileSync as
|
|
39514
|
+
import { readFileSync as readFileSync21, renameSync as renameSync6, rmSync as rmSync8, statSync as statSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
38679
39515
|
var DEFAULT_MAX_SCRUB_BYTES = 32 * 1024 * 1024;
|
|
38680
39516
|
async function scrubTranscriptTail(filePath, deps) {
|
|
38681
39517
|
try {
|
|
38682
39518
|
const realPath = resolveRedactableArtifact(filePath, deps.scope);
|
|
38683
39519
|
if (realPath === null) return null;
|
|
38684
|
-
const statBefore =
|
|
39520
|
+
const statBefore = statSync11(realPath);
|
|
38685
39521
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
38686
|
-
const content =
|
|
39522
|
+
const content = readFileSync21(realPath, "utf8");
|
|
38687
39523
|
const lines = content.split("\n");
|
|
38688
39524
|
let rewritten = 0;
|
|
38689
39525
|
for (const [i, line] of lines.entries()) {
|
|
38690
39526
|
if (line === "") continue;
|
|
38691
39527
|
const result = await deps.tokenizeText(line);
|
|
38692
39528
|
if (result.text === line) continue;
|
|
38693
|
-
if (result.pointers.length === 0 && result.degraded.length === 0)
|
|
39529
|
+
if (result.pointers.length === 0 && result.degraded.length === 0 && result.redacted.length === 0) {
|
|
39530
|
+
return null;
|
|
39531
|
+
}
|
|
38694
39532
|
lines[i] = result.text;
|
|
38695
39533
|
rewritten += 1;
|
|
38696
39534
|
}
|
|
@@ -38698,7 +39536,7 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
38698
39536
|
const tmpPath = `${realPath}.aka-scrub.tmp`;
|
|
38699
39537
|
try {
|
|
38700
39538
|
writeFileSync10(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
|
|
38701
|
-
const statNow =
|
|
39539
|
+
const statNow = statSync11(realPath);
|
|
38702
39540
|
if (statNow.size !== statBefore.size || statNow.mtimeMs !== statBefore.mtimeMs) {
|
|
38703
39541
|
rmSync8(tmpPath, { force: true, recursive: true });
|
|
38704
39542
|
return null;
|
|
@@ -38851,9 +39689,11 @@ async function reconcileSessionTail(config2, sessionId, transcriptPath) {
|
|
|
38851
39689
|
});
|
|
38852
39690
|
if (isVaultConsentValid(config2.settings.vaultConsent)) {
|
|
38853
39691
|
try {
|
|
39692
|
+
const resolver = createPolicyResolver(await gateway.getPolicyBundle());
|
|
38854
39693
|
const glue = createVaultGlue();
|
|
38855
39694
|
const scrubbed = await scrubTranscriptTail(transcriptPath, {
|
|
38856
39695
|
tokenizeText: (text) => glue.tokenizeText(text, {
|
|
39696
|
+
resolver,
|
|
38857
39697
|
sighting: { location: transcriptPath, kind: "transcript" }
|
|
38858
39698
|
}),
|
|
38859
39699
|
scope: platformRedactionScope()
|