@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.
@@ -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 join12 } from "path";
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
 
@@ -22137,6 +22162,26 @@ var AttachTokenResponse = external_exports.union([
22137
22162
  AttachTokenExpired,
22138
22163
  external_exports.object({ status: printable(64) })
22139
22164
  ]);
22165
+ var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
22166
+ var DeviceCommand = external_exports.object({
22167
+ id: printable(128).min(1),
22168
+ kind: DeviceCommandKind,
22169
+ issuedAt: printable(64).min(1),
22170
+ expiresAt: printable(64).min(1)
22171
+ }).strict();
22172
+ var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
22173
+ var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
22174
+ var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
22175
+ external_exports.object({
22176
+ outcome: external_exports.literal("reported"),
22177
+ projectsScanned: external_exports.number().int().nonnegative()
22178
+ }).strict(),
22179
+ external_exports.object({
22180
+ outcome: external_exports.literal("failed"),
22181
+ reason: DeviceCommandFailureReason,
22182
+ projectsScanned: external_exports.number().int().nonnegative()
22183
+ }).strict()
22184
+ ]);
22140
22185
 
22141
22186
  // ../../packages/schema/src/zod/registry.ts
22142
22187
  var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
@@ -22303,7 +22348,7 @@ var PackManifest = external_exports.object({
22303
22348
  }).meta({ id: "PackManifest" });
22304
22349
 
22305
22350
  // ../../packages/schema/src/zod/detection.ts
22306
- var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
22351
+ var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
22307
22352
  var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
22308
22353
  var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
