@akasecurity/ai-tc-claude-code 0.9.5 → 0.9.7

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.
@@ -585,6 +585,10 @@ var SQLITE_MIGRATIONS = [
585
585
  {
586
586
  tag: "0020_secret_vault_pagination_indexes",
587
587
  sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
588
+ },
589
+ {
590
+ tag: "0021_finding_resolution_resolved_at_index",
591
+ sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
588
592
  }
589
593
  ];
590
594
 
@@ -15300,6 +15304,47 @@ function date4(params) {
15300
15304
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
15301
15305
  config(en_default());
15302
15306
 
15307
+ // ../../packages/schema/src/zod/harness-map.ts
15308
+ var HARNESS = {
15309
+ ClaudeCode: "claudecode",
15310
+ Cursor: "cursor",
15311
+ Copilot: "copilot",
15312
+ Codex: "codex",
15313
+ Antigravity: "antigravity",
15314
+ Windsurf: "windsurf",
15315
+ ClaudeDesktop: "claudedesktop",
15316
+ ChatGpt: "chatgpt",
15317
+ ClaudeAi: "claudeai",
15318
+ Api: "api"
15319
+ };
15320
+ var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
15321
+ var SOURCE_TOOL = {
15322
+ ClaudeCode: "claude-code",
15323
+ ClaudeDesktop: "claude-desktop",
15324
+ Cursor: "cursor",
15325
+ ChatGpt: "chatgpt",
15326
+ ClaudeAi: "claude-ai",
15327
+ Copilot: "github-copilot",
15328
+ Codex: "codex",
15329
+ Antigravity: "antigravity",
15330
+ // No harness counterpart, deliberately: the CLI's own captures and a capture
15331
+ // whose tool could not be identified both render through the read side's
15332
+ // miss path rather than as a harness of their own.
15333
+ Cli: "cli",
15334
+ Unknown: "unknown"
15335
+ };
15336
+ var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
15337
+ var TOOL_TO_HARNESS = {
15338
+ [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
15339
+ [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
15340
+ [SOURCE_TOOL.Copilot]: HARNESS.Copilot,
15341
+ [SOURCE_TOOL.Cursor]: HARNESS.Cursor,
15342
+ [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
15343
+ [SOURCE_TOOL.Codex]: HARNESS.Codex,
15344
+ [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
15345
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
15346
+ };
15347
+
15303
15348
  // ../../packages/schema/src/zod/finding.ts
15304
15349
  var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
15305
15350
  var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
@@ -15322,21 +15367,22 @@ var Finding = external_exports.object({
15322
15367
  }).meta({ id: "Finding" });
15323
15368
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15324
15369
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15325
- var FindingProvider = external_exports.enum([
15326
- "claudecode",
15327
- "claudedesktop",
15328
- "cursor",
15329
- "copilot",
15330
- "chatgpt",
15331
- "claudeai",
15332
- "codex",
15333
- "antigravity",
15334
- "api"
15370
+ var FindingProvider = Harness.extract([
15371
+ "ClaudeCode",
15372
+ "ClaudeDesktop",
15373
+ "Cursor",
15374
+ "Copilot",
15375
+ "ChatGpt",
15376
+ "ClaudeAi",
15377
+ "Codex",
15378
+ "Antigravity",
15379
+ "Api"
15335
15380
  ]).meta({ id: "FindingProvider" });
15336
15381
  var FindingCategory = external_exports.enum([
15337
15382
  "secret",
15338
15383
  "pii",
15339
15384
  "source_code",
15385
+ "code_flaw",
15340
15386
  "external_share",
15341
15387
  "mcp_server",
15342
15388
  "customer_data",
@@ -15516,6 +15562,7 @@ var FindingInstanceDetail = FindingInstance.extend({
15516
15562
  policy: FindingPolicyRef
15517
15563
  }).meta({ id: "FindingInstanceDetail" });
15518
15564
  var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15565
+ var MAX_FLAT_FINDINGS_LIMIT = 200;
15519
15566
  var ListFindingInstancesQuery = external_exports.object({
15520
15567
  severity: external_exports.array(Severity).optional(),
15521
15568
  // Rule ids, the same vocabulary the grouped list's `subtype` carries.
@@ -15535,7 +15582,7 @@ var ListFindingInstancesQuery = external_exports.object({
15535
15582
  q: external_exports.string().optional(),
15536
15583
  sessionId: external_exports.string().optional(),
15537
15584
  from: external_exports.iso.datetime().optional(),
15538
- limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15585
+ limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
15539
15586
  cursor: external_exports.string().optional()
15540
15587
  });
15541
15588
  var ListFindingInstancesResponse = external_exports.object({
@@ -15597,30 +15644,6 @@ var ListFindingLocationsResponse = external_exports.object({
15597
15644
  hasMore: external_exports.boolean()
15598
15645
  }).meta({ id: "ListFindingLocationsResponse" });
15599
15646
 
15600
- // ../../packages/schema/src/zod/harness-map.ts
15601
- var Harness = external_exports.enum([
15602
- "claudecode",
15603
- "cursor",
15604
- "copilot",
15605
- "codex",
15606
- "antigravity",
15607
- "windsurf",
15608
- "claudedesktop",
15609
- "chatgpt",
15610
- "claudeai",
15611
- "api"
15612
- ]).meta({ id: "Harness" });
15613
- var TOOL_TO_HARNESS = {
15614
- "claude-code": "claudecode",
15615
- "claude-desktop": "claudedesktop",
15616
- "github-copilot": "copilot",
15617
- cursor: "cursor",
15618
- chatgpt: "chatgpt",
15619
- codex: "codex",
15620
- antigravity: "antigravity",
15621
- "claude-ai": "claudeai"
15622
- };
15623
-
15624
15647
  // ../../packages/schema/src/zod/meta.ts
15625
15648
  var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
15626
15649
  var AuditEventType = external_exports.enum([
@@ -16073,366 +16096,122 @@ var ActivityOverviewResponse = external_exports.object({
16073
16096
  sessions: ListActivitySessionsResponse
16074
16097
  }).meta({ id: "ActivityOverviewResponse" });
16075
16098
 
16076
- // ../../packages/schema/src/zod/event.ts
16077
- var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
16078
- var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
16079
- var SourceTool = external_exports.enum([
16080
- "claude-code",
16081
- "claude-desktop",
16082
- "cursor",
16083
- "chatgpt",
16084
- "claude-ai",
16085
- "github-copilot",
16086
- "codex",
16087
- "antigravity",
16088
- "cli",
16089
- "unknown"
16090
- ]).meta({ id: "SourceTool" });
16091
- var EventMetadata = external_exports.object({
16092
- sessionId: external_exports.string().optional(),
16093
- repo: external_exports.string().optional(),
16094
- filePath: external_exports.string().optional(),
16095
- // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
16096
- // set by the tool-scanning hooks. The tool NAME only — never the tool's
16097
- // arguments or output, which can carry the very value a finding masked
16098
- // (metadata is stored unredacted). Gives findings on non-file captures a
16099
- // display location ("via Bash") when no filePath exists.
16100
- toolName: external_exports.string().optional(),
16101
- // Set (true) by the worktree scanner when the file is excluded by the
16102
- // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
16103
- // generated code can leak real secrets — but the provenance is recorded so
16104
- // policy/dashboards can treat those findings as informational rather than
16105
- // blocking. Omitted (not false) for tracked files and non-scan events.
16106
- gitignored: external_exports.boolean().optional(),
16107
- // Set (true) ONLY when the event's `content` is the COMPLETE file at
16108
- // capture time (a worktree scan reading from disk). Hook-captured edits
16109
- // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
16110
- // this. The resolver-on-ingest keys its fixed-at-source dropout
16111
- // diff on this marker: only a whole-file snapshot can prove a previously
16112
- // open finding is gone; a fragment's absence proves nothing (the secret
16113
- // may live outside the hunk). Omitted (not false) for fragments and
16114
- // non-scan events, so pre-marker clients safely default to the
16115
- // non-authoritative path.
16116
- wholeFile: external_exports.boolean().optional(),
16117
- model: external_exports.string().optional(),
16118
- turnIndex: external_exports.number().int().nonnegative().optional(),
16119
- // Distributed-tracing correlation. `correlationId` ties a recorded event back
16120
- // to the request that captured/ingested it (a UUID, generated independently of
16121
- // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
16122
- // originating span when telemetry is enabled. Both optional + backward
16123
- // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
16124
- correlationId: external_exports.uuid().optional(),
16125
- traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
16126
- // Ids of the detection exceptions that downgraded findings in this capture
16127
- // to 'allow' — the enforcement audit trail's link back to the grant that
16128
- // authorized the bypass. Absent on captures where no exception applied.
16129
- exceptionIds: external_exports.array(external_exports.guid()).optional()
16130
- }).meta({ id: "EventMetadata" });
16131
- var Event = external_exports.object({
16132
- id: external_exports.guid(),
16133
- sourceTool: SourceTool,
16134
- kind: EventKind,
16135
- occurredAt: external_exports.iso.datetime(),
16136
- contentHash: external_exports.string(),
16137
- content: external_exports.string(),
16138
- metadata: EventMetadata.optional()
16139
- }).meta({ id: "Event" });
16140
- var IngestEvent = Event.meta({ id: "IngestEvent" });
16141
- var IngestBatch = external_exports.object({
16142
- events: external_exports.array(IngestEvent).min(1).max(100),
16143
- // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
16144
- // additionally rejects any event whose contentHash the store has already
16145
- // recorded — for re-runnable bulk ingest (worktree scan, transcript
16146
- // backfill), where a re-run mints fresh event ids for identical content and
16147
- // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
16148
- // two genuinely separate prompts can be byte-identical and both belong on
16149
- // the timeline.
16150
- dedupe: external_exports.literal("content-hash").optional()
16151
- }).meta({ id: "IngestBatch" });
16152
-
16153
- // ../../packages/schema/src/zod/inventory.ts
16154
- var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16155
- var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16156
- var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16157
- var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16158
- var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16159
- var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16160
- var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16161
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16162
- var AccessCounts = external_exports.object({
16163
- open: external_exports.number().int().nonnegative(),
16164
- approved: external_exports.number().int().nonnegative(),
16165
- blocked: external_exports.number().int().nonnegative(),
16166
- total: external_exports.number().int().nonnegative()
16167
- }).meta({ id: "AccessCounts" });
16168
- var AssetSummary = external_exports.object({
16169
- id: external_exports.string(),
16170
- type: AssetType,
16171
- name: external_exports.string(),
16172
- sub: external_exports.string(),
16173
- flags: external_exports.array(Flag),
16174
- /** MCP servers only — omitted for all other types. */
16175
- trust: TrustLevel.optional()
16176
- }).meta({ id: "AssetSummary" });
16177
- var ProjectSummary = external_exports.object({
16178
- id: external_exports.string(),
16179
- name: external_exports.string(),
16180
- repo: external_exports.string(),
16181
- visibility: Visibility,
16182
- language: external_exports.string(),
16183
- policyDefault: AccessLevel,
16184
- updatedAt: external_exports.iso.datetime(),
16185
- accessCounts: AccessCounts,
16186
- findingsCount: external_exports.number().int().nonnegative()
16187
- }).meta({ id: "ProjectSummary" });
16188
- var HarnessCategory = external_exports.object({
16189
- /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16190
- type: AssetType,
16191
- assets: external_exports.array(AssetSummary)
16099
+ // ../../packages/schema/src/zod/config-inventory.ts
16100
+ var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16101
+ var SkillScanEntry = external_exports.object({
16102
+ name: external_exports.string().min(1),
16103
+ // The identity source: a marketplace repo for plugin skills (e.g.
16104
+ // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16105
+ // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16106
+ // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16107
+ source: external_exports.string().min(1),
16108
+ scope: ConfigScope,
16109
+ pluginName: external_exports.string().optional(),
16110
+ // Volatile — rides the attribute bag, never the identity hash.
16111
+ version: external_exports.string().optional(),
16112
+ description: external_exports.string().optional(),
16113
+ // Skill directory mtime (ISO) the "updated Nd ago" freshness signal.
16114
+ updatedAt: external_exports.iso.datetime().optional(),
16115
+ // Filesystem path — the promoted inventory `location` column.
16116
+ location: external_exports.string().optional()
16192
16117
  });
16193
- var HarnessSummary = external_exports.object({
16194
- id: HarnessId,
16195
- label: external_exports.string(),
16196
- kind: external_exports.string(),
16197
- version: external_exports.string(),
16198
- sessions: external_exports.number().int().nonnegative(),
16199
- assetCount: external_exports.number().int().nonnegative(),
16200
- flagCount: external_exports.number().int().nonnegative(),
16201
- projects: external_exports.array(ProjectSummary),
16202
- categories: external_exports.array(HarnessCategory)
16203
- }).meta({ id: "HarnessSummary" });
16204
- var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16205
- var AssetGroup = external_exports.object({
16206
- /** Group key — never project (enforced at service layer). */
16207
- type: AssetType,
16208
- total: external_exports.number().int().nonnegative(),
16209
- /**
16210
- * MCP group only — omitted for all other types.
16211
- * Partial: only TrustLevel keys with non-zero counts are included.
16212
- * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16213
- */
16214
- trustRollup: external_exports.object({
16215
- "known-good": external_exports.number().int().nonnegative(),
16216
- risky: external_exports.number().int().nonnegative(),
16217
- unapproved: external_exports.number().int().nonnegative()
16218
- }).partial().strict().optional(),
16219
- /**
16220
- * Partial: only Flag keys with non-zero counts are included.
16221
- * Strict: unknown keys are rejected — only Flag values are valid keys.
16222
- */
16223
- flagRollup: external_exports.object({
16224
- update: external_exports.number().int().nonnegative(),
16225
- stale: external_exports.number().int().nonnegative(),
16226
- conflict: external_exports.number().int().nonnegative(),
16227
- unknown: external_exports.number().int().nonnegative(),
16228
- change: external_exports.number().int().nonnegative(),
16229
- untracked: external_exports.number().int().nonnegative(),
16230
- risk: external_exports.number().int().nonnegative(),
16231
- findings: external_exports.number().int().nonnegative()
16232
- }).partial().strict(),
16233
- items: external_exports.array(AssetSummary)
16234
- }).meta({ id: "AssetGroup" });
16235
- var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16236
- var McpTool = external_exports.object({
16237
- name: external_exports.string(),
16238
- signature: external_exports.string(),
16239
- description: external_exports.string(),
16240
- write: external_exports.boolean(),
16241
- /** Non-null string when tool is dangerous / blocked; null otherwise. */
16242
- risk: external_exports.string().nullable()
16243
- }).meta({ id: "McpTool" });
16244
- var AssetFindingRef = external_exports.object({
16245
- id: external_exports.string(),
16246
- title: external_exports.string(),
16247
- note: external_exports.string()
16118
+ var HookScanEntry = external_exports.object({
16119
+ // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16120
+ // set is harness-defined and grows without a schema change.
16121
+ event: external_exports.string().min(1),
16122
+ // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16123
+ matcher: external_exports.string().optional(),
16124
+ command: external_exports.string().min(1),
16125
+ timeout: external_exports.number().optional(),
16126
+ scope: ConfigScope,
16127
+ pluginName: external_exports.string().optional(),
16128
+ // The settings file / hooks.json the entry came from.
16129
+ location: external_exports.string().optional()
16248
16130
  });
16249
- var AssetDetail = AssetSummary.extend({
16250
- /** string | null null when no description is available. */
16251
- description: external_exports.string().nullable(),
16252
- /** trustLevel | null — null for non-MCP assets. */
16253
- trust: TrustLevel.nullable(),
16254
- /** Type-specific raw key/values — FE renders the grid. */
16255
- meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16256
- /** always present object when there is an active finding, null when absent. */
16257
- finding: AssetFindingRef.nullable(),
16258
- /** MCP exposed-tools list — omitted for non-mcp. */
16259
- tools: external_exports.array(McpTool).optional()
16260
- }).meta({ id: "AssetDetail" });
16261
- var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16262
- var InventoryStats = external_exports.object({
16263
- /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16264
- attention: external_exports.number().int().nonnegative(),
16265
- byType: external_exports.object({
16266
- project: external_exports.number().int().nonnegative(),
16267
- skill: external_exports.number().int().nonnegative(),
16268
- mcp: external_exports.number().int().nonnegative(),
16269
- hook: external_exports.number().int().nonnegative(),
16270
- config: external_exports.number().int().nonnegative()
16271
- }),
16272
- harnesses: external_exports.number().int().nonnegative(),
16273
- mcpTrust: external_exports.object({
16274
- "known-good": external_exports.number().int().nonnegative(),
16275
- risky: external_exports.number().int().nonnegative(),
16276
- unapproved: external_exports.number().int().nonnegative()
16277
- })
16278
- }).meta({ id: "InventoryStats" });
16279
- var FileSummary = external_exports.object({
16280
- path: external_exports.string(),
16281
- name: external_exports.string(),
16282
- origin: Origin,
16283
- /** Effective access (override applied). */
16284
- access: AccessLevel,
16285
- /** True when a file_access_override differs from the computed default. */
16286
- isCustom: external_exports.boolean(),
16287
- findings: external_exports.number().int().nonnegative(),
16288
- /** When the file was auto-blocked by a detection; null when not blocked. */
16289
- blockedAt: external_exports.iso.datetime().nullable().optional(),
16290
- /** Why the file was blocked; null when absent. */
16291
- note: external_exports.string().nullable().optional()
16292
- }).meta({ id: "FileSummary" });
16293
- var FolderSummary = external_exports.object({
16294
- name: external_exports.string(),
16295
- path: external_exports.string(),
16296
- /** Rollup of effective access across all descendants. */
16297
- accessCounts: AccessCounts
16298
- }).meta({ id: "FolderSummary" });
16299
- var ProjectTreeResponse = external_exports.object({
16300
- project: external_exports.object({
16301
- id: external_exports.string(),
16302
- repo: external_exports.string(),
16303
- visibility: Visibility
16304
- }),
16305
- path: external_exports.string(),
16306
- /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16307
- folders: external_exports.array(FolderSummary).optional(),
16308
- files: external_exports.array(FileSummary)
16309
- }).meta({ id: "ProjectTreeResponse" });
16310
- var FileDetail = FileSummary.extend({
16311
- project: external_exports.object({
16312
- repo: external_exports.string(),
16313
- visibility: Visibility,
16314
- language: external_exports.string(),
16315
- policyDefault: AccessLevel,
16316
- updatedAt: external_exports.iso.datetime()
16317
- }),
16318
- findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16319
- }).meta({ id: "FileDetail" });
16320
- var SetFileAccessBody = external_exports.object({
16321
- path: external_exports.string(),
16322
- access: AccessLevel
16323
- }).meta({ id: "SetFileAccessBody" });
16324
- var SetFileAccessResponse = external_exports.object({
16325
- file: FileSummary,
16326
- accessCounts: AccessCounts
16327
- }).meta({ id: "SetFileAccessResponse" });
16328
- var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16329
- var HarnessEventItem = external_exports.object({
16330
- kind: HarnessEventKind,
16331
- title: external_exports.string(),
16332
- detail: external_exports.string(),
16333
- occurredAt: external_exports.iso.datetime(),
16334
- findingId: external_exports.string().nullable().optional()
16335
- }).meta({ id: "HarnessEventItem" });
16336
- var HarnessEventsResponse = external_exports.object({
16337
- counts: external_exports.object({
16338
- block: external_exports.number().int().nonnegative(),
16339
- redact: external_exports.number().int().nonnegative(),
16340
- warn: external_exports.number().int().nonnegative()
16341
- }),
16342
- items: external_exports.array(HarnessEventItem)
16343
- }).meta({ id: "HarnessEventsResponse" });
16344
- var RescanResponse = external_exports.object({
16345
- jobId: external_exports.string(),
16346
- startedAt: external_exports.iso.datetime()
16347
- }).meta({ id: "RescanResponse" });
16348
- var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16349
- var ListAssetsQuery = external_exports.object({
16350
- /** Filter by one or more AssetType values; absent means all types. */
16351
- type: external_exports.array(AssetType).optional(),
16352
- /** Free-text search term. */
16353
- q: external_exports.string().optional()
16354
- });
16355
- var GetProjectTreeQuery = external_exports.object({
16356
- /** Subtree root path; defaults to repository root when absent. */
16357
- path: external_exports.string().optional(),
16358
- /** Free-text filter applied to file paths. */
16359
- q: external_exports.string().optional(),
16360
- /**
16361
- * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16362
- * file across the whole repo (folders omitted, most-recent first), ignoring
16363
- * `path`/`q` — powers the project-wide "recently blocked" strip.
16364
- */
16365
- filter: external_exports.enum(["blocked"]).optional()
16131
+ var McpServerScanEntry = external_exports.object({
16132
+ // The server's config key ("github", "filesystem", …) identity, with the
16133
+ // qualified scope (see mcpServerIdentityKey).
16134
+ name: external_exports.string().min(1),
16135
+ scope: ConfigScope,
16136
+ pluginName: external_exports.string().optional(),
16137
+ // The owning plugin's marketplace — part of PLUGIN-scope identity: two
16138
+ // marketplaces can each ship a plugin named `guard`, and without this their
16139
+ // same-named servers would collapse to one row (the second silently dropped,
16140
+ // inheriting the first's trust).
16141
+ marketplace: external_exports.string().optional(),
16142
+ // The repo identity (remote url, or the cwd for un-remoted repos) — part of
16143
+ // PROJECT/LOCAL-scope identity: a server named `github` in repo A and one in
16144
+ // repo B are different servers with different commands, and MUST NOT share a
16145
+ // row a shared row would let a cloned repo's .mcp.json inherit the trust
16146
+ // the user granted elsewhere.
16147
+ project: external_exports.string().optional(),
16148
+ // 'stdio' when the entry carries a command; otherwise the config's `type`
16149
+ // ('http' / 'sse' / …). Open string — the transport set is harness-defined.
16150
+ transport: external_exports.string().min(1),
16151
+ // Volatile on purpose (unlike hook `command`): a changed command/url is drift
16152
+ // on a stable row — visible across config_scan snapshots and preserving the
16153
+ // user's trust decision — never a quiet new row. One of the two is present.
16154
+ // Secret-masked at collection time (the scanner runs the bundled detection
16155
+ // packs over both — tokens routinely ride command args and URLs).
16156
+ command: external_exports.string().optional(),
16157
+ url: external_exports.string().optional(),
16158
+ // Env var NAMES only, never values (the no-secrets rule).
16159
+ envKeys: external_exports.array(external_exports.string()).optional(),
16160
+ // The config file the entry came from.
16161
+ location: external_exports.string().optional()
16366
16162
  });
16367
- var GetProjectFileQuery = external_exports.object({
16368
- /** Repository-relative file path; absent or empty 400. */
16369
- path: external_exports.string()
16163
+ var ConfigFileScanEntry = external_exports.object({
16164
+ // Basename (settings.json, CLAUDE.md) or dir name (commands/, agents/).
16165
+ name: external_exports.string().min(1),
16166
+ // The absolute path — identity (with scope) and the promoted `location`.
16167
+ path: external_exports.string().min(1),
16168
+ scope: ConfigScope,
16169
+ // Human label: "User settings", "Project memory", "Slash commands", …
16170
+ kind: external_exports.string().min(1),
16171
+ // Derived SHAPE summary — top-level key names, entry counts, line counts.
16172
+ // Never file content or values (memory files can carry sensitive detail).
16173
+ detail: external_exports.string().optional(),
16174
+ // Dir configs (commands/, agents/) and .mcp.json: how many entries.
16175
+ entryCount: external_exports.number().optional(),
16176
+ // File mtime (ISO) — the freshness signal.
16177
+ updatedAt: external_exports.iso.datetime().optional()
16370
16178
  });
16371
- var GetHarnessEventsQuery = external_exports.object({
16372
- /** Maximum number of events to return. Range: 1–50; default: 7. */
16373
- limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16179
+ var ConfigScanResult = external_exports.object({
16180
+ scannedAt: external_exports.iso.datetime(),
16181
+ skills: external_exports.array(SkillScanEntry),
16182
+ hooks: external_exports.array(HookScanEntry),
16183
+ mcpServers: external_exports.array(McpServerScanEntry),
16184
+ configFiles: external_exports.array(ConfigFileScanEntry),
16185
+ errors: external_exports.array(external_exports.object({ source: external_exports.string(), reason: external_exports.string() }))
16374
16186
  });
16375
-
16376
- // ../../packages/schema/src/zod/exception.ts
16377
- var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
16378
- var ExceptionConditions = external_exports.object({
16379
- repo: external_exports.string().optional(),
16380
- sourceTool: external_exports.string().optional(),
16381
- provider: external_exports.string().optional()
16382
- }).strict();
16383
- var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16384
- var DetectionException = external_exports.object({
16385
- id: external_exports.guid(),
16386
- ruleId: external_exports.string(),
16387
- // Denormalized from the rule, for reporting — never matched on.
16388
- category: DetectionCategory,
16389
- // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
16390
- // fingerprint, never the raw value, and never reversible. Matching recomputes
16391
- // the fingerprint from a fresh capture; the value itself is never stored.
16392
- // Shape-constrained so a malformed — or accidentally raw — value is rejected
16393
- // at the boundary rather than persisted.
16394
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
16395
- // Version of the fingerprint key the grant was written under; a rotated key
16396
- // invalidates old grants rather than silently mismatching them.
16397
- keyVersion: external_exports.number().int().positive(),
16398
- // maskMatch() preview of the approved value — never the raw value.
16399
- maskedValue: external_exports.string(),
16400
- capability: ExceptionCapability.default("suppress"),
16401
- scope: ExceptionScope,
16402
- expiresAt: external_exports.iso.datetime().nullable(),
16403
- maxUses: external_exports.number().int().positive().nullable(),
16404
- useCount: external_exports.number().int().nonnegative(),
16405
- lastUsedAt: external_exports.iso.datetime().nullable(),
16406
- // Mandatory: every grant carries the human reason it exists.
16407
- justification: external_exports.string().min(1),
16408
- conditions: ExceptionConditions.nullable(),
16409
- createdBy: external_exports.string(),
16410
- createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
16411
- createdAt: external_exports.iso.datetime(),
16412
- updatedAt: external_exports.iso.datetime(),
16413
- // Revocation is terminal and retained — consumed/expired/revoked rows are
16414
- // audit evidence; nothing in the exception lifecycle hard-deletes.
16415
- revokedAt: external_exports.iso.datetime().nullable(),
16416
- revokedBy: external_exports.string().nullable(),
16417
- revokeReason: external_exports.string().nullable()
16187
+ var ConfigPostureFindingInput = external_exports.object({
16188
+ ruleId: external_exports.string().min(1),
16189
+ version: external_exports.string().min(1),
16190
+ span: Span,
16191
+ // For posture rules this is the offending COMMAND (config the user already
16192
+ // holds locally, not captured secret content) — it is also the correlation
16193
+ // key the read surface matches back to a hook row.
16194
+ maskedMatch: external_exports.string(),
16195
+ actionTaken: ActionTaken,
16196
+ confidence: external_exports.number().min(0).max(1)
16418
16197
  });
16419
- var ExceptionBundleEntry = DetectionException.pick({
16420
- id: true,
16421
- ruleId: true,
16422
- valueFingerprint: true,
16423
- keyVersion: true,
16424
- capability: true,
16425
- expiresAt: true,
16426
- maxUses: true,
16427
- useCount: true,
16428
- conditions: true
16198
+ var ConfigScanRecord = external_exports.object({
16199
+ items: external_exports.array(InventoryInput),
16200
+ scanEvent: AuditEventInput,
16201
+ definitions: external_exports.array(InspectionDefinitionInput).optional(),
16202
+ findings: external_exports.array(ConfigPostureFindingInput).optional()
16429
16203
  });
16430
- var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16204
+
16205
+ // ../../packages/schema/src/zod/registry.ts
16206
+ var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16207
+ var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16208
+ var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16209
+ var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16431
16210
 
16432
16211
  // ../../packages/schema/src/zod/rule.ts
16433
- var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16212
+ var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
16434
16213
  var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16435
- var KeywordMatcher = external_exports.object({
16214
+ var KeywordMatcher = external_exports.strictObject({
16436
16215
  type: external_exports.literal("keyword"),
16437
16216
  // An empty keyword matches at every position, yielding one zero-length span
16438
16217
  // per character. Rejected here because a keyword that matches everything is
@@ -16448,16 +16227,31 @@ function isValidRegex(pattern, flags) {
16448
16227
  return false;
16449
16228
  }
16450
16229
  }
16230
+ function probeFlags(flags) {
16231
+ return flags.replace(/[gy]/g, "");
16232
+ }
16451
16233
  function matchesEmptyString(pattern, flags) {
16452
16234
  try {
16453
- const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
16235
+ const re = new RegExp(pattern, probeFlags(flags));
16454
16236
  return re.exec("")?.[0].length === 0;
16455
16237
  } catch {
16456
16238
  return false;
16457
16239
  }
16458
16240
  }
16241
+ function spansWholeMatch(captureGroup) {
16242
+ return captureGroup === void 0 || captureGroup === 0;
16243
+ }
16244
+ function captureGroupCount(pattern, flags) {
16245
+ try {
16246
+ const probe = new RegExp(`${pattern}|`, probeFlags(flags));
16247
+ const result = probe.exec("");
16248
+ return result ? result.length - 1 : void 0;
16249
+ } catch {
16250
+ return void 0;
16251
+ }
16252
+ }
16459
16253
  var MAX_PATTERN_LENGTH = 2e3;
16460
- var RegexMatcher = external_exports.object({
16254
+ var RegexMatcher = external_exports.strictObject({
16461
16255
  type: external_exports.literal("regex"),
16462
16256
  pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16463
16257
  flags: external_exports.string().default("gi"),
@@ -16465,28 +16259,47 @@ var RegexMatcher = external_exports.object({
16465
16259
  }).refine((v) => isValidRegex(v.pattern, v.flags), {
16466
16260
  message: "pattern/flags do not form a valid JavaScript regular expression",
16467
16261
  path: ["pattern"]
16468
- }).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
16262
+ }).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
16469
16263
  message: 'a whole-match regex that can match the empty string (e.g. "\\d*", "a?", "(?:)") can hang the matcher \u2014 scope the quantifier to a captureGroup, or require at least one character',
16470
16264
  path: ["pattern"]
16265
+ }).superRefine((v, ctx) => {
16266
+ if (v.captureGroup === void 0) return;
16267
+ const groups = captureGroupCount(v.pattern, v.flags);
16268
+ if (groups === void 0 || v.captureGroup <= groups) return;
16269
+ ctx.addIssue({
16270
+ code: "custom",
16271
+ path: ["captureGroup"],
16272
+ message: `captureGroup ${String(v.captureGroup)} is out of range \u2014 the pattern declares ${String(groups)} capture group(s), so valid values are 0-${String(groups)}. An out-of-range group never matches, which would make the rule silently never fire.`
16273
+ });
16471
16274
  });
16472
- var ValidatorMatcher = external_exports.object({
16473
- type: external_exports.literal("validator"),
16474
- name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
16475
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16476
- });
16477
- var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
16478
- var AppliesTo = external_exports.object({
16275
+ var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
16276
+ var MATCHER_TYPES = MatcherType.options;
16277
+ var AppliesTo = external_exports.strictObject({
16479
16278
  // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16480
16279
  extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16481
16280
  }).meta({ id: "AppliesTo" });
16482
- var PostValidatorRef = external_exports.union([
16483
- external_exports.string(),
16484
- external_exports.object({
16485
- name: external_exports.string(),
16486
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16487
- })
16488
- ]).meta({ id: "PostValidatorRef" });
16489
- var RequiresNearby = external_exports.object({
16281
+ var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
16282
+ var PostValidatorRef = external_exports.union(
16283
+ [
16284
+ PostValidatorName,
16285
+ external_exports.strictObject({
16286
+ name: PostValidatorName,
16287
+ config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16288
+ })
16289
+ ],
16290
+ {
16291
+ // A union reports one collapsed issue for every way its arms can fail, so
16292
+ // this has to describe the whole shape rather than just the name — it is
16293
+ // what an author sees for a misspelled name AND for a stray key in the
16294
+ // object form. The names come from the enum so the message cannot go
16295
+ // stale. Without it Zod says only "Invalid input", which is precisely the
16296
+ // no-feedback outcome this schema exists to remove.
16297
+ error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
16298
+ " or "
16299
+ )}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
16300
+ }
16301
+ ).meta({ id: "PostValidatorRef" });
16302
+ var RequiresNearby = external_exports.strictObject({
16490
16303
  // Each array, when present, must be non-empty and contain non-empty strings —
16491
16304
  // an empty/blank criterion would either never fire or (for labels) match
16492
16305
  // everything.
@@ -16501,16 +16314,23 @@ var RequiresNearby = external_exports.object({
16501
16314
  (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16502
16315
  { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16503
16316
  ).meta({ id: "RequiresNearby" });
16504
- var RuleFixture = external_exports.object({
16317
+ var RuleFixture = external_exports.strictObject({
16505
16318
  label: external_exports.string(),
16506
16319
  text: external_exports.string().max(5e4),
16507
16320
  shouldMatch: external_exports.boolean(),
16508
16321
  // Simulated file context for the scan, so fixtures can assert `appliesTo`
16509
16322
  // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16510
16323
  filePath: external_exports.string().optional(),
16511
- expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
16324
+ expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
16512
16325
  }).meta({ id: "RuleFixture" });
16513
- var Rule = external_exports.object({
16326
+ var Rule = external_exports.strictObject({
16327
+ // A pinned literal over a STRICT object, and the two together decide how this
16328
+ // format may grow. A rule carrying a key not listed below is refused with
16329
+ // `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
16330
+ // `invalid_value`. So the only additive path is adding an OPTIONAL field here
16331
+ // — that keeps every rule authored before it valid — and a rule author has no
16332
+ // way to introduce a field of their own or to opt into a later version.
16333
+ // Widening the format means changing this literal and every consumer of it.
16514
16334
  specVersion: external_exports.literal(1),
16515
16335
  // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16516
16336
  // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
@@ -16546,294 +16366,6 @@ var PackManifest = external_exports.object({
16546
16366
  sourceUrl: external_exports.url().optional()
16547
16367
  }).meta({ id: "PackManifest" });
16548
16368
 
16549
- // ../../packages/schema/src/zod/policy.ts
16550
- var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
16551
- var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
16552
- var Policy = external_exports.object({
16553
- id: external_exports.guid(),
16554
- scope: PolicyScope,
16555
- target: PolicyTarget,
16556
- action: ActionTaken,
16557
- enabled: external_exports.boolean().default(true),
16558
- customKeywords: external_exports.array(external_exports.string()).optional(),
16559
- // Display name — optional so older policy rows without name still parse.
16560
- // Added for the findings API (policy.name column migration).
16561
- name: external_exports.string().optional()
16562
- }).meta({ id: "Policy" });
16563
- var PolicyBundle = external_exports.object({
16564
- version: external_exports.string(),
16565
- policies: external_exports.array(Policy),
16566
- // Rules from the installed marketplace packs (snapshotted by the
16567
- // control plane). The plugin registers these in addition to its bundled
16568
- // packs. Optional so older backends — and older on-disk caches — that omit
16569
- // the field still parse; consumers read `bundle.rules ?? []`.
16570
- rules: external_exports.array(Rule).optional(),
16571
- // When true, `rules` IS the complete effective ruleset and the runtime must
16572
- // NOT merge its compiled-in bundled packs — the standalone gateway sets this
16573
- // after reading the user's installed snapshot (installed_packs, enabled
16574
- // packs only), which is how detection updates stay manual: new bundled
16575
- // rules run only after the user applies the pack update. Absent/false keeps
16576
- // the historical composition (bundled packs + rules) — older caches.
16577
- rulesComplete: external_exports.boolean().optional(),
16578
- // Active detection exceptions, evaluation subset only (see
16579
- // ExceptionBundleEntry). Optional so older bundle producers — and older
16580
- // on-disk caches — that omit the field still parse; consumers read
16581
- // `bundle.exceptions ?? []`.
16582
- exceptions: external_exports.array(ExceptionBundleEntry).optional(),
16583
- // Installed pack version, keyed by ruleId, for rules in `rules` that came
16584
- // from a versioned installed pack. Optional so older backends — and older
16585
- // on-disk caches — that omit the field still parse; consumers fall back to
16586
- // the rule's own spec version. NOT the bundle version above — see
16587
- // installedRuleset's ruleVersions for the source of truth.
16588
- ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
16589
- customKeywords: external_exports.array(external_exports.string()),
16590
- fetchedAt: external_exports.iso.datetime()
16591
- }).meta({ id: "PolicyBundle" });
16592
- var OBSERVE_ONLY_CATEGORIES = ["config"];
16593
- var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
16594
- var CATEGORY_PEAK_SEVERITY = {
16595
- secret: "critical",
16596
- financial: "critical",
16597
- // core-financial/credit-card
16598
- code_flaw: "critical",
16599
- pii: "high",
16600
- phi: "high",
16601
- custom: "high",
16602
- // user-defined; conservative
16603
- code_context: "low",
16604
- config: "low"
16605
- // observe-only; floors to monitor regardless
16606
- };
16607
- function severityFloorPolicy(category) {
16608
- if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
16609
- const peak = CATEGORY_PEAK_SEVERITY[category];
16610
- return peak === "critical" || peak === "high" ? "warn" : "monitor";
16611
- }
16612
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
16613
- var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
16614
- var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
16615
- var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
16616
- var BUILTIN_POLICY_SPECS = {
16617
- monitor: {
16618
- name: "Monitor",
16619
- action: "log",
16620
- description: "Log every match for audit. The request is allowed through untouched."
16621
- },
16622
- warn: {
16623
- name: "Warn",
16624
- action: "warn",
16625
- description: "Allow the request, but warn the user inline before it is sent."
16626
- },
16627
- redact: {
16628
- name: "Redact",
16629
- action: "redact",
16630
- description: "Automatically strip the matched value from the request, then continue."
16631
- },
16632
- block: {
16633
- name: "Block",
16634
- action: "block",
16635
- description: "Refuse the request entirely whenever any rule in this detection matches."
16636
- }
16637
- };
16638
- function builtinPolicyToAction(id) {
16639
- return BUILTIN_POLICY_SPECS[id].action;
16640
- }
16641
- var DEFAULT_ACTIONS = Object.fromEntries(
16642
- DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
16643
- );
16644
- var BUILTIN_POLICIES = Object.fromEntries(
16645
- KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
16646
- );
16647
- var DEFAULT_PACK_POLICY_ID = "monitor";
16648
- function policyIdToAction(policyId) {
16649
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
16650
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
16651
- return BUILTIN_POLICIES[id].action;
16652
- }
16653
- var UsedByItem = external_exports.object({
16654
- id: external_exports.string(),
16655
- name: external_exports.string(),
16656
- ruleCount: external_exports.number().int().nonnegative(),
16657
- enabled: external_exports.boolean()
16658
- }).meta({ id: "UsedByItem" });
16659
- var PolicyListItem = external_exports.object({
16660
- id: external_exports.string(),
16661
- kind: PolicyKind,
16662
- name: external_exports.string(),
16663
- enabled: external_exports.boolean(),
16664
- usedByCount: external_exports.number().int().nonnegative()
16665
- }).meta({ id: "PolicyListItem" });
16666
- var PolicyDetail = external_exports.object({
16667
- specVersion: external_exports.literal(1),
16668
- id: external_exports.string(),
16669
- kind: PolicyKind,
16670
- name: external_exports.string(),
16671
- enabled: external_exports.boolean(),
16672
- description: external_exports.string(),
16673
- usedBy: external_exports.array(UsedByItem)
16674
- }).meta({ id: "PolicyDetail" });
16675
- var PolicyStatsResponse = external_exports.object({
16676
- policies: external_exports.number().int().nonnegative(),
16677
- builtin: external_exports.number().int().nonnegative(),
16678
- custom: external_exports.number().int().nonnegative(),
16679
- detectionsGoverned: external_exports.number().int().nonnegative()
16680
- }).meta({ id: "PolicyStatsResponse" });
16681
-
16682
- // ../../packages/schema/src/zod/api.ts
16683
- var LIST_QUERY_MAX_LIMIT = 200;
16684
- var ListEventsQuery = external_exports.object({
16685
- cursor: external_exports.string().optional(),
16686
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16687
- sourceTool: external_exports.string().optional(),
16688
- kind: external_exports.string().optional(),
16689
- from: external_exports.iso.datetime().optional(),
16690
- to: external_exports.iso.datetime().optional()
16691
- });
16692
- var ListEventsResponse = external_exports.object({
16693
- items: external_exports.array(Event),
16694
- nextCursor: external_exports.string().nullable()
16695
- }).meta({ id: "ListEventsResponse" });
16696
- var IngestResponse = external_exports.object({
16697
- accepted: external_exports.number().int().nonnegative(),
16698
- duplicates: external_exports.number().int().nonnegative()
16699
- }).meta({ id: "IngestResponse" });
16700
- var ListFindingsQuery = external_exports.object({
16701
- cursor: external_exports.string().optional(),
16702
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16703
- severity: external_exports.string().optional(),
16704
- category: external_exports.string().optional(),
16705
- eventId: external_exports.guid().optional()
16706
- });
16707
- var ListFindingsResponse = external_exports.object({
16708
- items: external_exports.array(Finding),
16709
- nextCursor: external_exports.string().nullable()
16710
- }).meta({ id: "ListFindingsResponse" });
16711
- var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
16712
- var CreatePolicyRequest = Policy.omit({ id: true }).meta({
16713
- id: "CreatePolicyRequest"
16714
- });
16715
- var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
16716
- var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
16717
- var ErrorResponse = external_exports.object({
16718
- error: external_exports.object({
16719
- code: external_exports.string(),
16720
- message: external_exports.string(),
16721
- details: external_exports.unknown().optional()
16722
- })
16723
- }).meta({ id: "ErrorResponse" });
16724
-
16725
- // ../../packages/schema/src/zod/config-inventory.ts
16726
- var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16727
- var SkillScanEntry = external_exports.object({
16728
- name: external_exports.string().min(1),
16729
- // The identity source: a marketplace repo for plugin skills (e.g.
16730
- // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16731
- // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16732
- // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16733
- source: external_exports.string().min(1),
16734
- scope: ConfigScope,
16735
- pluginName: external_exports.string().optional(),
16736
- // Volatile — rides the attribute bag, never the identity hash.
16737
- version: external_exports.string().optional(),
16738
- description: external_exports.string().optional(),
16739
- // Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
16740
- updatedAt: external_exports.iso.datetime().optional(),
16741
- // Filesystem path — the promoted inventory `location` column.
16742
- location: external_exports.string().optional()
16743
- });
16744
- var HookScanEntry = external_exports.object({
16745
- // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16746
- // set is harness-defined and grows without a schema change.
16747
- event: external_exports.string().min(1),
16748
- // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16749
- matcher: external_exports.string().optional(),
16750
- command: external_exports.string().min(1),
16751
- timeout: external_exports.number().optional(),
16752
- scope: ConfigScope,
16753
- pluginName: external_exports.string().optional(),
16754
- // The settings file / hooks.json the entry came from.
16755
- location: external_exports.string().optional()
16756
- });
16757
- var McpServerScanEntry = external_exports.object({
16758
- // The server's config key ("github", "filesystem", …) — identity, with the
16759
- // qualified scope (see mcpServerIdentityKey).
16760
- name: external_exports.string().min(1),
16761
- scope: ConfigScope,
16762
- pluginName: external_exports.string().optional(),
16763
- // The owning plugin's marketplace — part of PLUGIN-scope identity: two
16764
- // marketplaces can each ship a plugin named `guard`, and without this their
16765
- // same-named servers would collapse to one row (the second silently dropped,
16766
- // inheriting the first's trust).
16767
- marketplace: external_exports.string().optional(),
16768
- // The repo identity (remote url, or the cwd for un-remoted repos) — part of
16769
- // PROJECT/LOCAL-scope identity: a server named `github` in repo A and one in
16770
- // repo B are different servers with different commands, and MUST NOT share a
16771
- // row — a shared row would let a cloned repo's .mcp.json inherit the trust
16772
- // the user granted elsewhere.
16773
- project: external_exports.string().optional(),
16774
- // 'stdio' when the entry carries a command; otherwise the config's `type`
16775
- // ('http' / 'sse' / …). Open string — the transport set is harness-defined.
16776
- transport: external_exports.string().min(1),
16777
- // Volatile on purpose (unlike hook `command`): a changed command/url is drift
16778
- // on a stable row — visible across config_scan snapshots and preserving the
16779
- // user's trust decision — never a quiet new row. One of the two is present.
16780
- // Secret-masked at collection time (the scanner runs the bundled detection
16781
- // packs over both — tokens routinely ride command args and URLs).
16782
- command: external_exports.string().optional(),
16783
- url: external_exports.string().optional(),
16784
- // Env var NAMES only, never values (the no-secrets rule).
16785
- envKeys: external_exports.array(external_exports.string()).optional(),
16786
- // The config file the entry came from.
16787
- location: external_exports.string().optional()
16788
- });
16789
- var ConfigFileScanEntry = external_exports.object({
16790
- // Basename (settings.json, CLAUDE.md) or dir name (commands/, agents/).
16791
- name: external_exports.string().min(1),
16792
- // The absolute path — identity (with scope) and the promoted `location`.
16793
- path: external_exports.string().min(1),
16794
- scope: ConfigScope,
16795
- // Human label: "User settings", "Project memory", "Slash commands", …
16796
- kind: external_exports.string().min(1),
16797
- // Derived SHAPE summary — top-level key names, entry counts, line counts.
16798
- // Never file content or values (memory files can carry sensitive detail).
16799
- detail: external_exports.string().optional(),
16800
- // Dir configs (commands/, agents/) and .mcp.json: how many entries.
16801
- entryCount: external_exports.number().optional(),
16802
- // File mtime (ISO) — the freshness signal.
16803
- updatedAt: external_exports.iso.datetime().optional()
16804
- });
16805
- var ConfigScanResult = external_exports.object({
16806
- scannedAt: external_exports.iso.datetime(),
16807
- skills: external_exports.array(SkillScanEntry),
16808
- hooks: external_exports.array(HookScanEntry),
16809
- mcpServers: external_exports.array(McpServerScanEntry),
16810
- configFiles: external_exports.array(ConfigFileScanEntry),
16811
- errors: external_exports.array(external_exports.object({ source: external_exports.string(), reason: external_exports.string() }))
16812
- });
16813
- var ConfigPostureFindingInput = external_exports.object({
16814
- ruleId: external_exports.string().min(1),
16815
- version: external_exports.string().min(1),
16816
- span: Span,
16817
- // For posture rules this is the offending COMMAND (config the user already
16818
- // holds locally, not captured secret content) — it is also the correlation
16819
- // key the read surface matches back to a hook row.
16820
- maskedMatch: external_exports.string(),
16821
- actionTaken: ActionTaken,
16822
- confidence: external_exports.number().min(0).max(1)
16823
- });
16824
- var ConfigScanRecord = external_exports.object({
16825
- items: external_exports.array(InventoryInput),
16826
- scanEvent: AuditEventInput,
16827
- definitions: external_exports.array(InspectionDefinitionInput).optional(),
16828
- findings: external_exports.array(ConfigPostureFindingInput).optional()
16829
- });
16830
-
16831
- // ../../packages/schema/src/zod/registry.ts
16832
- var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16833
- var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16834
- var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16835
- var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16836
-
16837
16369
  // ../../packages/schema/src/zod/detection.ts
16838
16370
  var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
16839
16371
  var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
@@ -16878,159 +16410,384 @@ var ListDetectionsQuery = external_exports.object({
16878
16410
  filter: DetectionFilterEnum.optional().default("all"),
16879
16411
  q: external_exports.string().optional()
16880
16412
  });
16881
- var DetectionStats = external_exports.object({
16882
- detections: external_exports.number().int().nonnegative(),
16883
- rules: external_exports.number().int().nonnegative(),
16884
- active: external_exports.number().int().nonnegative(),
16885
- findingsLast30d: external_exports.number().int().nonnegative()
16886
- }).meta({ id: "DetectionStats" });
16887
- var DetectionRule = external_exports.object({
16888
- id: external_exports.string(),
16889
- name: external_exports.string(),
16890
- category: DetectionCategory,
16891
- severity: Severity,
16892
- matcher: Matcher
16893
- }).meta({ id: "DetectionRule" });
16894
- var DetectionUpdate = external_exports.object({
16895
- available: external_exports.boolean(),
16896
- latestVersion: SemVer,
16897
- // Rule count of the latest snapshot. Lets the update UI show a meaningful
16898
- // delta ("2 rules → 14 rules") when the version did NOT change but the rule
16899
- // content did — the OSS store compares content, not just version. Optional:
16900
- // registry-backed updates omit it.
16901
- latestRuleCount: external_exports.number().int().nonnegative().optional()
16902
- }).nullable().meta({ id: "DetectionUpdate" });
16903
- var DetectionDetail = external_exports.object({
16904
- id: external_exports.string(),
16905
- name: external_exports.string(),
16906
- version: SemVer,
16907
- enabled: external_exports.boolean(),
16908
- origin: OriginEnum,
16909
- publisher: Namespace.optional(),
16910
- publisherKind: PublisherKind.optional(),
16911
- ruleCount: external_exports.number().int().nonnegative(),
16912
- namespace: Namespace,
16913
- packId: PackId,
16914
- description: external_exports.string().optional(),
16915
- editedAt: external_exports.iso.datetime(),
16916
- findingsLast30d: external_exports.number().int().nonnegative(),
16917
- latestVersion: SemVer.nullable().optional(),
16918
- update: DetectionUpdate,
16919
- rules: external_exports.array(DetectionRule),
16920
- modified: external_exports.boolean(),
16921
- // Per-pack enforcement-policy assignment. Holds a BuiltinPolicyId ARCHETYPE
16922
- // (monitor|warn|redact|block) — NOT a policies-table Policy.id guid; a
16923
- // detection is a PACK, and its policy is the archetype applied to all its
16924
- // rules. Absent == unassigned, which resolves to Monitor everywhere
16925
- // (DEFAULT_PACK_POLICY_ID). Every enforcement surface expands it into
16926
- // per-rule policies (see policyIdToAction). Typed z.string() (not
16927
- // the enum) to keep the OpenAPI response tolerant of a future custom id.
16928
- policyId: external_exports.string().optional()
16929
- }).meta({ id: "DetectionDetail" });
16930
- var LibraryItem = external_exports.object({
16931
- id: external_exports.string(),
16932
- name: external_exports.string(),
16933
- publisher: Namespace,
16934
- publisherKind: PublisherKind.optional(),
16935
- // LOSSY single-category view of a pack. A pack MAY span several categories;
16936
- // this carries only the canonical-first one for display. Do NOT filter/facet
16937
- // on it — the library filter matches a pack's full category set (see
16938
- // ListLibraryResponse.categories).
16939
- category: DetectionCategory.optional(),
16940
- version: SemVer,
16941
- ruleCount: external_exports.number().int().nonnegative(),
16942
- description: external_exports.string().optional(),
16943
- updatedAt: external_exports.iso.datetime(),
16944
- state: LibraryStateEnum,
16945
- importedAs: external_exports.string().nullable()
16946
- }).meta({ id: "LibraryItem" });
16947
- var ListLibraryResponse = external_exports.object({
16948
- categories: external_exports.array(DetectionCategory),
16949
- items: external_exports.array(LibraryItem)
16950
- }).meta({ id: "ListLibraryResponse" });
16951
- var ImportDetectionRequest = external_exports.object({
16952
- libraryId: external_exports.string().refine((v) => /^[^/]+\/[^/]+$/.test(v), {
16953
- message: "libraryId must be in namespace/packId format"
16954
- })
16955
- }).meta({ id: "ImportDetectionRequest" });
16956
-
16957
- // ../../packages/schema/src/zod/detection-build.ts
16958
- function summaryToDetectionListItem(s) {
16959
- return {
16960
- id: `${s.namespace}/${s.packId}`,
16961
- name: s.name,
16962
- version: s.version,
16963
- enabled: s.enabled,
16964
- origin: "library",
16965
- // v1: every installed pack is library origin
16966
- namespace: s.namespace,
16967
- packId: s.packId,
16968
- ruleCount: s.ruleCount,
16969
- ...s.policyId != null ? { policyId: s.policyId } : {},
16970
- ...s.latestVersion != null ? { latestVersion: s.latestVersion } : {}
16971
- };
16972
- }
16973
- function rowToDetectionDetail(row, findingsLast30d, update) {
16974
- const rules = row.rules.flatMap((r) => {
16975
- const parsed = Matcher.safeParse(r.matcher);
16976
- if (!parsed.success) return [];
16977
- return [
16978
- {
16979
- id: r.id,
16980
- name: r.name,
16981
- category: r.category,
16982
- severity: r.severity,
16983
- matcher: parsed.data
16984
- }
16985
- ];
16986
- });
16987
- return {
16988
- id: `${row.namespace}/${row.packId}`,
16989
- name: row.name,
16990
- version: row.version,
16991
- enabled: row.enabled,
16992
- origin: "library",
16993
- namespace: row.namespace,
16994
- packId: row.packId,
16995
- ruleCount: row.rules.length,
16996
- editedAt: row.updatedAt.toISOString(),
16997
- findingsLast30d,
16998
- latestVersion: update ? update.latestVersion : null,
16999
- update,
17000
- rules,
17001
- modified: false,
17002
- ...row.policyId != null ? { policyId: row.policyId } : {}
17003
- };
17004
- }
17005
- function splitDetectionId(id) {
17006
- const idx = id.indexOf("/");
17007
- if (idx < 1 || idx === id.length - 1) return null;
17008
- return { namespace: id.slice(0, idx), packId: id.slice(idx + 1) };
17009
- }
17010
- function buildDetectionsList(summaries, query) {
17011
- const withUpdate = summaries.filter((s) => s.latestVersion != null);
17012
- const counts = {
17013
- all: summaries.length,
17014
- library: summaries.length,
17015
- // all origin=library in v1
17016
- custom: 0,
17017
- customized: 0,
17018
- updates: withUpdate.length
17019
- };
17020
- const filter = query.filter;
17021
- let filtered = filter === "custom" || filter === "customized" ? [] : filter === "updates" ? [...withUpdate] : [...summaries];
17022
- if (query.q) {
17023
- const q = query.q.toLowerCase();
17024
- filtered = filtered.filter(
17025
- (s) => s.name.toLowerCase().includes(q) || s.packId.toLowerCase().includes(q) || s.namespace.toLowerCase().includes(q)
17026
- );
17027
- }
17028
- filtered.sort((a, b) => {
17029
- if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
17030
- return a.name.localeCompare(b.name);
17031
- });
17032
- return { counts, items: filtered.map(summaryToDetectionListItem) };
17033
- }
16413
+ var DetectionStats = external_exports.object({
16414
+ detections: external_exports.number().int().nonnegative(),
16415
+ rules: external_exports.number().int().nonnegative(),
16416
+ active: external_exports.number().int().nonnegative(),
16417
+ findingsLast30d: external_exports.number().int().nonnegative()
16418
+ }).meta({ id: "DetectionStats" });
16419
+ var DetectionRule = external_exports.object({
16420
+ id: external_exports.string(),
16421
+ name: external_exports.string(),
16422
+ category: DetectionCategory,
16423
+ severity: Severity,
16424
+ matcher: Matcher
16425
+ }).meta({ id: "DetectionRule" });
16426
+ var DetectionUpdate = external_exports.object({
16427
+ available: external_exports.boolean(),
16428
+ latestVersion: SemVer,
16429
+ // Rule count of the latest snapshot. Lets the update UI show a meaningful
16430
+ // delta ("2 rules → 14 rules") when the version did NOT change but the rule
16431
+ // content did — the OSS store compares content, not just version. Optional:
16432
+ // registry-backed updates omit it.
16433
+ latestRuleCount: external_exports.number().int().nonnegative().optional()
16434
+ }).nullable().meta({ id: "DetectionUpdate" });
16435
+ var DetectionDetail = external_exports.object({
16436
+ id: external_exports.string(),
16437
+ name: external_exports.string(),
16438
+ version: SemVer,
16439
+ enabled: external_exports.boolean(),
16440
+ origin: OriginEnum,
16441
+ publisher: Namespace.optional(),
16442
+ publisherKind: PublisherKind.optional(),
16443
+ ruleCount: external_exports.number().int().nonnegative(),
16444
+ namespace: Namespace,
16445
+ packId: PackId,
16446
+ description: external_exports.string().optional(),
16447
+ editedAt: external_exports.iso.datetime(),
16448
+ findingsLast30d: external_exports.number().int().nonnegative(),
16449
+ latestVersion: SemVer.nullable().optional(),
16450
+ update: DetectionUpdate,
16451
+ rules: external_exports.array(DetectionRule),
16452
+ modified: external_exports.boolean(),
16453
+ // Per-pack enforcement-policy assignment. Holds a BuiltinPolicyId ARCHETYPE
16454
+ // (monitor|warn|redact|block) — NOT a policies-table Policy.id guid; a
16455
+ // detection is a PACK, and its policy is the archetype applied to all its
16456
+ // rules. Absent == unassigned, which resolves to Monitor everywhere
16457
+ // (DEFAULT_PACK_POLICY_ID). Every enforcement surface expands it into
16458
+ // per-rule policies (see policyIdToAction). Typed z.string() (not
16459
+ // the enum) to keep the OpenAPI response tolerant of a future custom id.
16460
+ policyId: external_exports.string().optional()
16461
+ }).meta({ id: "DetectionDetail" });
16462
+ var LibraryItem = external_exports.object({
16463
+ id: external_exports.string(),
16464
+ name: external_exports.string(),
16465
+ publisher: Namespace,
16466
+ publisherKind: PublisherKind.optional(),
16467
+ // LOSSY single-category view of a pack. A pack MAY span several categories;
16468
+ // this carries only the canonical-first one for display. Do NOT filter/facet
16469
+ // on it — the library filter matches a pack's full category set (see
16470
+ // ListLibraryResponse.categories).
16471
+ category: DetectionCategory.optional(),
16472
+ version: SemVer,
16473
+ ruleCount: external_exports.number().int().nonnegative(),
16474
+ description: external_exports.string().optional(),
16475
+ updatedAt: external_exports.iso.datetime(),
16476
+ state: LibraryStateEnum,
16477
+ importedAs: external_exports.string().nullable()
16478
+ }).meta({ id: "LibraryItem" });
16479
+ var ListLibraryResponse = external_exports.object({
16480
+ categories: external_exports.array(DetectionCategory),
16481
+ items: external_exports.array(LibraryItem)
16482
+ }).meta({ id: "ListLibraryResponse" });
16483
+ var ImportDetectionRequest = external_exports.object({
16484
+ libraryId: external_exports.string().refine((v) => /^[^/]+\/[^/]+$/.test(v), {
16485
+ message: "libraryId must be in namespace/packId format"
16486
+ })
16487
+ }).meta({ id: "ImportDetectionRequest" });
16488
+
16489
+ // ../../packages/schema/src/zod/detection-build.ts
16490
+ function summaryToDetectionListItem(s) {
16491
+ return {
16492
+ id: `${s.namespace}/${s.packId}`,
16493
+ name: s.name,
16494
+ version: s.version,
16495
+ enabled: s.enabled,
16496
+ origin: "library",
16497
+ // v1: every installed pack is library origin
16498
+ namespace: s.namespace,
16499
+ packId: s.packId,
16500
+ ruleCount: s.ruleCount,
16501
+ ...s.policyId != null ? { policyId: s.policyId } : {},
16502
+ ...s.latestVersion != null ? { latestVersion: s.latestVersion } : {}
16503
+ };
16504
+ }
16505
+ function rowToDetectionDetail(row, findingsLast30d, update) {
16506
+ const rules = row.rules.flatMap((r) => {
16507
+ const parsed = Matcher.safeParse(r.matcher);
16508
+ if (!parsed.success) return [];
16509
+ return [
16510
+ {
16511
+ id: r.id,
16512
+ name: r.name,
16513
+ category: r.category,
16514
+ severity: r.severity,
16515
+ matcher: parsed.data
16516
+ }
16517
+ ];
16518
+ });
16519
+ return {
16520
+ id: `${row.namespace}/${row.packId}`,
16521
+ name: row.name,
16522
+ version: row.version,
16523
+ enabled: row.enabled,
16524
+ origin: "library",
16525
+ namespace: row.namespace,
16526
+ packId: row.packId,
16527
+ ruleCount: row.rules.length,
16528
+ editedAt: row.updatedAt.toISOString(),
16529
+ findingsLast30d,
16530
+ latestVersion: update ? update.latestVersion : null,
16531
+ update,
16532
+ rules,
16533
+ modified: false,
16534
+ ...row.policyId != null ? { policyId: row.policyId } : {}
16535
+ };
16536
+ }
16537
+ function splitDetectionId(id) {
16538
+ const idx = id.indexOf("/");
16539
+ if (idx < 1 || idx === id.length - 1) return null;
16540
+ return { namespace: id.slice(0, idx), packId: id.slice(idx + 1) };
16541
+ }
16542
+ function buildDetectionsList(summaries, query) {
16543
+ const withUpdate = summaries.filter((s) => s.latestVersion != null);
16544
+ const counts = {
16545
+ all: summaries.length,
16546
+ library: summaries.length,
16547
+ // all origin=library in v1
16548
+ custom: 0,
16549
+ customized: 0,
16550
+ updates: withUpdate.length
16551
+ };
16552
+ const filter = query.filter;
16553
+ let filtered = filter === "custom" || filter === "customized" ? [] : filter === "updates" ? [...withUpdate] : [...summaries];
16554
+ if (query.q) {
16555
+ const q = query.q.toLowerCase();
16556
+ filtered = filtered.filter(
16557
+ (s) => s.name.toLowerCase().includes(q) || s.packId.toLowerCase().includes(q) || s.namespace.toLowerCase().includes(q)
16558
+ );
16559
+ }
16560
+ filtered.sort((a, b) => {
16561
+ if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
16562
+ return a.name.localeCompare(b.name);
16563
+ });
16564
+ return { counts, items: filtered.map(summaryToDetectionListItem) };
16565
+ }
16566
+
16567
+ // ../../packages/schema/src/zod/inventory.ts
16568
+ var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16569
+ var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16570
+ var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16571
+ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16572
+ var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16573
+ var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16574
+ var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16575
+ var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
16576
+ id: "HarnessId"
16577
+ });
16578
+ var AccessCounts = external_exports.object({
16579
+ open: external_exports.number().int().nonnegative(),
16580
+ approved: external_exports.number().int().nonnegative(),
16581
+ blocked: external_exports.number().int().nonnegative(),
16582
+ total: external_exports.number().int().nonnegative()
16583
+ }).meta({ id: "AccessCounts" });
16584
+ var AssetSummary = external_exports.object({
16585
+ id: external_exports.string(),
16586
+ type: AssetType,
16587
+ name: external_exports.string(),
16588
+ sub: external_exports.string(),
16589
+ flags: external_exports.array(Flag),
16590
+ /** MCP servers only — omitted for all other types. */
16591
+ trust: TrustLevel.optional()
16592
+ }).meta({ id: "AssetSummary" });
16593
+ var ProjectSummary = external_exports.object({
16594
+ id: external_exports.string(),
16595
+ name: external_exports.string(),
16596
+ repo: external_exports.string(),
16597
+ visibility: Visibility,
16598
+ language: external_exports.string(),
16599
+ policyDefault: AccessLevel,
16600
+ updatedAt: external_exports.iso.datetime(),
16601
+ accessCounts: AccessCounts,
16602
+ findingsCount: external_exports.number().int().nonnegative()
16603
+ }).meta({ id: "ProjectSummary" });
16604
+ var HarnessCategory = external_exports.object({
16605
+ /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16606
+ type: AssetType,
16607
+ assets: external_exports.array(AssetSummary)
16608
+ });
16609
+ var HarnessSummary = external_exports.object({
16610
+ id: HarnessId,
16611
+ label: external_exports.string(),
16612
+ kind: external_exports.string(),
16613
+ version: external_exports.string(),
16614
+ sessions: external_exports.number().int().nonnegative(),
16615
+ assetCount: external_exports.number().int().nonnegative(),
16616
+ flagCount: external_exports.number().int().nonnegative(),
16617
+ projects: external_exports.array(ProjectSummary),
16618
+ categories: external_exports.array(HarnessCategory)
16619
+ }).meta({ id: "HarnessSummary" });
16620
+ var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16621
+ var AssetGroup = external_exports.object({
16622
+ /** Group key — never project (enforced at service layer). */
16623
+ type: AssetType,
16624
+ total: external_exports.number().int().nonnegative(),
16625
+ /**
16626
+ * MCP group only — omitted for all other types.
16627
+ * Partial: only TrustLevel keys with non-zero counts are included.
16628
+ * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16629
+ */
16630
+ trustRollup: external_exports.object({
16631
+ "known-good": external_exports.number().int().nonnegative(),
16632
+ risky: external_exports.number().int().nonnegative(),
16633
+ unapproved: external_exports.number().int().nonnegative()
16634
+ }).partial().strict().optional(),
16635
+ /**
16636
+ * Partial: only Flag keys with non-zero counts are included.
16637
+ * Strict: unknown keys are rejected — only Flag values are valid keys.
16638
+ */
16639
+ flagRollup: external_exports.object({
16640
+ update: external_exports.number().int().nonnegative(),
16641
+ stale: external_exports.number().int().nonnegative(),
16642
+ conflict: external_exports.number().int().nonnegative(),
16643
+ unknown: external_exports.number().int().nonnegative(),
16644
+ change: external_exports.number().int().nonnegative(),
16645
+ untracked: external_exports.number().int().nonnegative(),
16646
+ risk: external_exports.number().int().nonnegative(),
16647
+ findings: external_exports.number().int().nonnegative()
16648
+ }).partial().strict(),
16649
+ items: external_exports.array(AssetSummary)
16650
+ }).meta({ id: "AssetGroup" });
16651
+ var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16652
+ var McpTool = external_exports.object({
16653
+ name: external_exports.string(),
16654
+ signature: external_exports.string(),
16655
+ description: external_exports.string(),
16656
+ write: external_exports.boolean(),
16657
+ /** Non-null string when tool is dangerous / blocked; null otherwise. */
16658
+ risk: external_exports.string().nullable()
16659
+ }).meta({ id: "McpTool" });
16660
+ var AssetFindingRef = external_exports.object({
16661
+ id: external_exports.string(),
16662
+ title: external_exports.string(),
16663
+ note: external_exports.string()
16664
+ });
16665
+ var AssetDetail = AssetSummary.extend({
16666
+ /** string | null — null when no description is available. */
16667
+ description: external_exports.string().nullable(),
16668
+ /** trustLevel | null — null for non-MCP assets. */
16669
+ trust: TrustLevel.nullable(),
16670
+ /** Type-specific raw key/values — FE renders the grid. */
16671
+ meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16672
+ /** always present — object when there is an active finding, null when absent. */
16673
+ finding: AssetFindingRef.nullable(),
16674
+ /** MCP exposed-tools list — omitted for non-mcp. */
16675
+ tools: external_exports.array(McpTool).optional()
16676
+ }).meta({ id: "AssetDetail" });
16677
+ var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16678
+ var InventoryStats = external_exports.object({
16679
+ /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16680
+ attention: external_exports.number().int().nonnegative(),
16681
+ byType: external_exports.object({
16682
+ project: external_exports.number().int().nonnegative(),
16683
+ skill: external_exports.number().int().nonnegative(),
16684
+ mcp: external_exports.number().int().nonnegative(),
16685
+ hook: external_exports.number().int().nonnegative(),
16686
+ config: external_exports.number().int().nonnegative()
16687
+ }),
16688
+ harnesses: external_exports.number().int().nonnegative(),
16689
+ mcpTrust: external_exports.object({
16690
+ "known-good": external_exports.number().int().nonnegative(),
16691
+ risky: external_exports.number().int().nonnegative(),
16692
+ unapproved: external_exports.number().int().nonnegative()
16693
+ })
16694
+ }).meta({ id: "InventoryStats" });
16695
+ var FileSummary = external_exports.object({
16696
+ path: external_exports.string(),
16697
+ name: external_exports.string(),
16698
+ origin: Origin,
16699
+ /** Effective access (override applied). */
16700
+ access: AccessLevel,
16701
+ /** True when a file_access_override differs from the computed default. */
16702
+ isCustom: external_exports.boolean(),
16703
+ findings: external_exports.number().int().nonnegative(),
16704
+ /** When the file was auto-blocked by a detection; null when not blocked. */
16705
+ blockedAt: external_exports.iso.datetime().nullable().optional(),
16706
+ /** Why the file was blocked; null when absent. */
16707
+ note: external_exports.string().nullable().optional()
16708
+ }).meta({ id: "FileSummary" });
16709
+ var FolderSummary = external_exports.object({
16710
+ name: external_exports.string(),
16711
+ path: external_exports.string(),
16712
+ /** Rollup of effective access across all descendants. */
16713
+ accessCounts: AccessCounts
16714
+ }).meta({ id: "FolderSummary" });
16715
+ var ProjectTreeResponse = external_exports.object({
16716
+ project: external_exports.object({
16717
+ id: external_exports.string(),
16718
+ repo: external_exports.string(),
16719
+ visibility: Visibility
16720
+ }),
16721
+ path: external_exports.string(),
16722
+ /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16723
+ folders: external_exports.array(FolderSummary).optional(),
16724
+ files: external_exports.array(FileSummary)
16725
+ }).meta({ id: "ProjectTreeResponse" });
16726
+ var FileDetail = FileSummary.extend({
16727
+ project: external_exports.object({
16728
+ repo: external_exports.string(),
16729
+ visibility: Visibility,
16730
+ language: external_exports.string(),
16731
+ policyDefault: AccessLevel,
16732
+ updatedAt: external_exports.iso.datetime()
16733
+ }),
16734
+ findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16735
+ }).meta({ id: "FileDetail" });
16736
+ var SetFileAccessBody = external_exports.object({
16737
+ path: external_exports.string(),
16738
+ access: AccessLevel
16739
+ }).meta({ id: "SetFileAccessBody" });
16740
+ var SetFileAccessResponse = external_exports.object({
16741
+ file: FileSummary,
16742
+ accessCounts: AccessCounts
16743
+ }).meta({ id: "SetFileAccessResponse" });
16744
+ var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16745
+ var HarnessEventItem = external_exports.object({
16746
+ kind: HarnessEventKind,
16747
+ title: external_exports.string(),
16748
+ detail: external_exports.string(),
16749
+ occurredAt: external_exports.iso.datetime(),
16750
+ findingId: external_exports.string().nullable().optional()
16751
+ }).meta({ id: "HarnessEventItem" });
16752
+ var HarnessEventsResponse = external_exports.object({
16753
+ counts: external_exports.object({
16754
+ block: external_exports.number().int().nonnegative(),
16755
+ redact: external_exports.number().int().nonnegative(),
16756
+ warn: external_exports.number().int().nonnegative()
16757
+ }),
16758
+ items: external_exports.array(HarnessEventItem)
16759
+ }).meta({ id: "HarnessEventsResponse" });
16760
+ var RescanResponse = external_exports.object({
16761
+ jobId: external_exports.string(),
16762
+ startedAt: external_exports.iso.datetime()
16763
+ }).meta({ id: "RescanResponse" });
16764
+ var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16765
+ var ListAssetsQuery = external_exports.object({
16766
+ /** Filter by one or more AssetType values; absent means all types. */
16767
+ type: external_exports.array(AssetType).optional(),
16768
+ /** Free-text search term. */
16769
+ q: external_exports.string().optional()
16770
+ });
16771
+ var GetProjectTreeQuery = external_exports.object({
16772
+ /** Subtree root path; defaults to repository root when absent. */
16773
+ path: external_exports.string().optional(),
16774
+ /** Free-text filter applied to file paths. */
16775
+ q: external_exports.string().optional(),
16776
+ /**
16777
+ * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16778
+ * file across the whole repo (folders omitted, most-recent first), ignoring
16779
+ * `path`/`q` — powers the project-wide "recently blocked" strip.
16780
+ */
16781
+ filter: external_exports.enum(["blocked"]).optional()
16782
+ });
16783
+ var GetProjectFileQuery = external_exports.object({
16784
+ /** Repository-relative file path; absent or empty → 400. */
16785
+ path: external_exports.string()
16786
+ });
16787
+ var GetHarnessEventsQuery = external_exports.object({
16788
+ /** Maximum number of events to return. Range: 1–50; default: 7. */
16789
+ limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16790
+ });
17034
16791
 
17035
16792
  // ../../packages/schema/src/zod/shares.ts
17036
16793
  var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
@@ -17238,6 +16995,127 @@ var EgressWriteSummary = external_exports.object({
17238
16995
  droppedFiles: external_exports.array(external_exports.string()).default([])
17239
16996
  }).meta({ id: "EgressWriteSummary" });
17240
16997
 
16998
+ // ../../packages/schema/src/zod/event.ts
16999
+ var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
17000
+ var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
17001
+ var EventMetadata = external_exports.object({
17002
+ sessionId: external_exports.string().optional(),
17003
+ repo: external_exports.string().optional(),
17004
+ filePath: external_exports.string().optional(),
17005
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
17006
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
17007
+ // arguments or output, which can carry the very value a finding masked
17008
+ // (metadata is stored unredacted). Gives findings on non-file captures a
17009
+ // display location ("via Bash") when no filePath exists.
17010
+ toolName: external_exports.string().optional(),
17011
+ // Set (true) by the worktree scanner when the file is excluded by the
17012
+ // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
17013
+ // generated code can leak real secrets — but the provenance is recorded so
17014
+ // policy/dashboards can treat those findings as informational rather than
17015
+ // blocking. Omitted (not false) for tracked files and non-scan events.
17016
+ gitignored: external_exports.boolean().optional(),
17017
+ // Set (true) ONLY when the event's `content` is the COMPLETE file at
17018
+ // capture time (a worktree scan reading from disk). Hook-captured edits
17019
+ // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
17020
+ // this. The resolver-on-ingest keys its fixed-at-source dropout
17021
+ // diff on this marker: only a whole-file snapshot can prove a previously
17022
+ // open finding is gone; a fragment's absence proves nothing (the secret
17023
+ // may live outside the hunk). Omitted (not false) for fragments and
17024
+ // non-scan events, so pre-marker clients safely default to the
17025
+ // non-authoritative path.
17026
+ wholeFile: external_exports.boolean().optional(),
17027
+ model: external_exports.string().optional(),
17028
+ turnIndex: external_exports.number().int().nonnegative().optional(),
17029
+ // Distributed-tracing correlation. `correlationId` ties a recorded event back
17030
+ // to the request that captured/ingested it (a UUID, generated independently of
17031
+ // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
17032
+ // originating span when telemetry is enabled. Both optional + backward
17033
+ // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
17034
+ correlationId: external_exports.uuid().optional(),
17035
+ traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
17036
+ // Ids of the detection exceptions that downgraded findings in this capture
17037
+ // to 'allow' — the enforcement audit trail's link back to the grant that
17038
+ // authorized the bypass. Absent on captures where no exception applied.
17039
+ exceptionIds: external_exports.array(external_exports.guid()).optional()
17040
+ }).meta({ id: "EventMetadata" });
17041
+ var Event = external_exports.object({
17042
+ id: external_exports.guid(),
17043
+ sourceTool: SourceTool,
17044
+ kind: EventKind,
17045
+ occurredAt: external_exports.iso.datetime(),
17046
+ contentHash: external_exports.string(),
17047
+ content: external_exports.string(),
17048
+ metadata: EventMetadata.optional()
17049
+ }).meta({ id: "Event" });
17050
+ var IngestEvent = Event.meta({ id: "IngestEvent" });
17051
+ var IngestBatch = external_exports.object({
17052
+ events: external_exports.array(IngestEvent).min(1).max(100),
17053
+ // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
17054
+ // additionally rejects any event whose contentHash the store has already
17055
+ // recorded — for re-runnable bulk ingest (worktree scan, transcript
17056
+ // backfill), where a re-run mints fresh event ids for identical content and
17057
+ // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
17058
+ // two genuinely separate prompts can be byte-identical and both belong on
17059
+ // the timeline.
17060
+ dedupe: external_exports.literal("content-hash").optional()
17061
+ }).meta({ id: "IngestBatch" });
17062
+
17063
+ // ../../packages/schema/src/zod/exception.ts
17064
+ var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
17065
+ var ExceptionConditions = external_exports.object({
17066
+ repo: external_exports.string().optional(),
17067
+ sourceTool: external_exports.string().optional(),
17068
+ provider: external_exports.string().optional()
17069
+ }).strict();
17070
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
17071
+ var DetectionException = external_exports.object({
17072
+ id: external_exports.guid(),
17073
+ ruleId: external_exports.string(),
17074
+ // Denormalized from the rule, for reporting — never matched on.
17075
+ category: DetectionCategory,
17076
+ // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
17077
+ // fingerprint, never the raw value, and never reversible. Matching recomputes
17078
+ // the fingerprint from a fresh capture; the value itself is never stored.
17079
+ // Shape-constrained so a malformed — or accidentally raw — value is rejected
17080
+ // at the boundary rather than persisted.
17081
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17082
+ // Version of the fingerprint key the grant was written under; a rotated key
17083
+ // invalidates old grants rather than silently mismatching them.
17084
+ keyVersion: external_exports.number().int().positive(),
17085
+ // maskMatch() preview of the approved value — never the raw value.
17086
+ maskedValue: external_exports.string(),
17087
+ capability: ExceptionCapability.default("suppress"),
17088
+ scope: ExceptionScope,
17089
+ expiresAt: external_exports.iso.datetime().nullable(),
17090
+ maxUses: external_exports.number().int().positive().nullable(),
17091
+ useCount: external_exports.number().int().nonnegative(),
17092
+ lastUsedAt: external_exports.iso.datetime().nullable(),
17093
+ // Mandatory: every grant carries the human reason it exists.
17094
+ justification: external_exports.string().min(1),
17095
+ conditions: ExceptionConditions.nullable(),
17096
+ createdBy: external_exports.string(),
17097
+ createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
17098
+ createdAt: external_exports.iso.datetime(),
17099
+ updatedAt: external_exports.iso.datetime(),
17100
+ // Revocation is terminal and retained — consumed/expired/revoked rows are
17101
+ // audit evidence; nothing in the exception lifecycle hard-deletes.
17102
+ revokedAt: external_exports.iso.datetime().nullable(),
17103
+ revokedBy: external_exports.string().nullable(),
17104
+ revokeReason: external_exports.string().nullable()
17105
+ });
17106
+ var ExceptionBundleEntry = DetectionException.pick({
17107
+ id: true,
17108
+ ruleId: true,
17109
+ valueFingerprint: true,
17110
+ keyVersion: true,
17111
+ capability: true,
17112
+ expiresAt: true,
17113
+ maxUses: true,
17114
+ useCount: true,
17115
+ conditions: true
17116
+ });
17117
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
17118
+
17241
17119
  // ../../packages/schema/src/zod/exception-action.ts
17242
17120
  var confirmation = external_exports.string().optional();
17243
17121
  var ApproveBlockedInput = external_exports.object({
@@ -17280,10 +17158,11 @@ function toApiAction(dbVal) {
17280
17158
  }
17281
17159
  function toApiCategory(dbVal) {
17282
17160
  if (dbVal === "code_context") return "source_code";
17283
- return dbVal;
17161
+ const parsed = FindingCategory.safeParse(dbVal);
17162
+ return parsed.success ? parsed.data : "custom";
17284
17163
  }
17285
17164
  function toApiProvider(sourceTool) {
17286
- return TOOL_TO_HARNESS[sourceTool] ?? "api";
17165
+ return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
17287
17166
  }
17288
17167
  var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
17289
17168
  function foldGroupStatus(instanceStatuses) {
@@ -17870,7 +17749,14 @@ function isVaultConsentValid(consent) {
17870
17749
 
17871
17750
  // ../../packages/schema/src/zod/local.ts
17872
17751
  var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17873
- var RunMode = external_exports.enum(["standalone"]);
17752
+ var MODEL_JUDGE_PAYLOAD_VERSION = 1;
17753
+ var RunMode = external_exports.enum(["standalone", "attached"]);
17754
+ var ControlPlaneConnection = external_exports.object({
17755
+ endpoint: external_exports.string().min(1),
17756
+ // Display name for the deployment, shown instead of the raw endpoint.
17757
+ label: external_exports.string().min(1).optional(),
17758
+ attachedAt: external_exports.iso.datetime()
17759
+ }).meta({ id: "ControlPlaneConnection" });
17874
17760
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17875
17761
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
17876
17762
  var ModelJudgeConsent = external_exports.object({
@@ -17879,12 +17765,10 @@ var ModelJudgeConsent = external_exports.object({
17879
17765
  });
17880
17766
  var WorkspaceSettings = external_exports.object({
17881
17767
  specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
17882
- // Settings files written by earlier releases may carry the retired 'attached'
17883
- // value; it parses as 'standalone' so those files keep loading.
17884
- runMode: external_exports.preprocess(
17885
- (v) => v === "attached" ? "standalone" : v,
17886
- RunMode.default("standalone")
17887
- ),
17768
+ runMode: RunMode.default("standalone"),
17769
+ // Present only while attached; a detach clears it. Its presence is what makes
17770
+ // `runMode: 'attached'` mean anything — see isAttached.
17771
+ controlPlane: ControlPlaneConnection.optional(),
17888
17772
  policy: SimpleDetectionPolicy.default("redact"),
17889
17773
  // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
17890
17774
  historicalAccess: HistoricalAccess.default("session-only"),
@@ -18020,6 +17904,211 @@ function toCaptureDefinitionInput(finding) {
18020
17904
  };
18021
17905
  }
18022
17906
 
17907
+ // ../../packages/schema/src/zod/managed.ts
17908
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
17909
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
17910
+ var ManagedSettingKey = external_exports.enum([
17911
+ "runMode",
17912
+ "historicalAccess",
17913
+ "vaultConsent",
17914
+ "vaultKeyCustody",
17915
+ "vaultInlineReveal",
17916
+ "modelJudgeConsent",
17917
+ "dataSharesInPlace"
17918
+ ]).meta({ id: "ManagedSettingKey" });
17919
+ var ManagedSettingsValues = external_exports.object({
17920
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
17921
+ controlPlane: external_exports.object({
17922
+ endpoint: external_exports.string().min(1),
17923
+ label: external_exports.string().min(1).optional()
17924
+ }).optional(),
17925
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
17926
+ vaultConsent: external_exports.boolean().optional(),
17927
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
17928
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
17929
+ modelJudgeConsent: external_exports.boolean().optional(),
17930
+ dataSharesInPlace: external_exports.boolean().optional()
17931
+ }).meta({ id: "ManagedSettingsValues" });
17932
+ var ManagedSettings = external_exports.object({
17933
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
17934
+ // Shown on every locked control, so the user can tell an administrative
17935
+ // decision from a bug. Absent renders as a generic "your organization".
17936
+ organization: external_exports.string().min(1).optional(),
17937
+ // What the administrator pinned.
17938
+ values: ManagedSettingsValues.default({}),
17939
+ // Which of those the user may not change. A key here with no matching value
17940
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
17941
+ // the user may still override. The two are separable on purpose.
17942
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
17943
+ }).meta({ id: "ManagedSettings" });
17944
+
17945
+ // ../../packages/schema/src/zod/policy.ts
17946
+ var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
17947
+ var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
17948
+ var Policy = external_exports.object({
17949
+ id: external_exports.guid(),
17950
+ scope: PolicyScope,
17951
+ target: PolicyTarget,
17952
+ action: ActionTaken,
17953
+ enabled: external_exports.boolean().default(true),
17954
+ customKeywords: external_exports.array(external_exports.string()).optional(),
17955
+ // Display name — optional so older policy rows without name still parse.
17956
+ // Added for the findings API (policy.name column migration).
17957
+ name: external_exports.string().optional()
17958
+ }).meta({ id: "Policy" });
17959
+ var PolicyBundle = external_exports.object({
17960
+ version: external_exports.string(),
17961
+ policies: external_exports.array(Policy),
17962
+ // Rules from the installed marketplace packs (snapshotted by the
17963
+ // control plane). The plugin registers these in addition to its bundled
17964
+ // packs. Optional so older backends — and older on-disk caches — that omit
17965
+ // the field still parse; consumers read `bundle.rules ?? []`.
17966
+ rules: external_exports.array(Rule).optional(),
17967
+ // When true, `rules` IS the complete effective ruleset and the runtime must
17968
+ // NOT merge its compiled-in bundled packs — the standalone gateway sets this
17969
+ // after reading the user's installed snapshot (installed_packs, enabled
17970
+ // packs only), which is how detection updates stay manual: new bundled
17971
+ // rules run only after the user applies the pack update. Absent/false keeps
17972
+ // the historical composition (bundled packs + rules) — older caches.
17973
+ rulesComplete: external_exports.boolean().optional(),
17974
+ // Active detection exceptions, evaluation subset only (see
17975
+ // ExceptionBundleEntry). Optional so older bundle producers — and older
17976
+ // on-disk caches — that omit the field still parse; consumers read
17977
+ // `bundle.exceptions ?? []`.
17978
+ exceptions: external_exports.array(ExceptionBundleEntry).optional(),
17979
+ // Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
17980
+ // A second axis over the same `redact` action, carried beside the policies
17981
+ // rather than on them: nothing writes ruleId-targeted policies to disk, so
17982
+ // widening Policy itself would change a persisted shape to express something
17983
+ // only the in-memory bundle needs. Optional so an older producer — or an
17984
+ // older on-disk cache — still parses; consumers read `?? []` and get the
17985
+ // pre-existing one-way behaviour, which is the safe direction to default.
17986
+ reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
17987
+ // Installed pack version, keyed by ruleId, for rules in `rules` that came
17988
+ // from a versioned installed pack. Optional so older backends — and older
17989
+ // on-disk caches — that omit the field still parse; consumers fall back to
17990
+ // the rule's own spec version. NOT the bundle version above — see
17991
+ // installedRuleset's ruleVersions for the source of truth.
17992
+ ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
17993
+ customKeywords: external_exports.array(external_exports.string()),
17994
+ fetchedAt: external_exports.iso.datetime()
17995
+ }).meta({ id: "PolicyBundle" });
17996
+ var OBSERVE_ONLY_CATEGORIES = ["config"];
17997
+ var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
17998
+ var CATEGORY_PEAK_SEVERITY = {
17999
+ secret: "critical",
18000
+ financial: "critical",
18001
+ // core-financial/credit-card
18002
+ code_flaw: "critical",
18003
+ pii: "high",
18004
+ phi: "high",
18005
+ custom: "high",
18006
+ // user-defined; conservative
18007
+ code_context: "low",
18008
+ config: "low"
18009
+ // observe-only; floors to monitor regardless
18010
+ };
18011
+ function severityFloorPolicy(category) {
18012
+ if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
18013
+ const peak = CATEGORY_PEAK_SEVERITY[category];
18014
+ return peak === "critical" || peak === "high" ? "warn" : "monitor";
18015
+ }
18016
+ var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
18017
+ var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
18018
+ var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
18019
+ var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
18020
+ var BUILTIN_POLICY_SPECS = {
18021
+ monitor: {
18022
+ name: "Monitor",
18023
+ action: "log",
18024
+ reversible: false,
18025
+ description: "Log every match for audit. The request is allowed through untouched."
18026
+ },
18027
+ warn: {
18028
+ name: "Warn",
18029
+ action: "warn",
18030
+ reversible: false,
18031
+ description: "Allow the request, but warn the user inline before it is sent."
18032
+ },
18033
+ redact: {
18034
+ name: "Redact",
18035
+ action: "redact",
18036
+ reversible: false,
18037
+ description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
18038
+ },
18039
+ vault: {
18040
+ name: "Redact & Vault",
18041
+ action: "redact",
18042
+ reversible: true,
18043
+ description: "Strip the matched value from the request and keep an encrypted, recoverable copy in the local vault, leaving a pointer in its place. Needs the vault consent granted under Settings; without it this behaves as Redact."
18044
+ },
18045
+ block: {
18046
+ name: "Block",
18047
+ action: "block",
18048
+ reversible: false,
18049
+ description: "Refuse the request entirely whenever any rule in this detection matches."
18050
+ }
18051
+ };
18052
+ function builtinPolicyToAction(id) {
18053
+ return BUILTIN_POLICY_SPECS[id].action;
18054
+ }
18055
+ var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18056
+ (id) => !BUILTIN_POLICY_SPECS[id].reversible
18057
+ );
18058
+ var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18059
+ (id) => BUILTIN_POLICY_SPECS[id].reversible
18060
+ );
18061
+ var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
18062
+ function builtinPolicyIsReversible(id) {
18063
+ return BUILTIN_POLICY_SPECS[id].reversible;
18064
+ }
18065
+ function policyIdIsReversible(policyId) {
18066
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18067
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18068
+ return builtinPolicyIsReversible(id);
18069
+ }
18070
+ var DEFAULT_ACTIONS = Object.fromEntries(
18071
+ DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
18072
+ );
18073
+ var BUILTIN_POLICIES = Object.fromEntries(
18074
+ KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
18075
+ );
18076
+ var DEFAULT_PACK_POLICY_ID = "monitor";
18077
+ function policyIdToAction(policyId) {
18078
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18079
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18080
+ return BUILTIN_POLICIES[id].action;
18081
+ }
18082
+ var UsedByItem = external_exports.object({
18083
+ id: external_exports.string(),
18084
+ name: external_exports.string(),
18085
+ ruleCount: external_exports.number().int().nonnegative(),
18086
+ enabled: external_exports.boolean()
18087
+ }).meta({ id: "UsedByItem" });
18088
+ var PolicyListItem = external_exports.object({
18089
+ id: external_exports.string(),
18090
+ kind: PolicyKind,
18091
+ name: external_exports.string(),
18092
+ enabled: external_exports.boolean(),
18093
+ usedByCount: external_exports.number().int().nonnegative()
18094
+ }).meta({ id: "PolicyListItem" });
18095
+ var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
18096
+ var PolicyDetail = external_exports.object({
18097
+ specVersion: external_exports.literal(1),
18098
+ id: external_exports.string(),
18099
+ kind: PolicyKind,
18100
+ name: external_exports.string(),
18101
+ enabled: external_exports.boolean(),
18102
+ description: external_exports.string(),
18103
+ usedBy: external_exports.array(UsedByItem)
18104
+ }).meta({ id: "PolicyDetail" });
18105
+ var PolicyStatsResponse = external_exports.object({
18106
+ policies: external_exports.number().int().nonnegative(),
18107
+ builtin: external_exports.number().int().nonnegative(),
18108
+ custom: external_exports.number().int().nonnegative(),
18109
+ detectionsGoverned: external_exports.number().int().nonnegative()
18110
+ }).meta({ id: "PolicyStatsResponse" });
18111
+
18023
18112
  // ../../packages/schema/src/zod/project-files.ts
18024
18113
  var ProjectFileInput = external_exports.object({
18025
18114
  path: external_exports.string().min(1),
@@ -18098,44 +18187,6 @@ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
18098
18187
  NoRemediationDecision
18099
18188
  ]);
18100
18189
 
18101
- // ../../packages/schema/src/zod/rule-test.ts
18102
- var TestRulesRequest = external_exports.object({
18103
- rules: external_exports.array(Rule).min(1).max(100),
18104
- text: external_exports.string().max(5e4).optional(),
18105
- fixtures: external_exports.array(RuleFixture).max(200).optional()
18106
- }).refine((v) => v.text !== void 0 || (v.fixtures?.length ?? 0) > 0, {
18107
- message: "Provide `text`, `fixtures`, or both \u2014 there must be something to test"
18108
- }).meta({ id: "TestRulesRequest" });
18109
- var RuleTestMatch = external_exports.object({
18110
- ruleId: external_exports.string(),
18111
- category: DetectionCategory,
18112
- severity: Severity,
18113
- span: Span,
18114
- confidence: external_exports.number().min(0).max(1),
18115
- match: external_exports.string()
18116
- }).meta({ id: "RuleTestMatch" });
18117
- var FixtureResult = external_exports.object({
18118
- label: external_exports.string(),
18119
- shouldMatch: external_exports.boolean(),
18120
- didMatch: external_exports.boolean(),
18121
- passed: external_exports.boolean(),
18122
- matches: external_exports.array(RuleTestMatch)
18123
- }).meta({ id: "FixtureResult" });
18124
- var TestRulesResponse = external_exports.object({
18125
- // Present only when the request supplied `text`.
18126
- adhoc: external_exports.object({ matches: external_exports.array(RuleTestMatch) }).optional(),
18127
- fixtures: external_exports.array(FixtureResult),
18128
- summary: external_exports.object({
18129
- total: external_exports.number().int().nonnegative(),
18130
- passed: external_exports.number().int().nonnegative(),
18131
- failed: external_exports.number().int().nonnegative()
18132
- }),
18133
- // Ids of rules whose matcher type the engine cannot evaluate today (e.g.
18134
- // `validator`), so they silently never match. Surfaced so an author is not
18135
- // misled by a green run that actually skipped a rule.
18136
- unsupportedRuleIds: external_exports.array(external_exports.string())
18137
- }).meta({ id: "TestRulesResponse" });
18138
-
18139
18190
  // ../../packages/schema/src/zod/security.ts
18140
18191
  var SeveritySummaryItem = external_exports.object({
18141
18192
  severity: Severity,
@@ -18233,10 +18284,22 @@ var TopSourcesQuery = external_exports.object({
18233
18284
  // Omit for both kinds.
18234
18285
  kind: external_exports.enum(SOURCE_KINDS).optional()
18235
18286
  });
18236
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18287
+ var Provider = Harness.extract([
18288
+ "ClaudeCode",
18289
+ "Cursor",
18290
+ "Codex",
18291
+ "Antigravity",
18292
+ "ClaudeAi",
18293
+ "ChatGpt",
18294
+ "Copilot",
18295
+ "Api"
18296
+ ]).meta({ id: "Provider" });
18237
18297
  var ScanCoverageProvider = external_exports.object({
18238
18298
  provider: Provider,
18239
- // Percent of that provider's traffic scanned in the window. 0 when unsupported.
18299
+ // Percent of that provider's traffic the shipped capture surface reaches.
18300
+ // A curated business fact, constant across every `range` — not a measured
18301
+ // per-window metric. 0 exactly when `supported` is false. See the comment
18302
+ // above the block for where these numbers are decided.
18240
18303
  coverage: external_exports.number().int().min(0).max(100),
18241
18304
  supported: external_exports.boolean()
18242
18305
  }).meta({ id: "ScanCoverageProvider" });
@@ -18290,6 +18353,18 @@ var ApplyRecommendedActionResponse = external_exports.object({
18290
18353
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
18291
18354
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
18292
18355
 
18356
+ // ../../packages/schema/src/zod/settings-action.ts
18357
+ var SaveSettingsInput = external_exports.object({
18358
+ historicalAccess: external_exports.string(),
18359
+ modelJudgeConsent: external_exports.boolean(),
18360
+ vaultConsent: external_exports.string(),
18361
+ vaultInlineReveal: external_exports.string()
18362
+ });
18363
+ var AttachInput = external_exports.object({
18364
+ endpoint: external_exports.string(),
18365
+ label: external_exports.string().optional()
18366
+ });
18367
+
18293
18368
  // ../../packages/schema/src/zod/triage.ts
18294
18369
  var TriageHit = external_exports.object({
18295
18370
  ruleId: external_exports.string(),
@@ -18304,7 +18379,7 @@ var TriageHit = external_exports.object({
18304
18379
  valueFingerprint: external_exports.string().optional(),
18305
18380
  keyVersion: external_exports.number().int().nonnegative().optional()
18306
18381
  });
18307
- var TriagePolicy = BuiltinPolicyId;
18382
+ var TriagePolicy = CategoryPolicyId;
18308
18383
  var TriageCategoryRec = external_exports.object({
18309
18384
  category: DetectionCategory,
18310
18385
  action: TriagePolicy,
@@ -18522,8 +18597,11 @@ function chmodBestEffort(path, mode) {
18522
18597
  function tightenDir(dir) {
18523
18598
  chmodBestEffort(dir, DATA_DIR_MODE);
18524
18599
  }
18600
+ function mkdirOwnerOnlySync(dir, recursive = false) {
18601
+ mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
18602
+ }
18525
18603
  function ensureDataDirSync(dir) {
18526
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18604
+ mkdirOwnerOnlySync(dir, true);
18527
18605
  tightenDir(dir);
18528
18606
  }
18529
18607
  function dbSidecars(file2) {
@@ -18535,6 +18613,26 @@ function tightenFile(file2) {
18535
18613
  function tightenPerms(file2) {
18536
18614
  for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18537
18615
  }
18616
+ function writeExclusiveOwnerOnlySync(file2, data) {
18617
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18618
+ }
18619
+ function writeOwnerOnlyFileSync(file2, data) {
18620
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18621
+ try {
18622
+ rmSync(tmp, { force: true });
18623
+ } catch {
18624
+ }
18625
+ try {
18626
+ writeExclusiveOwnerOnlySync(tmp, data);
18627
+ renameSync(tmp, file2);
18628
+ } finally {
18629
+ try {
18630
+ rmSync(tmp, { force: true });
18631
+ } catch {
18632
+ }
18633
+ }
18634
+ tightenFile(file2);
18635
+ }
18538
18636
  function classifyOccupant(file2) {
18539
18637
  try {
18540
18638
  if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
@@ -18563,7 +18661,7 @@ function createOwnerOnlyFileSync(file2, data) {
18563
18661
  }
18564
18662
  let created;
18565
18663
  try {
18566
- writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18664
+ writeExclusiveOwnerOnlySync(tmp, data);
18567
18665
  created = publishByLink(tmp, file2, data);
18568
18666
  } finally {
18569
18667
  try {
@@ -18585,7 +18683,7 @@ function publishByLink(tmp, file2, data) {
18585
18683
  if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18586
18684
  }
18587
18685
  try {
18588
- writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18686
+ writeExclusiveOwnerOnlySync(file2, data);
18589
18687
  return true;
18590
18688
  } catch (err) {
18591
18689
  if (err.code === "EEXIST") return false;
@@ -18598,6 +18696,25 @@ function backupPath(file2, tag) {
18598
18696
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18599
18697
  }
18600
18698
  var STALE_PARTIAL_MS = 5 * 6e4;
18699
+ var SNAPSHOT_STAGING_SUFFIX = ".partial";
18700
+ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18701
+ var SNAPSHOT_STAGING_COPY = "copy";
18702
+ function createSnapshotStaging(backup) {
18703
+ const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18704
+ rmSync2(stage, { recursive: true, force: true });
18705
+ mkdirOwnerOnlySync(stage);
18706
+ tightenDir(stage);
18707
+ return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18708
+ }
18709
+ function idleMs(entry) {
18710
+ for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18711
+ try {
18712
+ return Date.now() - statSync(candidate).mtimeMs;
18713
+ } catch {
18714
+ }
18715
+ }
18716
+ return null;
18717
+ }
18601
18718
  function reapStalePartials(file2) {
18602
18719
  const dir = dirname(file2);
18603
18720
  const prefix = `${basename(file2)}.`;
@@ -18608,30 +18725,34 @@ function reapStalePartials(file2) {
18608
18725
  return;
18609
18726
  }
18610
18727
  for (const name of entries) {
18611
- if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18612
- const partial2 = join(dir, name);
18728
+ if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18729
+ const staging = join(dir, name);
18613
18730
  try {
18614
- if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18615
- rmSync2(partial2, { force: true });
18731
+ const idle = idleMs(staging);
18732
+ if (idle !== null && idle > STALE_PARTIAL_MS) {
18733
+ rmSync2(staging, { recursive: true, force: true });
18616
18734
  }
18617
18735
  } catch {
18618
18736
  }
18619
18737
  }
18620
18738
  }
18621
18739
  function snapshotStore(db, backup) {
18622
- const partial2 = `${backup}.partial`;
18740
+ const { stage, copy } = createSnapshotStaging(backup);
18623
18741
  try {
18624
- rmSync2(partial2, { force: true });
18625
- db.prepare("VACUUM INTO ?").run(partial2);
18626
- tightenFile(partial2);
18627
- renameSync2(partial2, backup);
18742
+ db.prepare("VACUUM INTO ?").run(copy);
18743
+ tightenFile(copy);
18744
+ renameSync2(copy, backup);
18628
18745
  } catch (error51) {
18629
18746
  try {
18630
- rmSync2(partial2, { force: true });
18747
+ rmSync2(stage, { recursive: true, force: true });
18631
18748
  } catch {
18632
18749
  }
18633
18750
  throw error51;
18634
18751
  }
18752
+ try {
18753
+ rmSync2(stage, { recursive: true, force: true });
18754
+ } catch {
18755
+ }
18635
18756
  }
18636
18757
  function moveStoreAside(file2, backup) {
18637
18758
  const undo = [];
@@ -19359,9 +19480,10 @@ function safeParseStringArray(raw) {
19359
19480
  const parsed = safeJson(raw, null);
19360
19481
  return Array.isArray(parsed) ? parsed : [];
19361
19482
  }
19483
+ var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19362
19484
  function toHarness(raw) {
19363
19485
  const parsed = Harness.safeParse(raw);
19364
- return parsed.success ? parsed.data : "claudecode";
19486
+ return parsed.success ? parsed.data : DEFAULT_HARNESS;
19365
19487
  }
19366
19488
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19367
19489
  if (row.status) {
@@ -19492,7 +19614,7 @@ var SqliteActivityRepository = class {
19492
19614
  const params = [];
19493
19615
  if (query.harness && query.harness.length > 0) {
19494
19616
  conditions.push(
19495
- `coalesce(json_extract(attributes, '$.harness'), 'claudecode') IN (${placeholders(query.harness.length)})`
19617
+ `coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
19496
19618
  );
19497
19619
  params.push(...query.harness);
19498
19620
  }
@@ -19699,13 +19821,13 @@ var SqliteActivityRepository = class {
19699
19821
  * The DISTINCT harnesses that actually have sessions (optionally within a
19700
19822
  * `started_at >= fromMs` window), so the filter can offer only the harnesses
19701
19823
  * present rather than the full enum. Each stored value is normalized through
19702
- * the SAME `toHarness` default the list uses (missing → 'claudecode'), so a
19703
- * store of bare (harness-less) roots surfaces exactly `['claudecode']`.
19824
+ * the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
19825
+ * a store of bare (harness-less) roots surfaces exactly that one harness.
19704
19826
  */
19705
19827
  harnessFacets(fromMs) {
19706
19828
  const where = fromMs === void 0 ? "" : " AND started_at >= ?";
19707
19829
  const stmt = this.db.prepare(
19708
- `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), 'claudecode') AS harness
19830
+ `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
19709
19831
  FROM audit_events WHERE ${SESSION_ROOT}${where}`
19710
19832
  );
19711
19833
  const rows = allRows(
@@ -19919,8 +20041,8 @@ var SqliteAuditEventsRepository = class {
19919
20041
  }
19920
20042
  // Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
19921
20043
  // (which takes a caller-supplied random id), the id here is MINTED internally
19922
- // from the natural key — `llmCallId(sessionId, messageId)` — tenant-free like the
19923
- // sibling local-store ids (the local store is single-tenant). The deterministic
20044
+ // from the natural key — `llmCallId(sessionId, messageId)` — derived from the
20045
+ // session and message alone, like the sibling local-store ids. The deterministic
19924
20046
  // id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
19925
20047
  // AND converge a streaming partial/final split across two incremental passes:
19926
20048
  // a whole-file re-read no-ops (equal output), a lagging final replaces a
@@ -20885,6 +21007,42 @@ var SqliteFindingsRepository = class {
20885
21007
  this.db = db;
20886
21008
  }
20887
21009
  db;
21010
+ /**
21011
+ * The newest `limit` findings, newest first.
21012
+ *
21013
+ * THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
21014
+ * pin it. The natural spelling — drive from `inspection_findings`, order by the
21015
+ * JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
21016
+ * not on the driving table: SQLite sorts every finding in the store through a
21017
+ * temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
21018
+ * against 0.9 ms for the form below, and the gap is a ratio of the store size
21019
+ * rather than a constant.
21020
+ *
21021
+ * What it takes to make `started_at` order come out of an index instead:
21022
+ *
21023
+ * - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
21024
+ * planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
21025
+ * index cannot serve the ORDER BY: the predicate spans four event types, so
21026
+ * satisfying a global `started_at` order across them needs a range merge
21027
+ * SQLite will not do, and it sorts instead. Freed of it, the planner scans
21028
+ * `idx_audit_started_at` — a bare `started_at` index — in DESC order and
21029
+ * filters the type per row, which lets the LIMIT stop the scan early.
21030
+ * - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
21031
+ * purely to stop the tables being reordered. With plain JOINs the planner
21032
+ * drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
21033
+ * unary plus ALONE recovers almost none of the win. Both are needed.
21034
+ *
21035
+ * Neither is a micro-optimisation that a later reader should tidy away, and
21036
+ * `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
21037
+ * the temp B-tree comes back.
21038
+ *
21039
+ * Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
21040
+ * identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
21041
+ * renaming the index turns this read into an ERROR, where `+` turns it into a
21042
+ * scan-and-sort — slower, still correct. The worst case for the chosen form is
21043
+ * a store whose recent captures carry no findings at all, where the scan walks
21044
+ * the whole index; that is still no worse than the full sort it replaced.
21045
+ */
20888
21046
  recentFindings(opts) {
20889
21047
  const limit = opts?.limit ?? 50;
20890
21048
  const rows = allRows(
@@ -20893,10 +21051,10 @@ var SqliteFindingsRepository = class {
20893
21051
  f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
20894
21052
  json_extract(e.attributes, '$.source_tool') AS source_tool,
20895
21053
  e.event_type AS kind
20896
- FROM inspection_findings f
20897
- JOIN audit_events e ON e.id = f.audit_event_id
20898
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20899
- WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
21054
+ FROM audit_events e
21055
+ CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
21056
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21057
+ WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
20900
21058
  ORDER BY e.started_at DESC, f.rowid DESC
20901
21059
  LIMIT :limit`
20902
21060
  ),
@@ -21540,7 +21698,8 @@ var SqliteInspectionDefinitionsRepository = class {
21540
21698
  }
21541
21699
  db;
21542
21700
  insertStmt;
21543
- // Idempotent upsert; returns the content-addressed definition id.
21701
+ // Insert-if-absent; returns the content-addressed definition id. An id already
21702
+ // present keeps the stored row untouched — see the class doc.
21544
21703
  upsert(input) {
21545
21704
  const id = inspectionDefinitionId(input.ruleId, input.version);
21546
21705
  const row = toInspectionDefinitionRow(input, id);
@@ -21684,11 +21843,23 @@ function isParseableBinaryVersion(version2) {
21684
21843
 
21685
21844
  // ../../packages/persistence/src/repositories/installed-packs.ts
21686
21845
  var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
21846
+ function printableRuleId(entry) {
21847
+ if (typeof entry !== "object" || entry === null) return null;
21848
+ const candidate = entry.id;
21849
+ return Rule.shape.id.safeParse(candidate).success ? candidate : null;
21850
+ }
21851
+ function firstIssueReason(error51) {
21852
+ const issue2 = error51.issues[0];
21853
+ if (!issue2) return "unknown";
21854
+ const path = issue2.path.map((segment) => String(segment)).join(".");
21855
+ return path ? `${path}: ${issue2.code}` : issue2.code;
21856
+ }
21857
+ var REJECTED_RULE_DETAIL_CAP = 10;
21687
21858
  function inventorySignature(packs2) {
21688
21859
  return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
21689
21860
  }
21690
21861
  function hashRules(rulesJson) {
21691
- return createHash2("sha1").update(rulesJson).digest("hex");
21862
+ return createHash2("sha256").update(rulesJson).digest("hex");
21692
21863
  }
21693
21864
  function parseVersion(v) {
21694
21865
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -21900,10 +22071,21 @@ var SqliteInstalledPacksRepository = class {
21900
22071
  * (all detection off) instead of falling back to the bundled packs. Every
21901
22072
  * JSON-level failure therefore counts as invalid.
21902
22073
  */
22074
+ /**
22075
+ * ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
22076
+ * index is (namespace, pack_id) — so two enabled packs may contribute the same
22077
+ * id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
22078
+ * winner is whatever order SQLite happens to return, which makes a collision
22079
+ * resolve differently on two machines holding identical stores. Ordering by
22080
+ * (namespace, pack_id) makes the loser deterministic and therefore testable.
22081
+ */
21903
22082
  installedRuleset() {
21904
22083
  const rows = allRows(
21905
22084
  this.db.prepare(
21906
- `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version FROM installed_packs`
22085
+ `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
22086
+ namespace, pack_id AS packId
22087
+ FROM installed_packs
22088
+ ORDER BY namespace, pack_id`
21907
22089
  )
21908
22090
  );
21909
22091
  const out = {
@@ -21911,22 +22093,32 @@ var SqliteInstalledPacksRepository = class {
21911
22093
  enabledPacks: 0,
21912
22094
  rules: [],
21913
22095
  invalidRules: 0,
22096
+ rejectedRules: [],
21914
22097
  ruleActions: /* @__PURE__ */ new Map(),
21915
- ruleVersions: /* @__PURE__ */ new Map()
22098
+ ruleVersions: /* @__PURE__ */ new Map(),
22099
+ reversibleRules: /* @__PURE__ */ new Set()
22100
+ };
22101
+ const reject = (pack, ruleId, reason) => {
22102
+ if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
22103
+ out.rejectedRules.push({ pack, ruleId, reason });
21916
22104
  };
21917
22105
  for (const row of rows) {
21918
22106
  if (!intToBool(row.enabled)) continue;
21919
22107
  out.enabledPacks += 1;
21920
22108
  const action = policyIdToAction(row.policyId);
22109
+ const reversible = policyIdIsReversible(row.policyId);
22110
+ const pack = `${row.namespace}/${row.packId}`;
21921
22111
  let raw;
21922
22112
  try {
21923
22113
  raw = JSON.parse(row.rulesJson);
21924
22114
  } catch {
21925
22115
  out.invalidRules += 1;
22116
+ reject(pack, null, "rules_json: malformed JSON");
21926
22117
  continue;
21927
22118
  }
21928
22119
  if (!Array.isArray(raw)) {
21929
22120
  out.invalidRules += 1;
22121
+ reject(pack, null, "rules_json: not an array");
21930
22122
  continue;
21931
22123
  }
21932
22124
  for (const entry of raw) {
@@ -21935,7 +22127,12 @@ var SqliteInstalledPacksRepository = class {
21935
22127
  out.rules.push(parsed.data);
21936
22128
  out.ruleActions.set(parsed.data.id, action);
21937
22129
  out.ruleVersions.set(parsed.data.id, row.version);
21938
- } else out.invalidRules += 1;
22130
+ if (reversible) out.reversibleRules.add(parsed.data.id);
22131
+ else out.reversibleRules.delete(parsed.data.id);
22132
+ } else {
22133
+ out.invalidRules += 1;
22134
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22135
+ }
21939
22136
  }
21940
22137
  }
21941
22138
  return out;
@@ -22129,31 +22326,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
22129
22326
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
22130
22327
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
22131
22328
  var HARNESS_LABELS = {
22132
- claudecode: "Claude Code",
22133
- cursor: "Cursor",
22134
- codex: "Codex",
22135
- antigravity: "Antigravity"
22329
+ [HARNESS.ClaudeCode]: "Claude Code",
22330
+ [HARNESS.Cursor]: "Cursor",
22331
+ [HARNESS.Codex]: "Codex",
22332
+ [HARNESS.Antigravity]: "Antigravity"
22136
22333
  };
22137
22334
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
22138
22335
  var EMPTY_PROJECT_AGG = {
22139
22336
  accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
22140
22337
  findingsCount: 0
22141
22338
  };
22339
+ var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
22340
+ var TITLE_NEEDLES = {
22341
+ ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
22342
+ Cursor: stripSeparators(SOURCE_TOOL.Cursor),
22343
+ Codex: stripSeparators(SOURCE_TOOL.Codex),
22344
+ Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
22345
+ };
22142
22346
  function resolveHarnessId(attrs, row) {
22143
22347
  if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
22144
22348
  return attrs.provider;
22145
22349
  }
22146
- const t = (row.title ?? "").toLowerCase().replace(/[\s-]/g, "");
22147
- if (t.includes("claudecode") || t === "claude") return "claudecode";
22148
- if (t.includes("cursor")) return "cursor";
22149
- if (t.includes("codex")) return "codex";
22150
- if (t.includes("antigravity")) return "antigravity";
22350
+ const t = stripSeparators(row.title ?? "");
22351
+ if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
22352
+ if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
22353
+ if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
22354
+ if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
22151
22355
  return null;
22152
22356
  }
22153
22357
  function isLiveRealClaudeCode(rows) {
22154
22358
  return rows.some((r) => {
22155
22359
  const attrs = safeJson(r.attributes, {});
22156
- return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === "claudecode";
22360
+ return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
22157
22361
  });
22158
22362
  }
22159
22363
  function toAssetSummary(row) {
@@ -22421,7 +22625,7 @@ var SqliteInventoryAssetsRepository = class {
22421
22625
  const isRealHarness = rows.some(
22422
22626
  (r) => safeJson(r.attributes, {}).provenance !== "sample"
22423
22627
  );
22424
- const attachConfig = isRealHarness && harnessId === "claudecode" && configAssets.length > 0;
22628
+ const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
22425
22629
  const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
22426
22630
  if (q && assets.length === 0) continue;
22427
22631
  const firstRow = rows[0];
@@ -23095,9 +23299,10 @@ var SqliteProjectFilesRepository = class {
23095
23299
  // ../../packages/persistence/src/repositories/resolutions.ts
23096
23300
  import { randomUUID as randomUUID7 } from "crypto";
23097
23301
  var SqliteResolutionsRepository = class {
23098
- constructor(db, now = () => Date.now()) {
23302
+ constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
23099
23303
  this.db = db;
23100
23304
  this.now = now;
23305
+ this.newId = newId;
23101
23306
  this.insertStmt = db.prepare(
23102
23307
  `INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
23103
23308
  VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
@@ -23130,12 +23335,14 @@ var SqliteResolutionsRepository = class {
23130
23335
  }
23131
23336
  db;
23132
23337
  now;
23338
+ newId;
23133
23339
  insertStmt;
23134
23340
  latestStmt;
23135
23341
  openAtRestStmt;
23136
23342
  resolvedAtRestStmt;
23137
23343
  /**
23138
- * Insert one disposition row. The repo mints the id and stamps created_at.
23344
+ * Insert one disposition row. The repo mints the id and stamps created_at,
23345
+ * both through the constructor's injectable seams.
23139
23346
  * `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
23140
23347
  * FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
23141
23348
  * from the schema enums. NOTE for future manual-resolution writers: this is
@@ -23147,7 +23354,7 @@ var SqliteResolutionsRepository = class {
23147
23354
  */
23148
23355
  insertResolution(r) {
23149
23356
  this.insertStmt.run({
23150
- id: randomUUID7(),
23357
+ id: this.newId(),
23151
23358
  findingKey: r.findingKey,
23152
23359
  status: FindingStatus.parse(r.status),
23153
23360
  method: ResolutionMethod.parse(r.method),
@@ -23726,16 +23933,16 @@ var ACTION_TO_KIND = {
23726
23933
  warn: "warned"
23727
23934
  };
23728
23935
  var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
23729
- var SCAN_COVERAGE = [
23730
- { provider: "claudecode", coverage: 100, supported: true },
23731
- { provider: "cursor", coverage: 0, supported: false },
23732
- { provider: "codex", coverage: 80, supported: true },
23733
- { provider: "antigravity", coverage: 60, supported: true },
23734
- { provider: "claudeai", coverage: 0, supported: false },
23735
- { provider: "chatgpt", coverage: 0, supported: false },
23736
- { provider: "copilot", coverage: 0, supported: false },
23737
- { provider: "api", coverage: 0, supported: false }
23738
- ];
23936
+ var SCAN_COVERAGE = {
23937
+ [HARNESS.Antigravity]: { coverage: 60, supported: true },
23938
+ [HARNESS.Api]: { coverage: 0, supported: false },
23939
+ [HARNESS.ChatGpt]: { coverage: 40, supported: true },
23940
+ [HARNESS.ClaudeAi]: { coverage: 40, supported: true },
23941
+ [HARNESS.ClaudeCode]: { coverage: 100, supported: true },
23942
+ [HARNESS.Codex]: { coverage: 80, supported: true },
23943
+ [HARNESS.Copilot]: { coverage: 0, supported: false },
23944
+ [HARNESS.Cursor]: { coverage: 0, supported: false }
23945
+ };
23739
23946
  var GRANULARITY = {
23740
23947
  "7d": "day",
23741
23948
  "30d": "day",
@@ -23834,9 +24041,22 @@ var SqliteSecurityRepository = class {
23834
24041
  return Promise.resolve({ total, needsRemediation, bySeverity });
23835
24042
  }
23836
24043
  // Range is echoed but does not change the result today — coverage is a constant
23837
- // business fact (see SCAN_COVERAGE), not a measured per-window metric.
24044
+ // business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
24045
+ // comes from Provider.options (the enum's declaration order), not from
24046
+ // SCAN_COVERAGE's own key order — deliberately, not because object literals
24047
+ // leave key order unspecified (ES2015 guarantees insertion order for these
24048
+ // non-integer string keys, so iterating SCAN_COVERAGE directly would be
24049
+ // reliable too). The reason is the schema comment's promise: the returned
24050
+ // order must mirror the generated OpenAPI enum list, which is Provider's
24051
+ // contract, not this table's.
23838
24052
  scanCoverage(range) {
23839
- return Promise.resolve({ range, providers: SCAN_COVERAGE.map((p) => ({ ...p })) });
24053
+ return Promise.resolve({
24054
+ range,
24055
+ providers: Provider.options.map((provider) => ({
24056
+ provider,
24057
+ ...SCAN_COVERAGE[provider]
24058
+ }))
24059
+ });
23840
24060
  }
23841
24061
  enforcementActions(range) {
23842
24062
  const lenMs = RANGE_DAYS[range] * DAY_MS4;
@@ -23894,10 +24114,10 @@ var SqliteSecurityRepository = class {
23894
24114
  // count; a superseding open/redetected row means the finding is not
23895
24115
  // remediated and is excluded, same invariant as severitySummary. Legacy
23896
24116
  // at-rest findings with finding_key IS NULL can never have a resolution row
23897
- // (the lifecycle is keyed by finding_key), so the SQL guard excludes them
23898
- // outright. One raw-row query (fetch every trackable finding + its latest
23899
- // resolution's status/method/resolved_at) + pure-JS filter/bucket/mean,
23900
- // mirroring this file's other methods.
24117
+ // (the lifecycle is keyed by finding_key), so they cannot reach the driving
24118
+ // set below. One raw-row query (fetch the findings with resolution activity in
24119
+ // the window + each one's latest resolution status/method/resolved_at) +
24120
+ // pure-JS filter/bucket/mean, mirroring this file's other methods.
23901
24121
  mttrTrend(range) {
23902
24122
  const granularity = granularityFor(range);
23903
24123
  const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
@@ -23913,29 +24133,80 @@ var SqliteSecurityRepository = class {
23913
24133
  // started_at the upsert overwrites onto inspection_findings.audit_event_id.
23914
24134
  // COALESCE onto the parent event's started_at defends against any
23915
24135
  // legacy/edge row the backfill left null.
23916
- `SELECT COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
24136
+ `SELECT DISTINCT f.finding_key AS finding_key,
24137
+ COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
23917
24138
  latest.status AS latest_status,
23918
24139
  latest.method AS latest_method,
23919
24140
  latest.resolved_at AS latest_resolved_at
23920
- FROM inspection_findings f
23921
- JOIN audit_events e ON e.id = f.audit_event_id
23922
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24141
+ FROM finding_resolution fr
24142
+ CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
24143
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24144
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
23923
24145
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
23924
24146
  ON latest.finding_key = f.finding_key
23925
- WHERE f.finding_key IS NOT NULL
23926
- AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
23927
- AND EXISTS (
23928
- SELECT 1 FROM finding_resolution fr
23929
- WHERE fr.finding_key = f.finding_key
23930
- AND fr.resolved_at >= :windowStart
23931
- )`
23932
- // The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
23933
- // any resolution activity at/after the window start — a row this method
24147
+ WHERE fr.resolved_at >= :windowStart
24148
+ AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
24149
+ // `fr` is a SUPERSET prefilter, not the answer: a finding this method
23934
24150
  // ultimately counts has its LATEST resolution inside the window, which
23935
- // implies such a row exists, so nothing wanted is dropped. The exact
23936
- // latest-wins + status/method + window gate stays in JS below,
23937
- // dialect-agnostic. Without this, a
23938
- // 7d request evaluated the store's entire trackable-findings history.
24151
+ // implies a resolution row at/after the window start exists, so nothing
24152
+ // wanted is dropped. The exact latest-wins + status/method + window gate
24153
+ // stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
24154
+ // implied rather than dropped — the join key comes from
24155
+ // finding_resolution, whose finding_key is NOT NULL.
24156
+ //
24157
+ // IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
24158
+ // the correction this replaced. Spelled as an `EXISTS` in the WHERE it
24159
+ // READ as a bound and was not one: SQLite drove from `audit_events` on
24160
+ // event_type, joined every capture event to its findings, and evaluated
24161
+ // the EXISTS last — bounding the RESULT and not the scan, so a 7d request
24162
+ // still cost the store's whole trackable history. Measured at 44.6 ms on
24163
+ // 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
24164
+ // both cases returning rows for a window holding a fraction of it.
24165
+ //
24166
+ // Two things carry it, and they answer DIFFERENT halves — which is worth
24167
+ // stating precisely, because the obvious reading (both are needed for the
24168
+ // speed) is wrong and was measured to be wrong:
24169
+ //
24170
+ // - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
24171
+ // keyword is semantically identical to JOIN and exists only to stop the
24172
+ // tables being reordered; with plain JOINs the planner puts `e` back on
24173
+ // the outside, because with no ANALYZE statistics it prices
24174
+ // `event_type IN (...)` as a selective probe. Reverting it alone takes
24175
+ // the 2k->20k flatness ratio from 1.32 to 16.87.
24176
+ // - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
24177
+ // `resolved_at >= :windowStart` a range SEARCH instead of a bare
24178
+ // `SCAN fr` — finding_key was this table's only index before it, so the
24179
+ // range had none. It buys NO flatness in store size: remove it and the
24180
+ // ratio above does not move, because the latest-resolution derived
24181
+ // table already passes over the whole of finding_resolution, so this
24182
+ // read is O(resolutions) either way and resolutions are not the store.
24183
+ // What it buys is the criterion `hot-read-query-plans.test.ts` enforces
24184
+ // — no hot read may pass over a table with no index — and that is the
24185
+ // guard that goes red when it is dropped. Neither test catches the
24186
+ // other's defect.
24187
+ //
24188
+ // SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
24189
+ // tidy-up. finding_resolution is append-only, so a key that was fixed,
24190
+ // redetected and fixed again carries several rows inside one window and
24191
+ // matches once per row — and the value below is a MEAN, so a key matched
24192
+ // three times is a key weighted three times.
24193
+ //
24194
+ // The skew is easy to argue away and the argument is wrong, so it is worth
24195
+ // recording. Duplicate rows for ONE key are identical (every projected
24196
+ // column is per-key: `latest.*` is latest-wins, `first_detected_at` is
24197
+ // preserved), so sums and counts scale together and that key's own mean
24198
+ // does not move. What moves is a bucket holding TWO findings that duplicate
24199
+ // UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
24200
+ // 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
24201
+ // `security.test.ts`'s "weights a finding ONCE however many resolution rows
24202
+ // it has inside the window" — which needed a fixture built for it, since no
24203
+ // single-key case can show it.
24204
+ //
24205
+ // `finding_key` is selected to make the DISTINCT dedup by KEY rather than
24206
+ // by value tuple. On the other columns alone, two genuinely different
24207
+ // findings sharing a severity, a first-detection event and a resolution
24208
+ // instant — one commit fixing two secrets in one file — are one tuple, and
24209
+ // collapsing them would under-count in the other direction.
23939
24210
  ),
23940
24211
  { windowStart }
23941
24212
  );
@@ -23993,16 +24264,47 @@ var SqliteSecurityRepository = class {
23993
24264
  }
23994
24265
  // Recently-resolved activity feed: findings whose finding_key's LATEST
23995
24266
  // finding_resolution row is status:'resolved'/method:'fixed-at-source' —
23996
- // same latest-resolution-wins correlated subquery as severitySummary /
23997
- // mttrTrend (NOT a plain JOIN, which would surface every historical
23998
- // resolution row for a key rather than just its current disposition). A key
23999
- // whose latest row is a superseding 'open'/'redetected' row (the same
24000
- // secret came back) is excluded — it is not currently resolved. Legacy
24001
- // at-rest findings with finding_key IS NULL are excluded outright (the
24002
- // resolution lifecycle can never attach to them). Path comes from the
24003
- // finding's parent event (event_type 'code_change', attributes.file_path) —
24004
- // mirrors resolutions.ts's openAtRestStmt accessor. Ordered by resolved_at
24005
- // DESC, capped at `limit`.
24267
+ // same latest-resolution-wins derived table as severitySummary / mttrTrend
24268
+ // (NOT a plain JOIN, which would surface every historical resolution row for
24269
+ // a key rather than just its current disposition). A key whose latest row is
24270
+ // a superseding 'open'/'redetected' row (the same secret came back) is
24271
+ // excluded — it is not currently resolved. Legacy at-rest findings with
24272
+ // finding_key IS NULL are excluded outright (the resolution lifecycle can
24273
+ // never attach to them). Path comes from the finding's parent event
24274
+ // (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
24275
+ // openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
24276
+ //
24277
+ // THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
24278
+ // the plan rather than a preference. Written the other way round — driving
24279
+ // from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
24280
+ // cannot use the join key: `f` is reached FROM `latest` by finding_key, so
24281
+ // `latest` gets probed on (rn, status, method) instead and the plan enumerates
24282
+ // every (code_change event x resolved key) pair before `f` can reject it. That
24283
+ // is a cross product, and it is quadratic in the store: measured at 10,966 ms
24284
+ // on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
24285
+ // returned. It was invisible for as long as it was, and reported at 8 ms,
24286
+ // because an empty finding_resolution table makes the inner side empty and the
24287
+ // cross product collapses to nothing — so the shape is only observable on a
24288
+ // corpus that seeds resolutions.
24289
+ //
24290
+ // Driving from `latest` instead makes every step below it a unique-index or
24291
+ // primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
24292
+ // so the cost is the derived table's own — linear in resolutions, which is
24293
+ // what this feed is legitimately about.
24294
+ //
24295
+ // CROSS JOIN is what actually pins that, and it is load-bearing rather than
24296
+ // decorative: in SQLite the keyword is semantically identical to JOIN and
24297
+ // exists only to stop the optimizer reordering the tables. Written as plain
24298
+ // JOINs in this order the planner puts `e` back on the outside — it has no
24299
+ // ANALYZE statistics to price the alternatives with, so it takes
24300
+ // `event_type = 'code_change'` for a selective index probe and rebuilds the
24301
+ // cross product. The FROM order alone was measured to change the plan not at
24302
+ // all.
24303
+ //
24304
+ // The LEFT JOIN it replaced was already an inner join in effect: three
24305
+ // `latest.*` predicates sit in the WHERE, and each of them is false for a
24306
+ // null-extended row. Spelling it JOIN changes no row and stops the plan
24307
+ // reading as though the findings side could drive.
24006
24308
  recentlyResolved(limit = 20) {
24007
24309
  const rows = allRows(
24008
24310
  this.db.prepare(
@@ -24012,17 +24314,15 @@ var SqliteSecurityRepository = class {
24012
24314
  json_extract(e.attributes, '$.file_path') AS path,
24013
24315
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
24014
24316
  latest.resolved_at AS latest_resolved_at
24015
- FROM inspection_findings f
24016
- JOIN audit_events e ON e.id = f.audit_event_id
24017
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24018
- LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24019
- ON latest.finding_key = f.finding_key
24020
- WHERE e.event_type = 'code_change'
24021
- AND f.finding_key IS NOT NULL
24022
- AND latest.status = 'resolved'
24317
+ FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24318
+ CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
24319
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24320
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24321
+ WHERE latest.status = 'resolved'
24023
24322
  AND latest.method = 'fixed-at-source'
24024
24323
  AND latest.resolved_at IS NOT NULL
24025
- ORDER BY latest_resolved_at DESC
24324
+ AND e.event_type = 'code_change'
24325
+ ORDER BY latest.resolved_at DESC
24026
24326
  LIMIT :limit`
24027
24327
  ),
24028
24328
  { limit }
@@ -24959,6 +25259,7 @@ function openAndInitialize(file2) {
24959
25259
  function openLocalDatabase(dir) {
24960
25260
  ensureDataDirSync(dir);
24961
25261
  const file2 = join2(dir, DB_FILENAME);
25262
+ reapStalePartials(file2);
24962
25263
  const {
24963
25264
  db,
24964
25265
  events,
@@ -25393,11 +25694,79 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25393
25694
  }
25394
25695
  }
25395
25696
 
25697
+ // ../../packages/persistence/src/managed-settings.ts
25698
+ import { readFileSync as readFileSync3 } from "fs";
25699
+ import { posix, win32 } from "path";
25700
+ function managedSettingsPaths(platform2 = process.platform) {
25701
+ if (platform2 === "darwin") {
25702
+ return [
25703
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
25704
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
25705
+ ];
25706
+ }
25707
+ if (platform2 === "win32") {
25708
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
25709
+ }
25710
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
25711
+ }
25712
+ function readManagedSettings(paths = managedSettingsPaths()) {
25713
+ for (const path of paths) {
25714
+ let text;
25715
+ try {
25716
+ text = readFileSync3(path, "utf8");
25717
+ } catch {
25718
+ continue;
25719
+ }
25720
+ const record2 = parseJsonObject(text);
25721
+ if (!record2) continue;
25722
+ const parsed = ManagedSettings.safeParse(record2);
25723
+ if (parsed.success) return parsed.data;
25724
+ }
25725
+ return null;
25726
+ }
25727
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
25728
+ if (!managed) return settings;
25729
+ const { values } = managed;
25730
+ const merged = { ...settings };
25731
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
25732
+ if (values.controlPlane !== void 0) {
25733
+ merged.controlPlane = {
25734
+ ...values.controlPlane,
25735
+ // The administrator pinned WHICH deployment, not WHEN this machine
25736
+ // joined it. Keep the user's own attach time when the endpoint is
25737
+ // unchanged, so a managed machine does not appear to re-attach on every
25738
+ // read; stamp a fresh one when the administrator moved it.
25739
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
25740
+ };
25741
+ }
25742
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
25743
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
25744
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
25745
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
25746
+ if (values.vaultConsent !== void 0) {
25747
+ merged.vaultConsent = values.vaultConsent ? (
25748
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
25749
+ // at the current version otherwise.
25750
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
25751
+ ) : void 0;
25752
+ }
25753
+ if (values.modelJudgeConsent !== void 0) {
25754
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
25755
+ acknowledgedAt: now().toISOString(),
25756
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
25757
+ } : void 0;
25758
+ }
25759
+ return merged;
25760
+ }
25761
+
25396
25762
  // ../../packages/persistence/src/settings.ts
25397
- import { readFileSync as readFileSync3 } from "fs";
25763
+ import { readFileSync as readFileSync4 } from "fs";
25398
25764
  import { join as join5 } from "path";
25399
25765
  var SETTINGS_FILENAME = "settings.json";
25400
25766
  function readWorkspaceSettings(base = defaultDataDir()) {
25767
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25768
+ }
25769
+ function readUserSettings(base) {
25401
25770
  const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25402
25771
  if (!record2) return defaultWorkspaceSettings();
25403
25772
  try {
@@ -25409,7 +25778,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
25409
25778
  function readJson(file2) {
25410
25779
  let text;
25411
25780
  try {
25412
- text = readFileSync3(file2, "utf8");
25781
+ text = readFileSync4(file2, "utf8");
25413
25782
  } catch {
25414
25783
  return null;
25415
25784
  }
@@ -25527,15 +25896,7 @@ function formatPointer(category, keyVersion, pointerId, tag) {
25527
25896
  // ../../packages/persistence/src/vault/key-provider.ts
25528
25897
  import { execFileSync } from "child_process";
25529
25898
  import { randomBytes as randomBytes2 } from "crypto";
25530
- import {
25531
- chmodSync as chmodSync2,
25532
- mkdirSync as mkdirSync2,
25533
- readFileSync as readFileSync4,
25534
- renameSync as renameSync4,
25535
- rmSync as rmSync4,
25536
- statSync as statSync3,
25537
- writeFileSync as writeFileSync3
25538
- } from "fs";
25899
+ import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25539
25900
  import { join as join6 } from "path";
25540
25901
  var VAULT_OCCUPANT_REASON = {
25541
25902
  symlink: "the path is a symlink; remove it so a keyring can be created",
@@ -25628,7 +25989,8 @@ var LOCK_OWNER_FILE = "owner";
25628
25989
  var ROTATION_IN_PROGRESS = "vault: a key rotation is already in progress";
25629
25990
  function claimRotationLock(lock, owner) {
25630
25991
  try {
25631
- mkdirSync2(lock);
25992
+ mkdirOwnerOnlySync(lock);
25993
+ tightenDir(lock);
25632
25994
  } catch (err) {
25633
25995
  if (err.code === "EEXIST") return false;
25634
25996
  throw asError(err);
@@ -25670,7 +26032,7 @@ function acquireRotationLock(keysDir2) {
25670
26032
  }
25671
26033
  function releaseRotationLock(lease) {
25672
26034
  try {
25673
- if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26035
+ if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
25674
26036
  } catch {
25675
26037
  return;
25676
26038
  }
@@ -25721,7 +26083,7 @@ var FileKeyProvider = class {
25721
26083
  #read() {
25722
26084
  let raw;
25723
26085
  try {
25724
- raw = readFileSync4(this.filePath, "utf8");
26086
+ raw = readFileSync5(this.filePath, "utf8");
25725
26087
  } catch (err) {
25726
26088
  if (err.code === "ENOENT") return null;
25727
26089
  throw err instanceof Error ? err : new Error(String(err));
@@ -25760,15 +26122,19 @@ var FileKeyProvider = class {
25760
26122
  * Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
25761
26123
  * Used only for rotation, under the rotation lock — first creation goes
25762
26124
  * through the creation-exclusive path instead.
26125
+ *
26126
+ * Delegated to the shared owner-only write rather than spelled here, so the
26127
+ * create mode this file is published at is the one paths.ts owns and tests
26128
+ * directly. A local copy of the pair was a second place the mode could be
26129
+ * dropped with the trailing tighten still repairing the end state, which is
26130
+ * the shape no assertion on a published file can see. It also picks up that
26131
+ * primitive's per-process tmp name, its stale-tmp sweep, and an exclusive
26132
+ * create that refuses to follow a symlink planted at the tmp path.
25763
26133
  */
25764
26134
  #write(keyring) {
25765
26135
  ensureDataDirSync(this.#keysDir);
25766
- const file2 = this.filePath;
25767
- const tmp = `${file2}.tmp`;
25768
- writeFileSync3(tmp, `${serializeKeyring(keyring)}
25769
- `, { mode: DATA_FILE_MODE });
25770
- renameSync4(tmp, file2);
25771
- tightenFileMode(file2);
26136
+ writeOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
26137
+ `);
25772
26138
  return keyring;
25773
26139
  }
25774
26140
  };
@@ -25778,15 +26144,73 @@ function tightenFileMode(file2) {
25778
26144
  } catch {
25779
26145
  }
25780
26146
  }
25781
- var runSecurity = (args) => execFileSync("/usr/bin/security", args, {
26147
+ var SECURITY_TIMEOUT_MS = 5e3;
26148
+ var runSecurity = (args, stdin) => execFileSync("/usr/bin/security", args, {
25782
26149
  encoding: "utf8",
25783
- stdio: ["ignore", "pipe", "ignore"]
26150
+ input: stdin,
26151
+ timeout: SECURITY_TIMEOUT_MS,
26152
+ // stderr is discarded rather than captured, and that is deliberate: a
26153
+ // captured stream rides out on an execFileSync error's `.stderr`, and the
26154
+ // write paths here carry the keyring. Exit status is the only thing any
26155
+ // branch below reads.
26156
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
25784
26157
  });
25785
26158
  var SECURITY_ITEM_NOT_FOUND = 44;
26159
+ function corruptReason(err) {
26160
+ if (err instanceof SyntaxError) return "malformed JSON";
26161
+ return err instanceof Error ? err.message : "unknown";
26162
+ }
26163
+ function securityFailureMeta(err) {
26164
+ const e = err;
26165
+ const parts = [];
26166
+ if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
26167
+ if (typeof e.signal === "string" && e.signal) parts.push(`signal ${e.signal}`);
26168
+ if (typeof e.code === "string" && e.code) parts.push(e.code);
26169
+ return parts.length > 0 ? parts.join(", ") : "unknown error";
26170
+ }
26171
+ function writeCommand(keyring, update, keychain) {
26172
+ const hex3 = Buffer.from(serializeKeyring(keyring), "utf8").toString("hex");
26173
+ const parts = [
26174
+ "add-generic-password",
26175
+ ...update ? ["-U"] : [],
26176
+ "-s",
26177
+ KEYCHAIN_SERVICE,
26178
+ "-a",
26179
+ KEYCHAIN_ACCOUNT,
26180
+ "-X",
26181
+ hex3
26182
+ ];
26183
+ if (keychain !== void 0) {
26184
+ if (/['\\\n\r\0]/.test(keychain)) {
26185
+ throw new Error(
26186
+ "vault: keychain path contains a quote, backslash, line break or NUL, which security -i cannot carry intact"
26187
+ );
26188
+ }
26189
+ parts.push(`'${keychain}'`);
26190
+ }
26191
+ return `${parts.join(" ")}
26192
+ `;
26193
+ }
25786
26194
  var KeychainKeyProvider = class {
25787
26195
  #keysDir;
25788
26196
  #exec;
25789
- constructor(keysDir2, exec = runSecurity) {
26197
+ #keychain;
26198
+ /**
26199
+ * The trailing keychain argument, or nothing. Every subcommand used here
26200
+ * takes it last (`add-generic-password [keychain]`,
26201
+ * `find-generic-password [keychain...]`), and omitting it means the default
26202
+ * search list. Fixed at construction, so it is built once rather than per
26203
+ * call on the capture path.
26204
+ */
26205
+ #target;
26206
+ /**
26207
+ * `keychain` names the keychain to operate on, as `security`'s trailing
26208
+ * argument. Production passes nothing and gets the user's default keychain,
26209
+ * which is the whole point of the backend. A test driving the REAL binary
26210
+ * passes a throwaway one, because the alternative is writing vault key
26211
+ * material into the developer's own login keychain and leaving it there.
26212
+ */
26213
+ constructor(keysDir2, exec = runSecurity, keychain) {
25790
26214
  if (exec === runSecurity && process.platform !== "darwin") {
25791
26215
  throw new Error(
25792
26216
  `keychain custody is not available on this platform (${process.platform}); use file custody`
@@ -25794,6 +26218,8 @@ var KeychainKeyProvider = class {
25794
26218
  }
25795
26219
  this.#keysDir = keysDir2;
25796
26220
  this.#exec = exec;
26221
+ this.#keychain = keychain;
26222
+ this.#target = keychain === void 0 ? [] : [keychain];
25797
26223
  }
25798
26224
  /** Where a fallback file provider for the same vault would keep its keyring. */
25799
26225
  get keysDir() {
@@ -25832,18 +26258,22 @@ var KeychainKeyProvider = class {
25832
26258
  KEYCHAIN_SERVICE,
25833
26259
  "-a",
25834
26260
  KEYCHAIN_ACCOUNT,
25835
- "-w"
26261
+ "-w",
26262
+ ...this.#target
25836
26263
  ]);
25837
26264
  } catch (err) {
25838
26265
  if (err.status === SECURITY_ITEM_NOT_FOUND) return null;
25839
26266
  throw new Error(
25840
- `vault: keychain read failed (${err instanceof Error ? err.message : String(err)}); refusing to treat the failure as an absent keyring`,
25841
- { cause: err }
26267
+ `vault: keychain read failed (${securityFailureMeta(err)}); refusing to treat the failure as an absent keyring`
25842
26268
  );
25843
26269
  }
25844
26270
  const body = raw.trim();
25845
26271
  if (body.length === 0) return null;
25846
- return parseKeyring(body);
26272
+ try {
26273
+ return parseKeyring(body);
26274
+ } catch (err) {
26275
+ throw new Error(`vault: keychain item is not a usable keyring (${corruptReason(err)})`);
26276
+ }
25847
26277
  }
25848
26278
  /**
25849
26279
  * First mint: a plain `add-generic-password` (no `-U`) fails when an item
@@ -25851,37 +26281,25 @@ var KeychainKeyProvider = class {
25851
26281
  * keyring — the loser re-reads and adopts it instead.
25852
26282
  */
25853
26283
  #create(keyring) {
25854
- const args = [
25855
- "add-generic-password",
25856
- "-s",
25857
- KEYCHAIN_SERVICE,
25858
- "-a",
25859
- KEYCHAIN_ACCOUNT,
25860
- "-w",
25861
- serializeKeyring(keyring)
25862
- ];
26284
+ const line = writeCommand(keyring, false, this.#keychain);
25863
26285
  try {
25864
- this.#exec(args);
26286
+ this.#exec(["-i"], line);
25865
26287
  } catch (err) {
25866
26288
  const winner = this.#read();
25867
26289
  if (winner) return winner;
25868
- throw asError(err);
26290
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
25869
26291
  }
25870
26292
  return keyring;
25871
26293
  }
25872
26294
  // `-U` updates the item in place, deliberately replacing the stored map with
25873
26295
  // one that contains it — used only for rotation, under the rotation lock.
25874
26296
  #replace(keyring) {
25875
- this.#exec([
25876
- "add-generic-password",
25877
- "-U",
25878
- "-s",
25879
- KEYCHAIN_SERVICE,
25880
- "-a",
25881
- KEYCHAIN_ACCOUNT,
25882
- "-w",
25883
- serializeKeyring(keyring)
25884
- ]);
26297
+ const line = writeCommand(keyring, true, this.#keychain);
26298
+ try {
26299
+ this.#exec(["-i"], line);
26300
+ } catch (err) {
26301
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
26302
+ }
25885
26303
  return keyring;
25886
26304
  }
25887
26305
  };
@@ -26381,7 +26799,7 @@ function resolveProviderSafe(resolveProviderFn) {
26381
26799
  }
26382
26800
 
26383
26801
  // ../../packages/plugin-sdk/src/config-inventory.ts
26384
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
26802
+ import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
26385
26803
  import { homedir as homedir2 } from "os";
26386
26804
  import { basename as basename3, join as join10 } from "path";
26387
26805
 
@@ -26957,6 +27375,40 @@ function escapeRegExp2(value) {
26957
27375
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26958
27376
  }
26959
27377
 
27378
+ // ../../packages/detections/src/regex-cache.ts
27379
+ var singles = /* @__PURE__ */ new WeakMap();
27380
+ var keywordLists = /* @__PURE__ */ new WeakMap();
27381
+ var labelLists = /* @__PURE__ */ new WeakMap();
27382
+ function listCache(kind) {
27383
+ return kind === "keyword" ? keywordLists : labelLists;
27384
+ }
27385
+ function memoizedRegExp(owner, build) {
27386
+ const cached2 = singles.get(owner);
27387
+ if (cached2 !== void 0) {
27388
+ cached2.lastIndex = 0;
27389
+ return cached2;
27390
+ }
27391
+ const compiled = build();
27392
+ singles.set(owner, compiled);
27393
+ return compiled;
27394
+ }
27395
+ function memoizedRegExpList(kind, owner, build) {
27396
+ const cache = listCache(kind);
27397
+ const cached2 = cache.get(owner);
27398
+ if (cached2 !== void 0) {
27399
+ if (cached2.stateful) {
27400
+ for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
27401
+ }
27402
+ return cached2.entries;
27403
+ }
27404
+ const entries = build();
27405
+ cache.set(owner, {
27406
+ entries,
27407
+ stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
27408
+ });
27409
+ return entries;
27410
+ }
27411
+
26960
27412
  // ../../packages/detections/src/matchers/limits.ts
26961
27413
  var MAX_MATCHES_PER_RULE = 1e4;
26962
27414
  var MAX_REGEX_INPUT_LENGTH = 2e5;
@@ -26967,10 +27419,17 @@ var KeywordMatcher2 = class {
26967
27419
  if (rule.matcher.type !== "keyword") return [];
26968
27420
  const { keywords, caseSensitive } = rule.matcher;
26969
27421
  const spans = [];
26970
- for (const kw of keywords) {
26971
- if (kw.length === 0) continue;
27422
+ const compiled = memoizedRegExpList(
27423
+ "keyword",
27424
+ rule.matcher,
27425
+ () => keywords.map((kw) => {
27426
+ if (kw.length === 0) return void 0;
27427
+ return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
27428
+ })
27429
+ );
27430
+ for (const re of compiled) {
27431
+ if (re === void 0) continue;
26972
27432
  if (spans.length >= MAX_MATCHES_PER_RULE) break;
26973
- const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
26974
27433
  let m;
26975
27434
  while ((m = re.exec(text)) !== null) {
26976
27435
  spans.push({ start: m.index, end: m.index + m[0].length });
@@ -26986,7 +27445,10 @@ var RegexMatcher2 = class {
26986
27445
  match(text, rule) {
26987
27446
  if (rule.matcher.type !== "regex") return [];
26988
27447
  const { pattern, flags, captureGroup } = rule.matcher;
26989
- const re = new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`);
27448
+ const re = memoizedRegExp(
27449
+ rule.matcher,
27450
+ () => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
27451
+ );
26990
27452
  const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
26991
27453
  const spans = [];
26992
27454
  let m;
@@ -27045,6 +27507,10 @@ function luhnCheck(digits) {
27045
27507
  // ../../packages/detections/src/engine.ts
27046
27508
  var keywordMatcher = new KeywordMatcher2();
27047
27509
  var regexMatcher = new RegexMatcher2();
27510
+ var MATCHERS = {
27511
+ keyword: (text, rule) => keywordMatcher.match(text, rule),
27512
+ regex: (text, rule) => regexMatcher.match(text, rule)
27513
+ };
27048
27514
  var packs = /* @__PURE__ */ new Map();
27049
27515
  var POST_VALIDATORS = {
27050
27516
  entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
@@ -27060,8 +27526,7 @@ function passesPostValidators(rule, value) {
27060
27526
  for (const ref of validators) {
27061
27527
  const name = typeof ref === "string" ? ref : ref.name;
27062
27528
  const config2 = typeof ref === "string" ? void 0 : ref.config;
27063
- const validate = POST_VALIDATORS[name];
27064
- if (validate && !validate(value, config2)) return false;
27529
+ if (!POST_VALIDATORS[name](value, config2)) return false;
27065
27530
  }
27066
27531
  return true;
27067
27532
  }
@@ -27094,11 +27559,15 @@ function isCorroborated(candidate, candidates, text) {
27094
27559
  const labels = req.labels;
27095
27560
  if (labels && labels.length > 0) {
27096
27561
  const haystack = text.slice(Math.max(0, winStart), winEnd);
27097
- for (const label of labels) {
27098
- const trimmed = label.trim();
27099
- if (trimmed.length === 0) continue;
27100
- const re = new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
27101
- if (re.test(haystack)) return true;
27562
+ for (const re of memoizedRegExpList(
27563
+ "label",
27564
+ req,
27565
+ () => labels.map((label) => {
27566
+ const trimmed = label.trim();
27567
+ return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
27568
+ })
27569
+ )) {
27570
+ if (re?.test(haystack)) return true;
27102
27571
  }
27103
27572
  }
27104
27573
  return false;
@@ -27118,14 +27587,7 @@ function scan(text, rules, context) {
27118
27587
  const candidates = [];
27119
27588
  for (const rule of ruleset) {
27120
27589
  if (!ruleApplies(rule, extension)) continue;
27121
- let spans;
27122
- if (rule.matcher.type === "keyword") {
27123
- spans = keywordMatcher.match(text, rule);
27124
- } else if (rule.matcher.type === "regex") {
27125
- spans = regexMatcher.match(text, rule);
27126
- } else {
27127
- continue;
27128
- }
27590
+ const spans = MATCHERS[rule.matcher.type](text, rule);
27129
27591
  for (const span of spans) {
27130
27592
  const rawMatch = text.slice(span.start, span.end);
27131
27593
  if (!passesPostValidators(rule, rawMatch)) continue;
@@ -27366,24 +27828,33 @@ function probesFor(rule) {
27366
27828
  const derived = rule.matcher.type === "regex" ? derivedProbes(rule.matcher.pattern) : [];
27367
27829
  return [...derived, ...EXPONENTIAL_PROBES, ...POLYNOMIAL_PROBES];
27368
27830
  }
27369
- function worstProbeMs(rule) {
27831
+ var wallClock = () => performance.now();
27832
+ function worstProbeMs(rule, now = wallClock, corroborate) {
27370
27833
  let ms = 0;
27371
27834
  let probe = "";
27835
+ let corroboratedMs;
27372
27836
  for (const text of probesFor(rule)) {
27373
- const start = performance.now();
27837
+ const start = now();
27838
+ const corroborateStart = corroborate?.();
27374
27839
  scan(text, [rule]);
27375
- const elapsed = performance.now() - start;
27840
+ const elapsed = now() - start;
27841
+ const corroborateEnd = corroborate?.();
27376
27842
  if (elapsed > ms) {
27377
27843
  ms = elapsed;
27378
27844
  probe = text;
27845
+ corroboratedMs = corroborateStart === void 0 || corroborateEnd === void 0 ? void 0 : corroborateEnd - corroborateStart;
27379
27846
  }
27380
27847
  if (ms >= BUDGET_MS) break;
27381
27848
  }
27382
- return { ms, probe };
27849
+ return { ms, probe, corroboratedMs };
27383
27850
  }
27384
- function checkRuleTiming(rule) {
27385
- const { ms, probe } = worstProbeMs(rule);
27386
- return { safe: ms < BUDGET_MS, worstMs: ms, probe };
27851
+ var CPU_CORROBORATION_SHARE = 0.2;
27852
+ var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
27853
+ function checkRuleTiming(rule, corroborate) {
27854
+ const { ms, probe, corroboratedMs } = worstProbeMs(rule, wallClock, corroborate);
27855
+ const work = corroboratedMs ?? 0;
27856
+ const verdict = ms < BUDGET_MS ? "safe" : work >= CORROBORATION_FLOOR_MS ? "over-budget" : "uncorroborated";
27857
+ return { verdict, worstMs: ms, corroboratedMs: work, probe };
27387
27858
  }
27388
27859
 
27389
27860
  // ../../rules/code-flaws/auth-jwt-no-verify.json
@@ -29422,7 +29893,7 @@ function uniqueRuleIds(findings) {
29422
29893
  }
29423
29894
 
29424
29895
  // ../../packages/plugin-sdk/src/repo.ts
29425
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
29896
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
29426
29897
  import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
29427
29898
  function resolveRepo(cwd) {
29428
29899
  try {
@@ -29467,7 +29938,7 @@ function resolveGitContext(root) {
29467
29938
  }
29468
29939
  function safeRead(path) {
29469
29940
  try {
29470
- return readFileSync5(path, "utf8");
29941
+ return readFileSync6(path, "utf8");
29471
29942
  } catch {
29472
29943
  return void 0;
29473
29944
  }
@@ -29768,7 +30239,12 @@ function createIsolatedScanner(data, opts = {}) {
29768
30239
  build: (id) => ({ kind: "probe", id, rule }),
29769
30240
  reply: (message) => {
29770
30241
  if (message.kind !== "probed") return false;
29771
- resolve({ status: "ok", safe: message.safe, worstMs: message.worstMs });
30242
+ resolve({
30243
+ status: "ok",
30244
+ verdict: message.verdict,
30245
+ worstMs: message.worstMs,
30246
+ corroboratedMs: message.corroboratedMs
30247
+ });
29772
30248
  return true;
29773
30249
  }
29774
30250
  },
@@ -29788,6 +30264,12 @@ function createIsolatedScanner(data, opts = {}) {
29788
30264
  };
29789
30265
  }
29790
30266
 
30267
+ // ../../packages/plugin-sdk/src/work-clock.ts
30268
+ function workClockMs() {
30269
+ const usage = typeof process.threadCpuUsage === "function" ? process.threadCpuUsage() : process.cpuUsage();
30270
+ return (usage.user + usage.system) / 1e3;
30271
+ }
30272
+
29791
30273
  // ../../packages/plugin-sdk/src/rule-quarantine.ts
29792
30274
  var PASS_BUDGET_MS = 2e3;
29793
30275
  var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
@@ -29816,6 +30298,14 @@ function warnUnmeasured(rule) {
29816
30298
  false
29817
30299
  );
29818
30300
  }
30301
+ function warnUncorroborated(rule, worstMs, corroboratedMs) {
30302
+ warn(
30303
+ rule,
30304
+ "deferred",
30305
+ `its regex matcher ran past the ReDoS timing budget (${worstMs.toFixed(1)}ms) but spent only ${corroboratedMs.toFixed(1)}ms of CPU doing it, which is a busy machine rather than a slow pattern; excluded from this run and measured again next time, with nothing recorded against it.`,
30306
+ false
30307
+ );
30308
+ }
29819
30309
  function warnUnmeasurable(reason, count) {
29820
30310
  process.stderr.write(
29821
30311
  `[aka] ${String(count)} pulled/custom-pack rule(s) could not be time-checked: ${reason}. That is a problem with this install, not with the rules \u2014 until it is fixed they are excluded from every scan on this machine. Nothing was quarantined, so reinstalling AKA brings them straight back.
@@ -29862,24 +30352,31 @@ async function filterUnsafeRules(rules, gateway, opts) {
29862
30352
  warnUnmeasured(rule);
29863
30353
  continue;
29864
30354
  }
29865
- let isSafe;
30355
+ let verdict;
29866
30356
  let worstMs;
30357
+ let corroboratedMs = 0;
29867
30358
  if (prober) {
29868
30359
  const outcome = await prober.probe(rule);
29869
30360
  if (outcome.status === "unavailable") {
29870
30361
  unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
29871
30362
  continue;
29872
30363
  }
29873
- isSafe = outcome.status === "ok" ? outcome.safe : false;
30364
+ verdict = outcome.status === "ok" ? outcome.verdict : "over-budget";
29874
30365
  worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
30366
+ if (outcome.status === "ok") corroboratedMs = outcome.corroboratedMs;
29875
30367
  } else {
29876
30368
  try {
29877
- ({ safe: isSafe, worstMs } = checkRuleTiming(rule));
30369
+ ({ verdict, worstMs, corroboratedMs } = checkRuleTiming(rule, workClockMs));
29878
30370
  } catch {
29879
- isSafe = false;
30371
+ verdict = "over-budget";
29880
30372
  worstMs = Number.POSITIVE_INFINITY;
29881
30373
  }
29882
30374
  }
30375
+ if (verdict === "uncorroborated") {
30376
+ warnUncorroborated(rule, worstMs, corroboratedMs);
30377
+ continue;
30378
+ }
30379
+ const isSafe = verdict === "safe";
29883
30380
  let persisted = false;
29884
30381
  try {
29885
30382
  await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
@@ -29979,25 +30476,30 @@ function createGuardedScanner(partition, gateway, opts) {
29979
30476
  };
29980
30477
  }
29981
30478
 
30479
+ // ../../packages/plugin-sdk/src/ignore-layers.ts
30480
+ var import_ignore = __toESM(require_ignore(), 1);
30481
+ import { readFileSync as readFileSync8 } from "fs";
30482
+ import { join as join11 } from "path";
30483
+
29982
30484
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
29983
30485
  import { arch, hostname as hostname4, platform, release } from "os";
29984
30486
 
29985
30487
  // ../../packages/plugin-sdk/src/nudge.ts
29986
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
29987
- import { join as join11 } from "path";
30488
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
30489
+ import { join as join12 } from "path";
29988
30490
  var NUDGE_MARKER = "nudge-last-session";
29989
30491
  function claimOnboardingNudge(dataDir2, sessionId) {
29990
30492
  return claimOncePerSession(dataDir2, NUDGE_MARKER, sessionId);
29991
30493
  }
29992
30494
  function claimOncePerSession(dataDir2, marker, sessionId) {
29993
30495
  if (!sessionId) return true;
29994
- const path = join11(dataDir2, marker);
30496
+ const path = join12(dataDir2, marker);
29995
30497
  try {
29996
- if (readFileSync7(path, "utf8") === sessionId) return false;
30498
+ if (readFileSync9(path, "utf8") === sessionId) return false;
29997
30499
  } catch {
29998
30500
  }
29999
30501
  try {
30000
- mkdirSync3(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
30502
+ mkdirSync2(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
30001
30503
  writeFileSync5(path, sessionId, { mode: DATA_FILE_MODE });
30002
30504
  } catch {
30003
30505
  }
@@ -30009,9 +30511,8 @@ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
30009
30511
  import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
30010
30512
 
30011
30513
  // ../../packages/plugin-sdk/src/project-files.ts
30012
- var import_ignore = __toESM(require_ignore(), 1);
30013
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
30014
- import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
30514
+ import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
30515
+ import { basename as basename5, join as join13 } from "path";
30015
30516
 
30016
30517
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
30017
30518
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -30070,6 +30571,7 @@ function createPluginRuntime(gateway, settings, opts) {
30070
30571
  let initialized = false;
30071
30572
  const ruleActionIndex = /* @__PURE__ */ new Map();
30072
30573
  const categoryActionIndex = /* @__PURE__ */ new Map();
30574
+ let reversibleRuleIndex = /* @__PURE__ */ new Set();
30073
30575
  async function ensureInitialized() {
30074
30576
  if (initialized) return;
30075
30577
  const bundle = await gateway.getPolicyBundle();
@@ -30082,6 +30584,7 @@ function createPluginRuntime(gateway, settings, opts) {
30082
30584
  categoryActionIndex.set(p.target.category, p.action);
30083
30585
  }
30084
30586
  }
30587
+ reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
30085
30588
  const bundledProbeKeys = new Set(
30086
30589
  getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
30087
30590
  );
@@ -30160,11 +30663,13 @@ function createPluginRuntime(gateway, settings, opts) {
30160
30663
  if (worst === "block") return { action: "block", text: null, findings };
30161
30664
  if (worst === "redact") {
30162
30665
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
30666
+ const reversibleFindings = redactFindings.filter((f) => reversibleRuleIndex.has(f.ruleId));
30163
30667
  return {
30164
30668
  action: "redact",
30165
30669
  text: redact(text, redactFindings),
30166
30670
  findings,
30167
- enforcedFindings: redactFindings
30671
+ enforcedFindings: redactFindings,
30672
+ reversibleFindings
30168
30673
  };
30169
30674
  }
30170
30675
  return { action: worst, text, findings };
@@ -30378,8 +30883,8 @@ function createPluginRuntime(gateway, settings, opts) {
30378
30883
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
30379
30884
 
30380
30885
  // ../../packages/plugin-sdk/src/throttle.ts
30381
- import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30382
- import { join as join13 } from "path";
30886
+ import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30887
+ import { join as join14 } from "path";
30383
30888
 
30384
30889
  // ../../packages/plugin-sdk/src/tokenize.ts
30385
30890
  function redactedPlaceholder(category) {
@@ -30442,6 +30947,8 @@ var SecretVaultGlue = class {
30442
30947
  async tokenizeText(text, opts) {
30443
30948
  try {
30444
30949
  const findings = opts?.findings ?? this.#selfScan(text);
30950
+ const reversible = opts?.reversible;
30951
+ const keeps = (finding) => reversible === void 0 || reversible.has(finding);
30445
30952
  if (findings === null) return { text: "[REDACTED]", pointers: [], degraded: [] };
30446
30953
  if (findings.length === 0) return { text, pointers: [], degraded: [] };
30447
30954
  const groups = groupSpans(text, findings);
@@ -30458,6 +30965,8 @@ var SecretVaultGlue = class {
30458
30965
  } else if (original !== finding.rawMatch) {
30459
30966
  replacement = redactedPlaceholder(group.category);
30460
30967
  degraded.unshift({ category: group.category });
30968
+ } else if (!keeps(finding)) {
30969
+ replacement = redactedPlaceholder(finding.category);
30461
30970
  } else {
30462
30971
  replacement = await this.tokenizeValue(finding.rawMatch, {
30463
30972
  ruleId: finding.ruleId,
@@ -30677,53 +31186,6 @@ var UNOPENABLE_VAULT = {
30677
31186
  resolvePointerIdentity: () => Promise.resolve(null)
30678
31187
  };
30679
31188
 
30680
- // src/present.ts
30681
- var fg = (hex3) => (text) => {
30682
- const r = Number.parseInt(hex3.slice(1, 3), 16);
30683
- const g = Number.parseInt(hex3.slice(3, 5), 16);
30684
- const b = Number.parseInt(hex3.slice(5, 7), 16);
30685
- return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
30686
- };
30687
- var paint = {
30688
- brand: fg("#33e6c6"),
30689
- // --color-brand · ▸▸ AKA wordmark (accent text)
30690
- dim: fg("#838995"),
30691
- // --color-text-3 · separators · "/100" · the "unreviewed" label
30692
- bold: (text) => `\x1B[1m${text}\x1B[0m`,
30693
- // the health score number
30694
- ok: fg("#0db15f"),
30695
- // --color-ok · healthy ● dot
30696
- critical: fg("#e63448"),
30697
- // --color-sev-critical · ■ and the open-findings flag
30698
- high: fg("#e97a0a"),
30699
- // --color-sev-high · ■ and the mid-health dot
30700
- medium: fg("#f7bd00"),
30701
- // --color-sev-medium · ■
30702
- low: fg("#0581d4")
30703
- // --color-sev-low · ■ (azure blue, not purple)
30704
- };
30705
-
30706
- // src/exception-guidance.ts
30707
- function blockMessage(input) {
30708
- const preview = input.blockedRef ? ` (${input.blockedRef.maskedValue})` : "";
30709
- const commands = input.blockedRef ? [
30710
- ` aka exception approve ${input.blockedRef.reference} (asks for scope + reason, then resubmit)`,
30711
- " aka exception approve <value> (same flow, pasting the blocked value itself)"
30712
- ] : [" aka exception approve (asks for scope + reason, then resubmit)"];
30713
- const note = input.note ? ` ${input.note}` : "";
30714
- return [
30715
- `AKA blocked this ${input.subject} \u2014 flagged ${input.ruleIds}${preview}.${note} Remove the flagged content and resubmit.`,
30716
- "If this is intentional and you accept the risk, grant an exception:",
30717
- ...commands,
30718
- "More: aka exception --help"
30719
- ].join("\n");
30720
- }
30721
- function exceptionPointer(references) {
30722
- const ref = references?.[0];
30723
- if (ref === void 0) return "";
30724
- return ` To allow this exact value intentionally, run: aka exception approve ${ref.reference}.`;
30725
- }
30726
-
30727
31189
  // src/hooks/clipboard.ts
30728
31190
  import { spawnSync } from "child_process";
30729
31191
  var defaultSpawner = (cmd, args, input) => {
@@ -30765,21 +31227,6 @@ function writeClipboard(text, opts) {
30765
31227
  // src/hooks/onboarding-nudge.ts
30766
31228
  var ONBOARDING_NUDGE = "AKA Security is installed but not calibrated \u2014 run /aka:setup to tune notifications to this machine (about a minute).";
30767
31229
 
30768
- // src/hooks/resubmit-message.ts
30769
- var REWRITE_OPEN = "----- safe prompt (copy everything between these lines) -----";
30770
- var REWRITE_CLOSE = "----- end safe prompt -----";
30771
- function resubmitMessage(opts) {
30772
- const paste = opts.clipboardWrote ? "It is already on your clipboard \u2014 paste and resubmit." : "Copy it, then paste and resubmit.";
30773
- return [
30774
- `AKA blocked this prompt \u2014 flagged ${opts.ruleIds}. The flagged value never reached the model.`,
30775
- `Here is your prompt with each detected secret replaced by a vault pointer. ${paste}`,
30776
- REWRITE_OPEN,
30777
- opts.rewrite,
30778
- REWRITE_CLOSE,
30779
- "The model works with the pointers; the real values stay in your local vault." + exceptionPointer(opts.blockedRef ? [opts.blockedRef] : void 0)
30780
- ].join("\n");
30781
- }
30782
-
30783
31230
  // src/hooks/shared.ts
30784
31231
  async function readStdin() {
30785
31232
  return new Promise((resolve) => {
@@ -30837,8 +31284,8 @@ function baseMetadata(input) {
30837
31284
  }
30838
31285
 
30839
31286
  // src/hooks/store-health.ts
30840
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
30841
- import { join as join14 } from "path";
31287
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
31288
+ import { join as join15 } from "path";
30842
31289
 
30843
31290
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
30844
31291
  import { randomUUID as randomUUID15 } from "crypto";
@@ -30851,6 +31298,8 @@ var StandaloneDataGateway = class {
30851
31298
  db;
30852
31299
  // Kept for the fingerprint key lookup (exception.key lives beside the store).
30853
31300
  dataDir;
31301
+ // One notice per gateway — see warnRulesetDiscarded.
31302
+ warnedRulesetDiscarded = false;
30854
31303
  constructor(dataDir2, detections = [], meta3) {
30855
31304
  this.db = openLocalDatabase(dataDir2);
30856
31305
  this.dataDir = dataDir2;
@@ -30970,28 +31419,69 @@ var StandaloneDataGateway = class {
30970
31419
  // - ANY invalid rule among enabled packs (all-invalid, partial corruption,
30971
31420
  // or a single malformed entry) → undefined → bundled fallback. Serving a
30972
31421
  // reduced "complete" set would silently drop exactly the corrupted rules
30973
- // with no fallback; the bundled packs are a superset, so falling back
30974
- // never loses coverage. Steady-state installed rules are all valid
30975
- // (generated + Zod-checked), so this only fires on a genuinely
30976
- // malformed/foreign store;
31422
+ // with no fallback. The bundled packs are a superset of AKA's OWN packs,
31423
+ // so falling back never loses coverage there but they contain no
31424
+ // pulled or custom pack, so for those this trades a partial ruleset for
31425
+ // none of them plus the loss of every pack's per-detection enforcement
31426
+ // action. That is deliberate (a store this machine cannot fully validate
31427
+ // is not authoritative), and it is why the cost of REJECTING a rule
31428
+ // matters: `Rule` is strict, so one unrecognized key in one custom rule
31429
+ // reaches this branch, not just a genuinely malformed or foreign store.
31430
+ // `installed-packs.test.ts` pins that per-rule counting;
30977
31431
  // - enabled packs that produce ZERO rules with no invalids (e.g. every
30978
31432
  // enabled pack's rules_json is `[]`) → undefined → bundled fallback: an
30979
31433
  // enabled pack contributing nothing is untrustworthy, not a real
30980
31434
  // "detect nothing" (that is expressed by disabling packs, handled above);
30981
31435
  // - otherwise → the enabled packs' validated rules, marked complete.
31436
+ /**
31437
+ * The discard above is the one ruleset decision this gateway reaches on its
31438
+ * own, and it is the most expensive one here: ONE rejected entry costs the
31439
+ * user every custom rule and every per-detection enforcement action, replaced
31440
+ * by bundled packs that contain neither. Nothing else reports it — a hook is a
31441
+ * short-lived process whose stderr is the only channel it has — so name what
31442
+ * was rejected and where the rest of the list lives.
31443
+ *
31444
+ * Unlike a quarantine verdict this caches nothing: the rejection is re-derived
31445
+ * from the store on every run, so the recovery is to fix or reinstall the pack,
31446
+ * and no line here may offer a command that clears a stored verdict.
31447
+ *
31448
+ * Written at most once per gateway — a second getPolicyBundle() in the same
31449
+ * process would re-report the same finding.
31450
+ */
31451
+ warnRulesetDiscarded(snapshot) {
31452
+ if (this.warnedRulesetDiscarded) return;
31453
+ this.warnedRulesetDiscarded = true;
31454
+ const listed = snapshot.rejectedRules.map((r) => `${r.pack}${r.ruleId === null ? "" : ` "${r.ruleId}"`} (${r.reason})`).join(", ");
31455
+ const undisclosed = snapshot.invalidRules - snapshot.rejectedRules.length;
31456
+ const more = undisclosed > 0 ? `, and ${String(undisclosed)} more` : "";
31457
+ process.stderr.write(
31458
+ `[aka] installed ruleset not used: ${String(snapshot.invalidRules)} rule(s) under enabled packs failed validation, so scanning fell back to the bundled packs and no custom rule or per-detection action is enforced \u2014 rejected: ${listed}${more}; review them with \`aka detections\`
31459
+ `
31460
+ );
31461
+ }
30982
31462
  installedScanRules() {
30983
31463
  try {
30984
31464
  const snapshot = this.db.installedPacks.installedRuleset();
30985
31465
  if (snapshot.installedPacks === 0) return void 0;
30986
31466
  if (snapshot.enabledPacks === 0) {
30987
- return { rules: [], ruleActions: /* @__PURE__ */ new Map(), ruleVersions: /* @__PURE__ */ new Map(), complete: true };
31467
+ return {
31468
+ rules: [],
31469
+ ruleActions: /* @__PURE__ */ new Map(),
31470
+ ruleVersions: /* @__PURE__ */ new Map(),
31471
+ reversibleRules: /* @__PURE__ */ new Set(),
31472
+ complete: true
31473
+ };
31474
+ }
31475
+ if (snapshot.invalidRules > 0) {
31476
+ this.warnRulesetDiscarded(snapshot);
31477
+ return void 0;
30988
31478
  }
30989
- if (snapshot.invalidRules > 0) return void 0;
30990
31479
  if (snapshot.rules.length === 0) return void 0;
30991
31480
  return {
30992
31481
  rules: snapshot.rules,
30993
31482
  ruleActions: snapshot.ruleActions,
30994
31483
  ruleVersions: snapshot.ruleVersions,
31484
+ reversibleRules: snapshot.reversibleRules,
30995
31485
  complete: true
30996
31486
  };
30997
31487
  } catch {
@@ -31019,6 +31509,11 @@ var StandaloneDataGateway = class {
31019
31509
  return {
31020
31510
  version: "local",
31021
31511
  policies: [...policies, ...rulePolicies],
31512
+ // The reversibility half of each pack's assignment. Emitted only under the
31513
+ // authoritative installed snapshot, exactly like rulePolicies above: the
31514
+ // bundled-packs fallback carries no per-pack assignment, so it carries no
31515
+ // reversibility either and every redaction there stays one-way.
31516
+ reversibleRuleIds: installed ? [...installed.reversibleRules] : [],
31022
31517
  rules: installed ? installed.rules : [],
31023
31518
  ...installed ? { rulesComplete: true } : {},
31024
31519
  ...installed ? { ruleVersions: Object.fromEntries(installed.ruleVersions) } : {},
@@ -31037,29 +31532,32 @@ var StandaloneDataGateway = class {
31037
31532
  return this.db.exceptions.recordBlocked(entry);
31038
31533
  }
31039
31534
  // Retention sweep over TERMINAL exception rows (revoked / expired / budget
31040
- // exhausted) — standalone-only store maintenance, invoked from SessionStart,
31041
- // not part of the DataGateway port. Active grants are never touched.
31535
+ // exhausted) — local-store maintenance, invoked from SessionStart through the
31536
+ // LocalStoreMaintenance capability rather than the DataGateway port. Active
31537
+ // grants are never touched.
31042
31538
  sweepTerminalExceptions(retentionMs) {
31043
31539
  return this.db.exceptions.sweepTerminal(retentionMs);
31044
31540
  }
31045
- // The warn-era enforcement cap, standalone-only store maintenance invoked
31046
- // from SessionStart, not part of the DataGateway port. Returns the number
31047
- // of block/redact rows capped to warn (0 for a redact-policy store or an
31048
- // already-capped one).
31541
+ // The warn-era enforcement cap — local-store maintenance, invoked from
31542
+ // SessionStart through the LocalStoreMaintenance capability rather than
31543
+ // the DataGateway port. Returns the number of block/redact rows capped to
31544
+ // warn (0 for a redact-policy store or an already-capped one).
31049
31545
  capWarnEraEnforcement(policyMode) {
31050
31546
  const { capped } = capWarnEraEnforcementOnce(this.db, policyMode, this.dataDir);
31051
31547
  return { capped };
31052
31548
  }
31053
31549
  // One project-file scan → the local project_file tree (one transaction inside
31054
- // the LocalDatabase, fail-open there). Like the sweep above, this is
31055
- // NOT part of the DataGateway port: the file tree is a local-store read model.
31550
+ // the LocalDatabase, fail-open there). Like the sweep above, this is reached
31551
+ // through the LocalStoreMaintenance capability rather than the DataGateway
31552
+ // port: the file tree is a local-store read model.
31056
31553
  recordProjectFiles(projectId, scan2) {
31057
31554
  this.db.recordProjectFiles(projectId, scan2);
31058
31555
  return Promise.resolve();
31059
31556
  }
31060
31557
  // Fold ghost source_project rows minted by the pre-worktree-fix resolver
31061
- // (checkout-path identities) into the repo's canonical row. Standalone-only
31062
- // store maintenance, invoked from SessionStart. Fail-open in the store.
31558
+ // (checkout-path identities) into the repo's canonical row. Local-store
31559
+ // maintenance, invoked from SessionStart through the LocalStoreMaintenance
31560
+ // capability. Fail-open in the store.
31063
31561
  reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
31064
31562
  this.db.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
31065
31563
  return Promise.resolve();
@@ -31070,10 +31568,10 @@ var StandaloneDataGateway = class {
31070
31568
  * executing the plugin generation they started with (Claude Code caches
31071
31569
  * plugin versions), and the write gate makes their installed-pack writes
31072
31570
  * silent no-ops — this is the one-line nudge telling the user WHY, and that
31073
- * a restart picks the newer plugin up. Standalone-only, invoked from
31074
- * SessionStart, not part of the DataGateway port. Fail-open: any error →
31075
- * null (no notice), and unparseable versions compare equal so garbage can
31076
- * never fire it.
31571
+ * a restart picks the newer plugin up. Local-store maintenance, invoked
31572
+ * from SessionStart through the LocalStoreMaintenance capability rather
31573
+ * than the DataGateway port. Fail-open: any error → null (no notice), and
31574
+ * unparseable versions compare equal so garbage can never fire it.
31077
31575
  */
31078
31576
  staleBinaryNotice(currentVersion) {
31079
31577
  try {
@@ -31151,7 +31649,8 @@ var StandaloneDataGateway = class {
31151
31649
 
31152
31650
  // ../../packages/plugin-runtime/src/resolve.ts
31153
31651
  var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
31154
- function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFactory) {
31652
+ var defaultGatewayFactory = standaloneGatewayFactory;
31653
+ function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
31155
31654
  return gatewayFactory(config2, meta3);
31156
31655
  }
31157
31656
 
@@ -31173,19 +31672,124 @@ function storeUnavailableMessage(dbPath2) {
31173
31672
  }
31174
31673
  function claimStoreUnavailableWarning(dataDir2, sessionId) {
31175
31674
  if (!sessionId) return true;
31176
- const path = join14(dataDir2, STORE_WARNING_MARKER);
31675
+ const path = join15(dataDir2, STORE_WARNING_MARKER);
31177
31676
  try {
31178
- if (readFileSync9(path, "utf8") === sessionId) return false;
31677
+ if (readFileSync10(path, "utf8") === sessionId) return false;
31179
31678
  } catch {
31180
31679
  }
31181
31680
  try {
31182
- mkdirSync5(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
31681
+ mkdirSync4(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
31183
31682
  writeFileSync7(path, sessionId, { mode: DATA_FILE_MODE });
31184
31683
  } catch {
31185
31684
  }
31186
31685
  return true;
31187
31686
  }
31188
31687
 
31688
+ // src/present.ts
31689
+ var fg = (hex3) => (text) => {
31690
+ const r = Number.parseInt(hex3.slice(1, 3), 16);
31691
+ const g = Number.parseInt(hex3.slice(3, 5), 16);
31692
+ const b = Number.parseInt(hex3.slice(5, 7), 16);
31693
+ return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
31694
+ };
31695
+ var paint = {
31696
+ brand: fg("#33e6c6"),
31697
+ // --color-brand · ▸▸ AKA wordmark (accent text)
31698
+ dim: fg("#838995"),
31699
+ // --color-text-3 · separators · "/100" · the "unreviewed" label
31700
+ bold: (text) => `\x1B[1m${text}\x1B[0m`,
31701
+ // the health score number
31702
+ ok: fg("#0db15f"),
31703
+ // --color-ok · healthy ● dot
31704
+ critical: fg("#e63448"),
31705
+ // --color-sev-critical · ■ and the open-findings flag
31706
+ high: fg("#e97a0a"),
31707
+ // --color-sev-high · ■ and the mid-health dot
31708
+ medium: fg("#f7bd00"),
31709
+ // --color-sev-medium · ■
31710
+ low: fg("#0581d4")
31711
+ // --color-sev-low · ■ (azure blue, not purple)
31712
+ };
31713
+
31714
+ // src/exception-guidance.ts
31715
+ function blockMessage(input) {
31716
+ const preview = input.blockedRef ? ` (${input.blockedRef.maskedValue})` : "";
31717
+ const commands = input.blockedRef ? [
31718
+ ` aka exception approve ${input.blockedRef.reference} (asks for scope + reason, then resubmit)`,
31719
+ " aka exception approve <value> (same flow, pasting the blocked value itself)"
31720
+ ] : [" aka exception approve (asks for scope + reason, then resubmit)"];
31721
+ const note = input.note ? ` ${input.note}` : "";
31722
+ return [
31723
+ `AKA blocked this ${input.subject} \u2014 flagged ${input.ruleIds}${preview}.${note} Remove the flagged content and resubmit.`,
31724
+ "If this is intentional and you accept the risk, grant an exception:",
31725
+ ...commands,
31726
+ "More: aka exception --help"
31727
+ ].join("\n");
31728
+ }
31729
+ function exceptionPointer(references) {
31730
+ const ref = references?.[0];
31731
+ if (ref === void 0) return "";
31732
+ return ` To allow this exact value intentionally, run: aka exception approve ${ref.reference}.`;
31733
+ }
31734
+
31735
+ // src/hooks/resubmit-message.ts
31736
+ var REWRITE_OPEN = "----- safe prompt (copy everything between these lines) -----";
31737
+ var REWRITE_CLOSE = "----- end safe prompt -----";
31738
+ function resubmitMessage(opts) {
31739
+ const paste = opts.clipboardWrote ? "It is already on your clipboard \u2014 paste and resubmit." : "Copy it, then paste and resubmit.";
31740
+ return [
31741
+ `AKA blocked this prompt \u2014 flagged ${opts.ruleIds}. The flagged value never reached the model.`,
31742
+ `Here is your prompt with each detected secret replaced by a vault pointer. ${paste}`,
31743
+ REWRITE_OPEN,
31744
+ opts.rewrite,
31745
+ REWRITE_CLOSE,
31746
+ "The model works with the pointers; the real values stay in your local vault." + exceptionPointer(opts.blockedRef ? [opts.blockedRef] : void 0)
31747
+ ].join("\n");
31748
+ }
31749
+
31750
+ // src/hooks/user-prompt-submit-decision.ts
31751
+ async function decideUserPromptSubmit(prompt, result, deps = {}) {
31752
+ if (result.action === "block" || result.action === "redact") {
31753
+ const ruleIds = uniqueRuleIds(result.findings);
31754
+ const blockedRef = result.blockedReferences?.[0];
31755
+ const custody = result.action === "redact" ? new Set(result.reversibleFindings ?? []) : new Set(result.findings);
31756
+ const rewrite = deps.tokenizePrompt ? await pointerizedRewrite(prompt, result.findings, custody, deps.tokenizePrompt) : null;
31757
+ if (rewrite !== null) {
31758
+ const clipboardWrote = writeClipboardSafely(rewrite, deps.writeClipboard);
31759
+ return {
31760
+ decision: "block",
31761
+ reason: resubmitMessage({ ruleIds, rewrite, clipboardWrote, blockedRef })
31762
+ };
31763
+ }
31764
+ return { decision: "block", reason: blockMessage({ subject: "prompt", ruleIds, blockedRef }) };
31765
+ }
31766
+ if (result.action === "warn") {
31767
+ return {
31768
+ systemMessage: `AKA flagged sensitive content (${uniqueRuleIds(result.findings)}) \u2014 sent unchanged.${exceptionPointer(result.blockedReferences)}`
31769
+ };
31770
+ }
31771
+ return null;
31772
+ }
31773
+ function writeClipboardSafely(text, write) {
31774
+ try {
31775
+ return write?.(text) ?? false;
31776
+ } catch {
31777
+ return false;
31778
+ }
31779
+ }
31780
+ async function pointerizedRewrite(prompt, findings, reversible, tokenize) {
31781
+ try {
31782
+ const tokenized = await tokenize(prompt, findings, reversible);
31783
+ if (tokenized.pointers.length === 0) return null;
31784
+ for (const finding of findings) {
31785
+ if (finding.rawMatch !== "" && tokenized.text.includes(finding.rawMatch)) return null;
31786
+ }
31787
+ return tokenized.text;
31788
+ } catch {
31789
+ return null;
31790
+ }
31791
+ }
31792
+
31189
31793
  // src/hooks/user-prompt-submit.ts
31190
31794
  async function main() {
31191
31795
  const input = parseJson(await readStdin());
@@ -31206,52 +31810,32 @@ async function main() {
31206
31810
  try {
31207
31811
  result = await runtime.capture({
31208
31812
  kind: "prompt",
31209
- sourceTool: "claude-code",
31813
+ sourceTool: SOURCE_TOOL.ClaudeCode,
31210
31814
  text: prompt,
31211
31815
  metadata
31212
31816
  });
31213
31817
  } finally {
31214
31818
  await runtime.close();
31215
31819
  }
31216
- if (result.action === "block" || result.action === "redact") {
31217
- const ruleIds = uniqueRuleIds(result.findings);
31218
- const blockedRef = result.blockedReferences?.[0];
31219
- let reason = blockMessage({ subject: "prompt", ruleIds, blockedRef });
31220
- if (isVaultConsentValid(config2.settings.vaultConsent)) {
31221
- const rewrite = await pointerizedRewrite(prompt, result.findings);
31222
- if (rewrite !== null) {
31223
- const clipboardWrote = writeClipboard(rewrite);
31224
- reason = resubmitMessage({ ruleIds, rewrite, clipboardWrote, blockedRef });
31225
- }
31226
- }
31227
- await emit({ decision: "block", reason });
31228
- return;
31229
- }
31230
- if (result.action === "warn") {
31231
- await emit({
31232
- systemMessage: `AKA flagged sensitive content (${uniqueRuleIds(result.findings)}) \u2014 sent unchanged.${exceptionPointer(result.blockedReferences)}`
31233
- });
31820
+ const decision = await decideUserPromptSubmit(prompt, result, {
31821
+ tokenizePrompt: isVaultConsentValid(config2.settings.vaultConsent) ? (text, findings, reversible) => createVaultGlue().tokenizeText(text, {
31822
+ findings,
31823
+ // Per-finding custody, exactly as the tool-call paths pass it.
31824
+ // Omitting it means "keep all", which would vault a value whose
31825
+ // detection chose one-way Redact.
31826
+ reversible,
31827
+ sighting: { location: "prompt", kind: "prompt" }
31828
+ }) : void 0,
31829
+ writeClipboard
31830
+ });
31831
+ if (decision !== null) {
31832
+ await emit(decision);
31234
31833
  return;
31235
31834
  }
31236
31835
  if (!config2.onboarded && claimOnboardingNudge(config2.dataDir, sessionId)) {
31237
31836
  await emit({ systemMessage: ONBOARDING_NUDGE });
31238
31837
  }
31239
31838
  }
31240
- async function pointerizedRewrite(prompt, findings) {
31241
- try {
31242
- const tokenized = await createVaultGlue().tokenizeText(prompt, {
31243
- findings,
31244
- sighting: { location: "prompt", kind: "prompt" }
31245
- });
31246
- if (tokenized.pointers.length === 0) return null;
31247
- for (const finding of findings) {
31248
- if (finding.rawMatch !== "" && tokenized.text.includes(finding.rawMatch)) return null;
31249
- }
31250
- return tokenized.text;
31251
- } catch {
31252
- return null;
31253
- }
31254
- }
31255
31839
  try {
31256
31840
  await main();
31257
31841
  } catch {