22309
22354
  var DetectionCounts = external_exports.object({
@@ -22440,14 +22485,17 @@ function optional2(key, parsed2, raw) {
22440
22485
  function isStringArray(value) {
22441
22486
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
22442
22487
  }
22488
+ var ORIGIN_VALUES = { library: true, custom: true };
22489
+ function resolveOrigin(origin) {
22490
+ return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
22491
+ }
22443
22492
  function summaryToDetectionListItem(s) {
22444
22493
  return {
22445
22494
  id: `${s.namespace}/${s.packId}`,
22446
22495
  name: s.name,
22447
22496
  version: s.version,
22448
22497
  enabled: s.enabled,
22449
- origin: "library",
22450
- // v1: every installed pack is library origin
22498
+ origin: resolveOrigin(s.origin),
22451
22499
  namespace: s.namespace,
22452
22500
  packId: s.packId,
22453
22501
  ruleCount: s.ruleCount,
@@ -22499,7 +22547,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
22499
22547
  name: row.name,
22500
22548
  version: row.version,
22501
22549
  enabled: row.enabled,
22502
- origin: "library",
22550
+ origin: resolveOrigin(row.origin),
22503
22551
  namespace: row.namespace,
22504
22552
  packId: row.packId,
22505
22553
  ruleCount: row.rules.length,
@@ -22519,16 +22567,20 @@ function splitDetectionId(id) {
22519
22567
  }
22520
22568
  function buildDetectionsList(summaries, query) {
22521
22569
  const withUpdate = summaries.filter((s) => s.latestVersion != null);
22570
+ const originOf = (s) => resolveOrigin(s.origin);
22522
22571
  const counts = {
22523
22572
  all: summaries.length,
22524
- library: summaries.length,
22525
- // all origin=library in v1
22526
- custom: 0,
22573
+ library: summaries.filter((s) => originOf(s) === "library").length,
22574
+ custom: summaries.filter((s) => originOf(s) === "custom").length,
22575
+ // No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
22576
+ // omission: `customized` would mean a LIBRARY pack whose rules were edited in
22577
+ // place, and that state does not exist — editing a library pack forks it. See
22578
+ // OriginEnum.
22527
22579
  customized: 0,
22528
22580
  updates: withUpdate.length
22529
22581
  };
22530
22582
  const filter = query.filter;
22531
- let filtered = filter === "custom" || filter === "customized" ? [] : filter === "updates" ? [...withUpdate] : [...summaries];
22583
+ let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
22532
22584
  if (query.q) {
22533
22585
  const q = query.q.toLowerCase();
22534
22586
  filtered = filtered.filter(
@@ -22608,8 +22660,9 @@ var Event = external_exports.object({
22608
22660
  metadata: EventMetadata.optional()
22609
22661
  }).meta({ id: "Event" });
22610
22662
  var IngestEvent = Event.meta({ id: "IngestEvent" });
22663
+ var INGEST_BATCH_MAX = 100;
22611
22664
  var IngestBatch = external_exports.object({
22612
- events: external_exports.array(IngestEvent).min(1).max(100),
22665
+ events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
22613
22666
  // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
22614
22667
  // additionally rejects any event whose contentHash the store has already
22615
22668
  // recorded — for re-runnable bulk ingest (worktree scan, transcript
@@ -23165,375 +23218,11 @@ var PatchInstalledPackRequest = external_exports.object({
23165
23218
  message: "At least one field must be provided"
23166
23219
  }).meta({ id: "PatchInstalledPackRequest" });
23167
23220
 
23168
- // ../../packages/schema/src/zod/vault.ts
23169
- var POINTER_FORMAT_VERSION = 2;
23170
- var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
23171
- var POINTER_TOKEN_PATTERN = new RegExp(
23172
- `\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
23173
- );
23174
- var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
23175
- function pointerTokenScanner() {
23176
- return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
23177
- }
23178
- var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
23179
- var ParsedPointer = external_exports.object({
23180
- category: DetectionCategory,
23181
- keyVersion: external_exports.number().int().positive(),
23182
- pointerId: external_exports.string(),
23183
- tag: external_exports.string()
23184
- });
23185
- var VaultEntry = external_exports.object({
23186
- pointerId: external_exports.string(),
23187
- // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
23188
- // derived under. This is what a reveal-to-model grant matches on, and it rotates
23189
- // independently of the vault encryption key below.
23190
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23191
- fingerprintKeyVersion: external_exports.number().int().positive(),
23192
- // The vault-key epoch this row's ciphertext was sealed under.
23193
- keyVersion: external_exports.number().int().positive(),
23194
- // Fixed at first mint and never updated: the same value detected later under a
23195
- // different rule's category keeps the category it was minted with, so one
23196
- // value always produces exactly one wire token.
23197
- category: DetectionCategory,
23198
- ruleId: external_exports.string(),
23199
- // Partial-reveal preview for badges and listings. Never the raw value.
23200
- maskedMatch: external_exports.string(),
23201
- provider: external_exports.string().optional(),
23202
- ciphertext: external_exports.string(),
23203
- nonce: external_exports.string(),
23204
- authTag: external_exports.string(),
23205
- // How many times this value has been detected on this machine — the reuse
23206
- // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
23207
- occurrenceCount: external_exports.number().int().nonnegative(),
23208
- firstSeen: external_exports.string(),
23209
- lastSeen: external_exports.string()
23210
- });
23211
- var PointerDescriptor = external_exports.object({
23212
- category: DetectionCategory,
23213
- provider: external_exports.string().optional(),
23214
- maskedMatch: external_exports.string(),
23215
- occurrences: external_exports.number().int().nonnegative(),
23216
- firstSeen: external_exports.string(),
23217
- lastSeen: external_exports.string()
23218
- });
23219
- var PointerIdentity = external_exports.object({
23220
- ruleId: external_exports.string(),
23221
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23222
- fingerprintKeyVersion: external_exports.number().int().positive()
23223
- });
23224
- var DetokenizeTarget = external_exports.enum(["human", "model"]);
23225
- var VaultDerefReason = external_exports.enum([
23226
- "display",
23227
- "explicit-reveal",
23228
- "view-render",
23229
- "model-input",
23230
- "remediation",
23231
- "purge"
23232
- ]);
23233
- var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
23234
- var VaultDeref = external_exports.object({
23235
- id: external_exports.guid(),
23236
- pointerId: external_exports.string(),
23237
- at: external_exports.string(),
23238
- target: DetokenizeTarget,
23239
- reason: VaultDerefReason,
23240
- outcome: VaultDerefOutcome,
23241
- // Present only on a model-target crossing that a reveal grant authorized.
23242
- grantId: external_exports.string().optional(),
23243
- // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
23244
- // apart from VaultEntry.occurrenceCount, which counts detections of a value.
23245
- pointerCount: external_exports.number().int().positive().default(1)
23246
- });
23247
- var VaultSightingKind = external_exports.enum([
23248
- "prompt",
23249
- "tool-input",
23250
- "tool-output",
23251
- "file",
23252
- "transcript"
23253
- ]);
23254
- var VaultSighting = external_exports.object({
23255
- location: external_exports.string(),
23256
- kind: VaultSightingKind,
23257
- firstSeen: external_exports.string(),
23258
- lastSeen: external_exports.string()
23259
- });
23260
- var VaultInventoryEntry = external_exports.object({
23261
- pointerId: external_exports.string(),
23262
- category: DetectionCategory,
23263
- provider: external_exports.string().optional(),
23264
- maskedMatch: external_exports.string(),
23265
- occurrences: external_exports.number().int().nonnegative(),
23266
- firstSeen: external_exports.string(),
23267
- lastSeen: external_exports.string(),
23268
- // The active reveal-to-model grant covering this value, when one exists —
23269
- // the inventory badges it, the row links to revocation.
23270
- revealGrantId: external_exports.string().nullable(),
23271
- sightings: external_exports.array(VaultSighting)
23272
- });
23273
- var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
23274
- var DEFAULT_VAULT_DEREFS_LIMIT = 50;
23275
- var MAX_VAULT_PAGE_LIMIT = 200;
23276
- var ListVaultInventoryQuery = external_exports.object({
23277
- limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23278
- // Opaque; names the last row of the page just served.
23279
- cursor: external_exports.string().optional()
23280
- });
23281
- var ListVaultInventoryResponse = external_exports.object({
23282
- // Vaulted values across the whole store, not just this page — cursor-
23283
- // independent, so paging never changes what the count claims.
23284
- totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
23285
- items: external_exports.array(VaultInventoryEntry),
23286
- // `null` once the last page is reached.
23287
- nextCursor: external_exports.string().nullable()
23288
- });
23289
- var ListVaultReuseQuery = external_exports.object({
23290
- limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23291
- cursor: external_exports.string().optional()
23292
- });
23293
- var ListVaultReuseResponse = external_exports.object({
23294
- // Reused values across the whole store — the number the section's claim
23295
- // ("values detected in more than one place") is about.
23296
- totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
23297
- items: external_exports.array(VaultInventoryEntry),
23298
- nextCursor: external_exports.string().nullable()
23299
- });
23300
- var ListVaultDerefsQuery = external_exports.object({
23301
- // Include the batched, high-volume reasons (display, view-render). Omitted
23302
- // hides them and counts them into `hiddenBatched` instead, so the model
23303
- // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
23304
- // over a Server Action, which preserves the type, never as a URL param.
23305
- includeBatched: external_exports.boolean().optional(),
23306
- limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23307
- cursor: external_exports.string().optional()
23308
- });
23309
- var ListVaultDerefsResponse = external_exports.object({
23310
- items: external_exports.array(VaultDeref),
23311
- nextCursor: external_exports.string().nullable(),
23312
- // Display/view-render rows the query hid, over the WHOLE trail rather than
23313
- // this page — it is the count the "N hidden" line and its toggle speak for.
23314
- // Always 0 when `includeBatched` was set, since nothing was hidden.
23315
- hiddenBatched: external_exports.number().int().nonnegative()
23316
- });
23317
- var VaultKeyCustody = external_exports.string();
23318
- var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
23319
- var VAULT_CONSENT_VERSION = 1;
23320
- var VaultConsent = external_exports.object({
23321
- acknowledgedAt: external_exports.iso.datetime(),
23322
- version: external_exports.number().int().positive()
23323
- });
23324
-
23325
- // ../../packages/schema/src/zod/local.ts
23326
- var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
23327
- var MODEL_JUDGE_PAYLOAD_VERSION = 1;
23328
- var RunMode = external_exports.enum(["standalone", "attached"]);
23329
- var ControlPlaneConnection = external_exports.object({
23330
- endpoint: external_exports.string().min(1),
23331
- // Display name for the deployment, shown instead of the raw endpoint.
23332
- label: external_exports.string().min(1).optional(),
23333
- attachedAt: external_exports.iso.datetime()
23334
- }).meta({ id: "ControlPlaneConnection" });
23335
- var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
23336
- var HistoricalAccess = external_exports.enum(["full", "session-only"]);
23337
- var ModelJudgeConsent = external_exports.object({
23338
- acknowledgedAt: external_exports.iso.datetime(),
23339
- payloadVersion: external_exports.number().int().positive()
23340
- });
23341
- var HistorySyncConsent = external_exports.object({
23342
- acknowledgedAt: external_exports.iso.datetime(),
23343
- payloadVersion: external_exports.number().int().positive(),
23344
- endpoint: external_exports.string()
23345
- });
23346
- var WorkspaceSettings = external_exports.object({
23347
- specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
23348
- runMode: RunMode.default("standalone"),
23349
- // Present only while attached; a detach clears it. Its presence is what makes
23350
- // `runMode: 'attached'` mean anything — see isAttached.
23351
- controlPlane: ControlPlaneConnection.optional(),
23352
- policy: SimpleDetectionPolicy.default("redact"),
23353
- // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
23354
- historicalAccess: HistoricalAccess.default("session-only"),
23355
- // In-place egress extraction on the scan paths; disable to stop all Data
23356
- // Shares writes.
23357
- dataSharesInPlace: external_exports.boolean().default(true),
23358
- // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
23359
- // vault, instead of destroying them. Absent by default: this is a custody
23360
- // change from one-way redaction, so it is never an assumed grant on upgrade.
23361
- // Revoking stops future vaulting; it does not erase what is already stored —
23362
- // purging the vault is the eraser.
23363
- vaultConsent: VaultConsent.optional(),
23364
- // Where the vault master key lives.
23365
- vaultKeyCustody: VaultKeyCustody.default("file"),
23366
- // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
23367
- vaultInlineReveal: VaultInlineReveal.default("masked"),
23368
- // Absent until /aka:setup completes; its presence is what "onboarded" means.
23369
- onboardedAt: external_exports.iso.datetime().optional(),
23370
- // Records that the user consented to sending findings to the model API for
23371
- // the /aka:setup judge, along with the payload-shape version they agreed to.
23372
- // Absent until granted; a stale payloadVersion means the consent no longer
23373
- // covers the current payload and must be re-granted.
23374
- modelJudgeConsent: ModelJudgeConsent.optional(),
23375
- // Records that the user consented to sending the activity already recorded on
23376
- // this machine to the deployment it is attached to, along with the payload
23377
- // shape and the endpoint they agreed to. Absent until granted, and a grant for
23378
- // a different endpoint or an older payload no longer counts.
23379
- historySyncConsent: HistorySyncConsent.optional()
23380
- });
23381
- function defaultWorkspaceSettings() {
23382
- return WorkspaceSettings.parse({});
23383
- }
23384
- function isAttached(settings) {
23385
- return settings.runMode === "attached" && settings.controlPlane !== void 0;
23386
- }
23387
- function toInventoryRow(input2, id, now) {
23388
- return {
23389
- id,
23390
- objectType: input2.objectType,
23391
- location: input2.location ?? null,
23392
- title: input2.title ?? null,
23393
- hostId: input2.hostId ?? null,
23394
- attributes: JSON.stringify(input2.attributes),
23395
- firstSeen: now,
23396
- lastSeen: now
23397
- };
23398
- }
23399
- function toSourceProjectRow(input2, id, now) {
23400
- return {
23401
- id,
23402
- url: input2.url,
23403
- name: input2.name ?? null,
23404
- attributes: JSON.stringify(input2.attributes),
23405
- firstSeen: now,
23406
- lastSeen: now
23407
- };
23408
- }
23409
- function toAuditEventRow(input2) {
23410
- return {
23411
- id: input2.id,
23412
- parentId: input2.parentId ?? null,
23413
- rootSessionId: input2.rootSessionId ?? null,
23414
- eventType: input2.eventType,
23415
- hostId: input2.hostId ?? null,
23416
- harnessId: input2.harnessId ?? null,
23417
- sourceProjectId: input2.sourceProjectId ?? null,
23418
- startedAt: isoToEpochMillis(input2.startedAt),
23419
- endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
23420
- severity: input2.severity ?? null,
23421
- priority: input2.priority ?? null,
23422
- content: input2.content ?? null,
23423
- contentHash: input2.contentHash ?? null,
23424
- attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23425
- };
23426
- }
23427
- function toClassifiedDataRow(input2, id) {
23428
- return {
23429
- id,
23430
- class: input2.class,
23431
- label: input2.label ?? null,
23432
- attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23433
- };
23434
- }
23435
- function toInspectionDefinitionRow(input2, id) {
23436
- return {
23437
- id,
23438
- ruleId: input2.ruleId,
23439
- name: input2.name,
23440
- category: input2.category,
23441
- severity: input2.severity,
23442
- definition: input2.definition,
23443
- version: input2.version
23444
- };
23445
- }
23446
- function toInspectionFindingRow(input2) {
23447
- return {
23448
- id: input2.id,
23449
- auditEventId: input2.auditEventId,
23450
- inspectionDefinitionId: input2.inspectionDefinitionId,
23451
- classifiedDataId: input2.classifiedDataId ?? null,
23452
- spanStart: input2.span.start,
23453
- spanEnd: input2.span.end,
23454
- maskedMatch: input2.maskedMatch,
23455
- actionTaken: input2.actionTaken,
23456
- confidence: input2.confidence,
23457
- findingKey: input2.findingKey ?? null,
23458
- firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
23459
- };
23460
- }
23461
- function toCaptureAttributes(event) {
23462
- const metadata = event.metadata;
23463
- return {
23464
- source_tool: event.sourceTool,
23465
- ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
23466
- ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
23467
- ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
23468
- ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
23469
- ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
23470
- ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
23471
- ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
23472
- ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
23473
- ...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
23474
- // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
23475
- // has ever populated either), but every legacy metadata key still rides
23476
- // the bag rather than being silently dropped — CaptureAttributes'
23477
- // `.catchall(z.unknown())` carries the long tail.
23478
- ...metadata?.model !== void 0 ? { model: metadata.model } : {},
23479
- ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
23480
- };
23481
- }
23482
- function captureDefinitionVersion(finding) {
23483
- return `capture/${finding.category}/${finding.severity}`;
23484
- }
23485
- function toCaptureDefinitionInput(finding) {
23486
- return {
23487
- ruleId: finding.ruleId,
23488
- version: captureDefinitionVersion(finding),
23489
- name: finding.ruleId,
23490
- category: finding.category,
23491
- severity: finding.severity,
23492
- definition: JSON.stringify({ ruleId: finding.ruleId })
23493
- };
23494
- }
23495
-
23496
- // ../../packages/schema/src/zod/managed.ts
23497
- var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
23498
- var MANAGED_SETTINGS_SPEC_VERSION = 1;
23499
- var ManagedSettingKey = external_exports.enum([
23500
- "runMode",
23501
- "historicalAccess",
23502
- "vaultConsent",
23503
- "vaultKeyCustody",
23504
- "vaultInlineReveal",
23505
- "modelJudgeConsent",
23506
- "dataSharesInPlace"
23507
- ]).meta({ id: "ManagedSettingKey" });
23508
- var ManagedSettingsValues = external_exports.object({
23509
- runMode: external_exports.enum(["standalone", "attached"]).optional(),
23510
- controlPlane: external_exports.object({
23511
- endpoint: external_exports.string().min(1),
23512
- label: external_exports.string().min(1).optional()
23513
- }).optional(),
23514
- historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
23515
- vaultConsent: external_exports.boolean().optional(),
23516
- vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
23517
- vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
23518
- modelJudgeConsent: external_exports.boolean().optional(),
23519
- dataSharesInPlace: external_exports.boolean().optional()
23520
- }).meta({ id: "ManagedSettingsValues" });
23521
- var ManagedSettings = external_exports.object({
23522
- specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
23523
- // Shown on every locked control, so the user can tell an administrative
23524
- // decision from a bug. Absent renders as a generic "your organization".
23525
- organization: external_exports.string().min(1).optional(),
23526
- // What the administrator pinned.
23527
- values: ManagedSettingsValues.default({}),
23528
- // Which of those the user may not change. A key here with no matching value
23529
- // freezes whatever the user last chose; a value with no lock is a DEFAULT
23530
- // the user may still override. The two are separable on purpose.
23531
- lockedFields: external_exports.array(ManagedSettingKey).default([])
23532
- }).meta({ id: "ManagedSettings" });
23533
-
23534
23221
  // ../../packages/schema/src/zod/policy.ts
23535
23222
  var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
23536
23223
  var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
23224
+ var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
23225
+ var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
23537
23226
  var Policy = external_exports.object({
23538
23227
  id: external_exports.guid(),
23539
23228
  scope: PolicyScope,
@@ -23543,7 +23232,27 @@ var Policy = external_exports.object({
23543
23232
  customKeywords: external_exports.array(external_exports.string()).optional(),
23544
23233
  // Display name — optional so older policy rows without name still parse.
23545
23234
  // Added for the findings API (policy.name column migration).
23546
- name: external_exports.string().optional()
23235
+ name: external_exports.string().optional(),
23236
+ // Whether an AUTHORED policy governs this row's target — not a claim about
23237
+ // which row this is. A producer that collapses several rows onto one target
23238
+ // must carry the marker onto whichever row survives, or the collapse decides
23239
+ // the answer; a survivor may therefore be a built-in expansion still marked
23240
+ // 'authored' because an authored sibling targeted the same thing.
23241
+ // Optional so an older producer — and an older on-disk cache — still parses;
23242
+ // absent reads as 'builtin', which is the behaviour that predates the field.
23243
+ //
23244
+ // Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
23245
+ // built-in archetype catalog entry a policy is, which every catalog surface
23246
+ // reads and which a caller may state. This one is a statement the PRODUCER
23247
+ // of a bundle makes about a row, and only the bundle builder ever stamps it
23248
+ // — the CRUD routes neither accept nor set it.
23249
+ //
23250
+ // A device consumes this in exactly one direction: an 'authored' policy
23251
+ // arriving from a control plane marks the rules it targets as not
23252
+ // locally re-assignable. That can only ever ADD a refusal, never relax one,
23253
+ // which is what makes it safe to honour from an unsigned cache — the same
23254
+ // test `prohibitedModels` passes and `reversibleRuleIds` fails.
23255
+ provenance: PolicyProvenance.optional()
23547
23256
  }).meta({ id: "Policy" });
23548
23257
  var PolicyBundle = external_exports.object({
23549
23258
  version: external_exports.string(),
@@ -23595,6 +23304,12 @@ var PolicyBundle = external_exports.object({
23595
23304
  customKeywords: external_exports.array(external_exports.string()),
23596
23305
  fetchedAt: external_exports.iso.datetime()
23597
23306
  }).meta({ id: "PolicyBundle" });
23307
+ var POLICY_BUNDLE_SHAPE_ID = [
23308
+ ...Object.keys(PolicyBundle.shape),
23309
+ ...Object.keys(Policy.shape).map((key) => `policies.${key}`),
23310
+ ...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
23311
+ ...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
23312
+ ].sort().join(",");
23598
23313
  var OBSERVE_ONLY_CATEGORIES = ["config"];
23599
23314
  var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
23600
23315
  var CATEGORY_PEAK_SEVERITY = {
@@ -23615,9 +23330,11 @@ function severityFloorPolicy(category) {
23615
23330
  const peak = CATEGORY_PEAK_SEVERITY[category];
23616
23331
  return peak === "critical" || peak === "high" ? "warn" : "monitor";
23617
23332
  }
23618
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
23619
23333
  var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
23620
23334
  var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
23335
+ var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
23336
+ id: "RedactFallback"
23337
+ });
23621
23338
  var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
23622
23339
  var BUILTIN_POLICY_SPECS = {
23623
23340
  monitor: {
@@ -23654,6 +23371,42 @@ var BUILTIN_POLICY_SPECS = {
23654
23371
  function builtinPolicyToAction(id) {
23655
23372
  return BUILTIN_POLICY_SPECS[id].action;
23656
23373
  }
23374
+ var PALETTE_WEAKEST_FIRST = [
23375
+ ...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
23376
+ ];
23377
+ var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
23378
+ (action) => !PALETTE_WEAKEST_FIRST.includes(action)
23379
+ );
23380
+ var ACTION_STRENGTH_ORDER = [
23381
+ ...BELOW_PALETTE,
23382
+ ...PALETTE_WEAKEST_FIRST
23383
+ ];
23384
+ function actionRank(action) {
23385
+ return ACTION_STRENGTH_ORDER.indexOf(action);
23386
+ }
23387
+ function isActionAtLeast(action, floor) {
23388
+ return actionRank(action) >= actionRank(floor);
23389
+ }
23390
+ function strongerAction(a, b) {
23391
+ return actionRank(a) >= actionRank(b) ? a : b;
23392
+ }
23393
+ function weakestBuiltinAtLeast(floor) {
23394
+ return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
23395
+ }
23396
+ var PackPolicyFloor = external_exports.object({
23397
+ /**
23398
+ * The weakest archetype the device may assign. Stated as a BuiltinPolicyId
23399
+ * rather than a raw ActionTaken because that is the vocabulary the user
23400
+ * picks from — a floor a UI cannot name is one it cannot explain.
23401
+ */
23402
+ floor: BuiltinPolicyId,
23403
+ /**
23404
+ * True when the organization AUTHORED a policy governing this pack rather
23405
+ * than stating a minimum: it gave the answer, so the pack is not
23406
+ * re-assignable locally in either direction.
23407
+ */
23408
+ locked: external_exports.boolean()
23409
+ }).describe("PackPolicyFloor");
23657
23410
  var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
23658
23411
  (id) => !BUILTIN_POLICY_SPECS[id].reversible
23659
23412
  );
@@ -23711,6 +23464,397 @@ var PolicyStatsResponse = external_exports.object({
23711
23464
  detectionsGoverned: external_exports.number().int().nonnegative()
23712
23465
  }).meta({ id: "PolicyStatsResponse" });
23713
23466
 
23467
+ // ../../packages/schema/src/zod/vault.ts
23468
+ var POINTER_FORMAT_VERSION = 2;
23469
+ var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
23470
+ var POINTER_TOKEN_PATTERN = new RegExp(
23471
+ `\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
23472
+ );
23473
+ var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
23474
+ function pointerTokenScanner() {
23475
+ return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
23476
+ }
23477
+ var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
23478
+ var ParsedPointer = external_exports.object({
23479
+ category: DetectionCategory,
23480
+ keyVersion: external_exports.number().int().positive(),
23481
+ pointerId: external_exports.string(),
23482
+ tag: external_exports.string()
23483
+ });
23484
+ var VaultEntry = external_exports.object({
23485
+ pointerId: external_exports.string(),
23486
+ // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
23487
+ // derived under. This is what a reveal-to-model grant matches on, and it rotates
23488
+ // independently of the vault encryption key below.
23489
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23490
+ fingerprintKeyVersion: external_exports.number().int().positive(),
23491
+ // The vault-key epoch this row's ciphertext was sealed under.
23492
+ keyVersion: external_exports.number().int().positive(),
23493
+ // Fixed at first mint and never updated: the same value detected later under a
23494
+ // different rule's category keeps the category it was minted with, so one
23495
+ // value always produces exactly one wire token.
23496
+ category: DetectionCategory,
23497
+ ruleId: external_exports.string(),
23498
+ // Partial-reveal preview for badges and listings. Never the raw value.
23499
+ maskedMatch: external_exports.string(),
23500
+ provider: external_exports.string().optional(),
23501
+ ciphertext: external_exports.string(),
23502
+ nonce: external_exports.string(),
23503
+ authTag: external_exports.string(),
23504
+ // How many times this value has been detected on this machine — the reuse
23505
+ // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
23506
+ occurrenceCount: external_exports.number().int().nonnegative(),
23507
+ // True when a PERSON asked for this value to be replaced — the surfaced-
23508
+ // secrets strike — rather than a pack enforcing its assignment. One value is
23509
+ // one row however many paths vault it, so this is what tells a policy sweep
23510
+ // that the row carries somebody's own instruction and not just an assignment
23511
+ // that has since been lowered. STICKY and MONOTONIC: a later automatic
23512
+ // vaulting of the same value must never clear it — what the user said about
23513
+ // the value does not expire.
23514
+ userAuthorized: external_exports.boolean(),
23515
+ firstSeen: external_exports.string(),
23516
+ lastSeen: external_exports.string()
23517
+ });
23518
+ var PointerDescriptor = external_exports.object({
23519
+ category: DetectionCategory,
23520
+ provider: external_exports.string().optional(),
23521
+ maskedMatch: external_exports.string(),
23522
+ occurrences: external_exports.number().int().nonnegative(),
23523
+ firstSeen: external_exports.string(),
23524
+ lastSeen: external_exports.string()
23525
+ });
23526
+ var PointerIdentity = external_exports.object({
23527
+ ruleId: external_exports.string(),
23528
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23529
+ fingerprintKeyVersion: external_exports.number().int().positive()
23530
+ });
23531
+ var DetokenizeTarget = external_exports.enum(["human", "model"]);
23532
+ var VaultDerefReason = external_exports.enum([
23533
+ "display",
23534
+ "explicit-reveal",
23535
+ "view-render",
23536
+ "model-input",
23537
+ "remediation",
23538
+ "purge"
23539
+ ]);
23540
+ var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
23541
+ var VaultDeref = external_exports.object({
23542
+ id: external_exports.guid(),
23543
+ pointerId: external_exports.string(),
23544
+ at: external_exports.string(),
23545
+ target: DetokenizeTarget,
23546
+ reason: VaultDerefReason,
23547
+ outcome: VaultDerefOutcome,
23548
+ // Present only on a model-target crossing that a reveal grant authorized.
23549
+ grantId: external_exports.string().optional(),
23550
+ // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
23551
+ // apart from VaultEntry.occurrenceCount, which counts detections of a value.
23552
+ pointerCount: external_exports.number().int().positive().default(1)
23553
+ });
23554
+ var VaultSightingKind = external_exports.enum([
23555
+ "prompt",
23556
+ "tool-input",
23557
+ "tool-output",
23558
+ "file",
23559
+ "transcript"
23560
+ ]);
23561
+ var VaultSighting = external_exports.object({
23562
+ location: external_exports.string(),
23563
+ kind: VaultSightingKind,
23564
+ firstSeen: external_exports.string(),
23565
+ lastSeen: external_exports.string()
23566
+ });
23567
+ var VaultInventoryEntry = external_exports.object({
23568
+ pointerId: external_exports.string(),
23569
+ category: DetectionCategory,
23570
+ provider: external_exports.string().optional(),
23571
+ maskedMatch: external_exports.string(),
23572
+ occurrences: external_exports.number().int().nonnegative(),
23573
+ firstSeen: external_exports.string(),
23574
+ lastSeen: external_exports.string(),
23575
+ // The active reveal-to-model grant covering this value, when one exists —
23576
+ // the inventory badges it, the row links to revocation.
23577
+ revealGrantId: external_exports.string().nullable(),
23578
+ sightings: external_exports.array(VaultSighting)
23579
+ });
23580
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
23581
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
23582
+ var MAX_VAULT_PAGE_LIMIT = 200;
23583
+ var ListVaultInventoryQuery = external_exports.object({
23584
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23585
+ // Opaque; names the last row of the page just served.
23586
+ cursor: external_exports.string().optional()
23587
+ });
23588
+ var ListVaultInventoryResponse = external_exports.object({
23589
+ // Vaulted values across the whole store, not just this page — cursor-
23590
+ // independent, so paging never changes what the count claims.
23591
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
23592
+ items: external_exports.array(VaultInventoryEntry),
23593
+ // `null` once the last page is reached.
23594
+ nextCursor: external_exports.string().nullable()
23595
+ });
23596
+ var ListVaultReuseQuery = external_exports.object({
23597
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23598
+ cursor: external_exports.string().optional()
23599
+ });
23600
+ var ListVaultReuseResponse = external_exports.object({
23601
+ // Reused values across the whole store — the number the section's claim
23602
+ // ("values detected in more than one place") is about.
23603
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
23604
+ items: external_exports.array(VaultInventoryEntry),
23605
+ nextCursor: external_exports.string().nullable()
23606
+ });
23607
+ var ListVaultDerefsQuery = external_exports.object({
23608
+ // Include the batched, high-volume reasons (display, view-render). Omitted
23609
+ // hides them and counts them into `hiddenBatched` instead, so the model
23610
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
23611
+ // over a Server Action, which preserves the type, never as a URL param.
23612
+ includeBatched: external_exports.boolean().optional(),
23613
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23614
+ cursor: external_exports.string().optional()
23615
+ });
23616
+ var ListVaultDerefsResponse = external_exports.object({
23617
+ items: external_exports.array(VaultDeref),
23618
+ nextCursor: external_exports.string().nullable(),
23619
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
23620
+ // this page — it is the count the "N hidden" line and its toggle speak for.
23621
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
23622
+ hiddenBatched: external_exports.number().int().nonnegative()
23623
+ });
23624
+ var VaultKeyCustody = external_exports.string();
23625
+ var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
23626
+ var VAULT_CONSENT_VERSION = 1;
23627
+ var VaultConsent = external_exports.object({
23628
+ acknowledgedAt: external_exports.iso.datetime(),
23629
+ version: external_exports.number().int().positive()
23630
+ });
23631
+
23632
+ // ../../packages/schema/src/zod/local.ts
23633
+ var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
23634
+ var MODEL_JUDGE_PAYLOAD_VERSION = 1;
23635
+ var RunMode = external_exports.enum(["standalone", "attached"]);
23636
+ var ControlPlaneConnection = external_exports.object({
23637
+ endpoint: external_exports.string().min(1),
23638
+ // Display name for the deployment, shown instead of the raw endpoint.
23639
+ label: external_exports.string().min(1).optional(),
23640
+ attachedAt: external_exports.iso.datetime()
23641
+ }).meta({ id: "ControlPlaneConnection" });
23642
+ var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
23643
+ var HistoricalAccess = external_exports.enum(["full", "session-only"]);
23644
+ var ModelJudgeConsent = external_exports.object({
23645
+ acknowledgedAt: external_exports.iso.datetime(),
23646
+ payloadVersion: external_exports.number().int().positive()
23647
+ });
23648
+ var HistorySyncConsent = external_exports.object({
23649
+ acknowledgedAt: external_exports.iso.datetime(),
23650
+ payloadVersion: external_exports.number().int().positive(),
23651
+ endpoint: external_exports.string()
23652
+ });
23653
+ var WorkspaceSettings = external_exports.object({
23654
+ specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
23655
+ runMode: RunMode.default("standalone"),
23656
+ // Present only while attached; a detach clears it. Its presence is what makes
23657
+ // `runMode: 'attached'` mean anything — see isAttached.
23658
+ controlPlane: ControlPlaneConnection.optional(),
23659
+ policy: SimpleDetectionPolicy.default("redact"),
23660
+ // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
23661
+ historicalAccess: HistoricalAccess.default("session-only"),
23662
+ // In-place egress extraction on the scan paths; disable to stop all Data
23663
+ // Shares writes.
23664
+ dataSharesInPlace: external_exports.boolean().default(true),
23665
+ // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
23666
+ // vault, instead of destroying them. Absent by default: this is a custody
23667
+ // change from one-way redaction, so it is never an assumed grant on upgrade.
23668
+ // Revoking stops future vaulting; it does not erase what is already stored —
23669
+ // purging the vault is the eraser.
23670
+ vaultConsent: VaultConsent.optional(),
23671
+ // Where the vault master key lives.
23672
+ vaultKeyCustody: VaultKeyCustody.default("file"),
23673
+ // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
23674
+ vaultInlineReveal: VaultInlineReveal.default("masked"),
23675
+ // What a `redact` policy degrades to on a FIELD the host cannot rewrite in
23676
+ // place. Not a handling policy: the policy has already resolved to redact,
23677
+ // and this only says what happens when the host offers no channel to carry it
23678
+ // out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
23679
+ // Claude Code decline to mask a field that EXECUTES because masking would
23680
+ // change what runs. Per FIELD rather than per host, so a host that can
23681
+ // rewrite some inputs keeps true redaction on those.
23682
+ //
23683
+ // Spelled in the built-in policy vocabulary rather than as a fresh enum, so
23684
+ // an attached machine's merge is `strongerAction` over the one action ladder
23685
+ // and no second rank order exists to drift from it. 'deny' is a host wire
23686
+ // word and stays out of the stored value.
23687
+ redactFallback: RedactFallback.default("warn"),
23688
+ // Absent until /aka:setup completes; its presence is what "onboarded" means.
23689
+ onboardedAt: external_exports.iso.datetime().optional(),
23690
+ // Records that the user consented to sending findings to the model API for
23691
+ // the /aka:setup judge, along with the payload-shape version they agreed to.
23692
+ // Absent until granted; a stale payloadVersion means the consent no longer
23693
+ // covers the current payload and must be re-granted.
23694
+ modelJudgeConsent: ModelJudgeConsent.optional(),
23695
+ // Records that the user consented to the DEFERRED send — the outbox — along
23696
+ // with the payload shape and the endpoint they agreed to. Since payload v2
23697
+ // that covers both the pre-attach backlog and undelivered captures (which
23698
+ // carry prompt/reply text in `content`); the key name predates the widening.
23699
+ // Absent until granted, and a grant for a different endpoint or an older
23700
+ // payload no longer counts.
23701
+ historySyncConsent: HistorySyncConsent.optional()
23702
+ });
23703
+ function defaultWorkspaceSettings() {
23704
+ return WorkspaceSettings.parse({});
23705
+ }
23706
+ function isAttached(settings) {
23707
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
23708
+ }
23709
+ function toInventoryRow(input2, id, now) {
23710
+ return {
23711
+ id,
23712
+ objectType: input2.objectType,
23713
+ location: input2.location ?? null,
23714
+ title: input2.title ?? null,
23715
+ hostId: input2.hostId ?? null,
23716
+ attributes: JSON.stringify(input2.attributes),
23717
+ firstSeen: now,
23718
+ lastSeen: now
23719
+ };
23720
+ }
23721
+ function toSourceProjectRow(input2, id, now) {
23722
+ return {
23723
+ id,
23724
+ url: input2.url,
23725
+ name: input2.name ?? null,
23726
+ attributes: JSON.stringify(input2.attributes),
23727
+ firstSeen: now,
23728
+ lastSeen: now
23729
+ };
23730
+ }
23731
+ function toAuditEventRow(input2) {
23732
+ return {
23733
+ id: input2.id,
23734
+ parentId: input2.parentId ?? null,
23735
+ rootSessionId: input2.rootSessionId ?? null,
23736
+ eventType: input2.eventType,
23737
+ hostId: input2.hostId ?? null,
23738
+ harnessId: input2.harnessId ?? null,
23739
+ sourceProjectId: input2.sourceProjectId ?? null,
23740
+ startedAt: isoToEpochMillis(input2.startedAt),
23741
+ endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
23742
+ severity: input2.severity ?? null,
23743
+ priority: input2.priority ?? null,
23744
+ content: input2.content ?? null,
23745
+ contentHash: input2.contentHash ?? null,
23746
+ attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23747
+ };
23748
+ }
23749
+ function toClassifiedDataRow(input2, id) {
23750
+ return {
23751
+ id,
23752
+ class: input2.class,
23753
+ label: input2.label ?? null,
23754
+ attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23755
+ };
23756
+ }
23757
+ function toInspectionDefinitionRow(input2, id) {
23758
+ return {
23759
+ id,
23760
+ ruleId: input2.ruleId,
23761
+ name: input2.name,
23762
+ category: input2.category,
23763
+ severity: input2.severity,
23764
+ definition: input2.definition,
23765
+ version: input2.version
23766
+ };
23767
+ }
23768
+ function toInspectionFindingRow(input2) {
23769
+ return {
23770
+ id: input2.id,
23771
+ auditEventId: input2.auditEventId,
23772
+ inspectionDefinitionId: input2.inspectionDefinitionId,
23773
+ classifiedDataId: input2.classifiedDataId ?? null,
23774
+ spanStart: input2.span.start,
23775
+ spanEnd: input2.span.end,
23776
+ maskedMatch: input2.maskedMatch,
23777
+ actionTaken: input2.actionTaken,
23778
+ confidence: input2.confidence,
23779
+ findingKey: input2.findingKey ?? null,
23780
+ firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
23781
+ };
23782
+ }
23783
+ function toCaptureAttributes(event) {
23784
+ const metadata = event.metadata;
23785
+ return {
23786
+ source_tool: event.sourceTool,
23787
+ ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
23788
+ ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
23789
+ ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
23790
+ ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
23791
+ ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
23792
+ ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
23793
+ ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
23794
+ ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
23795
+ ...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
23796
+ // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
23797
+ // has ever populated either), but every legacy metadata key still rides
23798
+ // the bag rather than being silently dropped — CaptureAttributes'
23799
+ // `.catchall(z.unknown())` carries the long tail.
23800
+ ...metadata?.model !== void 0 ? { model: metadata.model } : {},
23801
+ ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
23802
+ };
23803
+ }
23804
+ function captureDefinitionVersion(finding) {
23805
+ return `capture/${finding.category}/${finding.severity}`;
23806
+ }
23807
+ function toCaptureDefinitionInput(finding) {
23808
+ return {
23809
+ ruleId: finding.ruleId,
23810
+ version: captureDefinitionVersion(finding),
23811
+ name: finding.ruleId,
23812
+ category: finding.category,
23813
+ severity: finding.severity,
23814
+ definition: JSON.stringify({ ruleId: finding.ruleId })
23815
+ };
23816
+ }
23817
+
23818
+ // ../../packages/schema/src/zod/managed.ts
23819
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
23820
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
23821
+ var ManagedSettingKey = external_exports.enum([
23822
+ "runMode",
23823
+ "historicalAccess",
23824
+ "vaultConsent",
23825
+ "vaultKeyCustody",
23826
+ "vaultInlineReveal",
23827
+ "modelJudgeConsent",
23828
+ "dataSharesInPlace",
23829
+ "redactFallback"
23830
+ ]).meta({ id: "ManagedSettingKey" });
23831
+ var ManagedSettingsValues = external_exports.object({
23832
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
23833
+ controlPlane: external_exports.object({
23834
+ endpoint: external_exports.string().min(1),
23835
+ label: external_exports.string().min(1).optional()
23836
+ }).optional(),
23837
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
23838
+ vaultConsent: external_exports.boolean().optional(),
23839
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
23840
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
23841
+ modelJudgeConsent: external_exports.boolean().optional(),
23842
+ dataSharesInPlace: external_exports.boolean().optional(),
23843
+ redactFallback: RedactFallback.optional()
23844
+ }).meta({ id: "ManagedSettingsValues" });
23845
+ var ManagedSettings = external_exports.object({
23846
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
23847
+ // Shown on every locked control, so the user can tell an administrative
23848
+ // decision from a bug. Absent renders as a generic "your organization".
23849
+ organization: external_exports.string().min(1).optional(),
23850
+ // What the administrator pinned.
23851
+ values: ManagedSettingsValues.default({}),
23852
+ // Which of those the user may not change. A key here with no matching value
23853
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
23854
+ // the user may still override. The two are separable on purpose.
23855
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
23856
+ }).meta({ id: "ManagedSettings" });
23857
+
23714
23858
  // ../../packages/schema/src/zod/project-files.ts
23715
23859
  var ProjectFileInput = external_exports.object({
23716
23860
  path: external_exports.string().min(1),
@@ -23956,10 +24100,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
23956
24100
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
23957
24101
 
23958
24102
  // ../../packages/schema/src/zod/settings-action.ts
24103
+ var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
24104
+ var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
23959
24105
  var SaveSettingsInput = external_exports.object({
23960
24106
  historicalAccess: external_exports.string(),
23961
- modelJudgeConsent: external_exports.boolean(),
23962
- historySyncConsent: external_exports.boolean(),
24107
+ modelJudgeConsent: ModelJudgeConsentChoice,
24108
+ historySyncConsent: HistorySyncConsentChoice,
23963
24109
  vaultConsent: external_exports.string(),
23964
24110
  vaultInlineReveal: external_exports.string()
23965
24111
  });
@@ -24109,9 +24255,9 @@ function deriveReviewReasons(trust, transports) {
24109
24255
  if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
24110
24256
  return reasons;
24111
24257
  }
24112
- function buildReviewInfo(trust, transports) {
24258
+ function buildReviewInfo(trust, transports, decided) {
24113
24259
  const reasons = deriveReviewReasons(trust, transports);
24114
- return { needsReview: reasons.length > 0, reasons };
24260
+ return { needsReview: reasons.length > 0 && !decided, reasons };
24115
24261
  }
24116
24262
  function distinctTransports(transports) {
24117
24263
  return Array.from(new Set(transports));
@@ -24329,8 +24475,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
24329
24475
  }
24330
24476
 
24331
24477
  // ../../packages/persistence/src/database.ts
24332
- import { randomUUID as randomUUID10 } from "crypto";
24333
- import { join as join4, sep } from "path";
24478
+ import { randomUUID as randomUUID11 } from "crypto";
24479
+ import { dirname as dirname2, join as join7, sep } from "path";
24334
24480
  import { DatabaseSync } from "node:sqlite";
24335
24481
 
24336
24482
  // ../../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, table2) {
25063
25213
  if (!columns.includes("sync_claimed_at")) {
25064
25214
  db.exec(`ALTER TABLE ${table2} ADD COLUMN sync_claimed_at integer`);
25065
25215
  }
25216
+ if (!columns.includes("outbox_owed")) {
25217
+ db.exec(`ALTER TABLE ${table2} 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 max(
25330
- s.started_at,
25331
- coalesce(
25332
- (SELECT max(${LAST_ACTIVITY_EXPR}) FROM audit_events e WHERE e.root_session_id = s.id),
25333
- s.started_at
25334
- )
25335
- ) >= ?`,
25336
- [liveThreshold]
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` leaf (optionally windowed to
25551
- * `started_at >= fromMs`) grouped into per-session `SessionTokenReport`s, with
25552
- * USD cost DERIVED at read time via the shared `defaultCostModel` (never
25553
- * stored). `fromMs` lets the Activity page scope the usage panel to its
25554
- * selected time range; omit it for all-time (the CLI/TUI overview). The
25555
- * caller collapses these onto per-model rows with `aggregateTokenUsage`.
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 leaves = this.readLlmCallLeaves(fromMs === void 0 ? {} : { fromMs });
25559
- return Promise.resolve(buildTokenReports(leaves, defaultCostModel));
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` leaves grouped per (provider,
25563
- * model) with derived cost, or `null` when the session made no `llm_call`s
25564
- * (an empty/tool-only session). Feeds the session-detail pane's per-model
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 reports = buildTokenReports(this.readLlmCallLeaves({ sessionId }), defaultCostModel);
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 root_session_id AS id, max(${LAST_ACTIVITY_EXPR}) AS m FROM audit_events
25643
- WHERE root_session_id IN (${inClause})
25644
- GROUP BY root_session_id`
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
- if (entry && row.m !== null) entry.lastActivityMs = row.m;
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 FROM audit_events
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
- json_extract(e.attributes, '$.source_tool') AS source_tool,
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 = allRows(
26942
- this.db.prepare(
26943
- `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
26944
- occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
26945
- kind, finding_key, latest_status
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 are pulled in batches so memory stays flat
27077
- * while the counting runs, and only the page itself is retained.
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
- * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
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. Each batch advances the same keyset
27245
- * predicate the page read uses, so the scan is a sequence of bounded reads
27246
- * rather than one unbounded result set.
27247
- *
27248
- * The latest-resolution lookup is the CORRELATED form, not the derived table
27249
- * the grouped path joins: only `status` is needed, idx_finding_resolution_key
27250
- * makes it a point lookup per row, and the derived table would re-materialize
27251
- * a window over the whole resolution table once per batch.
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.
27252
27436
  *
27253
- * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
27254
- * would be missing from its own facet, which is computed by excluding that
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 conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
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
- json_extract(e.attributes, '$.source_tool') AS source_tool,
27273
- json_extract(e.attributes, '$.repo') AS repo,
27274
- json_extract(e.attributes, '$.file_path') AS file,
27275
- json_extract(e.attributes, '$.tool_name') AS tool_name,
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 inspection_findings f
27280
- JOIN audit_events e ON e.id = f.audit_event_id
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
- AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
27284
- ORDER BY e.started_at DESC, f.id DESC
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 json_extract(e.attributes, '$.repo')) AS repos,
27322
- group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
27323
- group_concat(DISTINCT 'via ' || json_extract(e.attributes, '$.tool_name')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
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 json_extract(e.attributes, '$.source_tool')) AS source_tools,
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,256 @@ var SqliteInspectionFindingsRepository = class {
27977
28223
  };
27978
28224
 
27979
28225
  // ../../packages/persistence/src/repositories/installed-packs.ts
27980
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
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
+ async function ensureDataDir(dir = defaultDataDir()) {
28250
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
28251
+ tightenDir(dir);
28252
+ }
28253
+ function ensureLayoutDirSync(dir = defaultDataDir()) {
28254
+ ensureDataDirSync(dir);
28255
+ }
28256
+ function migrateLegacyLayout(base = defaultDataDir()) {
28257
+ const moves = [
28258
+ { name: "config.json", dest: settingsDir(base) },
28259
+ { name: "policy-cache.json", dest: dataDir(base) }
28260
+ ];
28261
+ for (const { name, dest } of moves) {
28262
+ try {
28263
+ ensureDataDirSync(dest);
28264
+ const moved = join4(dest, name);
28265
+ renameSync3(join4(base, name), moved);
28266
+ tightenFile(moved);
28267
+ } catch {
28268
+ }
28269
+ }
28270
+ }
28271
+
28272
+ // ../../packages/persistence/src/settings.ts
28273
+ import { readFileSync as readFileSync4 } from "fs";
28274
+ import { join as join5 } from "path";
28275
+
28276
+ // ../../packages/persistence/src/file-lock.ts
28277
+ import { randomUUID as randomUUID3 } from "crypto";
28278
+ import {
28279
+ closeSync,
28280
+ existsSync as existsSync2,
28281
+ openSync,
28282
+ readFileSync as readFileSync2,
28283
+ rmSync as rmSync5,
28284
+ statSync as statSync3,
28285
+ writeFileSync as writeFileSync2
28286
+ } from "fs";
28287
+ import { hostname as hostname3 } from "os";
28288
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
28289
+
28290
+ // ../../packages/persistence/src/managed-settings.ts
28291
+ import { readFileSync as readFileSync3 } from "fs";
28292
+ import { posix, win32 } from "path";
28293
+ function managedSettingsPaths(platform2 = process.platform) {
28294
+ if (platform2 === "darwin") {
28295
+ return [
28296
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
28297
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
28298
+ ];
28299
+ }
28300
+ if (platform2 === "win32") {
28301
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
28302
+ }
28303
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
28304
+ }
28305
+ function readManagedSettings(paths = managedSettingsPaths()) {
28306
+ for (const path of paths) {
28307
+ let text;
28308
+ try {
28309
+ text = readFileSync3(path, "utf8");
28310
+ } catch {
28311
+ continue;
28312
+ }
28313
+ const record2 = parseJsonObject(text);
28314
+ if (!record2) continue;
28315
+ const parsed2 = ManagedSettings.safeParse(record2);
28316
+ if (parsed2.success) return parsed2.data;
28317
+ }
28318
+ return null;
28319
+ }
28320
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
28321
+ if (!managed) return settings;
28322
+ const { values } = managed;
28323
+ const merged = { ...settings };
28324
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
28325
+ if (values.controlPlane !== void 0) {
28326
+ merged.controlPlane = {
28327
+ ...values.controlPlane,
28328
+ // The administrator pinned WHICH deployment, not WHEN this machine
28329
+ // joined it. Keep the user's own attach time when the endpoint is
28330
+ // unchanged, so a managed machine does not appear to re-attach on every
28331
+ // read; stamp a fresh one when the administrator moved it.
28332
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
28333
+ };
28334
+ }
28335
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
28336
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
28337
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
28338
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
28339
+ if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
28340
+ if (values.vaultConsent !== void 0) {
28341
+ merged.vaultConsent = values.vaultConsent ? (
28342
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
28343
+ // at the current version otherwise.
28344
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
28345
+ ) : void 0;
28346
+ }
28347
+ if (values.modelJudgeConsent !== void 0) {
28348
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
28349
+ acknowledgedAt: now().toISOString(),
28350
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
28351
+ } : void 0;
28352
+ }
28353
+ return merged;
28354
+ }
28355
+
28356
+ // ../../packages/persistence/src/settings.ts
28357
+ var SETTINGS_FILENAME = "settings.json";
28358
+ function readWorkspaceSettings(base = defaultDataDir()) {
28359
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
28360
+ }
28361
+ function readUserSettings(base) {
28362
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
28363
+ if (!record2) return defaultWorkspaceSettings();
28364
+ try {
28365
+ return WorkspaceSettings.parse(record2);
28366
+ } catch {
28367
+ return defaultWorkspaceSettings();
28368
+ }
28369
+ }
28370
+ function readJson(file2) {
28371
+ let text;
28372
+ try {
28373
+ text = readFileSync4(file2, "utf8");
28374
+ } catch {
28375
+ return null;
28376
+ }
28377
+ return parseJsonObject(text) ?? null;
28378
+ }
28379
+
28380
+ // ../../packages/persistence/src/policy-floor.ts
28381
+ function refusalMessage(pack, attempted, floor, refusal) {
28382
+ switch (refusal) {
28383
+ case "lock":
28384
+ return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
28385
+ case "disable":
28386
+ return `refusing to disable '${pack}': it is governed by the connected control plane`;
28387
+ case "floor":
28388
+ return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
28389
+ }
28390
+ }
28391
+ var PolicyFloorError = class extends Error {
28392
+ /** `namespace/packId` of the detection whose write was refused. */
28393
+ pack;
28394
+ /**
28395
+ * The archetype the caller asked for, or null when the write named none —
28396
+ * clearing the assignment, or switching the detection off.
28397
+ */
28398
+ attempted;
28399
+ /** The weakest archetype the control plane permits for this pack. */
28400
+ floor;
28401
+ refusal;
28402
+ constructor(pack, attempted, floor, refusal) {
28403
+ super(refusalMessage(pack, attempted, floor, refusal));
28404
+ this.name = "PolicyFloorError";
28405
+ this.pack = pack;
28406
+ this.attempted = attempted;
28407
+ this.floor = floor;
28408
+ this.refusal = refusal;
28409
+ }
28410
+ };
28411
+ function readCachedPolicyBundle(base = defaultDataDir()) {
28412
+ try {
28413
+ const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
28414
+ const parsed2 = JSON.parse(raw);
28415
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
28416
+ return PolicyBundle.parse(parsed2.bundle);
28417
+ } catch {
28418
+ return null;
28419
+ }
28420
+ }
28421
+ function indexEnabled(policies) {
28422
+ const byRuleId = /* @__PURE__ */ new Map();
28423
+ const byCategory = /* @__PURE__ */ new Map();
28424
+ for (const policy of policies) {
28425
+ if (!policy.enabled) continue;
28426
+ if ("ruleId" in policy.target) {
28427
+ if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
28428
+ } else if (!byCategory.has(policy.target.category)) {
28429
+ byCategory.set(policy.target.category, policy.action);
28430
+ }
28431
+ }
28432
+ return { byRuleId, byCategory };
28433
+ }
28434
+ function hasAuthoredPolicy(policies, rules, byRuleId) {
28435
+ const ruleIds = new Set(rules.map((rule) => rule.id));
28436
+ const categories = new Set(rules.map((rule) => rule.category));
28437
+ const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
28438
+ return policies.some((policy) => {
28439
+ if (!policy.enabled || policy.provenance !== "authored") return false;
28440
+ return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
28441
+ });
28442
+ }
28443
+ function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
28444
+ const floors = openControlPlaneFloors(base);
28445
+ return floors === null ? null : floors.floorFor(rules);
28446
+ }
28447
+ function openControlPlaneFloors(base = defaultDataDir()) {
28448
+ if (!isAttached(readWorkspaceSettings(base))) return null;
28449
+ const bundle = readCachedPolicyBundle(base);
28450
+ if (bundle === null) return null;
28451
+ const indexes = indexEnabled(bundle.policies);
28452
+ return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
28453
+ }
28454
+ function resolveFloor(rules, policies, { byRuleId, byCategory }) {
28455
+ let action = null;
28456
+ for (const rule of rules) {
28457
+ const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
28458
+ if (resolved === void 0) continue;
28459
+ action = action === null ? resolved : strongerAction(action, resolved);
28460
+ }
28461
+ if (action === null) return null;
28462
+ return {
28463
+ floor: weakestBuiltinAtLeast(action),
28464
+ locked: hasAuthoredPolicy(policies, rules, byRuleId)
28465
+ };
28466
+ }
28467
+ function policyAssignmentRefusal(policyId, floor) {
28468
+ if (floor.locked) return "lock";
28469
+ const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
28470
+ return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
28471
+ }
28472
+ function packEnablementRefusal(enabled, floor) {
28473
+ if (floor === null || enabled) return null;
28474
+ return "disable";
28475
+ }
27981
28476
 
27982
28477
  // ../../packages/persistence/src/semver.ts
27983
28478
  function parse3(version2) {
@@ -28071,8 +28566,19 @@ function ruleIdsOf(rulesJson) {
28071
28566
  return ids;
28072
28567
  }
28073
28568
  var SqliteInstalledPacksRepository = class {
28074
- constructor(db) {
28569
+ /**
28570
+ * `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
28571
+ * floor needs both halves of it (settings/ says whether this machine is
28572
+ * attached, data/ holds the cached bundle). It is optional because a caller
28573
+ * holding only a DatabaseSync — every test construction site, and any embedder
28574
+ * that opens the store itself — has no layout to point at, and such a caller
28575
+ * gets the pre-existing behaviour: no floor, no lock. Production threads it in
28576
+ * from `openLocalDatabase`, which is the single construction site that owns a
28577
+ * real `~/.aka`.
28578
+ */
28579
+ constructor(db, baseDir) {
28075
28580
  this.db = db;
28581
+ this.baseDir = baseDir;
28076
28582
  this.insertMissingStmt = db.prepare(
28077
28583
  `INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
28078
28584
  VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
@@ -28094,11 +28600,17 @@ var SqliteInstalledPacksRepository = class {
28094
28600
  this.signatureStmt = db.prepare(
28095
28601
  `SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
28096
28602
  );
28603
+ this.packRulesStmt = db.prepare(
28604
+ `SELECT rules_json AS rulesJson FROM installed_packs
28605
+ WHERE namespace = ? AND pack_id = ?`
28606
+ );
28097
28607
  }
28098
28608
  db;
28609
+ baseDir;
28099
28610
  insertMissingStmt;
28100
28611
  upsertAvailableStmt;
28101
28612
  signatureStmt;
28613
+ packRulesStmt;
28102
28614
  /**
28103
28615
  * Record the running binary's detection inventory. Refreshes the
28104
28616
  * available_packs mirror (pruning packs the binary no longer ships) and
@@ -28140,7 +28652,7 @@ var SqliteInstalledPacksRepository = class {
28140
28652
  let behind = false;
28141
28653
  for (const row of rows) {
28142
28654
  const params = {
28143
- id: randomUUID3(),
28655
+ id: randomUUID4(),
28144
28656
  namespace: row.namespace,
28145
28657
  packId: row.packId,
28146
28658
  version: row.version,
@@ -28152,7 +28664,7 @@ var SqliteInstalledPacksRepository = class {
28152
28664
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
28153
28665
  this.upsertAvailableStmt.run({
28154
28666
  ...params,
28155
- id: randomUUID3(),
28667
+ id: randomUUID4(),
28156
28668
  recordedBy: meta4?.recordedBy ?? null
28157
28669
  });
28158
28670
  } else {
@@ -28398,9 +28910,65 @@ var SqliteInstalledPacksRepository = class {
28398
28910
  // NOT on the hook path — so, unlike recordInventory, these surface errors to the
28399
28911
  // caller rather than swallowing them. Each returns whether a row matched, so the
28400
28912
  // caller can tell an edit from a no-such-detection.
28913
+ /**
28914
+ * The rules one installed pack owns, reduced to what a floor computation
28915
+ * reads. Display-tolerant parsing on purpose: a pack whose snapshot is
28916
+ * unreadable contributes no rules to a scan either, so it is not a detection
28917
+ * the control plane can be governing, and an empty list correctly imposes no
28918
+ * floor. Enabled state is deliberately not filtered — a disabled pack is one
28919
+ * the user can re-enable, and its assignment stays governed meanwhile.
28920
+ */
28921
+ packFloorRules(namespace, packId) {
28922
+ const row = getRow(this.packRulesStmt, [namespace, packId]);
28923
+ if (!row) return [];
28924
+ return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
28925
+ }
28926
+ /**
28927
+ * What the connected control plane imposes on one installed pack, or null on a
28928
+ * machine that is its own authority (standalone, no cached bundle, or a
28929
+ * repository constructed without a layout base).
28930
+ *
28931
+ * Exposed as a READ so a surface can render the constraint — grey out the
28932
+ * choices below the floor, mark a locked detection as locked — rather than
28933
+ * offer the user a picker whose selections it will then be told it may not
28934
+ * make. The refusal in `setPolicy` does not depend on any surface calling this.
28935
+ */
28936
+ policyFloor(namespace, packId) {
28937
+ if (this.baseDir === void 0) return null;
28938
+ return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
28939
+ }
28940
+ /**
28941
+ * The same answer for several packs, keyed `namespace/packId` and carrying an
28942
+ * entry only for a pack the control plane actually governs.
28943
+ *
28944
+ * A surface listing every detection asks per pack, and asking through
28945
+ * `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
28946
+ * bundle and rebuilds its indexes once per pack — the entire cost of one
28947
+ * answer, repeated for each row, on every render. This reads all of that once.
28948
+ * Packs whose rules the snapshot cannot produce simply contribute no entry,
28949
+ * exactly as the single-pack read returns null for them.
28950
+ */
28951
+ policyFloors(packs2) {
28952
+ const floors = /* @__PURE__ */ new Map();
28953
+ if (this.baseDir === void 0) return floors;
28954
+ const source = openControlPlaneFloors(this.baseDir);
28955
+ if (source === null) return floors;
28956
+ for (const pack of packs2) {
28957
+ const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
28958
+ if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
28959
+ }
28960
+ return floors;
28961
+ }
28401
28962
  /**
28402
28963
  * 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).
28964
+ * `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
28965
+ *
28966
+ * On an ATTACHED machine the organization's bundle is a floor this refuses to
28967
+ * write below, and a detection the organization has authored a policy for is
28968
+ * refused outright — see policy-floor.ts for both, and for why the refusal is
28969
+ * a throw rather than a silently substituted value. This is the one device-local
28970
+ * write path for the assignment, so the check belongs here rather than on any
28971
+ * surface that offers the choice.
28404
28972
  */
28405
28973
  setPolicy(namespace, packId, policyId) {
28406
28974
  if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
@@ -28408,14 +28976,38 @@ var SqliteInstalledPacksRepository = class {
28408
28976
  `Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
28409
28977
  );
28410
28978
  }
28979
+ const requested = policyId;
28980
+ const floor = this.policyFloor(namespace, packId);
28981
+ if (floor !== null) {
28982
+ const refusal = policyAssignmentRefusal(requested, floor);
28983
+ if (refusal !== null) {
28984
+ throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
28985
+ }
28986
+ }
28411
28987
  const res = this.db.prepare(
28412
28988
  `UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
28413
28989
  WHERE namespace = :namespace AND pack_id = :packId`
28414
28990
  ).run({ policyId, now: Date.now(), namespace, packId });
28415
28991
  return Number(res.changes) > 0;
28416
28992
  }
28417
- /** Enable or disable one installed pack. */
28993
+ /**
28994
+ * Enable or disable one installed pack.
28995
+ *
28996
+ * On an ATTACHED machine a detection the organization's bundle governs at all
28997
+ * may not be switched OFF here — see packEnablementRefusal for why that is not
28998
+ * merely another point below the floor, and why re-enabling stays open. Like
28999
+ * the assignment above, the check belongs at this write path rather than on a
29000
+ * surface: this is the one device-local writer of the column, and a refusal
29001
+ * that lived in a page would leave the CLI free.
29002
+ */
28418
29003
  setEnabled(namespace, packId, enabled) {
29004
+ const floor = this.policyFloor(namespace, packId);
29005
+ if (floor !== null) {
29006
+ const refusal = packEnablementRefusal(enabled, floor);
29007
+ if (refusal !== null) {
29008
+ throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
29009
+ }
29010
+ }
28419
29011
  const res = this.db.prepare(
28420
29012
  `UPDATE installed_packs SET enabled = :enabled, updated_at = :now
28421
29013
  WHERE namespace = :namespace AND pack_id = :packId`
@@ -28501,7 +29093,7 @@ var SqliteInventoryRepository = class {
28501
29093
  };
28502
29094
 
28503
29095
  // ../../packages/persistence/src/repositories/inventory-assets.ts
28504
- import { randomUUID as randomUUID4 } from "crypto";
29096
+ import { randomUUID as randomUUID5 } from "crypto";
28505
29097
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
28506
29098
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
28507
29099
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
@@ -28990,7 +29582,7 @@ var SqliteInventoryAssetsRepository = class {
28990
29582
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
28991
29583
  VALUES (:id, :projectId, :path, :access, :now, :now)
28992
29584
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
28993
- ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
29585
+ ).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
28994
29586
  }
28995
29587
  return true;
28996
29588
  }
@@ -29011,7 +29603,7 @@ var SqliteInventoryAssetsRepository = class {
29011
29603
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
29012
29604
  VALUES (:id, :assetId, :trust, :now, :now)
29013
29605
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
29014
- ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
29606
+ ).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
29015
29607
  }
29016
29608
  this.configRowsCache = void 0;
29017
29609
  return "ok";
@@ -29308,7 +29900,7 @@ var SqliteInventoryAssetsRepository = class {
29308
29900
  };
29309
29901
 
29310
29902
  // ../../packages/persistence/src/repositories/policies.ts
29311
- import { randomUUID as randomUUID5 } from "crypto";
29903
+ import { randomUUID as randomUUID6 } from "crypto";
29312
29904
  var SqlitePoliciesRepository = class {
29313
29905
  constructor(db) {
29314
29906
  this.db = db;
@@ -29343,7 +29935,7 @@ var SqlitePoliciesRepository = class {
29343
29935
  failOpenTransaction(this.db, () => {
29344
29936
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
29345
29937
  stmt.run({
29346
- id: randomUUID5(),
29938
+ id: randomUUID6(),
29347
29939
  target: JSON.stringify({ category }),
29348
29940
  action,
29349
29941
  now: Date.now()
@@ -29363,7 +29955,7 @@ var SqlitePoliciesRepository = class {
29363
29955
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
29364
29956
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
29365
29957
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
29366
- ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
29958
+ ).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
29367
29959
  }
29368
29960
  // Caps every global per-category policy currently set to block/redact down
29369
29961
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -29431,7 +30023,7 @@ var SqlitePolicyCatalogRepository = class {
29431
30023
  };
29432
30024
 
29433
30025
  // ../../packages/persistence/src/repositories/project-files.ts
29434
- import { randomUUID as randomUUID6 } from "crypto";
30026
+ import { randomUUID as randomUUID7 } from "crypto";
29435
30027
  var SqliteProjectFilesRepository = class {
29436
30028
  constructor(db) {
29437
30029
  this.db = db;
@@ -29463,7 +30055,7 @@ var SqliteProjectFilesRepository = class {
29463
30055
  const stamp = Math.max(now, maxStamp + 1);
29464
30056
  for (const file2 of scan2.files) {
29465
30057
  this.upsertStmt.run({
29466
- id: randomUUID6(),
30058
+ id: randomUUID7(),
29467
30059
  projectId,
29468
30060
  path: file2.path,
29469
30061
  name: file2.name,
@@ -29477,9 +30069,9 @@ var SqliteProjectFilesRepository = class {
29477
30069
  };
29478
30070
 
29479
30071
  // ../../packages/persistence/src/repositories/resolutions.ts
29480
- import { randomUUID as randomUUID7 } from "crypto";
30072
+ import { randomUUID as randomUUID8 } from "crypto";
29481
30073
  var SqliteResolutionsRepository = class {
29482
- constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
30074
+ constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
29483
30075
  this.db = db;
29484
30076
  this.now = now;
29485
30077
  this.newId = newId;
@@ -29692,7 +30284,7 @@ var SqliteScanLedgerRepository = class {
29692
30284
  };
29693
30285
 
29694
30286
  // ../../packages/persistence/src/repositories/secret-vault.ts
29695
- import { randomUUID as randomUUID8 } from "crypto";
30287
+ import { randomUUID as randomUUID9 } from "crypto";
29696
30288
  function pageLimit(requested, fallback) {
29697
30289
  if (requested === void 0) return fallback;
29698
30290
  return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
@@ -29738,12 +30330,14 @@ var SELECT_COLUMNS = `
29738
30330
  ciphertext,
29739
30331
  nonce,
29740
30332
  auth_tag AS authTag,
30333
+ user_authorized AS userAuthorized,
29741
30334
  occurrence_count AS occurrenceCount,
29742
30335
  first_seen AS firstSeen,
29743
30336
  last_seen AS lastSeen`;
29744
30337
  function toRow(raw) {
29745
- const { provider, ...rest } = raw;
29746
- return provider === null ? rest : { ...rest, provider };
30338
+ const { provider, userAuthorized, ...rest } = raw;
30339
+ const row = { ...rest, userAuthorized: userAuthorized !== 0 };
30340
+ return provider === null ? row : { ...row, provider };
29747
30341
  }
29748
30342
  var SqliteSecretVaultRepository = class {
29749
30343
  constructor(db) {
@@ -29753,17 +30347,18 @@ var SqliteSecretVaultRepository = class {
29753
30347
  pointer_id, value_fingerprint, fingerprint_key_version, key_version,
29754
30348
  format_version, category, rule_id, masked_match, provider,
29755
30349
  ciphertext, nonce, auth_tag,
29756
- occurrence_count, first_seen, last_seen
30350
+ user_authorized, occurrence_count, first_seen, last_seen
29757
30351
  ) VALUES (
29758
30352
  :pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
29759
30353
  :formatVersion, :category, :ruleId, :maskedMatch, :provider,
29760
30354
  :ciphertext, :nonce, :authTag,
29761
- 1, :now, :now
30355
+ :userAuthorized, 1, :now, :now
29762
30356
  )`
29763
30357
  );
29764
30358
  this.bumpStmt = db.prepare(
29765
30359
  `UPDATE secret_vault
29766
- SET occurrence_count = occurrence_count + 1, last_seen = :now
30360
+ SET occurrence_count = occurrence_count + 1, last_seen = :now,
30361
+ user_authorized = max(user_authorized, :userAuthorized)
29767
30362
  WHERE value_fingerprint = :valueFingerprint`
29768
30363
  );
29769
30364
  this.byPointerStmt = db.prepare(
@@ -29783,6 +30378,7 @@ var SqliteSecretVaultRepository = class {
29783
30378
  SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
29784
30379
  WHERE pointer_id = :pointerId`
29785
30380
  );
30381
+ this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
29786
30382
  this.derefStmt = db.prepare(
29787
30383
  `INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
29788
30384
  VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
@@ -29796,6 +30392,7 @@ var SqliteSecretVaultRepository = class {
29796
30392
  listStmt;
29797
30393
  replaceCiphertextStmt;
29798
30394
  refreshFingerprintStmt;
30395
+ deleteByPointerStmt;
29799
30396
  derefStmt;
29800
30397
  /**
29801
30398
  * Vault a value, or record another sighting of one already vaulted. Keyed on
@@ -29804,6 +30401,11 @@ var SqliteSecretVaultRepository = class {
29804
30401
  * pointer, category and ciphertext, so the same secret always resolves to one
29805
30402
  * wire token. `minted` is true only when this call created the row.
29806
30403
  *
30404
+ * `userAuthorized` is the one field a repeat call may still change, and only
30405
+ * upwards: it records that a PERSON asked for this value to be replaced, and
30406
+ * the row is shared with every automatic path that vaults the same value. See
30407
+ * `bumpStmt` for why clearing it is the defect this shape exists to refuse.
30408
+ *
29807
30409
  * The read-then-write runs in one IMMEDIATE transaction so two concurrent
29808
30410
  * writers cannot both decide they are minting.
29809
30411
  */
@@ -29830,13 +30432,18 @@ var SqliteSecretVaultRepository = class {
29830
30432
  ciphertext: input2.ciphertext,
29831
30433
  nonce: input2.nonce,
29832
30434
  authTag: input2.authTag,
30435
+ userAuthorized: input2.userAuthorized === true ? 1 : 0,
29833
30436
  now
29834
30437
  })
29835
30438
  );
29836
30439
  minted = true;
29837
30440
  return;
29838
30441
  }
29839
- this.bumpStmt.run({ valueFingerprint: input2.valueFingerprint, now });
30442
+ this.bumpStmt.run({
30443
+ valueFingerprint: input2.valueFingerprint,
30444
+ userAuthorized: input2.userAuthorized === true ? 1 : 0,
30445
+ now
30446
+ });
29840
30447
  },
29841
30448
  "IMMEDIATE"
29842
30449
  );
@@ -29896,6 +30503,42 @@ var SqliteSecretVaultRepository = class {
29896
30503
  );
29897
30504
  return destroyed;
29898
30505
  }
30506
+ /**
30507
+ * Destroy the named entries and report WHICH ones went — the scoped
30508
+ * counterpart to `purgeAll`, for a caller that has already put those specific
30509
+ * values back where they came from. Ids the store does not hold are absent
30510
+ * from the answer rather than an error, so a set assembled from a stale read
30511
+ * is not a fault. The deref audit is left alone, exactly as the purge leaves
30512
+ * it.
30513
+ *
30514
+ * The ids come back rather than a count because the caller's next act is to
30515
+ * write a purge row per destroyed entry, and a record of destruction has to
30516
+ * be a record of what was really destroyed: a selection is a claim about a
30517
+ * read that has since gone stale, and auditing from it invents a purge for an
30518
+ * entry still sitting in the vault.
30519
+ *
30520
+ * One transaction over the whole set rather than a statement per id: the
30521
+ * caller hands this the result of a restore pass it has completed, and a
30522
+ * fault partway through must leave the vault as it was found rather than
30523
+ * destroying a prefix of it. The vault holds the only copy of what a pointer
30524
+ * stands for, so half a delete is not a state anything can recover from.
30525
+ */
30526
+ deleteByPointerIds(pointerIds) {
30527
+ if (pointerIds.length === 0) return [];
30528
+ const deleted = [];
30529
+ withTransaction(
30530
+ this.db,
30531
+ () => {
30532
+ for (const pointerId of pointerIds) {
30533
+ if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
30534
+ deleted.push(pointerId);
30535
+ }
30536
+ }
30537
+ },
30538
+ "IMMEDIATE"
30539
+ );
30540
+ return deleted;
30541
+ }
29899
30542
  /**
29900
30543
  * Record (or re-stamp) one place a pointer has been written. One row per
29901
30544
  * (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
@@ -29908,7 +30551,7 @@ var SqliteSecretVaultRepository = class {
29908
30551
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
29909
30552
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
29910
30553
  ).run({
29911
- id: randomUUID8(),
30554
+ id: randomUUID9(),
29912
30555
  pointerId: entry.pointerId,
29913
30556
  location: entry.location,
29914
30557
  kind: entry.kind,
@@ -30421,15 +31064,15 @@ var SqliteSecurityRepository = class {
30421
31064
  const from = now - RANGE_DAYS[range] * DAY_MS4;
30422
31065
  const rows = allRows(
30423
31066
  this.db.prepare(
30424
- `SELECT json_extract(e.attributes, '$.repo') AS repo, count(*) AS c
31067
+ `SELECT e.repo AS repo, count(*) AS c
30425
31068
  FROM inspection_findings f
30426
31069
  JOIN audit_events e ON e.id = f.audit_event_id
30427
31070
  WHERE e.started_at >= :from AND e.started_at < :to
30428
31071
  AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
30429
- AND json_extract(e.attributes, '$.repo') IS NOT NULL
30430
- AND json_extract(e.attributes, '$.repo') != ''
30431
- GROUP BY repo
30432
- ORDER BY c DESC, repo
31072
+ AND e.repo IS NOT NULL
31073
+ AND e.repo != ''
31074
+ GROUP BY e.repo
31075
+ ORDER BY c DESC, e.repo
30433
31076
  LIMIT :limit`
30434
31077
  ),
30435
31078
  { from, to: now, limit }
@@ -30491,7 +31134,7 @@ var SqliteSecurityRepository = class {
30491
31134
  `SELECT f.finding_key AS finding_key,
30492
31135
  d.rule_id AS rule_id,
30493
31136
  d.severity AS severity,
30494
- json_extract(e.attributes, '$.file_path') AS path,
31137
+ e.file_path AS path,
30495
31138
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
30496
31139
  latest.resolved_at AS latest_resolved_at
30497
31140
  FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
@@ -30544,7 +31187,7 @@ var SqliteSecurityRepository = class {
30544
31187
  };
30545
31188
 
30546
31189
  // ../../packages/persistence/src/repositories/shares.ts
30547
- import { randomUUID as randomUUID9 } from "crypto";
31190
+ import { randomUUID as randomUUID10 } from "crypto";
30548
31191
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
30549
31192
  var IN_CHUNK = 500;
30550
31193
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -30632,7 +31275,7 @@ function buildSummary(dest, endpoints) {
30632
31275
  callSiteCount,
30633
31276
  transports: distinctTransports(transports),
30634
31277
  dataClasses: distinctDataClasses(dataClasses),
30635
- review: buildReviewInfo(dest.trust, transports),
31278
+ review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
30636
31279
  network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
30637
31280
  endpoints: endpoints.map(toEndpointSummary)
30638
31281
  };
@@ -30659,7 +31302,7 @@ function buildDetail(dest, endpoints, callSites) {
30659
31302
  lastSeen: new Date(lastSeenMs).toISOString(),
30660
31303
  transports: distinctTransports(transports),
30661
31304
  dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
30662
- review: buildReviewInfo(dest.trust, transports),
31305
+ review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
30663
31306
  network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
30664
31307
  note: dest.note,
30665
31308
  endpoints: endpoints.map((ep) => ({
@@ -30688,7 +31331,11 @@ var SqliteSharesRepository = class {
30688
31331
  FROM share_destination d
30689
31332
  LEFT JOIN share_endpoint e ON e.destination_id = d.id
30690
31333
  AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
30691
- WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL`
31334
+ WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
31335
+ AND NOT EXISTS (
31336
+ SELECT 1 FROM egress_decision_override o
31337
+ WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
31338
+ )`
30692
31339
  );
30693
31340
  const kindCounts = countBy(
30694
31341
  this.db,
@@ -30800,7 +31447,7 @@ var SqliteSharesRepository = class {
30800
31447
  (id, destination_id, host, decision, created_at, updated_at)
30801
31448
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
30802
31449
  ).run({
30803
- id: randomUUID9(),
31450
+ id: randomUUID10(),
30804
31451
  destinationId,
30805
31452
  host: dest.host,
30806
31453
  decision,
@@ -30949,7 +31596,7 @@ var SqliteSharesRepository = class {
30949
31596
  let destinationId = destIds.get(hit.host);
30950
31597
  if (destinationId === void 0) {
30951
31598
  destStmt.run({
30952
- id: randomUUID9(),
31599
+ id: randomUUID10(),
30953
31600
  kind: hit.kind,
30954
31601
  name: hit.name,
30955
31602
  host: hit.host,
@@ -30965,7 +31612,7 @@ var SqliteSharesRepository = class {
30965
31612
  let endpointId = endpointIds.get(endpointKey);
30966
31613
  if (endpointId === void 0) {
30967
31614
  endpointStmt.run({
30968
- id: randomUUID9(),
31615
+ id: randomUUID10(),
30969
31616
  destinationId,
30970
31617
  method: hit.method,
30971
31618
  transport: hit.transport,
@@ -30978,7 +31625,7 @@ var SqliteSharesRepository = class {
30978
31625
  endpointIds.set(endpointKey, endpointId);
30979
31626
  }
30980
31627
  siteStmt.run({
30981
- id: randomUUID9(),
31628
+ id: randomUUID10(),
30982
31629
  endpointId,
30983
31630
  project: input2.project,
30984
31631
  projectKey: input2.projectKey,
@@ -31343,6 +31990,7 @@ function purgeSampleData(db) {
31343
31990
  }
31344
31991
 
31345
31992
  // ../../packages/persistence/src/database.ts
31993
+ var CAPTURE_GRAIN = new Set(EventKind.options);
31346
31994
  var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
31347
31995
  "aka.persistence.unsafeTestOnlyRawHandle"
31348
31996
  );
@@ -31390,7 +32038,7 @@ function backupLegacyStore(db, file2) {
31390
32038
  discardStore(file2, backup);
31391
32039
  return backup;
31392
32040
  }
31393
- function openAndInitialize(file2) {
32041
+ function openAndInitialize(file2, base) {
31394
32042
  let db = openWithPragmas(file2);
31395
32043
  try {
31396
32044
  if (isForeignSqliteLineage(db)) {
@@ -31403,7 +32051,7 @@ function openAndInitialize(file2) {
31403
32051
  applyMigrations(db, file2);
31404
32052
  tightenPerms(file2);
31405
32053
  const policies = new SqlitePoliciesRepository(db);
31406
- const installedPacks = new SqliteInstalledPacksRepository(db);
32054
+ const installedPacks = new SqliteInstalledPacksRepository(db, base);
31407
32055
  const repositories = {
31408
32056
  events: new SqliteEventsRepository(db),
31409
32057
  findings: new SqliteFindingsRepository(db),
@@ -31439,7 +32087,7 @@ function openAndInitialize(file2) {
31439
32087
  }
31440
32088
  function openLocalDatabase(dir) {
31441
32089
  ensureDataDirSync(dir);
31442
- const file2 = join4(dir, DB_FILENAME);
32090
+ const file2 = join7(dir, DB_FILENAME);
31443
32091
  reapStalePartials(file2);
31444
32092
  const {
31445
32093
  db,
@@ -31467,7 +32115,13 @@ function openLocalDatabase(dir) {
31467
32115
  inspectionDefinitions,
31468
32116
  inspectionFindings,
31469
32117
  configInventory
31470
- } = openAndInitialize(file2);
32118
+ } = openAndInitialize(
32119
+ file2,
32120
+ // `dir` is always `<base>/data` — every caller resolves it through
32121
+ // `dataDir()` — so its parent is the `~/.aka` base the layout splits into
32122
+ // settings/ and data/, and the pack-policy floor needs both halves.
32123
+ dirname2(dir)
32124
+ );
31471
32125
  function captureRowId(event) {
31472
32126
  return captureId(
31473
32127
  event.metadata?.sessionId ?? null,
@@ -31480,6 +32134,21 @@ function openLocalDatabase(dir) {
31480
32134
  historySync.markSynced([captureRowId(event)], atMs);
31481
32135
  });
31482
32136
  }
32137
+ function markCaptureOwed(event) {
32138
+ failOpenTransaction(db, () => {
32139
+ historySync.markCaptureOwed(captureRowId(event));
32140
+ });
32141
+ }
32142
+ function markAuditEventsDelivered(events2, atMs) {
32143
+ const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
32144
+ if (stampable.length === 0) return;
32145
+ failOpenTransaction(db, () => {
32146
+ historySync.markSynced(
32147
+ stampable.map((event) => event.id),
32148
+ atMs
32149
+ );
32150
+ });
32151
+ }
31483
32152
  function recordCapture(event, detected) {
31484
32153
  failOpenTransaction(db, () => {
31485
32154
  const sessionId = event.metadata?.sessionId;
@@ -31566,7 +32235,7 @@ function openLocalDatabase(dir) {
31566
32235
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
31567
32236
  if (!definitionId) continue;
31568
32237
  inspectionFindings.insertFinding({
31569
- id: randomUUID10(),
32238
+ id: randomUUID11(),
31570
32239
  auditEventId: record2.scanEvent.id,
31571
32240
  inspectionDefinitionId: definitionId,
31572
32241
  span: finding.span,
@@ -31662,6 +32331,8 @@ function openLocalDatabase(dir) {
31662
32331
  inspectionFindings,
31663
32332
  recordCapture,
31664
32333
  markCaptureDelivered,
32334
+ markCaptureOwed,
32335
+ markAuditEventsDelivered,
31665
32336
  ensureInventory,
31666
32337
  recordConfigScan,
31667
32338
  recordProjectFiles,
@@ -31680,20 +32351,6 @@ function openLocalDatabase(dir) {
31680
32351
  };
31681
32352
  }
31682
32353
 
31683
- // ../../packages/persistence/src/file-lock.ts
31684
- import { randomUUID as randomUUID11 } from "crypto";
31685
- import {
31686
- closeSync,
31687
- existsSync as existsSync2,
31688
- openSync,
31689
- readFileSync as readFileSync2,
31690
- rmSync as rmSync5,
31691
- statSync as statSync3,
31692
- writeFileSync as writeFileSync2
31693
- } from "fs";
31694
- import { hostname as hostname3 } from "os";
31695
- var PARK = new Int32Array(new SharedArrayBuffer(4));
31696
-
31697
32354
  // ../../packages/persistence/src/finding-key.ts
31698
32355
  import { createHash as createHash3 } from "crypto";
31699
32356
  function normalizeFilePath(filePath) {
@@ -31706,13 +32363,13 @@ function computeFindingKey(input2) {
31706
32363
 
31707
32364
  // ../../packages/persistence/src/fingerprint.ts
31708
32365
  import { createHmac, randomBytes } from "crypto";
31709
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
31710
- import { join as join5 } from "path";
32366
+ import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
32367
+ import { join as join8 } from "path";
31711
32368
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
31712
32369
  var EXCEPTION_KEY_FILENAME = "exception.key";
31713
32370
  var KEY_MATERIAL_BYTES = 32;
31714
32371
  function keyFilePath(dataDir2) {
31715
- return join5(dataDir2, EXCEPTION_KEY_FILENAME);
32372
+ return join8(dataDir2, EXCEPTION_KEY_FILENAME);
31716
32373
  }
31717
32374
  function parseKeyFile(raw) {
31718
32375
  const parsed2 = JSON.parse(raw);
@@ -31750,7 +32407,7 @@ var FloorUnreadableError = class extends Error {
31750
32407
  }
31751
32408
  };
31752
32409
  function storedKeyVersionFloor(dataDir2) {
31753
- const file2 = join5(dataDir2, DB_FILENAME);
32410
+ const file2 = join8(dataDir2, DB_FILENAME);
31754
32411
  if (!existsSync3(file2)) return 0;
31755
32412
  let db;
31756
32413
  try {
@@ -31805,7 +32462,7 @@ function occupantMessage(file2, kind) {
31805
32462
  function readFingerprintKey(dataDir2) {
31806
32463
  let raw;
31807
32464
  try {
31808
- raw = readFileSync3(keyFilePath(dataDir2), "utf8");
32465
+ raw = readFileSync6(keyFilePath(dataDir2), "utf8");
31809
32466
  } catch (err) {
31810
32467
  if (err.code === "ENOENT") return null;
31811
32468
  throw err instanceof Error ? err : new Error(String(err));
@@ -31829,143 +32486,12 @@ function fingerprintValue(key, raw) {
31829
32486
 
31830
32487
  // ../../packages/persistence/src/history-preview.ts
31831
32488
  import { existsSync as existsSync4 } from "fs";
31832
- import { join as join6 } from "path";
32489
+ import { join as join9 } from "path";
31833
32490
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
31834
32491
 
31835
- // ../../packages/persistence/src/local-layout.ts
31836
- import { renameSync as renameSync3 } from "fs";
31837
- import { mkdir } from "fs/promises";
31838
- import { homedir } from "os";
31839
- import { join as join7 } from "path";
31840
- function defaultDataDir() {
31841
- return join7(homedir(), ".aka");
31842
- }
31843
- function settingsDir(base = defaultDataDir()) {
31844
- return join7(base, "settings");
31845
- }
31846
- function dataDir(base = defaultDataDir()) {
31847
- return join7(base, "data");
31848
- }
31849
- function dbPath(base = defaultDataDir()) {
31850
- return join7(dataDir(base), "aka.db");
31851
- }
31852
- async function ensureDataDir(dir = defaultDataDir()) {
31853
- await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
31854
- tightenDir(dir);
31855
- }
31856
- function ensureLayoutDirSync(dir = defaultDataDir()) {
31857
- ensureDataDirSync(dir);
31858
- }
31859
- function migrateLegacyLayout(base = defaultDataDir()) {
31860
- const moves = [
31861
- { name: "config.json", dest: settingsDir(base) },
31862
- { name: "policy-cache.json", dest: dataDir(base) }
31863
- ];
31864
- for (const { name, dest } of moves) {
31865
- try {
31866
- ensureDataDirSync(dest);
31867
- const moved = join7(dest, name);
31868
- renameSync3(join7(base, name), moved);
31869
- tightenFile(moved);
31870
- } catch {
31871
- }
31872
- }
31873
- }
31874
-
31875
- // ../../packages/persistence/src/managed-settings.ts
31876
- import { readFileSync as readFileSync4 } from "fs";
31877
- import { posix, win32 } from "path";
31878
- function managedSettingsPaths(platform2 = process.platform) {
31879
- if (platform2 === "darwin") {
31880
- return [
31881
- posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
31882
- posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
31883
- ];
31884
- }
31885
- if (platform2 === "win32") {
31886
- return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
31887
- }
31888
- return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
31889
- }
31890
- function readManagedSettings(paths = managedSettingsPaths()) {
31891
- for (const path of paths) {
31892
- let text;
31893
- try {
31894
- text = readFileSync4(path, "utf8");
31895
- } catch {
31896
- continue;
31897
- }
31898
- const record2 = parseJsonObject(text);
31899
- if (!record2) continue;
31900
- const parsed2 = ManagedSettings.safeParse(record2);
31901
- if (parsed2.success) return parsed2.data;
31902
- }
31903
- return null;
31904
- }
31905
- function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
31906
- if (!managed) return settings;
31907
- const { values } = managed;
31908
- const merged = { ...settings };
31909
- if (values.runMode !== void 0) merged.runMode = values.runMode;
31910
- if (values.controlPlane !== void 0) {
31911
- merged.controlPlane = {
31912
- ...values.controlPlane,
31913
- // The administrator pinned WHICH deployment, not WHEN this machine
31914
- // joined it. Keep the user's own attach time when the endpoint is
31915
- // unchanged, so a managed machine does not appear to re-attach on every
31916
- // read; stamp a fresh one when the administrator moved it.
31917
- attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
31918
- };
31919
- }
31920
- if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
31921
- if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
31922
- if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
31923
- if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
31924
- if (values.vaultConsent !== void 0) {
31925
- merged.vaultConsent = values.vaultConsent ? (
31926
- // Keep an existing valid grant so its acknowledgedAt survives; mint one
31927
- // at the current version otherwise.
31928
- settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
31929
- ) : void 0;
31930
- }
31931
- if (values.modelJudgeConsent !== void 0) {
31932
- merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
31933
- acknowledgedAt: now().toISOString(),
31934
- payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
31935
- } : void 0;
31936
- }
31937
- return merged;
31938
- }
31939
-
31940
- // ../../packages/persistence/src/settings.ts
31941
- import { readFileSync as readFileSync5 } from "fs";
31942
- import { join as join8 } from "path";
31943
- var SETTINGS_FILENAME = "settings.json";
31944
- function readWorkspaceSettings(base = defaultDataDir()) {
31945
- return overlayManagedSettings(readUserSettings(base), readManagedSettings());
31946
- }
31947
- function readUserSettings(base) {
31948
- const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
31949
- if (!record2) return defaultWorkspaceSettings();
31950
- try {
31951
- return WorkspaceSettings.parse(record2);
31952
- } catch {
31953
- return defaultWorkspaceSettings();
31954
- }
31955
- }
31956
- function readJson(file2) {
31957
- let text;
31958
- try {
31959
- text = readFileSync5(file2, "utf8");
31960
- } catch {
31961
- return null;
31962
- }
31963
- return parseJsonObject(text) ?? null;
31964
- }
31965
-
31966
32492
  // ../../packages/persistence/src/store-symlinks.ts
31967
32493
  import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
31968
- import { dirname as dirname2, join as join9, resolve } from "path";
32494
+ import { dirname as dirname3, join as join10, resolve } from "path";
31969
32495
 
31970
32496
  // ../../packages/persistence/src/vault/crypto.ts
31971
32497
  import {
@@ -31979,19 +32505,19 @@ import {
31979
32505
  // ../../packages/persistence/src/vault/key-provider.ts
31980
32506
  import { execFileSync } from "child_process";
31981
32507
  import { randomBytes as randomBytes2 } from "crypto";
31982
- import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
31983
- import { join as join10 } from "path";
32508
+ import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
32509
+ import { join as join11 } from "path";
31984
32510
 
31985
32511
  // ../../packages/persistence/src/vault/vault.ts
31986
32512
  import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
31987
32513
 
31988
32514
  // ../../packages/persistence/src/warn-era-cap.ts
31989
32515
  import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
31990
- import { join as join11 } from "path";
32516
+ import { join as join12 } from "path";
31991
32517
  var MARKER = "warn-era-capped";
31992
32518
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
31993
32519
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
31994
- const marker = join11(dataDir2, MARKER);
32520
+ const marker = join12(dataDir2, MARKER);
31995
32521
  if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
31996
32522
  const capped = db.policies.capCategoryActions();
31997
32523
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
@@ -32051,7 +32577,7 @@ function resolveProvider() {
32051
32577
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
32052
32578
  try {
32053
32579
  ensureLayoutDirSync(base);
32054
- const settingsFile = join12(settingsDir(base), "settings.json");
32580
+ const settingsFile = join13(settingsDir(base), "settings.json");
32055
32581
  if (existsSync7(settingsFile)) tightenFile(settingsFile);
32056
32582
  } catch {
32057
32583
  }
@@ -32075,9 +32601,9 @@ function resolveProviderSafe(resolveProviderFn) {
32075
32601
  }
32076
32602
 
32077
32603
  // ../../packages/plugin-sdk/src/config-inventory.ts
32078
- import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
32604
+ import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
32079
32605
  import { homedir as homedir2 } from "os";
32080
- import { basename as basename3, join as join14 } from "path";
32606
+ import { basename as basename3, join as join15 } from "path";
32081
32607
 
32082
32608
  // ../../packages/detections/src/egress/registry.ts
32083
32609
  var EXTRACTOR_VERSION = "1";
@@ -36001,8 +36527,8 @@ function bundledDetections() {
36001
36527
  }
36002
36528
 
36003
36529
  // ../../packages/plugin-sdk/src/repo.ts
36004
- import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
36005
- import { basename as basename2, dirname as dirname3, isAbsolute, join as join13, sep as sep2 } from "path";
36530
+ import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
36531
+ import { basename as basename2, dirname as dirname4, isAbsolute, join as join14, sep as sep2 } from "path";
36006
36532
  function resolveRepoIdentity(cwd) {
36007
36533
  try {
36008
36534
  const root = findGitRoot(cwd);
@@ -36031,36 +36557,36 @@ function resolveWorktreeRoot(cwd) {
36031
36557
  function findGitRoot(start) {
36032
36558
  let dir = start;
36033
36559
  for (; ; ) {
36034
- if (existsSync8(join13(dir, ".git"))) return dir;
36035
- const parent = dirname3(dir);
36560
+ if (existsSync8(join14(dir, ".git"))) return dir;
36561
+ const parent = dirname4(dir);
36036
36562
  if (parent === dir) return void 0;
36037
36563
  dir = parent;
36038
36564
  }
36039
36565
  }
36040
36566
  function resolveGitContext(root) {
36041
- const dotGit = join13(root, ".git");
36567
+ const dotGit = join14(root, ".git");
36042
36568
  try {
36043
36569
  if (statSync6(dotGit).isDirectory()) {
36044
- return { configPath: join13(dotGit, "config"), headRoot: root };
36570
+ return { configPath: join14(dotGit, "config"), headRoot: root };
36045
36571
  }
36046
36572
  } catch {
36047
36573
  return void 0;
36048
36574
  }
36049
36575
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
36050
36576
  if (!target) return void 0;
36051
- const gitdir = isAbsolute(target) ? target : join13(root, target);
36052
- if (existsSync8(join13(gitdir, "config"))) {
36053
- return { configPath: join13(gitdir, "config"), headRoot: root };
36577
+ const gitdir = isAbsolute(target) ? target : join14(root, target);
36578
+ if (existsSync8(join14(gitdir, "config"))) {
36579
+ return { configPath: join14(gitdir, "config"), headRoot: root };
36054
36580
  }
36055
- const commonRaw = safeRead(join13(gitdir, "commondir"))?.trim();
36581
+ const commonRaw = safeRead(join14(gitdir, "commondir"))?.trim();
36056
36582
  if (!commonRaw) return void 0;
36057
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join13(gitdir, commonRaw);
36058
- const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
36059
- return { configPath: join13(commonGitDir, "config"), headRoot };
36583
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join14(gitdir, commonRaw);
36584
+ const headRoot = basename2(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
36585
+ return { configPath: join14(commonGitDir, "config"), headRoot };
36060
36586
  }
36061
36587
  function safeRead(path) {
36062
36588
  try {
36063
- return readFileSync7(path, "utf8");
36589
+ return readFileSync8(path, "utf8");
36064
36590
  } catch {
36065
36591
  return void 0;
36066
36592
  }
@@ -36605,11 +37131,11 @@ function createGuardedScanner(partition, gateway, opts) {
36605
37131
 
36606
37132
  // ../../packages/plugin-sdk/src/ignore-layers.ts
36607
37133
  var import_ignore = __toESM(require_ignore(), 1);
36608
- import { readFileSync as readFileSync9 } from "fs";
36609
- import { join as join15 } from "path";
37134
+ import { readFileSync as readFileSync10 } from "fs";
37135
+ import { join as join16 } from "path";
36610
37136
  function readIgnoreLayer(dir, filename, anchorLen) {
36611
37137
  try {
36612
- return { matcher: (0, import_ignore.default)().add(readFileSync9(join15(dir, filename), "utf8")), anchorLen };
37138
+ return { matcher: (0, import_ignore.default)().add(readFileSync10(join16(dir, filename), "utf8")), anchorLen };
36613
37139
  } catch {
36614
37140
  return void 0;
36615
37141
  }
@@ -36645,20 +37171,20 @@ import {
36645
37171
  fstatSync,
36646
37172
  mkdirSync as mkdirSync2,
36647
37173
  openSync as openSync2,
36648
- readFileSync as readFileSync10,
37174
+ readFileSync as readFileSync11,
36649
37175
  readSync,
36650
37176
  writeFileSync as writeFileSync5
36651
37177
  } from "fs";
36652
- import { join as join16 } from "path";
37178
+ import { join as join17 } from "path";
36653
37179
  var TAIL_BYTES = 256 * 1024;
36654
37180
 
36655
37181
  // ../../packages/plugin-sdk/src/nudge.ts
36656
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
36657
- import { join as join17 } from "path";
37182
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
37183
+ import { join as join18 } from "path";
36658
37184
 
36659
37185
  // ../../packages/plugin-sdk/src/paths.ts
36660
37186
  import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
36661
- import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
37187
+ import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
36662
37188
  function toPosix(path) {
36663
37189
  return path.split(sep3).join("/");
36664
37190
  }
@@ -36668,7 +37194,7 @@ function findProjectRoot(startDir, recognizeMarker) {
36668
37194
  let root = null;
36669
37195
  for (let level = 0; level < MAX_PROJECT_ROOT_LEVELS; level += 1) {
36670
37196
  if (directoryHasMarker(dir, recognizeMarker)) root = dir;
36671
- const parent = dirname4(dir);
37197
+ const parent = dirname5(dir);
36672
37198
  if (parent === dir) break;
36673
37199
  dir = parent;
36674
37200
  }
@@ -36690,9 +37216,44 @@ function resolveNonGitProject(startDir, recognizeMarker) {
36690
37216
  return { root: projectRoot, projectKey: `path:${realRoot}`, project: basename4(realRoot) };
36691
37217
  }
36692
37218
 
37219
+ // ../../packages/plugin-sdk/src/policy-resolver.ts
37220
+ function createPolicyResolver(bundle) {
37221
+ const byRule = /* @__PURE__ */ new Map();
37222
+ const byCategory = /* @__PURE__ */ new Map();
37223
+ let reversible = /* @__PURE__ */ new Set();
37224
+ try {
37225
+ for (const policy of bundle.policies) {
37226
+ if (!policy.enabled) continue;
37227
+ if ("ruleId" in policy.target) {
37228
+ if (!byRule.has(policy.target.ruleId)) byRule.set(policy.target.ruleId, policy.action);
37229
+ } else if (!byCategory.has(policy.target.category)) {
37230
+ byCategory.set(policy.target.category, policy.action);
37231
+ }
37232
+ }
37233
+ reversible = new Set(bundle.reversibleRuleIds ?? []);
37234
+ } catch {
37235
+ byRule.clear();
37236
+ byCategory.clear();
37237
+ reversible = /* @__PURE__ */ new Set();
37238
+ }
37239
+ return {
37240
+ actionFor(ruleId, category) {
37241
+ const byRuleAction = byRule.get(ruleId);
37242
+ if (byRuleAction !== void 0) return byRuleAction;
37243
+ const byCategoryAction = byCategory.get(category);
37244
+ if (byCategoryAction !== void 0) return byCategoryAction;
37245
+ const fallback = DEFAULT_ACTIONS[category];
37246
+ return fallback ?? "log";
37247
+ },
37248
+ isReversible(ruleId) {
37249
+ return reversible.has(ruleId);
37250
+ }
37251
+ };
37252
+ }
37253
+
36693
37254
  // ../../packages/plugin-sdk/src/project-files.ts
36694
37255
  import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
36695
- import { basename as basename5, join as join18 } from "path";
37256
+ import { basename as basename5, join as join19 } from "path";
36696
37257
 
36697
37258
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
36698
37259
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -36723,7 +37284,6 @@ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
36723
37284
  // ../../packages/plugin-sdk/src/runtime.ts
36724
37285
  import { randomUUID as randomUUID14 } from "crypto";
36725
37286
  var ENFORCEMENT_CEILING_ENABLED = false;
36726
- var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
36727
37287
  function startTiming() {
36728
37288
  try {
36729
37289
  return performance.now();
@@ -36760,28 +37320,22 @@ function createPluginRuntime(gateway, settings, opts) {
36760
37320
  bundlesPacked = true;
36761
37321
  }
36762
37322
  const policyMode = settings.policy;
37323
+ const redactFallback = settings.redactFallback;
36763
37324
  const dataDir2 = opts?.dataDir;
36764
- let policies = [];
36765
37325
  let rules = [];
36766
37326
  let scanner;
36767
37327
  let bundleExceptions = [];
36768
37328
  let initialized = false;
36769
- const ruleActionIndex = /* @__PURE__ */ new Map();
36770
- const categoryActionIndex = /* @__PURE__ */ new Map();
36771
- let reversibleRuleIndex = /* @__PURE__ */ new Set();
37329
+ let resolver = createPolicyResolver({
37330
+ version: "",
37331
+ policies: [],
37332
+ customKeywords: [],
37333
+ fetchedAt: ""
37334
+ });
36772
37335
  async function ensureInitialized() {
36773
37336
  if (initialized) return;
36774
37337
  const bundle = await gateway.getPolicyBundle();
36775
- policies = bundle.policies;
36776
- for (const p of policies) {
36777
- if (!p.enabled) continue;
36778
- if ("ruleId" in p.target) {
36779
- if (!ruleActionIndex.has(p.target.ruleId)) ruleActionIndex.set(p.target.ruleId, p.action);
36780
- } else if (!categoryActionIndex.has(p.target.category)) {
36781
- categoryActionIndex.set(p.target.category, p.action);
36782
- }
36783
- }
36784
- reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
37338
+ resolver = createPolicyResolver(bundle);
36785
37339
  const bundledProbeKeys = new Set(
36786
37340
  getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
36787
37341
  );
@@ -36834,33 +37388,28 @@ function createPluginRuntime(gateway, settings, opts) {
36834
37388
  return cachedKey;
36835
37389
  }
36836
37390
  function resolveAction(ruleId, category) {
36837
- const byRule = ruleActionIndex.get(ruleId);
36838
- if (byRule !== void 0) return byRule;
36839
- const byCategory = categoryActionIndex.get(category);
36840
- if (byCategory !== void 0) return byCategory;
36841
- const fallback = DEFAULT_ACTIONS[category];
36842
- return fallback ?? "log";
36843
- }
36844
- function actionForFinding(finding, excepted) {
37391
+ return resolver.actionFor(ruleId, category);
37392
+ }
37393
+ function actionForFinding(finding, excepted, rewritable = true) {
36845
37394
  if (excepted?.has(finding)) return "allow";
36846
37395
  const action = resolveAction(finding.ruleId, finding.category);
37396
+ if (!rewritable && action === "redact") return builtinPolicyToAction(redactFallback);
36847
37397
  if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
36848
37398
  return "warn";
36849
37399
  }
36850
37400
  return action;
36851
37401
  }
36852
- function decide(findings, text, excepted) {
37402
+ function decide(findings, text, excepted, rewritable = true) {
36853
37403
  if (findings.length === 0) return { action: "log", text, findings: [] };
36854
- const actionFor = (finding) => actionForFinding(finding, excepted);
37404
+ const actionFor = (finding) => actionForFinding(finding, excepted, rewritable);
36855
37405
  let worst = "log";
36856
37406
  for (const finding of findings) {
36857
- const action = actionFor(finding);
36858
- if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
37407
+ worst = strongerAction(worst, actionFor(finding));
36859
37408
  }
36860
37409
  if (worst === "block") return { action: "block", text: null, findings };
36861
37410
  if (worst === "redact") {
36862
37411
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
36863
- const reversibleFindings = redactFindings.filter((f) => reversibleRuleIndex.has(f.ruleId));
37412
+ const reversibleFindings = redactFindings.filter((f) => resolver.isReversible(f.ruleId));
36864
37413
  return {
36865
37414
  action: "redact",
36866
37415
  text: redact(text, redactFindings),
@@ -36936,7 +37485,7 @@ function createPluginRuntime(gateway, settings, opts) {
36936
37485
  return { excepted: /* @__PURE__ */ new Set(), exceptionIds: [] };
36937
37486
  }
36938
37487
  }
36939
- async function recordBlockedDetections(decision, excepted, ctx, fpCache) {
37488
+ async function recordBlockedDetections(decision, excepted, ctx, fpCache, rewritable = true) {
36940
37489
  const references = [];
36941
37490
  try {
36942
37491
  if (decision.action !== "block" && decision.action !== "redact") return references;
@@ -36944,7 +37493,7 @@ function createPluginRuntime(gateway, settings, opts) {
36944
37493
  if (!key) return references;
36945
37494
  const seen = /* @__PURE__ */ new Set();
36946
37495
  for (const finding of decision.findings) {
36947
- const action = actionForFinding(finding, excepted);
37496
+ const action = actionForFinding(finding, excepted, rewritable);
36948
37497
  if (action !== "block" && action !== "redact") continue;
36949
37498
  const fp = fingerprintOf(key, finding, fpCache);
36950
37499
  const pair = `${finding.ruleId}:${fp}`;
@@ -36971,7 +37520,7 @@ function createPluginRuntime(gateway, settings, opts) {
36971
37520
  }
36972
37521
  return references;
36973
37522
  }
36974
- async function evaluate(text, context, ctx) {
37523
+ async function evaluate(text, context, ctx, rewritable = true) {
36975
37524
  try {
36976
37525
  await ensureInitialized();
36977
37526
  if (!scanner) throw new Error("the runtime initialized without a scanner");
@@ -36980,8 +37529,14 @@ function createPluginRuntime(gateway, settings, opts) {
36980
37529
  const findings = dropShieldedFindings(matched, shielded.spans);
36981
37530
  const fpCache = /* @__PURE__ */ new Map();
36982
37531
  const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
36983
- const decision = decide(findings, text, excepted);
36984
- const blockedReferences = await recordBlockedDetections(decision, excepted, ctx, fpCache);
37532
+ const decision = decide(findings, text, excepted, rewritable);
37533
+ const blockedReferences = await recordBlockedDetections(
37534
+ decision,
37535
+ excepted,
37536
+ ctx,
37537
+ fpCache,
37538
+ rewritable
37539
+ );
36985
37540
  if (blockedReferences.length > 0) decision.blockedReferences = blockedReferences;
36986
37541
  return { decision, excepted, exceptionIds };
36987
37542
  } catch {
@@ -37005,12 +37560,16 @@ function createPluginRuntime(gateway, settings, opts) {
37005
37560
  sourceTool: input2.sourceTool,
37006
37561
  metadata: input2.metadata,
37007
37562
  preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
37008
- }
37563
+ },
37564
+ opts2.rewritable
37009
37565
  );
37010
37566
  if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
37011
37567
  try {
37012
37568
  const contentHash = contentHashOf(input2.text);
37013
- const storedContent = decision.findings.length > 0 ? redact(input2.text, decision.findings) : input2.text;
37569
+ const maskedFindings = decision.findings.filter(
37570
+ (match) => isActionAtLeast(actionForFinding(match, excepted, opts2.rewritable), "redact")
37571
+ );
37572
+ const storedContent = maskedFindings.length > 0 ? redact(input2.text, maskedFindings) : input2.text;
37014
37573
  const inspectionMs = elapsedMs(timingStartedAt);
37015
37574
  const metadata = exceptionIds.length > 0 || inspectionMs !== void 0 ? {
37016
37575
  ...input2.metadata,
@@ -37047,7 +37606,7 @@ function createPluginRuntime(gateway, settings, opts) {
37047
37606
  severity: match.severity,
37048
37607
  span: match.span,
37049
37608
  maskedMatch,
37050
- actionTaken: actionForFinding(match, excepted),
37609
+ actionTaken: actionForFinding(match, excepted, opts2.rewritable),
37051
37610
  confidence: match.confidence,
37052
37611
  ...findingKey ? { findingKey } : {}
37053
37612
  };
@@ -37087,11 +37646,11 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
37087
37646
 
37088
37647
  // ../../packages/plugin-sdk/src/throttle.ts
37089
37648
  import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
37090
- import { join as join19 } from "path";
37649
+ import { join as join20 } from "path";
37091
37650
 
37092
37651
  // ../../packages/scanner/src/discover.ts
37093
37652
  import { readdirSync as readdirSync5 } from "fs";
37094
- import { join as join20 } from "path";
37653
+ import { join as join21 } from "path";
37095
37654
 
37096
37655
  // ../../packages/scanner/src/constants.ts
37097
37656
  var COMMON_SKIP_DIRS = ["node_modules", "__pycache__", ".venv", "venv", ".cache"];
@@ -37136,7 +37695,7 @@ function discoverGitRepos(opts) {
37136
37695
  if (!entry.isDirectory()) continue;
37137
37696
  if (DISCOVER_SKIP.has(entry.name)) continue;
37138
37697
  if (entry.name.startsWith(".")) continue;
37139
- visit2(join20(dir, entry.name), depth + 1);
37698
+ visit2(join21(dir, entry.name), depth + 1);
37140
37699
  }
37141
37700
  }
37142
37701
  for (const root of searchRoots) {
@@ -37239,7 +37798,7 @@ function renderMultiRepoSummary(summary, opts = {}) {
37239
37798
  }
37240
37799
 
37241
37800
  // ../../packages/scanner/src/scan.ts
37242
- import { existsSync as existsSync11, readFileSync as readFileSync18 } from "fs";
37801
+ import { existsSync as existsSync11, readFileSync as readFileSync19 } from "fs";
37243
37802
  import { extname as extname2, isAbsolute as isAbsolute2, relative as relative3 } from "path";
37244
37803
 
37245
37804
  // ../../packages/plugin-runtime/src/attached/egress-wire.ts
@@ -37279,6 +37838,307 @@ function toEgressIngestRequest(input2) {
37279
37838
  };
37280
37839
  }
37281
37840
 
37841
+ // ../../packages/remote/src/http.ts
37842
+ import { request as httpRequest } from "http";
37843
+ import { request as httpsRequest } from "https";
37844
+ var DEFAULT_TIMEOUT_MS = 1e4;
37845
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
37846
+ var RemoteRequestError = class extends Error {
37847
+ constructor(status) {
37848
+ super(`control-plane request failed with status ${String(status)}`);
37849
+ this.status = status;
37850
+ this.name = "RemoteRequestError";
37851
+ }
37852
+ status;
37853
+ };
37854
+ var RemoteRouteAbsent = class extends Error {
37855
+ constructor(route) {
37856
+ super(`control plane does not serve ${route}`);
37857
+ this.route = route;
37858
+ this.name = "RemoteRouteAbsent";
37859
+ }
37860
+ route;
37861
+ };
37862
+ var RemoteRequestInvalid = class extends Error {
37863
+ constructor(route, cause) {
37864
+ super(`refusing to send a malformed body to ${route}`);
37865
+ this.cause = cause;
37866
+ this.name = "RemoteRequestInvalid";
37867
+ }
37868
+ cause;
37869
+ };
37870
+ var RemoteResponseInvalid = class extends Error {
37871
+ constructor(route, detail) {
37872
+ super(`control plane answered ${route} with ${detail}`);
37873
+ this.name = "RemoteResponseInvalid";
37874
+ }
37875
+ };
37876
+ var RemoteTransportError = class extends Error {
37877
+ /**
37878
+ * The status the peer sent, when headers arrived and only the BODY was
37879
+ * refused.
37880
+ *
37881
+ * Undefined for the ordinary case this class was written for — no answer at
37882
+ * all. It exists because two paths reject after a status has already been
37883
+ * delivered: an oversized body and an aborted response. Discarding it there
37884
+ * reported a deployment answering 401 with a verbose body as a network
37885
+ * outage, which sends the reader to look at their network instead of their
37886
+ * credential.
37887
+ */
37888
+ constructor(reason, status) {
37889
+ super(`control-plane request did not complete: ${reason}`);
37890
+ this.status = status;
37891
+ this.name = "RemoteTransportError";
37892
+ }
37893
+ status;
37894
+ };
37895
+ async function send(options) {
37896
+ const url2 = new URL(options.url);
37897
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
37898
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
37899
+ const requestOptions = {
37900
+ method: options.method,
37901
+ headers: {
37902
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
37903
+ // last they win, and two of the values below are ones no caller may
37904
+ // replace: `x-api-key` is the credential, and `content-length` is the
37905
+ // byte count that stops a multi-byte body being truncated by the
37906
+ // receiver. `SendOptions.headers` is a free-form record on an exported
37907
+ // function, so "no caller does that today" is not the guarantee to rely
37908
+ // on. The one header any caller actually passes — `if-none-match` on the
37909
+ // conditional GET — is untouched by this order.
37910
+ ...options.headers,
37911
+ // The credential. One header, matching what the deployment authenticates
37912
+ // on; a second copy in an `Authorization` header would be one more place
37913
+ // it can be logged by an intermediary for no gain.
37914
+ //
37915
+ // Spread conditionally rather than assigned as `undefined`: Node's header
37916
+ // handling and `content-length` bookkeeping treat a present-but-undefined
37917
+ // key differently from an absent one, and "the header is not there" is
37918
+ // the property the attach flow needs.
37919
+ ...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
37920
+ accept: "application/json",
37921
+ ...options.body === void 0 ? {} : {
37922
+ "content-type": "application/json",
37923
+ // Byte length, not string length: a multi-byte body sent with a
37924
+ // character count is truncated by the receiver.
37925
+ "content-length": String(Buffer.byteLength(options.body))
37926
+ }
37927
+ }
37928
+ };
37929
+ return new Promise((resolve2, reject) => {
37930
+ let settled = false;
37931
+ const fail = (reason, status) => {
37932
+ if (settled) return;
37933
+ settled = true;
37934
+ reject(new RemoteTransportError(reason, status));
37935
+ };
37936
+ const req = send_(url2, requestOptions, (res) => {
37937
+ const chunks = [];
37938
+ let size = 0;
37939
+ res.on("data", (chunk) => {
37940
+ size += chunk.length;
37941
+ if (size > MAX_RESPONSE_BYTES) {
37942
+ fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
37943
+ res.destroy();
37944
+ req.destroy();
37945
+ return;
37946
+ }
37947
+ chunks.push(chunk);
37948
+ });
37949
+ res.on("aborted", () => {
37950
+ fail("the response was aborted", res.statusCode);
37951
+ });
37952
+ res.on("end", () => {
37953
+ if (settled) return;
37954
+ settled = true;
37955
+ resolve2({
37956
+ status: res.statusCode ?? 0,
37957
+ headers: res.headers,
37958
+ body: Buffer.concat(chunks).toString("utf8")
37959
+ });
37960
+ });
37961
+ });
37962
+ const deadline = setTimeout(() => {
37963
+ fail(`no response within ${String(timeoutMs)}ms`);
37964
+ req.destroy();
37965
+ }, timeoutMs);
37966
+ deadline.unref();
37967
+ req.on("upgrade", (_res, socket) => {
37968
+ fail("the deployment answered with a protocol upgrade");
37969
+ socket.destroy();
37970
+ });
37971
+ req.on("close", () => {
37972
+ fail("the connection closed before a response was read");
37973
+ clearTimeout(deadline);
37974
+ });
37975
+ req.on("error", (err) => {
37976
+ fail(err.message);
37977
+ });
37978
+ if (options.body !== void 0) req.write(options.body);
37979
+ req.end();
37980
+ });
37981
+ }
37982
+
37983
+ // ../../packages/remote/src/client.ts
37984
+ var ROUTES = {
37985
+ events: "/v1/events",
37986
+ auditEvents: "/v1/audit-events",
37987
+ auditEventsBatch: "/v1/audit-events/batch",
37988
+ inventory: "/v1/inventory",
37989
+ storePosture: "/v1/store-posture",
37990
+ policyBundle: "/v1/policy-bundle",
37991
+ whoami: "/v1/plugin/whoami",
37992
+ shares: "/v1/shares",
37993
+ commands: "/v1/plugin/commands"
37994
+ };
37995
+ function ackRoute(id) {
37996
+ return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
37997
+ }
37998
+ function headerValue(response, name) {
37999
+ const raw = response.headers[name];
38000
+ if (raw === void 0) return void 0;
38001
+ return Array.isArray(raw) ? raw[0] : raw;
38002
+ }
38003
+ function okBody(response) {
38004
+ if (response.status < 200 || response.status >= 300) {
38005
+ throw new RemoteRequestError(response.status);
38006
+ }
38007
+ return response.body;
38008
+ }
38009
+ function parsed(schema, body, route) {
38010
+ let json2;
38011
+ try {
38012
+ json2 = JSON.parse(body);
38013
+ } catch {
38014
+ throw new RemoteResponseInvalid(route, "a body that is not JSON");
38015
+ }
38016
+ const result = schema.safeParse(json2);
38017
+ if (!result.success) {
38018
+ throw new RemoteResponseInvalid(route, "a body this client cannot read");
38019
+ }
38020
+ return result.data;
38021
+ }
38022
+ function withoutTrailingSlashes(endpoint) {
38023
+ let end = endpoint.length;
38024
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
38025
+ return endpoint.slice(0, end);
38026
+ }
38027
+ var SLASH = "/".charCodeAt(0);
38028
+ function createRemoteClient(options) {
38029
+ const base = withoutTrailingSlashes(options.endpoint);
38030
+ const url2 = (route) => `${base}${route}`;
38031
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
38032
+ const sendOne = async (event) => {
38033
+ const validated = RecordAuditEventRequest.safeParse(event);
38034
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
38035
+ const response = await send({
38036
+ ...common,
38037
+ method: "POST",
38038
+ url: url2(ROUTES.auditEvents),
38039
+ body: JSON.stringify(validated.data)
38040
+ });
38041
+ okBody(response);
38042
+ };
38043
+ return {
38044
+ async ingestEvents(batch) {
38045
+ const response = await send({
38046
+ ...common,
38047
+ method: "POST",
38048
+ url: url2(ROUTES.events),
38049
+ body: JSON.stringify(batch)
38050
+ });
38051
+ return parsed(IngestAck, okBody(response), ROUTES.events);
38052
+ },
38053
+ async ingestInventory(context) {
38054
+ const response = await send({
38055
+ ...common,
38056
+ method: "POST",
38057
+ url: url2(ROUTES.inventory),
38058
+ body: JSON.stringify(context)
38059
+ });
38060
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
38061
+ },
38062
+ async recordAuditEvent(event) {
38063
+ await sendOne(event);
38064
+ },
38065
+ async recordAuditEvents(events, opts) {
38066
+ const validated = RecordAuditEventBatch.safeParse({ events });
38067
+ if (!validated.success) {
38068
+ throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
38069
+ }
38070
+ const response = await send({
38071
+ ...common,
38072
+ method: "POST",
38073
+ url: url2(ROUTES.auditEventsBatch),
38074
+ body: JSON.stringify(validated.data)
38075
+ });
38076
+ if (response.status === 404) {
38077
+ if (opts?.fallbackToSingleEvents !== true) {
38078
+ throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
38079
+ }
38080
+ for (const event of validated.data.events) await sendOne(event);
38081
+ return { accepted: validated.data.events.length };
38082
+ }
38083
+ return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
38084
+ },
38085
+ async reportStorePosture(snapshot) {
38086
+ const response = await send({
38087
+ ...common,
38088
+ method: "POST",
38089
+ url: url2(ROUTES.storePosture),
38090
+ body: JSON.stringify(snapshot)
38091
+ });
38092
+ okBody(response);
38093
+ },
38094
+ async getPolicyBundle(etag) {
38095
+ const response = await send({
38096
+ ...common,
38097
+ method: "GET",
38098
+ url: url2(ROUTES.policyBundle),
38099
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
38100
+ });
38101
+ if (response.status === 304) {
38102
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
38103
+ }
38104
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
38105
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
38106
+ },
38107
+ async whoami() {
38108
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
38109
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
38110
+ },
38111
+ async recordProjectEgress(request) {
38112
+ const validated = EgressIngestRequest.safeParse(request);
38113
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
38114
+ const response = await send({
38115
+ ...common,
38116
+ method: "POST",
38117
+ url: url2(ROUTES.shares),
38118
+ body: JSON.stringify(validated.data)
38119
+ });
38120
+ okBody(response);
38121
+ },
38122
+ async pollCommand() {
38123
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
38124
+ if (response.status === 404) return null;
38125
+ return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
38126
+ },
38127
+ async ackCommand(id, body) {
38128
+ const validated = DeviceCommandAckBody.safeParse(body);
38129
+ const route = ackRoute(id);
38130
+ if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
38131
+ const response = await send({
38132
+ ...common,
38133
+ method: "POST",
38134
+ url: url2(route),
38135
+ body: JSON.stringify(validated.data)
38136
+ });
38137
+ okBody(response);
38138
+ }
38139
+ };
38140
+ }
38141
+
37282
38142
  // ../../packages/plugin-runtime/src/attached/failure.ts
37283
38143
  function statusOf(err) {
37284
38144
  if (typeof err !== "object" || err === null || !("status" in err)) return null;
@@ -37297,12 +38157,27 @@ function classifyFailure(err) {
37297
38157
  }
37298
38158
  }
37299
38159
 
38160
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
38161
+ var REQUEST_TIMEOUT_MS = 2e3;
38162
+ function withTimeout(promise2, ms) {
38163
+ let timer;
38164
+ const timeout = new Promise((_, reject) => {
38165
+ timer = setTimeout(() => {
38166
+ reject(new Error("attached gateway request timed out"));
38167
+ }, ms);
38168
+ });
38169
+ promise2.catch(() => void 0);
38170
+ return Promise.race([promise2, timeout]).finally(() => {
38171
+ clearTimeout(timer);
38172
+ });
38173
+ }
38174
+
37300
38175
  // ../../packages/plugin-runtime/src/attached/forward-drops.ts
37301
- import { readFileSync as readFileSync12 } from "fs";
37302
- import { join as join21 } from "path";
38176
+ import { readFileSync as readFileSync13 } from "fs";
38177
+ import { join as join22 } from "path";
37303
38178
  var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
37304
38179
  function forwardDropsPath(dataDir2) {
37305
- return join21(dataDir2, FORWARD_DROPS_FILENAME);
38180
+ return join22(dataDir2, FORWARD_DROPS_FILENAME);
37306
38181
  }
37307
38182
  function recordForwardDrops(dataDir2, count, nowMs) {
37308
38183
  if (count <= 0) return;
@@ -37320,7 +38195,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
37320
38195
  }
37321
38196
  function readForwardDrops(dataDir2) {
37322
38197
  try {
37323
- const parsed2 = JSON.parse(readFileSync12(forwardDropsPath(dataDir2), "utf8"));
38198
+ const parsed2 = JSON.parse(readFileSync13(forwardDropsPath(dataDir2), "utf8"));
37324
38199
  if (typeof parsed2 !== "object" || parsed2 === null) return null;
37325
38200
  const record2 = parsed2;
37326
38201
  if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
@@ -37338,29 +38213,19 @@ function readForwardDrops(dataDir2) {
37338
38213
 
37339
38214
  // ../../packages/plugin-runtime/src/attached/forward-policy.ts
37340
38215
  import { randomUUID as randomUUID15 } from "crypto";
37341
- import { readFileSync as readFileSync13 } from "fs";
38216
+ import { readFileSync as readFileSync14 } from "fs";
37342
38217
  import { readFile, rename, writeFile } from "fs/promises";
37343
- import { join as join22 } from "path";
37344
-
37345
- // ../../packages/plugin-runtime/src/attached/with-timeout.ts
37346
- var REQUEST_TIMEOUT_MS = 2e3;
37347
- function withTimeout(promise2, ms) {
37348
- let timer;
37349
- const timeout = new Promise((_, reject) => {
37350
- timer = setTimeout(() => {
37351
- reject(new Error("attached gateway request timed out"));
37352
- }, ms);
37353
- });
37354
- promise2.catch(() => void 0);
37355
- return Promise.race([promise2, timeout]).finally(() => {
37356
- clearTimeout(timer);
37357
- });
37358
- }
37359
-
37360
- // ../../packages/plugin-runtime/src/attached/forward-policy.ts
38218
+ import { join as join23 } from "path";
37361
38219
  function isInvalidRequest(err) {
37362
38220
  return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
37363
38221
  }
38222
+ function isRouteAbsent(err) {
38223
+ return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
38224
+ }
38225
+ function isServerRejection(err) {
38226
+ const status = statusOf(err);
38227
+ return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
38228
+ }
37364
38229
  var FORWARD_BUDGET_MS = 1500;
37365
38230
  var DECISION_PATH_BUDGET_MS = 800;
37366
38231
  var BREAKER_FAILURE_THRESHOLD = 3;
@@ -37388,7 +38253,7 @@ function parseBreakerState(raw, nowMs) {
37388
38253
  }
37389
38254
  function createForwardPolicy(deps) {
37390
38255
  const now = deps.now ?? (() => Date.now());
37391
- const file2 = join22(deps.dir, STATE_FILENAME);
38256
+ const file2 = join23(deps.dir, STATE_FILENAME);
37392
38257
  let state = null;
37393
38258
  let loading = null;
37394
38259
  async function readState() {
@@ -37428,6 +38293,20 @@ function createForwardPolicy(deps) {
37428
38293
  } catch {
37429
38294
  current = { ...CLOSED };
37430
38295
  }
38296
+ const restoreOpenedAtMs = (openedAtMs) => persist({
38297
+ consecutiveFailures: current.consecutiveFailures,
38298
+ openedAtMs,
38299
+ lastFailure: current.lastFailure
38300
+ });
38301
+ const recordFailure = (cause) => {
38302
+ const failures = current.consecutiveFailures + 1;
38303
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
38304
+ return persist({
38305
+ consecutiveFailures: failures,
38306
+ openedAtMs: shouldOpen ? now() : null,
38307
+ lastFailure: cause
38308
+ });
38309
+ };
37431
38310
  const at = now();
37432
38311
  if (current.openedAtMs !== null) {
37433
38312
  if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
@@ -37446,15 +38325,20 @@ function createForwardPolicy(deps) {
37446
38325
  }
37447
38326
  return { ok: true, value };
37448
38327
  } catch (err) {
37449
- if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
38328
+ if (isInvalidRequest(err)) {
38329
+ if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
38330
+ return { ok: false, reason: "invalid-request" };
38331
+ }
38332
+ if (isRouteAbsent(err)) {
38333
+ if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
38334
+ return { ok: false, reason: "route-absent" };
38335
+ }
38336
+ if (isServerRejection(err)) {
38337
+ await recordFailure("unreachable");
38338
+ return { ok: false, reason: "rejected" };
38339
+ }
37450
38340
  const reason = classifyFailure(err);
37451
- const failures = current.consecutiveFailures + 1;
37452
- const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
37453
- await persist({
37454
- consecutiveFailures: failures,
37455
- openedAtMs: shouldOpen ? now() : null,
37456
- lastFailure: reason
37457
- });
38341
+ await recordFailure(reason);
37458
38342
  return { ok: false, reason };
37459
38343
  }
37460
38344
  }
@@ -37462,13 +38346,11 @@ function createForwardPolicy(deps) {
37462
38346
  }
37463
38347
 
37464
38348
  // ../../packages/plugin-runtime/src/attached/gateway.ts
37465
- var ACTION_STRENGTH = {
37466
- allow: 0,
37467
- log: 1,
37468
- warn: 2,
37469
- redact: 3,
37470
- block: 4
37471
- };
38349
+ function strongerOf(a, b) {
38350
+ if (a === null) return b;
38351
+ if (b === null) return a;
38352
+ return strongerAction(a, b);
38353
+ }
37472
38354
  function ruleCategoryMap(wireRules, localRules) {
37473
38355
  const map2 = /* @__PURE__ */ new Map();
37474
38356
  for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
@@ -37478,11 +38360,6 @@ function ruleCategoryMap(wireRules, localRules) {
37478
38360
  }
37479
38361
  return map2;
37480
38362
  }
37481
- function strongerOf(a, b) {
37482
- if (a === null) return b;
37483
- if (b === null) return a;
37484
- return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
37485
- }
37486
38363
  function policyKey(policy) {
37487
38364
  return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
37488
38365
  }
@@ -37501,7 +38378,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
37501
38378
  const floor = floorFor(policy, categoryByRuleId);
37502
38379
  remoteCategoryAction.set(
37503
38380
  policy.target.category,
37504
- floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
38381
+ floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
37505
38382
  );
37506
38383
  }
37507
38384
  for (const policy of localPolicies) {
@@ -37518,7 +38395,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
37518
38395
  }
37519
38396
  merged.set(
37520
38397
  key,
37521
- remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
38398
+ remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
37522
38399
  );
37523
38400
  }
37524
38401
  const localCategoryAction = /* @__PURE__ */ new Map();
@@ -37538,13 +38415,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
37538
38415
  if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
37539
38416
  }
37540
38417
  const effectiveFloor = strongerOf(floor, localFloor);
37541
- const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
38418
+ const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
37542
38419
  const existing = merged.get(key);
37543
38420
  if (existing === void 0) {
37544
38421
  merged.set(key, clamped);
37545
38422
  continue;
37546
38423
  }
37547
- if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
38424
+ if (actionRank(clamped.action) > actionRank(existing.action)) {
37548
38425
  merged.set(key, clamped);
37549
38426
  }
37550
38427
  }
@@ -37577,6 +38454,8 @@ var AttachedDataGateway = class {
37577
38454
  );
37578
38455
  if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
37579
38456
  this.deps.local.markCaptureDelivered(record2.event, Date.now());
38457
+ } else {
38458
+ this.deps.local.markCaptureOwed(record2.event);
37580
38459
  }
37581
38460
  }
37582
38461
  async ensureInventory(ctx) {
@@ -37613,9 +38492,10 @@ var AttachedDataGateway = class {
37613
38492
  // a retried tool_call, exactly this path — can never stomp a populated row.
37614
38493
  async recordAuditEvent(event) {
37615
38494
  await this.deps.local.recordAuditEvent(event);
37616
- await this.deps.forward.run(
38495
+ const forwarded = await this.deps.forward.run(
37617
38496
  () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
37618
38497
  );
38498
+ if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
37619
38499
  }
37620
38500
  // Attached `llm_call` is written locally by the inner gateway, then routed to
37621
38501
  // the control plane through the existing `recordAuditEvent` ingest (no dedicated
@@ -37624,44 +38504,170 @@ var AttachedDataGateway = class {
37624
38504
  // which would write the event to the local store a second time.
37625
38505
  async recordLlmCall(input2) {
37626
38506
  await this.deps.local.recordLlmCall(input2);
37627
- await this.deps.forward.run(
37628
- () => this.deps.client.recordAuditEvent(
37629
- reKeyForForward(llmAuditEvent(input2), this.remoteInventory)
37630
- )
38507
+ const event = llmAuditEvent(input2);
38508
+ const forwarded = await this.deps.forward.run(
38509
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
37631
38510
  );
38511
+ if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
37632
38512
  }
37633
38513
  /**
37634
- * Forward one batch, item by item, under ONE aggregate deadline.
38514
+ * Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
38515
+ *
38516
+ * This used to send one HTTP request per event, which is what made the batch
38517
+ * budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
38518
+ * away everything after them. The same rows now cross 50 at a time over
38519
+ * `POST /v1/audit-events/batch` — the route the attach-time drain has always
38520
+ * used — so the same budget admits ~750. The wire cap is the server's own
38521
+ * constant, sized against server cost, and the client REFUSES a longer array
38522
+ * client-side, so the chunking here is not a convention.
38523
+ *
38524
+ * Still serial, and still for the original reason: firing N requests at once
38525
+ * would trade a latency problem for a burst the plane's per-key rate limiting
38526
+ * answers with the refusals the breaker then counts. Fewer, fuller requests is
38527
+ * the fix; more concurrent ones is not.
38528
+ *
38529
+ * When the deadline passes the remainder is dropped rather than sent: the
38530
+ * local write has already succeeded, so every caller has a correct result to
38531
+ * return. What is dropped is COUNTED, everywhere it can happen — this path
38532
+ * returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
38533
+ * `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
38534
+ * the breaker closed, renders a healthy block, and discards the tail of every
38535
+ * batch indefinitely. The SAME tally also covers a single that fails inside
38536
+ * the per-item retry below — the breaker opening mid-retry is a failure the
38537
+ * breaker's own state DOES capture, but the events still in this chunk once
38538
+ * that happens are neither delivered nor otherwise counted anywhere, which is
38539
+ * the same invisibility with a different cause.
38540
+ *
38541
+ * `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
38542
+ * single-event ack and at fifty times the blast radius here:
38543
+ * `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
38544
+ * not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
38545
+ * fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
38546
+ * delivered and never re-offer the twenty the plane did not take. So success
38547
+ * is checked against `chunk.length`; anything short of it falls into the same
38548
+ * per-item pass as a refused chunk, which is the only way to recover the
38549
+ * rows that did not land, since the ack carries no per-row verdict to
38550
+ * resend by.
38551
+ *
38552
+ * That fallback ASSUMES a re-send of an already-landed row is a harmless
38553
+ * no-op rather than a second cost — an assumption this file cannot verify.
38554
+ * `AuditEventBatchAck` carries only `accepted`, unlike its sibling
38555
+ * `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
38556
+ * the batch size as the invariant `recordCapture` reads), so whether a
38557
+ * duplicate counts toward THIS route's `accepted` is not expressed
38558
+ * anywhere in this repo. If it follows its sibling's convention and does
38559
+ * NOT, a chunk containing even one already-delivered row — the ordinary
38560
+ * consequence of a lost stamp, which this file already treats as cheap —
38561
+ * answers short forever and enters the per-item pass on every pass it is
38562
+ * offered again. The cost of that is bounded rather than silent: the
38563
+ * pass converges (every row lands and stamps), so it is one wasted round
38564
+ * of singles rather than a stall, and it errs toward an extra resend
38565
+ * rather than toward the lost row the alternative risks.
37635
38566
  *
37636
- * Per-item budgets bound each request and nothing bounded their sum see
37637
- * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
37638
- * rather than sent: the local write has already succeeded, so every caller
37639
- * has a correct result to return, and a drop is the outcome this path is
37640
- * built to accept (G8) where a blown hook timeout is not.
38567
+ * BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
38568
+ * one transaction, so a full 2xx settles every event in it and a non-2xx
38569
+ * settles none which is why the whole chunk is stamped together on a FULL
38570
+ * accept and none of it otherwise. THREE reasons do not deserve whole-chunk
38571
+ * treatment, alongside a short accept, and all are re-sent one event at a
38572
+ * time:
37641
38573
  *
37642
- * Serial rather than concurrent on purpose. Firing N requests at once would
37643
- * trade a latency problem for a burst the plane's own per-key rate limiting
37644
- * would answer with the refusals the breaker then counts.
38574
+ * `invalid-request` a chunk the client refused to send at all. One malformed
38575
+ * event would otherwise cost the 49 good ones beside it
38576
+ * a new way to lose data introduced by the very change
38577
+ * meant to stop losing it.
38578
+ * `route-absent` a deployment that predates the batch route. The
38579
+ * single-event route is the one it serves, and re-sending
38580
+ * here rather than inside the client is what gives each
38581
+ * request its own budget instead of 50 inside one.
38582
+ * `rejected` the deployment's SERVER-side twin of `invalid-request` —
38583
+ * a 4xx body refusal from schema drift on the other side
38584
+ * of the wire. Settlement is batch-atomic on this reason
38585
+ * exactly as on the others, so leaving it out would cost
38586
+ * the whole chunk for one event the DEPLOYMENT considers
38587
+ * malformed, where the per-item form cost only that one.
37645
38588
  *
37646
- * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
37647
- * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
37648
- * lets status call the forward unhealthy; this path returns BEFORE `run` is
37649
- * reached, so without the tally in `forward-drops.ts` a slow-but-answering
37650
- * plane produces no failures, keeps the breaker closed, renders a healthy
37651
- * block, and discards the tail of every batch indefinitely.
38589
+ * Every other reason (breaker-open, a refusal, a timeout) applies to the whole
38590
+ * chunk, and re-sending it item by item would just spend the budget failing 50
38591
+ * more times for those, the blast radius stays exactly what it was before
38592
+ * batching.
37652
38593
  */
37653
38594
  async forwardBatch(inputs, toEvent) {
37654
38595
  const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
37655
- for (let i = 0; i < inputs.length; i += 1) {
37656
- const now = Date.now();
37657
- if (now >= deadline) {
37658
- recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
37659
- return;
38596
+ const delivered = [];
38597
+ try {
38598
+ for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
38599
+ const now = Date.now();
38600
+ if (now >= deadline) {
38601
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
38602
+ return;
38603
+ }
38604
+ const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
38605
+ const forwarded = await this.deps.forward.run(
38606
+ () => this.deps.client.recordAuditEvents(
38607
+ chunk.map((event) => reKeyForForward(event, this.remoteInventory))
38608
+ )
38609
+ );
38610
+ if (forwarded.ok) {
38611
+ if (forwarded.value.accepted === chunk.length) {
38612
+ delivered.push(...chunk);
38613
+ continue;
38614
+ }
38615
+ } else if (
38616
+ // THREE reasons are worth a second pass, one at a time, and they are
38617
+ // the three settled BEFORE the control plane refused anything, or
38618
+ // (for `rejected`) refused the BODY rather than the connection.
38619
+ //
38620
+ // `invalid-request` — the CLIENT refused the body before any request
38621
+ // went out: a defect in one event, not an outage. Re-sending singly
38622
+ // isolates the bad one instead of charging its 49 neighbours for it.
38623
+ //
38624
+ // `route-absent` — the deployment predates the batch route and serves
38625
+ // only the single-event one. The retry IS the compatibility path, and
38626
+ // it has to live HERE rather than inside the client: each single gets
38627
+ // its own FORWARD_BUDGET_MS through `run`, whereas the client's own
38628
+ // fallback would spend 50 sequential round trips inside the ONE
38629
+ // budget wrapping this call — turning a working older deployment into
38630
+ // a timeout, three of those into an open breaker, and every row into
38631
+ // a silent drop while the status surface called an answering
38632
+ // deployment down.
38633
+ //
38634
+ // `rejected` — the deployment's own 4xx refusal of the body, the
38635
+ // server-side twin of `invalid-request`: isolating it the same way
38636
+ // costs one event instead of the whole chunk for a defect the
38637
+ // deployment considers local to one row.
38638
+ //
38639
+ // Every other reason (breaker-open, a refusal, a timeout) applies to
38640
+ // the whole chunk; re-sending it item by item would just spend the
38641
+ // budget failing 50 more times.
38642
+ forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
38643
+ ) {
38644
+ continue;
38645
+ }
38646
+ for (const [j, event] of chunk.entries()) {
38647
+ const at = Date.now();
38648
+ if (at >= deadline) {
38649
+ recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
38650
+ return;
38651
+ }
38652
+ const single = await this.deps.forward.run(
38653
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
38654
+ );
38655
+ if (single.ok) {
38656
+ delivered.push(event);
38657
+ continue;
38658
+ }
38659
+ if (single.reason === "breaker-open") {
38660
+ recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
38661
+ return;
38662
+ }
38663
+ recordForwardDrops(this.deps.dataDir, 1, at);
38664
+ }
38665
+ }
38666
+ } finally {
38667
+ try {
38668
+ this.deps.local.markAuditEventsDelivered(delivered, Date.now());
38669
+ } catch {
37660
38670
  }
37661
- const input2 = inputs[i];
37662
- await this.deps.forward.run(
37663
- () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
37664
- );
37665
38671
  }
37666
38672
  }
37667
38673
  // Delegated as a BATCH rather than looped over recordLlmCall: the inner
@@ -37704,9 +38710,10 @@ var AttachedDataGateway = class {
37704
38710
  // local store.
37705
38711
  async recordConfigScan(record2) {
37706
38712
  await this.deps.local.recordConfigScan(record2);
37707
- await this.deps.forward.run(
38713
+ const forwarded = await this.deps.forward.run(
37708
38714
  () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
37709
38715
  );
38716
+ if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
37710
38717
  }
37711
38718
  async recordBlockedDetection(entry) {
37712
38719
  return this.deps.local.recordBlockedDetection(entry);
@@ -37840,6 +38847,18 @@ var AttachedDataGateway = class {
37840
38847
  // exactly what it did, leaving the whole control inert on every device
37841
38848
  // while every test around it stayed green.
37842
38849
  prohibitedModels: cached2.prohibitedModels
38850
+ // ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
38851
+ // merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
38852
+ // it emits, so an 'authored' policy arriving from the control plane
38853
+ // keeps that marker even where the clamp rebuilds it with a stronger
38854
+ // action. The device reads it in exactly one direction — the rules such a
38855
+ // policy targets are not locally re-assignable — so it sits on the
38856
+ // `prohibitedModels` side of the line for the same reason that field
38857
+ // does: it can only ever ADD a refusal, never relax one, and an unsigned
38858
+ // cache therefore has no relaxation to grant by carrying it. Dropping it
38859
+ // would be the silent failure rather than the safe one — the action would
38860
+ // still be enforced while the local override the organization authored
38861
+ // away quietly came back.
37843
38862
  // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
37844
38863
  // snapshot) and is taken from the LOCAL bundle only — never from the wire
37845
38864
  // or the on-disk cache. Honoring a cached one would hand the control plane, or
@@ -37879,10 +38898,10 @@ var AttachedDataGateway = class {
37879
38898
  //
37880
38899
  // Implementing these is what actually closes the skipped-local-maintenance
37881
38900
  // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
37882
- // any object carrying all five, so the composite qualifies and SessionStart
38901
+ // any object carrying them all, so the composite qualifies and SessionStart
37883
38902
  // runs maintenance on the device's real store.
37884
38903
  //
37885
- // ⚠ Three of the six are SYNCHRONOUS and must stay that way. `handle-session-start`
38904
+ // ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
37886
38905
  // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
37887
38906
  // return value directly; declaring them `async` here would hand those call
37888
38907
  // sites a Promise and silently break both.
@@ -37905,9 +38924,15 @@ var AttachedDataGateway = class {
37905
38924
  // Delegated like the rest, and SYNCHRONOUS for the reason the note above
37906
38925
  // gives: `recordCapture` calls it after the forward has already settled, on a
37907
38926
  // path that has nothing left to await.
38927
+ markCaptureOwed(event) {
38928
+ this.deps.local.markCaptureOwed(event);
38929
+ }
37908
38930
  markCaptureDelivered(event, atMs) {
37909
38931
  this.deps.local.markCaptureDelivered(event, atMs);
37910
38932
  }
38933
+ markAuditEventsDelivered(events, atMs) {
38934
+ this.deps.local.markAuditEventsDelivered(events, atMs);
38935
+ }
37911
38936
  };
37912
38937
  function reKeyForForward(event, remote) {
37913
38938
  if (remote === null) {
@@ -37950,281 +38975,17 @@ function toolAuditEvent(input2) {
37950
38975
  }
37951
38976
 
37952
38977
  // ../../packages/plugin-runtime/src/attached/history-state.ts
37953
- import { readFileSync as readFileSync14 } from "fs";
37954
- import { join as join23 } from "path";
38978
+ import { readFileSync as readFileSync15 } from "fs";
38979
+ import { join as join24 } from "path";
37955
38980
 
37956
38981
  // ../../packages/plugin-runtime/src/attached/history-sync.ts
37957
38982
  import { createHash as createHash6 } from "crypto";
37958
38983
  import { hostname as hostname5 } from "os";
37959
38984
 
37960
- // ../../packages/remote/src/http.ts
37961
- import { request as httpRequest } from "http";
37962
- import { request as httpsRequest } from "https";
37963
- var DEFAULT_TIMEOUT_MS = 1e4;
37964
- var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
37965
- var RemoteRequestError = class extends Error {
37966
- constructor(status) {
37967
- super(`control-plane request failed with status ${String(status)}`);
37968
- this.status = status;
37969
- this.name = "RemoteRequestError";
37970
- }
37971
- status;
37972
- };
37973
- var RemoteRequestInvalid = class extends Error {
37974
- constructor(route, cause) {
37975
- super(`refusing to send a malformed body to ${route}`);
37976
- this.cause = cause;
37977
- this.name = "RemoteRequestInvalid";
37978
- }
37979
- cause;
37980
- };
37981
- var RemoteResponseInvalid = class extends Error {
37982
- constructor(route, detail) {
37983
- super(`control plane answered ${route} with ${detail}`);
37984
- this.name = "RemoteResponseInvalid";
37985
- }
37986
- };
37987
- var RemoteTransportError = class extends Error {
37988
- /**
37989
- * The status the peer sent, when headers arrived and only the BODY was
37990
- * refused.
37991
- *
37992
- * Undefined for the ordinary case this class was written for — no answer at
37993
- * all. It exists because two paths reject after a status has already been
37994
- * delivered: an oversized body and an aborted response. Discarding it there
37995
- * reported a deployment answering 401 with a verbose body as a network
37996
- * outage, which sends the reader to look at their network instead of their
37997
- * credential.
37998
- */
37999
- constructor(reason, status) {
38000
- super(`control-plane request did not complete: ${reason}`);
38001
- this.status = status;
38002
- this.name = "RemoteTransportError";
38003
- }
38004
- status;
38005
- };
38006
- async function send(options) {
38007
- const url2 = new URL(options.url);
38008
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
38009
- const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
38010
- const requestOptions = {
38011
- method: options.method,
38012
- headers: {
38013
- // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
38014
- // last they win, and two of the values below are ones no caller may
38015
- // replace: `x-api-key` is the credential, and `content-length` is the
38016
- // byte count that stops a multi-byte body being truncated by the
38017
- // receiver. `SendOptions.headers` is a free-form record on an exported
38018
- // function, so "no caller does that today" is not the guarantee to rely
38019
- // on. The one header any caller actually passes — `if-none-match` on the
38020
- // conditional GET — is untouched by this order.
38021
- ...options.headers,
38022
- // The credential. One header, matching what the deployment authenticates
38023
- // on; a second copy in an `Authorization` header would be one more place
38024
- // it can be logged by an intermediary for no gain.
38025
- //
38026
- // Spread conditionally rather than assigned as `undefined`: Node's header
38027
- // handling and `content-length` bookkeeping treat a present-but-undefined
38028
- // key differently from an absent one, and "the header is not there" is
38029
- // the property the attach flow needs.
38030
- ...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
38031
- accept: "application/json",
38032
- ...options.body === void 0 ? {} : {
38033
- "content-type": "application/json",
38034
- // Byte length, not string length: a multi-byte body sent with a
38035
- // character count is truncated by the receiver.
38036
- "content-length": String(Buffer.byteLength(options.body))
38037
- }
38038
- }
38039
- };
38040
- return new Promise((resolve2, reject) => {
38041
- let settled = false;
38042
- const fail = (reason, status) => {
38043
- if (settled) return;
38044
- settled = true;
38045
- reject(new RemoteTransportError(reason, status));
38046
- };
38047
- const req = send_(url2, requestOptions, (res) => {
38048
- const chunks = [];
38049
- let size = 0;
38050
- res.on("data", (chunk) => {
38051
- size += chunk.length;
38052
- if (size > MAX_RESPONSE_BYTES) {
38053
- fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
38054
- res.destroy();
38055
- req.destroy();
38056
- return;
38057
- }
38058
- chunks.push(chunk);
38059
- });
38060
- res.on("aborted", () => {
38061
- fail("the response was aborted", res.statusCode);
38062
- });
38063
- res.on("end", () => {
38064
- if (settled) return;
38065
- settled = true;
38066
- resolve2({
38067
- status: res.statusCode ?? 0,
38068
- headers: res.headers,
38069
- body: Buffer.concat(chunks).toString("utf8")
38070
- });
38071
- });
38072
- });
38073
- const deadline = setTimeout(() => {
38074
- fail(`no response within ${String(timeoutMs)}ms`);
38075
- req.destroy();
38076
- }, timeoutMs);
38077
- deadline.unref();
38078
- req.on("upgrade", (_res, socket) => {
38079
- fail("the deployment answered with a protocol upgrade");
38080
- socket.destroy();
38081
- });
38082
- req.on("close", () => {
38083
- fail("the connection closed before a response was read");
38084
- clearTimeout(deadline);
38085
- });
38086
- req.on("error", (err) => {
38087
- fail(err.message);
38088
- });
38089
- if (options.body !== void 0) req.write(options.body);
38090
- req.end();
38091
- });
38092
- }
38093
-
38094
- // ../../packages/remote/src/client.ts
38095
- var ROUTES = {
38096
- events: "/v1/events",
38097
- auditEvents: "/v1/audit-events",
38098
- auditEventsBatch: "/v1/audit-events/batch",
38099
- inventory: "/v1/inventory",
38100
- storePosture: "/v1/store-posture",
38101
- policyBundle: "/v1/policy-bundle",
38102
- whoami: "/v1/plugin/whoami",
38103
- shares: "/v1/shares"
38104
- };
38105
- function headerValue(response, name) {
38106
- const raw = response.headers[name];
38107
- if (raw === void 0) return void 0;
38108
- return Array.isArray(raw) ? raw[0] : raw;
38109
- }
38110
- function okBody(response) {
38111
- if (response.status < 200 || response.status >= 300) {
38112
- throw new RemoteRequestError(response.status);
38113
- }
38114
- return response.body;
38115
- }
38116
- function parsed(schema, body, route) {
38117
- let json2;
38118
- try {
38119
- json2 = JSON.parse(body);
38120
- } catch {
38121
- throw new RemoteResponseInvalid(route, "a body that is not JSON");
38122
- }
38123
- const result = schema.safeParse(json2);
38124
- if (!result.success) {
38125
- throw new RemoteResponseInvalid(route, "a body this client cannot read");
38126
- }
38127
- return result.data;
38128
- }
38129
- function withoutTrailingSlashes(endpoint) {
38130
- let end = endpoint.length;
38131
- while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
38132
- return endpoint.slice(0, end);
38133
- }
38134
- var SLASH = "/".charCodeAt(0);
38135
- function createRemoteClient(options) {
38136
- const base = withoutTrailingSlashes(options.endpoint);
38137
- const url2 = (route) => `${base}${route}`;
38138
- const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
38139
- const sendOne = async (event) => {
38140
- const validated = RecordAuditEventRequest.safeParse(event);
38141
- if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
38142
- const response = await send({
38143
- ...common,
38144
- method: "POST",
38145
- url: url2(ROUTES.auditEvents),
38146
- body: JSON.stringify(validated.data)
38147
- });
38148
- okBody(response);
38149
- };
38150
- return {
38151
- async ingestEvents(batch) {
38152
- const response = await send({
38153
- ...common,
38154
- method: "POST",
38155
- url: url2(ROUTES.events),
38156
- body: JSON.stringify(batch)
38157
- });
38158
- return parsed(IngestAck, okBody(response), ROUTES.events);
38159
- },
38160
- async ingestInventory(context) {
38161
- const response = await send({
38162
- ...common,
38163
- method: "POST",
38164
- url: url2(ROUTES.inventory),
38165
- body: JSON.stringify(context)
38166
- });
38167
- return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
38168
- },
38169
- async recordAuditEvent(event) {
38170
- await sendOne(event);
38171
- },
38172
- async recordAuditEvents(events) {
38173
- const validated = RecordAuditEventBatch.safeParse({ events });
38174
- if (!validated.success) {
38175
- throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
38176
- }
38177
- const response = await send({
38178
- ...common,
38179
- method: "POST",
38180
- url: url2(ROUTES.auditEventsBatch),
38181
- body: JSON.stringify(validated.data)
38182
- });
38183
- if (response.status === 404) {
38184
- for (const event of validated.data.events) await sendOne(event);
38185
- return { accepted: validated.data.events.length };
38186
- }
38187
- return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
38188
- },
38189
- async reportStorePosture(snapshot) {
38190
- const response = await send({
38191
- ...common,
38192
- method: "POST",
38193
- url: url2(ROUTES.storePosture),
38194
- body: JSON.stringify(snapshot)
38195
- });
38196
- okBody(response);
38197
- },
38198
- async getPolicyBundle(etag) {
38199
- const response = await send({
38200
- ...common,
38201
- method: "GET",
38202
- url: url2(ROUTES.policyBundle),
38203
- ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
38204
- });
38205
- if (response.status === 304) {
38206
- return { changed: false, etag: headerValue(response, "etag") ?? etag };
38207
- }
38208
- const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
38209
- return { changed: true, bundle, etag: headerValue(response, "etag") };
38210
- },
38211
- async whoami() {
38212
- const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
38213
- return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
38214
- },
38215
- async recordProjectEgress(request) {
38216
- const validated = EgressIngestRequest.safeParse(request);
38217
- if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
38218
- const response = await send({
38219
- ...common,
38220
- method: "POST",
38221
- url: url2(ROUTES.shares),
38222
- body: JSON.stringify(validated.data)
38223
- });
38224
- okBody(response);
38225
- }
38226
- };
38227
- }
38985
+ // ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
38986
+ var CORRELATION_ID = EventMetadata.shape.correlationId;
38987
+ var TRACE_ID = EventMetadata.shape.traceId;
38988
+ var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
38228
38989
 
38229
38990
  // ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
38230
38991
  import { spawn } from "child_process";
@@ -38232,7 +38993,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
38232
38993
  var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
38233
38994
 
38234
38995
  // ../../packages/plugin-runtime/src/attached/plugin-block.ts
38235
- import { readFileSync as readFileSync15 } from "fs";
38996
+ import { readFileSync as readFileSync16 } from "fs";
38236
38997
  function createPluginBlock(build, policyStore) {
38237
38998
  return async () => {
38238
38999
  const cached2 = await policyStore.read();
@@ -38251,7 +39012,7 @@ function createPluginBlock(build, policyStore) {
38251
39012
  // ../../packages/plugin-runtime/src/attached/policy-store.ts
38252
39013
  import { randomUUID as randomUUID16 } from "crypto";
38253
39014
  import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
38254
- import { join as join24 } from "path";
39015
+ import { join as join25 } from "path";
38255
39016
 
38256
39017
  // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
38257
39018
  import { rename as rename2 } from "fs/promises";
@@ -38275,7 +39036,7 @@ async function publishByRename(tmp, file2, move = rename2) {
38275
39036
 
38276
39037
  // ../../packages/plugin-runtime/src/attached/policy-store.ts
38277
39038
  function createPolicyStore(dir = dataDir()) {
38278
- const file2 = join24(dir, "policy-cache.json");
39039
+ const file2 = join25(dir, "policy-cache.json");
38279
39040
  async function read() {
38280
39041
  try {
38281
39042
  const raw = await readFile2(file2, "utf8");
@@ -38284,22 +39045,32 @@ function createPolicyStore(dir = dataDir()) {
38284
39045
  const record2 = parsed2;
38285
39046
  const bundle = PolicyBundle.parse(record2.bundle);
38286
39047
  const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
38287
- const etag = typeof record2.etag === "string" ? record2.etag : void 0;
39048
+ const stored = typeof record2.etag === "string" ? record2.etag : void 0;
39049
+ const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
39050
+ const etag = replayable ? stored : void 0;
38288
39051
  return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
38289
39052
  } catch {
38290
39053
  return null;
38291
39054
  }
38292
39055
  }
38293
- async function write(bundle, etag) {
38294
- await ensureDataDir(dir);
38295
- const stored = {
38296
- bundle,
38297
- fetchedAtMs: Date.now(),
38298
- ...etag === void 0 ? {} : { etag }
38299
- };
39056
+ function knowsMoreThanThisBuild(shapeId) {
39057
+ if (typeof shapeId !== "string" || shapeId === "") return false;
39058
+ const theirs = new Set(shapeId.split(","));
39059
+ const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
39060
+ return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
39061
+ }
39062
+ async function priorRecord() {
39063
+ try {
39064
+ const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
39065
+ return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
39066
+ } catch {
39067
+ return null;
39068
+ }
39069
+ }
39070
+ async function publishRecord(record2) {
38300
39071
  const tmp = `${file2}.${randomUUID16()}.tmp`;
38301
39072
  try {
38302
- await writeFile2(tmp, JSON.stringify(stored), {
39073
+ await writeFile2(tmp, JSON.stringify(record2), {
38303
39074
  encoding: "utf8",
38304
39075
  mode: DATA_FILE_MODE,
38305
39076
  flag: "wx"
@@ -38310,6 +39081,27 @@ function createPolicyStore(dir = dataDir()) {
38310
39081
  throw err;
38311
39082
  }
38312
39083
  }
39084
+ async function write(bundle, etag) {
39085
+ await ensureDataDir(dir);
39086
+ const prior = await priorRecord();
39087
+ const priorVersion = prior?.bundle?.version;
39088
+ if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
39089
+ await publishRecord({
39090
+ ...prior,
39091
+ fetchedAtMs: Date.now()
39092
+ });
39093
+ return;
39094
+ }
39095
+ await publishRecord({
39096
+ bundle,
39097
+ fetchedAtMs: Date.now(),
39098
+ // Stamped on EVERY write, the 304 arm's included: that arm hands back the
39099
+ // bundle it already holds, and the point of the stamp is to describe the
39100
+ // build that last narrowed those bytes, which is this one.
39101
+ shapeId: POLICY_BUNDLE_SHAPE_ID,
39102
+ ...etag === void 0 ? {} : { etag }
39103
+ });
39104
+ }
38313
39105
  return { read, write, file: file2 };
38314
39106
  }
38315
39107
 
@@ -38475,11 +39267,11 @@ function readStorePosture(dbPath2) {
38475
39267
  // ../../packages/plugin-runtime/src/attached/posture-store.ts
38476
39268
  import { randomUUID as randomUUID17 } from "crypto";
38477
39269
  import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
38478
- import { join as join25 } from "path";
39270
+ import { join as join26 } from "path";
38479
39271
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
38480
39272
  function createPostureStore(dir = settingsDir(), legacyDir) {
38481
- const file2 = join25(dir, "posture-state.json");
38482
- const legacyFile = legacyDir === void 0 ? null : join25(legacyDir, "posture-state.json");
39273
+ const file2 = join26(dir, "posture-state.json");
39274
+ const legacyFile = legacyDir === void 0 ? null : join26(legacyDir, "posture-state.json");
38483
39275
  async function persist(state) {
38484
39276
  await ensureDataDir(dir);
38485
39277
  const tmp = `${file2}.${randomUUID17()}.tmp`;
@@ -38547,8 +39339,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
38547
39339
  }
38548
39340
 
38549
39341
  // ../../packages/plugin-runtime/src/attached/sync-state.ts
38550
- import { readFileSync as readFileSync16 } from "fs";
38551
- import { join as join26 } from "path";
39342
+ import { readFileSync as readFileSync17 } from "fs";
39343
+ import { join as join27 } from "path";
38552
39344
 
38553
39345
  // ../../packages/plugin-runtime/src/attached/status.ts
38554
39346
  var REFUSAL_LINES = {
@@ -38866,9 +39658,21 @@ var StandaloneDataGateway = class {
38866
39658
  // for the whole of it, so a member that threw would make that answer a lie
38867
39659
  // the moment a composite delegated to it. A store-level no-op is the honest
38868
39660
  // shape — a standalone machine has nothing delivered to record.
39661
+ markCaptureOwed(event) {
39662
+ this.db.markCaptureOwed(event);
39663
+ }
38869
39664
  markCaptureDelivered(event, atMs) {
38870
39665
  this.db.markCaptureDelivered(event, atMs);
38871
39666
  }
39667
+ // Implemented, not stubbed, for the same reason its sibling above is: the
39668
+ // attached gateway is a DECORATOR over an instance of this class
39669
+ // (`attached/factory.ts` builds one and passes it as `deps.local`), so every
39670
+ // stamp the live forward makes lands here with a non-empty array. This is the
39671
+ // production write path for that feature, not a shape-satisfying no-op — a
39672
+ // machine that is merely standalone simply never calls it.
39673
+ markAuditEventsDelivered(events, atMs) {
39674
+ this.db.markAuditEventsDelivered(events, atMs);
39675
+ }
38872
39676
  staleBinaryNotice(currentVersion) {
38873
39677
  try {
38874
39678
  const newest = this.db.installedPacks.newestRecordedBinary();
@@ -39010,8 +39814,8 @@ var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
39010
39814
  import { statSync as statSync11 } from "fs";
39011
39815
 
39012
39816
  // ../../packages/scanner/src/walk.ts
39013
- import { readdirSync as readdirSync6, readFileSync as readFileSync17, statSync as statSync10 } from "fs";
39014
- import { extname, join as join27, relative as relative2, sep as sep4 } from "path";
39817
+ import { readdirSync as readdirSync6, readFileSync as readFileSync18, statSync as statSync10 } from "fs";
39818
+ import { extname, join as join28, relative as relative2, sep as sep4 } from "path";
39015
39819
  var import_ignore2 = __toESM(require_ignore(), 1);
39016
39820
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
39017
39821
  ".ts",
@@ -39062,7 +39866,7 @@ function* walkTree(rootDir, opts = {}) {
39062
39866
  );
39063
39867
  for (const entry of dirents) {
39064
39868
  const name = entry.name;
39065
- const fullPath = join27(dir, name);
39869
+ const fullPath = join28(dir, name);
39066
39870
  if (entry.isDirectory()) {
39067
39871
  const skipState = evaluateIgnore(dirSkipLayers, dirRel, name, true);
39068
39872
  if (skipState !== "unignored" && (SKIP_DIRS.has(name) || skipState === "ignored")) {
@@ -39116,7 +39920,7 @@ function* walkSourceFiles(opts = {}) {
39116
39920
  if (opts.shouldRead && !opts.shouldRead(meta4)) continue;
39117
39921
  let content;
39118
39922
  try {
39119
- content = readFileSync17(file2.path, "utf8");
39923
+ content = readFileSync18(file2.path, "utf8");
39120
39924
  } catch {
39121
39925
  continue;
39122
39926
  }
@@ -39343,7 +40147,7 @@ function scanManifests(egress, ledger, updates, rootDir) {
39343
40147
  if (prev?.mtime === manifest.mtime) continue;
39344
40148
  let content;
39345
40149
  try {
39346
- content = readFileSync18(manifest.path, "utf8");
40150
+ content = readFileSync19(manifest.path, "utf8");
39347
40151
  } catch {
39348
40152
  continue;
39349
40153
  }