@akasecurity/ai-tc-claude-code 0.9.6 → 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.
@@ -588,6 +588,10 @@ var SQLITE_MIGRATIONS = [
588
588
  {
589
589
  tag: "0020_secret_vault_pagination_indexes",
590
590
  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');"
591
+ },
592
+ {
593
+ tag: "0021_finding_resolution_resolved_at_index",
594
+ sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
591
595
  }
592
596
  ];
593
597
 
@@ -15303,6 +15307,50 @@ function date4(params) {
15303
15307
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
15304
15308
  config(en_default());
15305
15309
 
15310
+ // ../../packages/schema/src/zod/harness-map.ts
15311
+ var HARNESS = {
15312
+ ClaudeCode: "claudecode",
15313
+ Cursor: "cursor",
15314
+ Copilot: "copilot",
15315
+ Codex: "codex",
15316
+ Antigravity: "antigravity",
15317
+ Windsurf: "windsurf",
15318
+ ClaudeDesktop: "claudedesktop",
15319
+ ChatGpt: "chatgpt",
15320
+ ClaudeAi: "claudeai",
15321
+ Api: "api"
15322
+ };
15323
+ var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
15324
+ var SOURCE_TOOL = {
15325
+ ClaudeCode: "claude-code",
15326
+ ClaudeDesktop: "claude-desktop",
15327
+ Cursor: "cursor",
15328
+ ChatGpt: "chatgpt",
15329
+ ClaudeAi: "claude-ai",
15330
+ Copilot: "github-copilot",
15331
+ Codex: "codex",
15332
+ Antigravity: "antigravity",
15333
+ // No harness counterpart, deliberately: the CLI's own captures and a capture
15334
+ // whose tool could not be identified both render through the read side's
15335
+ // miss path rather than as a harness of their own.
15336
+ Cli: "cli",
15337
+ Unknown: "unknown"
15338
+ };
15339
+ var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
15340
+ var TOOL_TO_HARNESS = {
15341
+ [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
15342
+ [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
15343
+ [SOURCE_TOOL.Copilot]: HARNESS.Copilot,
15344
+ [SOURCE_TOOL.Cursor]: HARNESS.Cursor,
15345
+ [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
15346
+ [SOURCE_TOOL.Codex]: HARNESS.Codex,
15347
+ [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
15348
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
15349
+ };
15350
+ function harnessFromTool(tool) {
15351
+ return TOOL_TO_HARNESS[tool] ?? tool;
15352
+ }
15353
+
15306
15354
  // ../../packages/schema/src/zod/finding.ts
15307
15355
  var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
15308
15356
  var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
@@ -15325,21 +15373,22 @@ var Finding = external_exports.object({
15325
15373
  }).meta({ id: "Finding" });
15326
15374
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15327
15375
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15328
- var FindingProvider = external_exports.enum([
15329
- "claudecode",
15330
- "claudedesktop",
15331
- "cursor",
15332
- "copilot",
15333
- "chatgpt",
15334
- "claudeai",
15335
- "codex",
15336
- "antigravity",
15337
- "api"
15376
+ var FindingProvider = Harness.extract([
15377
+ "ClaudeCode",
15378
+ "ClaudeDesktop",
15379
+ "Cursor",
15380
+ "Copilot",
15381
+ "ChatGpt",
15382
+ "ClaudeAi",
15383
+ "Codex",
15384
+ "Antigravity",
15385
+ "Api"
15338
15386
  ]).meta({ id: "FindingProvider" });
15339
15387
  var FindingCategory = external_exports.enum([
15340
15388
  "secret",
15341
15389
  "pii",
15342
15390
  "source_code",
15391
+ "code_flaw",
15343
15392
  "external_share",
15344
15393
  "mcp_server",
15345
15394
  "customer_data",
@@ -15519,6 +15568,7 @@ var FindingInstanceDetail = FindingInstance.extend({
15519
15568
  policy: FindingPolicyRef
15520
15569
  }).meta({ id: "FindingInstanceDetail" });
15521
15570
  var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15571
+ var MAX_FLAT_FINDINGS_LIMIT = 200;
15522
15572
  var ListFindingInstancesQuery = external_exports.object({
15523
15573
  severity: external_exports.array(Severity).optional(),
15524
15574
  // Rule ids, the same vocabulary the grouped list's `subtype` carries.
@@ -15538,7 +15588,7 @@ var ListFindingInstancesQuery = external_exports.object({
15538
15588
  q: external_exports.string().optional(),
15539
15589
  sessionId: external_exports.string().optional(),
15540
15590
  from: external_exports.iso.datetime().optional(),
15541
- limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15591
+ limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
15542
15592
  cursor: external_exports.string().optional()
15543
15593
  });
15544
15594
  var ListFindingInstancesResponse = external_exports.object({
@@ -15600,33 +15650,6 @@ var ListFindingLocationsResponse = external_exports.object({
15600
15650
  hasMore: external_exports.boolean()
15601
15651
  }).meta({ id: "ListFindingLocationsResponse" });
15602
15652
 
15603
- // ../../packages/schema/src/zod/harness-map.ts
15604
- var Harness = external_exports.enum([
15605
- "claudecode",
15606
- "cursor",
15607
- "copilot",
15608
- "codex",
15609
- "antigravity",
15610
- "windsurf",
15611
- "claudedesktop",
15612
- "chatgpt",
15613
- "claudeai",
15614
- "api"
15615
- ]).meta({ id: "Harness" });
15616
- var TOOL_TO_HARNESS = {
15617
- "claude-code": "claudecode",
15618
- "claude-desktop": "claudedesktop",
15619
- "github-copilot": "copilot",
15620
- cursor: "cursor",
15621
- chatgpt: "chatgpt",
15622
- codex: "codex",
15623
- antigravity: "antigravity",
15624
- "claude-ai": "claudeai"
15625
- };
15626
- function harnessFromTool(tool) {
15627
- return TOOL_TO_HARNESS[tool] ?? tool;
15628
- }
15629
-
15630
15653
  // ../../packages/schema/src/zod/meta.ts
15631
15654
  var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
15632
15655
  var AuditEventType = external_exports.enum([
@@ -16079,686 +16102,37 @@ var ActivityOverviewResponse = external_exports.object({
16079
16102
  sessions: ListActivitySessionsResponse
16080
16103
  }).meta({ id: "ActivityOverviewResponse" });
16081
16104
 
16082
- // ../../packages/schema/src/zod/event.ts
16083
- var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
16084
- var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
16085
- var SourceTool = external_exports.enum([
16086
- "claude-code",
16087
- "claude-desktop",
16088
- "cursor",
16089
- "chatgpt",
16090
- "claude-ai",
16091
- "github-copilot",
16092
- "codex",
16093
- "antigravity",
16094
- "cli",
16095
- "unknown"
16096
- ]).meta({ id: "SourceTool" });
16097
- var EventMetadata = external_exports.object({
16098
- sessionId: external_exports.string().optional(),
16099
- repo: external_exports.string().optional(),
16100
- filePath: external_exports.string().optional(),
16101
- // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
16102
- // set by the tool-scanning hooks. The tool NAME only — never the tool's
16103
- // arguments or output, which can carry the very value a finding masked
16104
- // (metadata is stored unredacted). Gives findings on non-file captures a
16105
- // display location ("via Bash") when no filePath exists.
16106
- toolName: external_exports.string().optional(),
16107
- // Set (true) by the worktree scanner when the file is excluded by the
16108
- // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
16109
- // generated code can leak real secrets — but the provenance is recorded so
16110
- // policy/dashboards can treat those findings as informational rather than
16111
- // blocking. Omitted (not false) for tracked files and non-scan events.
16112
- gitignored: external_exports.boolean().optional(),
16113
- // Set (true) ONLY when the event's `content` is the COMPLETE file at
16114
- // capture time (a worktree scan reading from disk). Hook-captured edits
16115
- // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
16116
- // this. The resolver-on-ingest keys its fixed-at-source dropout
16117
- // diff on this marker: only a whole-file snapshot can prove a previously
16118
- // open finding is gone; a fragment's absence proves nothing (the secret
16119
- // may live outside the hunk). Omitted (not false) for fragments and
16120
- // non-scan events, so pre-marker clients safely default to the
16121
- // non-authoritative path.
16122
- wholeFile: external_exports.boolean().optional(),
16123
- model: external_exports.string().optional(),
16124
- turnIndex: external_exports.number().int().nonnegative().optional(),
16125
- // Distributed-tracing correlation. `correlationId` ties a recorded event back
16126
- // to the request that captured/ingested it (a UUID, generated independently of
16127
- // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
16128
- // originating span when telemetry is enabled. Both optional + backward
16129
- // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
16130
- correlationId: external_exports.uuid().optional(),
16131
- traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
16132
- // Ids of the detection exceptions that downgraded findings in this capture
16133
- // to 'allow' — the enforcement audit trail's link back to the grant that
16134
- // authorized the bypass. Absent on captures where no exception applied.
16135
- exceptionIds: external_exports.array(external_exports.guid()).optional()
16136
- }).meta({ id: "EventMetadata" });
16137
- var Event = external_exports.object({
16138
- id: external_exports.guid(),
16139
- sourceTool: SourceTool,
16140
- kind: EventKind,
16141
- occurredAt: external_exports.iso.datetime(),
16142
- contentHash: external_exports.string(),
16143
- content: external_exports.string(),
16144
- metadata: EventMetadata.optional()
16145
- }).meta({ id: "Event" });
16146
- var IngestEvent = Event.meta({ id: "IngestEvent" });
16147
- var IngestBatch = external_exports.object({
16148
- events: external_exports.array(IngestEvent).min(1).max(100),
16149
- // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
16150
- // additionally rejects any event whose contentHash the store has already
16151
- // recorded — for re-runnable bulk ingest (worktree scan, transcript
16152
- // backfill), where a re-run mints fresh event ids for identical content and
16153
- // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
16154
- // two genuinely separate prompts can be byte-identical and both belong on
16155
- // the timeline.
16156
- dedupe: external_exports.literal("content-hash").optional()
16157
- }).meta({ id: "IngestBatch" });
16158
-
16159
- // ../../packages/schema/src/zod/inventory.ts
16160
- var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16161
- var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16162
- var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16163
- var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16164
- var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16165
- var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16166
- var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16167
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16168
- var AccessCounts = external_exports.object({
16169
- open: external_exports.number().int().nonnegative(),
16170
- approved: external_exports.number().int().nonnegative(),
16171
- blocked: external_exports.number().int().nonnegative(),
16172
- total: external_exports.number().int().nonnegative()
16173
- }).meta({ id: "AccessCounts" });
16174
- var AssetSummary = external_exports.object({
16175
- id: external_exports.string(),
16176
- type: AssetType,
16177
- name: external_exports.string(),
16178
- sub: external_exports.string(),
16179
- flags: external_exports.array(Flag),
16180
- /** MCP servers only — omitted for all other types. */
16181
- trust: TrustLevel.optional()
16182
- }).meta({ id: "AssetSummary" });
16183
- var ProjectSummary = external_exports.object({
16184
- id: external_exports.string(),
16185
- name: external_exports.string(),
16186
- repo: external_exports.string(),
16187
- visibility: Visibility,
16188
- language: external_exports.string(),
16189
- policyDefault: AccessLevel,
16190
- updatedAt: external_exports.iso.datetime(),
16191
- accessCounts: AccessCounts,
16192
- findingsCount: external_exports.number().int().nonnegative()
16193
- }).meta({ id: "ProjectSummary" });
16194
- var HarnessCategory = external_exports.object({
16195
- /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16196
- type: AssetType,
16197
- assets: external_exports.array(AssetSummary)
16105
+ // ../../packages/schema/src/zod/config-inventory.ts
16106
+ var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16107
+ var SkillScanEntry = external_exports.object({
16108
+ name: external_exports.string().min(1),
16109
+ // The identity source: a marketplace repo for plugin skills (e.g.
16110
+ // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16111
+ // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16112
+ // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16113
+ source: external_exports.string().min(1),
16114
+ scope: ConfigScope,
16115
+ pluginName: external_exports.string().optional(),
16116
+ // Volatile — rides the attribute bag, never the identity hash.
16117
+ version: external_exports.string().optional(),
16118
+ description: external_exports.string().optional(),
16119
+ // Skill directory mtime (ISO) the "updated Nd ago" freshness signal.
16120
+ updatedAt: external_exports.iso.datetime().optional(),
16121
+ // Filesystem path — the promoted inventory `location` column.
16122
+ location: external_exports.string().optional()
16198
16123
  });
16199
- var HarnessSummary = external_exports.object({
16200
- id: HarnessId,
16201
- label: external_exports.string(),
16202
- kind: external_exports.string(),
16203
- version: external_exports.string(),
16204
- sessions: external_exports.number().int().nonnegative(),
16205
- assetCount: external_exports.number().int().nonnegative(),
16206
- flagCount: external_exports.number().int().nonnegative(),
16207
- projects: external_exports.array(ProjectSummary),
16208
- categories: external_exports.array(HarnessCategory)
16209
- }).meta({ id: "HarnessSummary" });
16210
- var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16211
- var AssetGroup = external_exports.object({
16212
- /** Group key — never project (enforced at service layer). */
16213
- type: AssetType,
16214
- total: external_exports.number().int().nonnegative(),
16215
- /**
16216
- * MCP group only — omitted for all other types.
16217
- * Partial: only TrustLevel keys with non-zero counts are included.
16218
- * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16219
- */
16220
- trustRollup: external_exports.object({
16221
- "known-good": external_exports.number().int().nonnegative(),
16222
- risky: external_exports.number().int().nonnegative(),
16223
- unapproved: external_exports.number().int().nonnegative()
16224
- }).partial().strict().optional(),
16225
- /**
16226
- * Partial: only Flag keys with non-zero counts are included.
16227
- * Strict: unknown keys are rejected — only Flag values are valid keys.
16228
- */
16229
- flagRollup: external_exports.object({
16230
- update: external_exports.number().int().nonnegative(),
16231
- stale: external_exports.number().int().nonnegative(),
16232
- conflict: external_exports.number().int().nonnegative(),
16233
- unknown: external_exports.number().int().nonnegative(),
16234
- change: external_exports.number().int().nonnegative(),
16235
- untracked: external_exports.number().int().nonnegative(),
16236
- risk: external_exports.number().int().nonnegative(),
16237
- findings: external_exports.number().int().nonnegative()
16238
- }).partial().strict(),
16239
- items: external_exports.array(AssetSummary)
16240
- }).meta({ id: "AssetGroup" });
16241
- var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16242
- var McpTool = external_exports.object({
16243
- name: external_exports.string(),
16244
- signature: external_exports.string(),
16245
- description: external_exports.string(),
16246
- write: external_exports.boolean(),
16247
- /** Non-null string when tool is dangerous / blocked; null otherwise. */
16248
- risk: external_exports.string().nullable()
16249
- }).meta({ id: "McpTool" });
16250
- var AssetFindingRef = external_exports.object({
16251
- id: external_exports.string(),
16252
- title: external_exports.string(),
16253
- note: external_exports.string()
16254
- });
16255
- var AssetDetail = AssetSummary.extend({
16256
- /** string | null — null when no description is available. */
16257
- description: external_exports.string().nullable(),
16258
- /** trustLevel | null — null for non-MCP assets. */
16259
- trust: TrustLevel.nullable(),
16260
- /** Type-specific raw key/values — FE renders the grid. */
16261
- meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16262
- /** always present — object when there is an active finding, null when absent. */
16263
- finding: AssetFindingRef.nullable(),
16264
- /** MCP exposed-tools list — omitted for non-mcp. */
16265
- tools: external_exports.array(McpTool).optional()
16266
- }).meta({ id: "AssetDetail" });
16267
- var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16268
- var InventoryStats = external_exports.object({
16269
- /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16270
- attention: external_exports.number().int().nonnegative(),
16271
- byType: external_exports.object({
16272
- project: external_exports.number().int().nonnegative(),
16273
- skill: external_exports.number().int().nonnegative(),
16274
- mcp: external_exports.number().int().nonnegative(),
16275
- hook: external_exports.number().int().nonnegative(),
16276
- config: external_exports.number().int().nonnegative()
16277
- }),
16278
- harnesses: external_exports.number().int().nonnegative(),
16279
- mcpTrust: external_exports.object({
16280
- "known-good": external_exports.number().int().nonnegative(),
16281
- risky: external_exports.number().int().nonnegative(),
16282
- unapproved: external_exports.number().int().nonnegative()
16283
- })
16284
- }).meta({ id: "InventoryStats" });
16285
- var FileSummary = external_exports.object({
16286
- path: external_exports.string(),
16287
- name: external_exports.string(),
16288
- origin: Origin,
16289
- /** Effective access (override applied). */
16290
- access: AccessLevel,
16291
- /** True when a file_access_override differs from the computed default. */
16292
- isCustom: external_exports.boolean(),
16293
- findings: external_exports.number().int().nonnegative(),
16294
- /** When the file was auto-blocked by a detection; null when not blocked. */
16295
- blockedAt: external_exports.iso.datetime().nullable().optional(),
16296
- /** Why the file was blocked; null when absent. */
16297
- note: external_exports.string().nullable().optional()
16298
- }).meta({ id: "FileSummary" });
16299
- var FolderSummary = external_exports.object({
16300
- name: external_exports.string(),
16301
- path: external_exports.string(),
16302
- /** Rollup of effective access across all descendants. */
16303
- accessCounts: AccessCounts
16304
- }).meta({ id: "FolderSummary" });
16305
- var ProjectTreeResponse = external_exports.object({
16306
- project: external_exports.object({
16307
- id: external_exports.string(),
16308
- repo: external_exports.string(),
16309
- visibility: Visibility
16310
- }),
16311
- path: external_exports.string(),
16312
- /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16313
- folders: external_exports.array(FolderSummary).optional(),
16314
- files: external_exports.array(FileSummary)
16315
- }).meta({ id: "ProjectTreeResponse" });
16316
- var FileDetail = FileSummary.extend({
16317
- project: external_exports.object({
16318
- repo: external_exports.string(),
16319
- visibility: Visibility,
16320
- language: external_exports.string(),
16321
- policyDefault: AccessLevel,
16322
- updatedAt: external_exports.iso.datetime()
16323
- }),
16324
- findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16325
- }).meta({ id: "FileDetail" });
16326
- var SetFileAccessBody = external_exports.object({
16327
- path: external_exports.string(),
16328
- access: AccessLevel
16329
- }).meta({ id: "SetFileAccessBody" });
16330
- var SetFileAccessResponse = external_exports.object({
16331
- file: FileSummary,
16332
- accessCounts: AccessCounts
16333
- }).meta({ id: "SetFileAccessResponse" });
16334
- var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16335
- var HarnessEventItem = external_exports.object({
16336
- kind: HarnessEventKind,
16337
- title: external_exports.string(),
16338
- detail: external_exports.string(),
16339
- occurredAt: external_exports.iso.datetime(),
16340
- findingId: external_exports.string().nullable().optional()
16341
- }).meta({ id: "HarnessEventItem" });
16342
- var HarnessEventsResponse = external_exports.object({
16343
- counts: external_exports.object({
16344
- block: external_exports.number().int().nonnegative(),
16345
- redact: external_exports.number().int().nonnegative(),
16346
- warn: external_exports.number().int().nonnegative()
16347
- }),
16348
- items: external_exports.array(HarnessEventItem)
16349
- }).meta({ id: "HarnessEventsResponse" });
16350
- var RescanResponse = external_exports.object({
16351
- jobId: external_exports.string(),
16352
- startedAt: external_exports.iso.datetime()
16353
- }).meta({ id: "RescanResponse" });
16354
- var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16355
- var ListAssetsQuery = external_exports.object({
16356
- /** Filter by one or more AssetType values; absent means all types. */
16357
- type: external_exports.array(AssetType).optional(),
16358
- /** Free-text search term. */
16359
- q: external_exports.string().optional()
16360
- });
16361
- var GetProjectTreeQuery = external_exports.object({
16362
- /** Subtree root path; defaults to repository root when absent. */
16363
- path: external_exports.string().optional(),
16364
- /** Free-text filter applied to file paths. */
16365
- q: external_exports.string().optional(),
16366
- /**
16367
- * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16368
- * file across the whole repo (folders omitted, most-recent first), ignoring
16369
- * `path`/`q` — powers the project-wide "recently blocked" strip.
16370
- */
16371
- filter: external_exports.enum(["blocked"]).optional()
16372
- });
16373
- var GetProjectFileQuery = external_exports.object({
16374
- /** Repository-relative file path; absent or empty → 400. */
16375
- path: external_exports.string()
16376
- });
16377
- var GetHarnessEventsQuery = external_exports.object({
16378
- /** Maximum number of events to return. Range: 1–50; default: 7. */
16379
- limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16380
- });
16381
-
16382
- // ../../packages/schema/src/zod/exception.ts
16383
- var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
16384
- var ExceptionConditions = external_exports.object({
16385
- repo: external_exports.string().optional(),
16386
- sourceTool: external_exports.string().optional(),
16387
- provider: external_exports.string().optional()
16388
- }).strict();
16389
- var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16390
- var DetectionException = external_exports.object({
16391
- id: external_exports.guid(),
16392
- ruleId: external_exports.string(),
16393
- // Denormalized from the rule, for reporting — never matched on.
16394
- category: DetectionCategory,
16395
- // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
16396
- // fingerprint, never the raw value, and never reversible. Matching recomputes
16397
- // the fingerprint from a fresh capture; the value itself is never stored.
16398
- // Shape-constrained so a malformed — or accidentally raw — value is rejected
16399
- // at the boundary rather than persisted.
16400
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
16401
- // Version of the fingerprint key the grant was written under; a rotated key
16402
- // invalidates old grants rather than silently mismatching them.
16403
- keyVersion: external_exports.number().int().positive(),
16404
- // maskMatch() preview of the approved value — never the raw value.
16405
- maskedValue: external_exports.string(),
16406
- capability: ExceptionCapability.default("suppress"),
16407
- scope: ExceptionScope,
16408
- expiresAt: external_exports.iso.datetime().nullable(),
16409
- maxUses: external_exports.number().int().positive().nullable(),
16410
- useCount: external_exports.number().int().nonnegative(),
16411
- lastUsedAt: external_exports.iso.datetime().nullable(),
16412
- // Mandatory: every grant carries the human reason it exists.
16413
- justification: external_exports.string().min(1),
16414
- conditions: ExceptionConditions.nullable(),
16415
- createdBy: external_exports.string(),
16416
- createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
16417
- createdAt: external_exports.iso.datetime(),
16418
- updatedAt: external_exports.iso.datetime(),
16419
- // Revocation is terminal and retained — consumed/expired/revoked rows are
16420
- // audit evidence; nothing in the exception lifecycle hard-deletes.
16421
- revokedAt: external_exports.iso.datetime().nullable(),
16422
- revokedBy: external_exports.string().nullable(),
16423
- revokeReason: external_exports.string().nullable()
16424
- });
16425
- var ExceptionBundleEntry = DetectionException.pick({
16426
- id: true,
16427
- ruleId: true,
16428
- valueFingerprint: true,
16429
- keyVersion: true,
16430
- capability: true,
16431
- expiresAt: true,
16432
- maxUses: true,
16433
- useCount: true,
16434
- conditions: true
16435
- });
16436
- var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16437
-
16438
- // ../../packages/schema/src/zod/rule.ts
16439
- var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16440
- var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16441
- var KeywordMatcher = external_exports.object({
16442
- type: external_exports.literal("keyword"),
16443
- // An empty keyword matches at every position, yielding one zero-length span
16444
- // per character. Rejected here because a keyword that matches everything is
16445
- // never intentional.
16446
- keywords: external_exports.array(external_exports.string().min(1)).min(1),
16447
- caseSensitive: external_exports.boolean().default(false)
16448
- });
16449
- function isValidRegex(pattern, flags) {
16450
- try {
16451
- new RegExp(pattern, flags);
16452
- return true;
16453
- } catch {
16454
- return false;
16455
- }
16456
- }
16457
- function matchesEmptyString(pattern, flags) {
16458
- try {
16459
- const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
16460
- return re.exec("")?.[0].length === 0;
16461
- } catch {
16462
- return false;
16463
- }
16464
- }
16465
- var MAX_PATTERN_LENGTH = 2e3;
16466
- var RegexMatcher = external_exports.object({
16467
- type: external_exports.literal("regex"),
16468
- pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16469
- flags: external_exports.string().default("gi"),
16470
- captureGroup: external_exports.number().int().nonnegative().optional()
16471
- }).refine((v) => isValidRegex(v.pattern, v.flags), {
16472
- message: "pattern/flags do not form a valid JavaScript regular expression",
16473
- path: ["pattern"]
16474
- }).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
16475
- 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',
16476
- path: ["pattern"]
16477
- });
16478
- var ValidatorMatcher = external_exports.object({
16479
- type: external_exports.literal("validator"),
16480
- name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
16481
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16482
- });
16483
- var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
16484
- var AppliesTo = external_exports.object({
16485
- // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16486
- extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16487
- }).meta({ id: "AppliesTo" });
16488
- var PostValidatorRef = external_exports.union([
16489
- external_exports.string(),
16490
- external_exports.object({
16491
- name: external_exports.string(),
16492
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16493
- })
16494
- ]).meta({ id: "PostValidatorRef" });
16495
- var RequiresNearby = external_exports.object({
16496
- // Each array, when present, must be non-empty and contain non-empty strings —
16497
- // an empty/blank criterion would either never fire or (for labels) match
16498
- // everything.
16499
- categories: external_exports.array(DetectionCategory).min(1).optional(),
16500
- ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16501
- labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16502
- windowChars: external_exports.number().int().positive().default(160),
16503
- // Optional confidence bump applied when a gated match is corroborated. Capped
16504
- // small: it nudges confidence, it does not assert certainty.
16505
- confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16506
- }).refine(
16507
- (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16508
- { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16509
- ).meta({ id: "RequiresNearby" });
16510
- var RuleFixture = external_exports.object({
16511
- label: external_exports.string(),
16512
- text: external_exports.string().max(5e4),
16513
- shouldMatch: external_exports.boolean(),
16514
- // Simulated file context for the scan, so fixtures can assert `appliesTo`
16515
- // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16516
- filePath: external_exports.string().optional(),
16517
- expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
16518
- }).meta({ id: "RuleFixture" });
16519
- var Rule = external_exports.object({
16520
- specVersion: external_exports.literal(1),
16521
- // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16522
- // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
16523
- // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16524
- // therefore carries no namespace and is not globally unique across publishers;
16525
- // never feed one to splitDetectionId. `category` below (per-rule) is the
16526
- // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16527
- id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16528
- name: external_exports.string(),
16529
- category: DetectionCategory,
16530
- severity: Severity,
16531
- matcher: Matcher,
16532
- appliesTo: AppliesTo.optional(),
16533
- postValidators: external_exports.array(PostValidatorRef).optional(),
16534
- requiresNearby: RequiresNearby.optional(),
16535
- examples: external_exports.array(external_exports.string()).optional()
16536
- }).meta({ id: "Rule" });
16537
- var Author = external_exports.object({
16538
- name: external_exports.string(),
16539
- email: external_exports.email().optional(),
16540
- url: external_exports.url().optional()
16541
- }).meta({ id: "Author" });
16542
- var PackManifest = external_exports.object({
16543
- specVersion: external_exports.literal(1),
16544
- id: external_exports.string(),
16545
- name: external_exports.string(),
16546
- version: external_exports.string(),
16547
- rules: external_exports.array(external_exports.string()),
16548
- // Optional attribution/provenance — consumed by the rule marketplace.
16549
- description: external_exports.string().optional(),
16550
- author: Author.optional(),
16551
- license: external_exports.string().optional(),
16552
- sourceUrl: external_exports.url().optional()
16553
- }).meta({ id: "PackManifest" });
16554
-
16555
- // ../../packages/schema/src/zod/policy.ts
16556
- var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
16557
- var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
16558
- var Policy = external_exports.object({
16559
- id: external_exports.guid(),
16560
- scope: PolicyScope,
16561
- target: PolicyTarget,
16562
- action: ActionTaken,
16563
- enabled: external_exports.boolean().default(true),
16564
- customKeywords: external_exports.array(external_exports.string()).optional(),
16565
- // Display name — optional so older policy rows without name still parse.
16566
- // Added for the findings API (policy.name column migration).
16567
- name: external_exports.string().optional()
16568
- }).meta({ id: "Policy" });
16569
- var PolicyBundle = external_exports.object({
16570
- version: external_exports.string(),
16571
- policies: external_exports.array(Policy),
16572
- // Rules from the installed marketplace packs (snapshotted by the
16573
- // control plane). The plugin registers these in addition to its bundled
16574
- // packs. Optional so older backends — and older on-disk caches — that omit
16575
- // the field still parse; consumers read `bundle.rules ?? []`.
16576
- rules: external_exports.array(Rule).optional(),
16577
- // When true, `rules` IS the complete effective ruleset and the runtime must
16578
- // NOT merge its compiled-in bundled packs — the standalone gateway sets this
16579
- // after reading the user's installed snapshot (installed_packs, enabled
16580
- // packs only), which is how detection updates stay manual: new bundled
16581
- // rules run only after the user applies the pack update. Absent/false keeps
16582
- // the historical composition (bundled packs + rules) — older caches.
16583
- rulesComplete: external_exports.boolean().optional(),
16584
- // Active detection exceptions, evaluation subset only (see
16585
- // ExceptionBundleEntry). Optional so older bundle producers — and older
16586
- // on-disk caches — that omit the field still parse; consumers read
16587
- // `bundle.exceptions ?? []`.
16588
- exceptions: external_exports.array(ExceptionBundleEntry).optional(),
16589
- // Installed pack version, keyed by ruleId, for rules in `rules` that came
16590
- // from a versioned installed pack. Optional so older backends — and older
16591
- // on-disk caches — that omit the field still parse; consumers fall back to
16592
- // the rule's own spec version. NOT the bundle version above — see
16593
- // installedRuleset's ruleVersions for the source of truth.
16594
- ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
16595
- customKeywords: external_exports.array(external_exports.string()),
16596
- fetchedAt: external_exports.iso.datetime()
16597
- }).meta({ id: "PolicyBundle" });
16598
- var OBSERVE_ONLY_CATEGORIES = ["config"];
16599
- var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
16600
- var CATEGORY_PEAK_SEVERITY = {
16601
- secret: "critical",
16602
- financial: "critical",
16603
- // core-financial/credit-card
16604
- code_flaw: "critical",
16605
- pii: "high",
16606
- phi: "high",
16607
- custom: "high",
16608
- // user-defined; conservative
16609
- code_context: "low",
16610
- config: "low"
16611
- // observe-only; floors to monitor regardless
16612
- };
16613
- function severityFloorPolicy(category) {
16614
- if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
16615
- const peak = CATEGORY_PEAK_SEVERITY[category];
16616
- return peak === "critical" || peak === "high" ? "warn" : "monitor";
16617
- }
16618
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
16619
- var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
16620
- var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
16621
- var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
16622
- var BUILTIN_POLICY_SPECS = {
16623
- monitor: {
16624
- name: "Monitor",
16625
- action: "log",
16626
- description: "Log every match for audit. The request is allowed through untouched."
16627
- },
16628
- warn: {
16629
- name: "Warn",
16630
- action: "warn",
16631
- description: "Allow the request, but warn the user inline before it is sent."
16632
- },
16633
- redact: {
16634
- name: "Redact",
16635
- action: "redact",
16636
- description: "Automatically strip the matched value from the request, then continue."
16637
- },
16638
- block: {
16639
- name: "Block",
16640
- action: "block",
16641
- description: "Refuse the request entirely whenever any rule in this detection matches."
16642
- }
16643
- };
16644
- function builtinPolicyToAction(id) {
16645
- return BUILTIN_POLICY_SPECS[id].action;
16646
- }
16647
- var DEFAULT_ACTIONS = Object.fromEntries(
16648
- DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
16649
- );
16650
- var BUILTIN_POLICIES = Object.fromEntries(
16651
- KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
16652
- );
16653
- var DEFAULT_PACK_POLICY_ID = "monitor";
16654
- function policyIdToAction(policyId) {
16655
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
16656
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
16657
- return BUILTIN_POLICIES[id].action;
16658
- }
16659
- var UsedByItem = external_exports.object({
16660
- id: external_exports.string(),
16661
- name: external_exports.string(),
16662
- ruleCount: external_exports.number().int().nonnegative(),
16663
- enabled: external_exports.boolean()
16664
- }).meta({ id: "UsedByItem" });
16665
- var PolicyListItem = external_exports.object({
16666
- id: external_exports.string(),
16667
- kind: PolicyKind,
16668
- name: external_exports.string(),
16669
- enabled: external_exports.boolean(),
16670
- usedByCount: external_exports.number().int().nonnegative()
16671
- }).meta({ id: "PolicyListItem" });
16672
- var PolicyDetail = external_exports.object({
16673
- specVersion: external_exports.literal(1),
16674
- id: external_exports.string(),
16675
- kind: PolicyKind,
16676
- name: external_exports.string(),
16677
- enabled: external_exports.boolean(),
16678
- description: external_exports.string(),
16679
- usedBy: external_exports.array(UsedByItem)
16680
- }).meta({ id: "PolicyDetail" });
16681
- var PolicyStatsResponse = external_exports.object({
16682
- policies: external_exports.number().int().nonnegative(),
16683
- builtin: external_exports.number().int().nonnegative(),
16684
- custom: external_exports.number().int().nonnegative(),
16685
- detectionsGoverned: external_exports.number().int().nonnegative()
16686
- }).meta({ id: "PolicyStatsResponse" });
16687
-
16688
- // ../../packages/schema/src/zod/api.ts
16689
- var LIST_QUERY_MAX_LIMIT = 200;
16690
- var ListEventsQuery = external_exports.object({
16691
- cursor: external_exports.string().optional(),
16692
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16693
- sourceTool: external_exports.string().optional(),
16694
- kind: external_exports.string().optional(),
16695
- from: external_exports.iso.datetime().optional(),
16696
- to: external_exports.iso.datetime().optional()
16697
- });
16698
- var ListEventsResponse = external_exports.object({
16699
- items: external_exports.array(Event),
16700
- nextCursor: external_exports.string().nullable()
16701
- }).meta({ id: "ListEventsResponse" });
16702
- var IngestResponse = external_exports.object({
16703
- accepted: external_exports.number().int().nonnegative(),
16704
- duplicates: external_exports.number().int().nonnegative()
16705
- }).meta({ id: "IngestResponse" });
16706
- var ListFindingsQuery = external_exports.object({
16707
- cursor: external_exports.string().optional(),
16708
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16709
- severity: external_exports.string().optional(),
16710
- category: external_exports.string().optional(),
16711
- eventId: external_exports.guid().optional()
16712
- });
16713
- var ListFindingsResponse = external_exports.object({
16714
- items: external_exports.array(Finding),
16715
- nextCursor: external_exports.string().nullable()
16716
- }).meta({ id: "ListFindingsResponse" });
16717
- var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
16718
- var CreatePolicyRequest = Policy.omit({ id: true }).meta({
16719
- id: "CreatePolicyRequest"
16720
- });
16721
- var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
16722
- var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
16723
- var ErrorResponse = external_exports.object({
16724
- error: external_exports.object({
16725
- code: external_exports.string(),
16726
- message: external_exports.string(),
16727
- details: external_exports.unknown().optional()
16728
- })
16729
- }).meta({ id: "ErrorResponse" });
16730
-
16731
- // ../../packages/schema/src/zod/config-inventory.ts
16732
- var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16733
- var SkillScanEntry = external_exports.object({
16734
- name: external_exports.string().min(1),
16735
- // The identity source: a marketplace repo for plugin skills (e.g.
16736
- // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16737
- // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16738
- // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16739
- source: external_exports.string().min(1),
16740
- scope: ConfigScope,
16741
- pluginName: external_exports.string().optional(),
16742
- // Volatile — rides the attribute bag, never the identity hash.
16743
- version: external_exports.string().optional(),
16744
- description: external_exports.string().optional(),
16745
- // Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
16746
- updatedAt: external_exports.iso.datetime().optional(),
16747
- // Filesystem path — the promoted inventory `location` column.
16748
- location: external_exports.string().optional()
16749
- });
16750
- var HookScanEntry = external_exports.object({
16751
- // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16752
- // set is harness-defined and grows without a schema change.
16753
- event: external_exports.string().min(1),
16754
- // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16755
- matcher: external_exports.string().optional(),
16756
- command: external_exports.string().min(1),
16757
- timeout: external_exports.number().optional(),
16758
- scope: ConfigScope,
16759
- pluginName: external_exports.string().optional(),
16760
- // The settings file / hooks.json the entry came from.
16761
- location: external_exports.string().optional()
16124
+ var HookScanEntry = external_exports.object({
16125
+ // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16126
+ // set is harness-defined and grows without a schema change.
16127
+ event: external_exports.string().min(1),
16128
+ // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16129
+ matcher: external_exports.string().optional(),
16130
+ command: external_exports.string().min(1),
16131
+ timeout: external_exports.number().optional(),
16132
+ scope: ConfigScope,
16133
+ pluginName: external_exports.string().optional(),
16134
+ // The settings file / hooks.json the entry came from.
16135
+ location: external_exports.string().optional()
16762
16136
  });
16763
16137
  var McpServerScanEntry = external_exports.object({
16764
16138
  // The server's config key ("github", "filesystem", …) — identity, with the
@@ -16833,12 +16207,170 @@ var ConfigScanRecord = external_exports.object({
16833
16207
  definitions: external_exports.array(InspectionDefinitionInput).optional(),
16834
16208
  findings: external_exports.array(ConfigPostureFindingInput).optional()
16835
16209
  });
16836
-
16837
- // ../../packages/schema/src/zod/registry.ts
16838
- var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16839
- var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16840
- var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16841
- var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16210
+
16211
+ // ../../packages/schema/src/zod/registry.ts
16212
+ var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16213
+ var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16214
+ var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16215
+ var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16216
+
16217
+ // ../../packages/schema/src/zod/rule.ts
16218
+ var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
16219
+ var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16220
+ var KeywordMatcher = external_exports.strictObject({
16221
+ type: external_exports.literal("keyword"),
16222
+ // An empty keyword matches at every position, yielding one zero-length span
16223
+ // per character. Rejected here because a keyword that matches everything is
16224
+ // never intentional.
16225
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16226
+ caseSensitive: external_exports.boolean().default(false)
16227
+ });
16228
+ function isValidRegex(pattern, flags) {
16229
+ try {
16230
+ new RegExp(pattern, flags);
16231
+ return true;
16232
+ } catch {
16233
+ return false;
16234
+ }
16235
+ }
16236
+ function probeFlags(flags) {
16237
+ return flags.replace(/[gy]/g, "");
16238
+ }
16239
+ function matchesEmptyString(pattern, flags) {
16240
+ try {
16241
+ const re = new RegExp(pattern, probeFlags(flags));
16242
+ return re.exec("")?.[0].length === 0;
16243
+ } catch {
16244
+ return false;
16245
+ }
16246
+ }
16247
+ function spansWholeMatch(captureGroup) {
16248
+ return captureGroup === void 0 || captureGroup === 0;
16249
+ }
16250
+ function captureGroupCount(pattern, flags) {
16251
+ try {
16252
+ const probe = new RegExp(`${pattern}|`, probeFlags(flags));
16253
+ const result = probe.exec("");
16254
+ return result ? result.length - 1 : void 0;
16255
+ } catch {
16256
+ return void 0;
16257
+ }
16258
+ }
16259
+ var MAX_PATTERN_LENGTH = 2e3;
16260
+ var RegexMatcher = external_exports.strictObject({
16261
+ type: external_exports.literal("regex"),
16262
+ pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16263
+ flags: external_exports.string().default("gi"),
16264
+ captureGroup: external_exports.number().int().nonnegative().optional()
16265
+ }).refine((v) => isValidRegex(v.pattern, v.flags), {
16266
+ message: "pattern/flags do not form a valid JavaScript regular expression",
16267
+ path: ["pattern"]
16268
+ }).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
16269
+ 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',
16270
+ path: ["pattern"]
16271
+ }).superRefine((v, ctx) => {
16272
+ if (v.captureGroup === void 0) return;
16273
+ const groups = captureGroupCount(v.pattern, v.flags);
16274
+ if (groups === void 0 || v.captureGroup <= groups) return;
16275
+ ctx.addIssue({
16276
+ code: "custom",
16277
+ path: ["captureGroup"],
16278
+ 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.`
16279
+ });
16280
+ });
16281
+ var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
16282
+ var MATCHER_TYPES = MatcherType.options;
16283
+ var AppliesTo = external_exports.strictObject({
16284
+ // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16285
+ extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16286
+ }).meta({ id: "AppliesTo" });
16287
+ var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
16288
+ var PostValidatorRef = external_exports.union(
16289
+ [
16290
+ PostValidatorName,
16291
+ external_exports.strictObject({
16292
+ name: PostValidatorName,
16293
+ config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16294
+ })
16295
+ ],
16296
+ {
16297
+ // A union reports one collapsed issue for every way its arms can fail, so
16298
+ // this has to describe the whole shape rather than just the name — it is
16299
+ // what an author sees for a misspelled name AND for a stray key in the
16300
+ // object form. The names come from the enum so the message cannot go
16301
+ // stale. Without it Zod says only "Invalid input", which is precisely the
16302
+ // no-feedback outcome this schema exists to remove.
16303
+ error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
16304
+ " or "
16305
+ )}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
16306
+ }
16307
+ ).meta({ id: "PostValidatorRef" });
16308
+ var RequiresNearby = external_exports.strictObject({
16309
+ // Each array, when present, must be non-empty and contain non-empty strings —
16310
+ // an empty/blank criterion would either never fire or (for labels) match
16311
+ // everything.
16312
+ categories: external_exports.array(DetectionCategory).min(1).optional(),
16313
+ ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16314
+ labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16315
+ windowChars: external_exports.number().int().positive().default(160),
16316
+ // Optional confidence bump applied when a gated match is corroborated. Capped
16317
+ // small: it nudges confidence, it does not assert certainty.
16318
+ confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16319
+ }).refine(
16320
+ (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16321
+ { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16322
+ ).meta({ id: "RequiresNearby" });
16323
+ var RuleFixture = external_exports.strictObject({
16324
+ label: external_exports.string(),
16325
+ text: external_exports.string().max(5e4),
16326
+ shouldMatch: external_exports.boolean(),
16327
+ // Simulated file context for the scan, so fixtures can assert `appliesTo`
16328
+ // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16329
+ filePath: external_exports.string().optional(),
16330
+ expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
16331
+ }).meta({ id: "RuleFixture" });
16332
+ var Rule = external_exports.strictObject({
16333
+ // A pinned literal over a STRICT object, and the two together decide how this
16334
+ // format may grow. A rule carrying a key not listed below is refused with
16335
+ // `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
16336
+ // `invalid_value`. So the only additive path is adding an OPTIONAL field here
16337
+ // — that keeps every rule authored before it valid — and a rule author has no
16338
+ // way to introduce a field of their own or to opt into a later version.
16339
+ // Widening the format means changing this literal and every consumer of it.
16340
+ specVersion: external_exports.literal(1),
16341
+ // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16342
+ // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
16343
+ // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16344
+ // therefore carries no namespace and is not globally unique across publishers;
16345
+ // never feed one to splitDetectionId. `category` below (per-rule) is the
16346
+ // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16347
+ id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16348
+ name: external_exports.string(),
16349
+ category: DetectionCategory,
16350
+ severity: Severity,
16351
+ matcher: Matcher,
16352
+ appliesTo: AppliesTo.optional(),
16353
+ postValidators: external_exports.array(PostValidatorRef).optional(),
16354
+ requiresNearby: RequiresNearby.optional(),
16355
+ examples: external_exports.array(external_exports.string()).optional()
16356
+ }).meta({ id: "Rule" });
16357
+ var Author = external_exports.object({
16358
+ name: external_exports.string(),
16359
+ email: external_exports.email().optional(),
16360
+ url: external_exports.url().optional()
16361
+ }).meta({ id: "Author" });
16362
+ var PackManifest = external_exports.object({
16363
+ specVersion: external_exports.literal(1),
16364
+ id: external_exports.string(),
16365
+ name: external_exports.string(),
16366
+ version: external_exports.string(),
16367
+ rules: external_exports.array(external_exports.string()),
16368
+ // Optional attribution/provenance — consumed by the rule marketplace.
16369
+ description: external_exports.string().optional(),
16370
+ author: Author.optional(),
16371
+ license: external_exports.string().optional(),
16372
+ sourceUrl: external_exports.url().optional()
16373
+ }).meta({ id: "PackManifest" });
16842
16374
 
16843
16375
  // ../../packages/schema/src/zod/detection.ts
16844
16376
  var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
@@ -17038,6 +16570,231 @@ function buildDetectionsList(summaries, query) {
17038
16570
  return { counts, items: filtered.map(summaryToDetectionListItem) };
17039
16571
  }
17040
16572
 
16573
+ // ../../packages/schema/src/zod/inventory.ts
16574
+ var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16575
+ var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16576
+ var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16577
+ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16578
+ var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16579
+ var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16580
+ var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16581
+ var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
16582
+ id: "HarnessId"
16583
+ });
16584
+ var AccessCounts = external_exports.object({
16585
+ open: external_exports.number().int().nonnegative(),
16586
+ approved: external_exports.number().int().nonnegative(),
16587
+ blocked: external_exports.number().int().nonnegative(),
16588
+ total: external_exports.number().int().nonnegative()
16589
+ }).meta({ id: "AccessCounts" });
16590
+ var AssetSummary = external_exports.object({
16591
+ id: external_exports.string(),
16592
+ type: AssetType,
16593
+ name: external_exports.string(),
16594
+ sub: external_exports.string(),
16595
+ flags: external_exports.array(Flag),
16596
+ /** MCP servers only — omitted for all other types. */
16597
+ trust: TrustLevel.optional()
16598
+ }).meta({ id: "AssetSummary" });
16599
+ var ProjectSummary = external_exports.object({
16600
+ id: external_exports.string(),
16601
+ name: external_exports.string(),
16602
+ repo: external_exports.string(),
16603
+ visibility: Visibility,
16604
+ language: external_exports.string(),
16605
+ policyDefault: AccessLevel,
16606
+ updatedAt: external_exports.iso.datetime(),
16607
+ accessCounts: AccessCounts,
16608
+ findingsCount: external_exports.number().int().nonnegative()
16609
+ }).meta({ id: "ProjectSummary" });
16610
+ var HarnessCategory = external_exports.object({
16611
+ /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16612
+ type: AssetType,
16613
+ assets: external_exports.array(AssetSummary)
16614
+ });
16615
+ var HarnessSummary = external_exports.object({
16616
+ id: HarnessId,
16617
+ label: external_exports.string(),
16618
+ kind: external_exports.string(),
16619
+ version: external_exports.string(),
16620
+ sessions: external_exports.number().int().nonnegative(),
16621
+ assetCount: external_exports.number().int().nonnegative(),
16622
+ flagCount: external_exports.number().int().nonnegative(),
16623
+ projects: external_exports.array(ProjectSummary),
16624
+ categories: external_exports.array(HarnessCategory)
16625
+ }).meta({ id: "HarnessSummary" });
16626
+ var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16627
+ var AssetGroup = external_exports.object({
16628
+ /** Group key — never project (enforced at service layer). */
16629
+ type: AssetType,
16630
+ total: external_exports.number().int().nonnegative(),
16631
+ /**
16632
+ * MCP group only — omitted for all other types.
16633
+ * Partial: only TrustLevel keys with non-zero counts are included.
16634
+ * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16635
+ */
16636
+ trustRollup: external_exports.object({
16637
+ "known-good": external_exports.number().int().nonnegative(),
16638
+ risky: external_exports.number().int().nonnegative(),
16639
+ unapproved: external_exports.number().int().nonnegative()
16640
+ }).partial().strict().optional(),
16641
+ /**
16642
+ * Partial: only Flag keys with non-zero counts are included.
16643
+ * Strict: unknown keys are rejected — only Flag values are valid keys.
16644
+ */
16645
+ flagRollup: external_exports.object({
16646
+ update: external_exports.number().int().nonnegative(),
16647
+ stale: external_exports.number().int().nonnegative(),
16648
+ conflict: external_exports.number().int().nonnegative(),
16649
+ unknown: external_exports.number().int().nonnegative(),
16650
+ change: external_exports.number().int().nonnegative(),
16651
+ untracked: external_exports.number().int().nonnegative(),
16652
+ risk: external_exports.number().int().nonnegative(),
16653
+ findings: external_exports.number().int().nonnegative()
16654
+ }).partial().strict(),
16655
+ items: external_exports.array(AssetSummary)
16656
+ }).meta({ id: "AssetGroup" });
16657
+ var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16658
+ var McpTool = external_exports.object({
16659
+ name: external_exports.string(),
16660
+ signature: external_exports.string(),
16661
+ description: external_exports.string(),
16662
+ write: external_exports.boolean(),
16663
+ /** Non-null string when tool is dangerous / blocked; null otherwise. */
16664
+ risk: external_exports.string().nullable()
16665
+ }).meta({ id: "McpTool" });
16666
+ var AssetFindingRef = external_exports.object({
16667
+ id: external_exports.string(),
16668
+ title: external_exports.string(),
16669
+ note: external_exports.string()
16670
+ });
16671
+ var AssetDetail = AssetSummary.extend({
16672
+ /** string | null — null when no description is available. */
16673
+ description: external_exports.string().nullable(),
16674
+ /** trustLevel | null — null for non-MCP assets. */
16675
+ trust: TrustLevel.nullable(),
16676
+ /** Type-specific raw key/values — FE renders the grid. */
16677
+ meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16678
+ /** always present — object when there is an active finding, null when absent. */
16679
+ finding: AssetFindingRef.nullable(),
16680
+ /** MCP exposed-tools list — omitted for non-mcp. */
16681
+ tools: external_exports.array(McpTool).optional()
16682
+ }).meta({ id: "AssetDetail" });
16683
+ var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16684
+ var InventoryStats = external_exports.object({
16685
+ /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16686
+ attention: external_exports.number().int().nonnegative(),
16687
+ byType: external_exports.object({
16688
+ project: external_exports.number().int().nonnegative(),
16689
+ skill: external_exports.number().int().nonnegative(),
16690
+ mcp: external_exports.number().int().nonnegative(),
16691
+ hook: external_exports.number().int().nonnegative(),
16692
+ config: external_exports.number().int().nonnegative()
16693
+ }),
16694
+ harnesses: external_exports.number().int().nonnegative(),
16695
+ mcpTrust: external_exports.object({
16696
+ "known-good": external_exports.number().int().nonnegative(),
16697
+ risky: external_exports.number().int().nonnegative(),
16698
+ unapproved: external_exports.number().int().nonnegative()
16699
+ })
16700
+ }).meta({ id: "InventoryStats" });
16701
+ var FileSummary = external_exports.object({
16702
+ path: external_exports.string(),
16703
+ name: external_exports.string(),
16704
+ origin: Origin,
16705
+ /** Effective access (override applied). */
16706
+ access: AccessLevel,
16707
+ /** True when a file_access_override differs from the computed default. */
16708
+ isCustom: external_exports.boolean(),
16709
+ findings: external_exports.number().int().nonnegative(),
16710
+ /** When the file was auto-blocked by a detection; null when not blocked. */
16711
+ blockedAt: external_exports.iso.datetime().nullable().optional(),
16712
+ /** Why the file was blocked; null when absent. */
16713
+ note: external_exports.string().nullable().optional()
16714
+ }).meta({ id: "FileSummary" });
16715
+ var FolderSummary = external_exports.object({
16716
+ name: external_exports.string(),
16717
+ path: external_exports.string(),
16718
+ /** Rollup of effective access across all descendants. */
16719
+ accessCounts: AccessCounts
16720
+ }).meta({ id: "FolderSummary" });
16721
+ var ProjectTreeResponse = external_exports.object({
16722
+ project: external_exports.object({
16723
+ id: external_exports.string(),
16724
+ repo: external_exports.string(),
16725
+ visibility: Visibility
16726
+ }),
16727
+ path: external_exports.string(),
16728
+ /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16729
+ folders: external_exports.array(FolderSummary).optional(),
16730
+ files: external_exports.array(FileSummary)
16731
+ }).meta({ id: "ProjectTreeResponse" });
16732
+ var FileDetail = FileSummary.extend({
16733
+ project: external_exports.object({
16734
+ repo: external_exports.string(),
16735
+ visibility: Visibility,
16736
+ language: external_exports.string(),
16737
+ policyDefault: AccessLevel,
16738
+ updatedAt: external_exports.iso.datetime()
16739
+ }),
16740
+ findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16741
+ }).meta({ id: "FileDetail" });
16742
+ var SetFileAccessBody = external_exports.object({
16743
+ path: external_exports.string(),
16744
+ access: AccessLevel
16745
+ }).meta({ id: "SetFileAccessBody" });
16746
+ var SetFileAccessResponse = external_exports.object({
16747
+ file: FileSummary,
16748
+ accessCounts: AccessCounts
16749
+ }).meta({ id: "SetFileAccessResponse" });
16750
+ var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16751
+ var HarnessEventItem = external_exports.object({
16752
+ kind: HarnessEventKind,
16753
+ title: external_exports.string(),
16754
+ detail: external_exports.string(),
16755
+ occurredAt: external_exports.iso.datetime(),
16756
+ findingId: external_exports.string().nullable().optional()
16757
+ }).meta({ id: "HarnessEventItem" });
16758
+ var HarnessEventsResponse = external_exports.object({
16759
+ counts: external_exports.object({
16760
+ block: external_exports.number().int().nonnegative(),
16761
+ redact: external_exports.number().int().nonnegative(),
16762
+ warn: external_exports.number().int().nonnegative()
16763
+ }),
16764
+ items: external_exports.array(HarnessEventItem)
16765
+ }).meta({ id: "HarnessEventsResponse" });
16766
+ var RescanResponse = external_exports.object({
16767
+ jobId: external_exports.string(),
16768
+ startedAt: external_exports.iso.datetime()
16769
+ }).meta({ id: "RescanResponse" });
16770
+ var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16771
+ var ListAssetsQuery = external_exports.object({
16772
+ /** Filter by one or more AssetType values; absent means all types. */
16773
+ type: external_exports.array(AssetType).optional(),
16774
+ /** Free-text search term. */
16775
+ q: external_exports.string().optional()
16776
+ });
16777
+ var GetProjectTreeQuery = external_exports.object({
16778
+ /** Subtree root path; defaults to repository root when absent. */
16779
+ path: external_exports.string().optional(),
16780
+ /** Free-text filter applied to file paths. */
16781
+ q: external_exports.string().optional(),
16782
+ /**
16783
+ * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16784
+ * file across the whole repo (folders omitted, most-recent first), ignoring
16785
+ * `path`/`q` — powers the project-wide "recently blocked" strip.
16786
+ */
16787
+ filter: external_exports.enum(["blocked"]).optional()
16788
+ });
16789
+ var GetProjectFileQuery = external_exports.object({
16790
+ /** Repository-relative file path; absent or empty → 400. */
16791
+ path: external_exports.string()
16792
+ });
16793
+ var GetHarnessEventsQuery = external_exports.object({
16794
+ /** Maximum number of events to return. Range: 1–50; default: 7. */
16795
+ limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16796
+ });
16797
+
17041
16798
  // ../../packages/schema/src/zod/shares.ts
17042
16799
  var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
17043
16800
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
@@ -17244,6 +17001,127 @@ var EgressWriteSummary = external_exports.object({
17244
17001
  droppedFiles: external_exports.array(external_exports.string()).default([])
17245
17002
  }).meta({ id: "EgressWriteSummary" });
17246
17003
 
17004
+ // ../../packages/schema/src/zod/event.ts
17005
+ var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
17006
+ var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
17007
+ var EventMetadata = external_exports.object({
17008
+ sessionId: external_exports.string().optional(),
17009
+ repo: external_exports.string().optional(),
17010
+ filePath: external_exports.string().optional(),
17011
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
17012
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
17013
+ // arguments or output, which can carry the very value a finding masked
17014
+ // (metadata is stored unredacted). Gives findings on non-file captures a
17015
+ // display location ("via Bash") when no filePath exists.
17016
+ toolName: external_exports.string().optional(),
17017
+ // Set (true) by the worktree scanner when the file is excluded by the
17018
+ // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
17019
+ // generated code can leak real secrets — but the provenance is recorded so
17020
+ // policy/dashboards can treat those findings as informational rather than
17021
+ // blocking. Omitted (not false) for tracked files and non-scan events.
17022
+ gitignored: external_exports.boolean().optional(),
17023
+ // Set (true) ONLY when the event's `content` is the COMPLETE file at
17024
+ // capture time (a worktree scan reading from disk). Hook-captured edits
17025
+ // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
17026
+ // this. The resolver-on-ingest keys its fixed-at-source dropout
17027
+ // diff on this marker: only a whole-file snapshot can prove a previously
17028
+ // open finding is gone; a fragment's absence proves nothing (the secret
17029
+ // may live outside the hunk). Omitted (not false) for fragments and
17030
+ // non-scan events, so pre-marker clients safely default to the
17031
+ // non-authoritative path.
17032
+ wholeFile: external_exports.boolean().optional(),
17033
+ model: external_exports.string().optional(),
17034
+ turnIndex: external_exports.number().int().nonnegative().optional(),
17035
+ // Distributed-tracing correlation. `correlationId` ties a recorded event back
17036
+ // to the request that captured/ingested it (a UUID, generated independently of
17037
+ // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
17038
+ // originating span when telemetry is enabled. Both optional + backward
17039
+ // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
17040
+ correlationId: external_exports.uuid().optional(),
17041
+ traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
17042
+ // Ids of the detection exceptions that downgraded findings in this capture
17043
+ // to 'allow' — the enforcement audit trail's link back to the grant that
17044
+ // authorized the bypass. Absent on captures where no exception applied.
17045
+ exceptionIds: external_exports.array(external_exports.guid()).optional()
17046
+ }).meta({ id: "EventMetadata" });
17047
+ var Event = external_exports.object({
17048
+ id: external_exports.guid(),
17049
+ sourceTool: SourceTool,
17050
+ kind: EventKind,
17051
+ occurredAt: external_exports.iso.datetime(),
17052
+ contentHash: external_exports.string(),
17053
+ content: external_exports.string(),
17054
+ metadata: EventMetadata.optional()
17055
+ }).meta({ id: "Event" });
17056
+ var IngestEvent = Event.meta({ id: "IngestEvent" });
17057
+ var IngestBatch = external_exports.object({
17058
+ events: external_exports.array(IngestEvent).min(1).max(100),
17059
+ // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
17060
+ // additionally rejects any event whose contentHash the store has already
17061
+ // recorded — for re-runnable bulk ingest (worktree scan, transcript
17062
+ // backfill), where a re-run mints fresh event ids for identical content and
17063
+ // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
17064
+ // two genuinely separate prompts can be byte-identical and both belong on
17065
+ // the timeline.
17066
+ dedupe: external_exports.literal("content-hash").optional()
17067
+ }).meta({ id: "IngestBatch" });
17068
+
17069
+ // ../../packages/schema/src/zod/exception.ts
17070
+ var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
17071
+ var ExceptionConditions = external_exports.object({
17072
+ repo: external_exports.string().optional(),
17073
+ sourceTool: external_exports.string().optional(),
17074
+ provider: external_exports.string().optional()
17075
+ }).strict();
17076
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
17077
+ var DetectionException = external_exports.object({
17078
+ id: external_exports.guid(),
17079
+ ruleId: external_exports.string(),
17080
+ // Denormalized from the rule, for reporting — never matched on.
17081
+ category: DetectionCategory,
17082
+ // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
17083
+ // fingerprint, never the raw value, and never reversible. Matching recomputes
17084
+ // the fingerprint from a fresh capture; the value itself is never stored.
17085
+ // Shape-constrained so a malformed — or accidentally raw — value is rejected
17086
+ // at the boundary rather than persisted.
17087
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17088
+ // Version of the fingerprint key the grant was written under; a rotated key
17089
+ // invalidates old grants rather than silently mismatching them.
17090
+ keyVersion: external_exports.number().int().positive(),
17091
+ // maskMatch() preview of the approved value — never the raw value.
17092
+ maskedValue: external_exports.string(),
17093
+ capability: ExceptionCapability.default("suppress"),
17094
+ scope: ExceptionScope,
17095
+ expiresAt: external_exports.iso.datetime().nullable(),
17096
+ maxUses: external_exports.number().int().positive().nullable(),
17097
+ useCount: external_exports.number().int().nonnegative(),
17098
+ lastUsedAt: external_exports.iso.datetime().nullable(),
17099
+ // Mandatory: every grant carries the human reason it exists.
17100
+ justification: external_exports.string().min(1),
17101
+ conditions: ExceptionConditions.nullable(),
17102
+ createdBy: external_exports.string(),
17103
+ createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
17104
+ createdAt: external_exports.iso.datetime(),
17105
+ updatedAt: external_exports.iso.datetime(),
17106
+ // Revocation is terminal and retained — consumed/expired/revoked rows are
17107
+ // audit evidence; nothing in the exception lifecycle hard-deletes.
17108
+ revokedAt: external_exports.iso.datetime().nullable(),
17109
+ revokedBy: external_exports.string().nullable(),
17110
+ revokeReason: external_exports.string().nullable()
17111
+ });
17112
+ var ExceptionBundleEntry = DetectionException.pick({
17113
+ id: true,
17114
+ ruleId: true,
17115
+ valueFingerprint: true,
17116
+ keyVersion: true,
17117
+ capability: true,
17118
+ expiresAt: true,
17119
+ maxUses: true,
17120
+ useCount: true,
17121
+ conditions: true
17122
+ });
17123
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
17124
+
17247
17125
  // ../../packages/schema/src/zod/exception-action.ts
17248
17126
  var confirmation = external_exports.string().optional();
17249
17127
  var ApproveBlockedInput = external_exports.object({
@@ -17286,10 +17164,11 @@ function toApiAction(dbVal) {
17286
17164
  }
17287
17165
  function toApiCategory(dbVal) {
17288
17166
  if (dbVal === "code_context") return "source_code";
17289
- return dbVal;
17167
+ const parsed = FindingCategory.safeParse(dbVal);
17168
+ return parsed.success ? parsed.data : "custom";
17290
17169
  }
17291
17170
  function toApiProvider(sourceTool) {
17292
- return TOOL_TO_HARNESS[sourceTool] ?? "api";
17171
+ return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
17293
17172
  }
17294
17173
  var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
17295
17174
  function foldGroupStatus(instanceStatuses) {
@@ -17876,7 +17755,14 @@ function isVaultConsentValid(consent) {
17876
17755
 
17877
17756
  // ../../packages/schema/src/zod/local.ts
17878
17757
  var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17879
- var RunMode = external_exports.enum(["standalone"]);
17758
+ var MODEL_JUDGE_PAYLOAD_VERSION = 1;
17759
+ var RunMode = external_exports.enum(["standalone", "attached"]);
17760
+ var ControlPlaneConnection = external_exports.object({
17761
+ endpoint: external_exports.string().min(1),
17762
+ // Display name for the deployment, shown instead of the raw endpoint.
17763
+ label: external_exports.string().min(1).optional(),
17764
+ attachedAt: external_exports.iso.datetime()
17765
+ }).meta({ id: "ControlPlaneConnection" });
17880
17766
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17881
17767
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
17882
17768
  var ModelJudgeConsent = external_exports.object({
@@ -17885,12 +17771,10 @@ var ModelJudgeConsent = external_exports.object({
17885
17771
  });
17886
17772
  var WorkspaceSettings = external_exports.object({
17887
17773
  specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
17888
- // Settings files written by earlier releases may carry the retired 'attached'
17889
- // value; it parses as 'standalone' so those files keep loading.
17890
- runMode: external_exports.preprocess(
17891
- (v) => v === "attached" ? "standalone" : v,
17892
- RunMode.default("standalone")
17893
- ),
17774
+ runMode: RunMode.default("standalone"),
17775
+ // Present only while attached; a detach clears it. Its presence is what makes
17776
+ // `runMode: 'attached'` mean anything — see isAttached.
17777
+ controlPlane: ControlPlaneConnection.optional(),
17894
17778
  policy: SimpleDetectionPolicy.default("redact"),
17895
17779
  // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
17896
17780
  historicalAccess: HistoricalAccess.default("session-only"),
@@ -17992,39 +17876,244 @@ function toInspectionFindingRow(input) {
17992
17876
  firstDetectedAt: input.firstDetectedAt ? isoToEpochMillis(input.firstDetectedAt) : null
17993
17877
  };
17994
17878
  }
17995
- function toCaptureAttributes(event) {
17996
- const metadata = event.metadata;
17997
- return {
17998
- source_tool: event.sourceTool,
17999
- ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
18000
- ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
18001
- ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
18002
- ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
18003
- ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
18004
- ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
18005
- ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
18006
- ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
18007
- // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
18008
- // has ever populated either), but every legacy metadata key still rides
18009
- // the bag rather than being silently dropped — CaptureAttributes'
18010
- // `.catchall(z.unknown())` carries the long tail.
18011
- ...metadata?.model !== void 0 ? { model: metadata.model } : {},
18012
- ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
18013
- };
17879
+ function toCaptureAttributes(event) {
17880
+ const metadata = event.metadata;
17881
+ return {
17882
+ source_tool: event.sourceTool,
17883
+ ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
17884
+ ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
17885
+ ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
17886
+ ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
17887
+ ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
17888
+ ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
17889
+ ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
17890
+ ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
17891
+ // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
17892
+ // has ever populated either), but every legacy metadata key still rides
17893
+ // the bag rather than being silently dropped — CaptureAttributes'
17894
+ // `.catchall(z.unknown())` carries the long tail.
17895
+ ...metadata?.model !== void 0 ? { model: metadata.model } : {},
17896
+ ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
17897
+ };
17898
+ }
17899
+ function captureDefinitionVersion(finding) {
17900
+ return `capture/${finding.category}/${finding.severity}`;
17901
+ }
17902
+ function toCaptureDefinitionInput(finding) {
17903
+ return {
17904
+ ruleId: finding.ruleId,
17905
+ version: captureDefinitionVersion(finding),
17906
+ name: finding.ruleId,
17907
+ category: finding.category,
17908
+ severity: finding.severity,
17909
+ definition: JSON.stringify({ ruleId: finding.ruleId })
17910
+ };
17911
+ }
17912
+
17913
+ // ../../packages/schema/src/zod/managed.ts
17914
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
17915
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
17916
+ var ManagedSettingKey = external_exports.enum([
17917
+ "runMode",
17918
+ "historicalAccess",
17919
+ "vaultConsent",
17920
+ "vaultKeyCustody",
17921
+ "vaultInlineReveal",
17922
+ "modelJudgeConsent",
17923
+ "dataSharesInPlace"
17924
+ ]).meta({ id: "ManagedSettingKey" });
17925
+ var ManagedSettingsValues = external_exports.object({
17926
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
17927
+ controlPlane: external_exports.object({
17928
+ endpoint: external_exports.string().min(1),
17929
+ label: external_exports.string().min(1).optional()
17930
+ }).optional(),
17931
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
17932
+ vaultConsent: external_exports.boolean().optional(),
17933
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
17934
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
17935
+ modelJudgeConsent: external_exports.boolean().optional(),
17936
+ dataSharesInPlace: external_exports.boolean().optional()
17937
+ }).meta({ id: "ManagedSettingsValues" });
17938
+ var ManagedSettings = external_exports.object({
17939
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
17940
+ // Shown on every locked control, so the user can tell an administrative
17941
+ // decision from a bug. Absent renders as a generic "your organization".
17942
+ organization: external_exports.string().min(1).optional(),
17943
+ // What the administrator pinned.
17944
+ values: ManagedSettingsValues.default({}),
17945
+ // Which of those the user may not change. A key here with no matching value
17946
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
17947
+ // the user may still override. The two are separable on purpose.
17948
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
17949
+ }).meta({ id: "ManagedSettings" });
17950
+
17951
+ // ../../packages/schema/src/zod/policy.ts
17952
+ var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
17953
+ var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
17954
+ var Policy = external_exports.object({
17955
+ id: external_exports.guid(),
17956
+ scope: PolicyScope,
17957
+ target: PolicyTarget,
17958
+ action: ActionTaken,
17959
+ enabled: external_exports.boolean().default(true),
17960
+ customKeywords: external_exports.array(external_exports.string()).optional(),
17961
+ // Display name — optional so older policy rows without name still parse.
17962
+ // Added for the findings API (policy.name column migration).
17963
+ name: external_exports.string().optional()
17964
+ }).meta({ id: "Policy" });
17965
+ var PolicyBundle = external_exports.object({
17966
+ version: external_exports.string(),
17967
+ policies: external_exports.array(Policy),
17968
+ // Rules from the installed marketplace packs (snapshotted by the
17969
+ // control plane). The plugin registers these in addition to its bundled
17970
+ // packs. Optional so older backends — and older on-disk caches — that omit
17971
+ // the field still parse; consumers read `bundle.rules ?? []`.
17972
+ rules: external_exports.array(Rule).optional(),
17973
+ // When true, `rules` IS the complete effective ruleset and the runtime must
17974
+ // NOT merge its compiled-in bundled packs — the standalone gateway sets this
17975
+ // after reading the user's installed snapshot (installed_packs, enabled
17976
+ // packs only), which is how detection updates stay manual: new bundled
17977
+ // rules run only after the user applies the pack update. Absent/false keeps
17978
+ // the historical composition (bundled packs + rules) — older caches.
17979
+ rulesComplete: external_exports.boolean().optional(),
17980
+ // Active detection exceptions, evaluation subset only (see
17981
+ // ExceptionBundleEntry). Optional so older bundle producers — and older
17982
+ // on-disk caches — that omit the field still parse; consumers read
17983
+ // `bundle.exceptions ?? []`.
17984
+ exceptions: external_exports.array(ExceptionBundleEntry).optional(),
17985
+ // Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
17986
+ // A second axis over the same `redact` action, carried beside the policies
17987
+ // rather than on them: nothing writes ruleId-targeted policies to disk, so
17988
+ // widening Policy itself would change a persisted shape to express something
17989
+ // only the in-memory bundle needs. Optional so an older producer — or an
17990
+ // older on-disk cache — still parses; consumers read `?? []` and get the
17991
+ // pre-existing one-way behaviour, which is the safe direction to default.
17992
+ reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
17993
+ // Installed pack version, keyed by ruleId, for rules in `rules` that came
17994
+ // from a versioned installed pack. Optional so older backends — and older
17995
+ // on-disk caches — that omit the field still parse; consumers fall back to
17996
+ // the rule's own spec version. NOT the bundle version above — see
17997
+ // installedRuleset's ruleVersions for the source of truth.
17998
+ ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
17999
+ customKeywords: external_exports.array(external_exports.string()),
18000
+ fetchedAt: external_exports.iso.datetime()
18001
+ }).meta({ id: "PolicyBundle" });
18002
+ var OBSERVE_ONLY_CATEGORIES = ["config"];
18003
+ var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
18004
+ var CATEGORY_PEAK_SEVERITY = {
18005
+ secret: "critical",
18006
+ financial: "critical",
18007
+ // core-financial/credit-card
18008
+ code_flaw: "critical",
18009
+ pii: "high",
18010
+ phi: "high",
18011
+ custom: "high",
18012
+ // user-defined; conservative
18013
+ code_context: "low",
18014
+ config: "low"
18015
+ // observe-only; floors to monitor regardless
18016
+ };
18017
+ function severityFloorPolicy(category) {
18018
+ if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
18019
+ const peak = CATEGORY_PEAK_SEVERITY[category];
18020
+ return peak === "critical" || peak === "high" ? "warn" : "monitor";
18021
+ }
18022
+ var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
18023
+ var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
18024
+ var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
18025
+ var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
18026
+ var BUILTIN_POLICY_SPECS = {
18027
+ monitor: {
18028
+ name: "Monitor",
18029
+ action: "log",
18030
+ reversible: false,
18031
+ description: "Log every match for audit. The request is allowed through untouched."
18032
+ },
18033
+ warn: {
18034
+ name: "Warn",
18035
+ action: "warn",
18036
+ reversible: false,
18037
+ description: "Allow the request, but warn the user inline before it is sent."
18038
+ },
18039
+ redact: {
18040
+ name: "Redact",
18041
+ action: "redact",
18042
+ reversible: false,
18043
+ description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
18044
+ },
18045
+ vault: {
18046
+ name: "Redact & Vault",
18047
+ action: "redact",
18048
+ reversible: true,
18049
+ 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."
18050
+ },
18051
+ block: {
18052
+ name: "Block",
18053
+ action: "block",
18054
+ reversible: false,
18055
+ description: "Refuse the request entirely whenever any rule in this detection matches."
18056
+ }
18057
+ };
18058
+ function builtinPolicyToAction(id) {
18059
+ return BUILTIN_POLICY_SPECS[id].action;
18060
+ }
18061
+ var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18062
+ (id) => !BUILTIN_POLICY_SPECS[id].reversible
18063
+ );
18064
+ var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18065
+ (id) => BUILTIN_POLICY_SPECS[id].reversible
18066
+ );
18067
+ var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
18068
+ function builtinPolicyIsReversible(id) {
18069
+ return BUILTIN_POLICY_SPECS[id].reversible;
18014
18070
  }
18015
- function captureDefinitionVersion(finding) {
18016
- return `capture/${finding.category}/${finding.severity}`;
18071
+ function policyIdIsReversible(policyId) {
18072
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18073
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18074
+ return builtinPolicyIsReversible(id);
18017
18075
  }
18018
- function toCaptureDefinitionInput(finding) {
18019
- return {
18020
- ruleId: finding.ruleId,
18021
- version: captureDefinitionVersion(finding),
18022
- name: finding.ruleId,
18023
- category: finding.category,
18024
- severity: finding.severity,
18025
- definition: JSON.stringify({ ruleId: finding.ruleId })
18026
- };
18076
+ var DEFAULT_ACTIONS = Object.fromEntries(
18077
+ DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
18078
+ );
18079
+ var BUILTIN_POLICIES = Object.fromEntries(
18080
+ KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
18081
+ );
18082
+ var DEFAULT_PACK_POLICY_ID = "monitor";
18083
+ function policyIdToAction(policyId) {
18084
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18085
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18086
+ return BUILTIN_POLICIES[id].action;
18027
18087
  }
18088
+ var UsedByItem = external_exports.object({
18089
+ id: external_exports.string(),
18090
+ name: external_exports.string(),
18091
+ ruleCount: external_exports.number().int().nonnegative(),
18092
+ enabled: external_exports.boolean()
18093
+ }).meta({ id: "UsedByItem" });
18094
+ var PolicyListItem = external_exports.object({
18095
+ id: external_exports.string(),
18096
+ kind: PolicyKind,
18097
+ name: external_exports.string(),
18098
+ enabled: external_exports.boolean(),
18099
+ usedByCount: external_exports.number().int().nonnegative()
18100
+ }).meta({ id: "PolicyListItem" });
18101
+ var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
18102
+ var PolicyDetail = external_exports.object({
18103
+ specVersion: external_exports.literal(1),
18104
+ id: external_exports.string(),
18105
+ kind: PolicyKind,
18106
+ name: external_exports.string(),
18107
+ enabled: external_exports.boolean(),
18108
+ description: external_exports.string(),
18109
+ usedBy: external_exports.array(UsedByItem)
18110
+ }).meta({ id: "PolicyDetail" });
18111
+ var PolicyStatsResponse = external_exports.object({
18112
+ policies: external_exports.number().int().nonnegative(),
18113
+ builtin: external_exports.number().int().nonnegative(),
18114
+ custom: external_exports.number().int().nonnegative(),
18115
+ detectionsGoverned: external_exports.number().int().nonnegative()
18116
+ }).meta({ id: "PolicyStatsResponse" });
18028
18117
 
18029
18118
  // ../../packages/schema/src/zod/project-files.ts
18030
18119
  var ProjectFileInput = external_exports.object({
@@ -18104,44 +18193,6 @@ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
18104
18193
  NoRemediationDecision
18105
18194
  ]);
18106
18195
 
18107
- // ../../packages/schema/src/zod/rule-test.ts
18108
- var TestRulesRequest = external_exports.object({
18109
- rules: external_exports.array(Rule).min(1).max(100),
18110
- text: external_exports.string().max(5e4).optional(),
18111
- fixtures: external_exports.array(RuleFixture).max(200).optional()
18112
- }).refine((v) => v.text !== void 0 || (v.fixtures?.length ?? 0) > 0, {
18113
- message: "Provide `text`, `fixtures`, or both \u2014 there must be something to test"
18114
- }).meta({ id: "TestRulesRequest" });
18115
- var RuleTestMatch = external_exports.object({
18116
- ruleId: external_exports.string(),
18117
- category: DetectionCategory,
18118
- severity: Severity,
18119
- span: Span,
18120
- confidence: external_exports.number().min(0).max(1),
18121
- match: external_exports.string()
18122
- }).meta({ id: "RuleTestMatch" });
18123
- var FixtureResult = external_exports.object({
18124
- label: external_exports.string(),
18125
- shouldMatch: external_exports.boolean(),
18126
- didMatch: external_exports.boolean(),
18127
- passed: external_exports.boolean(),
18128
- matches: external_exports.array(RuleTestMatch)
18129
- }).meta({ id: "FixtureResult" });
18130
- var TestRulesResponse = external_exports.object({
18131
- // Present only when the request supplied `text`.
18132
- adhoc: external_exports.object({ matches: external_exports.array(RuleTestMatch) }).optional(),
18133
- fixtures: external_exports.array(FixtureResult),
18134
- summary: external_exports.object({
18135
- total: external_exports.number().int().nonnegative(),
18136
- passed: external_exports.number().int().nonnegative(),
18137
- failed: external_exports.number().int().nonnegative()
18138
- }),
18139
- // Ids of rules whose matcher type the engine cannot evaluate today (e.g.
18140
- // `validator`), so they silently never match. Surfaced so an author is not
18141
- // misled by a green run that actually skipped a rule.
18142
- unsupportedRuleIds: external_exports.array(external_exports.string())
18143
- }).meta({ id: "TestRulesResponse" });
18144
-
18145
18196
  // ../../packages/schema/src/zod/security.ts
18146
18197
  var SeveritySummaryItem = external_exports.object({
18147
18198
  severity: Severity,
@@ -18239,10 +18290,22 @@ var TopSourcesQuery = external_exports.object({
18239
18290
  // Omit for both kinds.
18240
18291
  kind: external_exports.enum(SOURCE_KINDS).optional()
18241
18292
  });
18242
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18293
+ var Provider = Harness.extract([
18294
+ "ClaudeCode",
18295
+ "Cursor",
18296
+ "Codex",
18297
+ "Antigravity",
18298
+ "ClaudeAi",
18299
+ "ChatGpt",
18300
+ "Copilot",
18301
+ "Api"
18302
+ ]).meta({ id: "Provider" });
18243
18303
  var ScanCoverageProvider = external_exports.object({
18244
18304
  provider: Provider,
18245
- // Percent of that provider's traffic scanned in the window. 0 when unsupported.
18305
+ // Percent of that provider's traffic the shipped capture surface reaches.
18306
+ // A curated business fact, constant across every `range` — not a measured
18307
+ // per-window metric. 0 exactly when `supported` is false. See the comment
18308
+ // above the block for where these numbers are decided.
18246
18309
  coverage: external_exports.number().int().min(0).max(100),
18247
18310
  supported: external_exports.boolean()
18248
18311
  }).meta({ id: "ScanCoverageProvider" });
@@ -18296,6 +18359,18 @@ var ApplyRecommendedActionResponse = external_exports.object({
18296
18359
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
18297
18360
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
18298
18361
 
18362
+ // ../../packages/schema/src/zod/settings-action.ts
18363
+ var SaveSettingsInput = external_exports.object({
18364
+ historicalAccess: external_exports.string(),
18365
+ modelJudgeConsent: external_exports.boolean(),
18366
+ vaultConsent: external_exports.string(),
18367
+ vaultInlineReveal: external_exports.string()
18368
+ });
18369
+ var AttachInput = external_exports.object({
18370
+ endpoint: external_exports.string(),
18371
+ label: external_exports.string().optional()
18372
+ });
18373
+
18299
18374
  // ../../packages/schema/src/zod/triage.ts
18300
18375
  var TriageHit = external_exports.object({
18301
18376
  ruleId: external_exports.string(),
@@ -18310,7 +18385,7 @@ var TriageHit = external_exports.object({
18310
18385
  valueFingerprint: external_exports.string().optional(),
18311
18386
  keyVersion: external_exports.number().int().nonnegative().optional()
18312
18387
  });
18313
- var TriagePolicy = BuiltinPolicyId;
18388
+ var TriagePolicy = CategoryPolicyId;
18314
18389
  var TriageCategoryRec = external_exports.object({
18315
18390
  category: DetectionCategory,
18316
18391
  action: TriagePolicy,
@@ -18528,8 +18603,11 @@ function chmodBestEffort(path, mode) {
18528
18603
  function tightenDir(dir) {
18529
18604
  chmodBestEffort(dir, DATA_DIR_MODE);
18530
18605
  }
18606
+ function mkdirOwnerOnlySync(dir, recursive = false) {
18607
+ mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
18608
+ }
18531
18609
  function ensureDataDirSync(dir) {
18532
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18610
+ mkdirOwnerOnlySync(dir, true);
18533
18611
  tightenDir(dir);
18534
18612
  }
18535
18613
  function dbSidecars(file2) {
@@ -18541,6 +18619,26 @@ function tightenFile(file2) {
18541
18619
  function tightenPerms(file2) {
18542
18620
  for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18543
18621
  }
18622
+ function writeExclusiveOwnerOnlySync(file2, data) {
18623
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18624
+ }
18625
+ function writeOwnerOnlyFileSync(file2, data) {
18626
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18627
+ try {
18628
+ rmSync(tmp, { force: true });
18629
+ } catch {
18630
+ }
18631
+ try {
18632
+ writeExclusiveOwnerOnlySync(tmp, data);
18633
+ renameSync(tmp, file2);
18634
+ } finally {
18635
+ try {
18636
+ rmSync(tmp, { force: true });
18637
+ } catch {
18638
+ }
18639
+ }
18640
+ tightenFile(file2);
18641
+ }
18544
18642
  function classifyOccupant(file2) {
18545
18643
  try {
18546
18644
  if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
@@ -18569,7 +18667,7 @@ function createOwnerOnlyFileSync(file2, data) {
18569
18667
  }
18570
18668
  let created;
18571
18669
  try {
18572
- writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18670
+ writeExclusiveOwnerOnlySync(tmp, data);
18573
18671
  created = publishByLink(tmp, file2, data);
18574
18672
  } finally {
18575
18673
  try {
@@ -18591,7 +18689,7 @@ function publishByLink(tmp, file2, data) {
18591
18689
  if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18592
18690
  }
18593
18691
  try {
18594
- writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18692
+ writeExclusiveOwnerOnlySync(file2, data);
18595
18693
  return true;
18596
18694
  } catch (err) {
18597
18695
  if (err.code === "EEXIST") return false;
@@ -18604,6 +18702,25 @@ function backupPath(file2, tag) {
18604
18702
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18605
18703
  }
18606
18704
  var STALE_PARTIAL_MS = 5 * 6e4;
18705
+ var SNAPSHOT_STAGING_SUFFIX = ".partial";
18706
+ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18707
+ var SNAPSHOT_STAGING_COPY = "copy";
18708
+ function createSnapshotStaging(backup) {
18709
+ const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18710
+ rmSync2(stage, { recursive: true, force: true });
18711
+ mkdirOwnerOnlySync(stage);
18712
+ tightenDir(stage);
18713
+ return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18714
+ }
18715
+ function idleMs(entry) {
18716
+ for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18717
+ try {
18718
+ return Date.now() - statSync(candidate).mtimeMs;
18719
+ } catch {
18720
+ }
18721
+ }
18722
+ return null;
18723
+ }
18607
18724
  function reapStalePartials(file2) {
18608
18725
  const dir = dirname(file2);
18609
18726
  const prefix = `${basename(file2)}.`;
@@ -18614,30 +18731,34 @@ function reapStalePartials(file2) {
18614
18731
  return;
18615
18732
  }
18616
18733
  for (const name of entries) {
18617
- if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18618
- const partial2 = join(dir, name);
18734
+ if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18735
+ const staging = join(dir, name);
18619
18736
  try {
18620
- if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18621
- rmSync2(partial2, { force: true });
18737
+ const idle = idleMs(staging);
18738
+ if (idle !== null && idle > STALE_PARTIAL_MS) {
18739
+ rmSync2(staging, { recursive: true, force: true });
18622
18740
  }
18623
18741
  } catch {
18624
18742
  }
18625
18743
  }
18626
18744
  }
18627
18745
  function snapshotStore(db, backup) {
18628
- const partial2 = `${backup}.partial`;
18746
+ const { stage, copy } = createSnapshotStaging(backup);
18629
18747
  try {
18630
- rmSync2(partial2, { force: true });
18631
- db.prepare("VACUUM INTO ?").run(partial2);
18632
- tightenFile(partial2);
18633
- renameSync2(partial2, backup);
18748
+ db.prepare("VACUUM INTO ?").run(copy);
18749
+ tightenFile(copy);
18750
+ renameSync2(copy, backup);
18634
18751
  } catch (error51) {
18635
18752
  try {
18636
- rmSync2(partial2, { force: true });
18753
+ rmSync2(stage, { recursive: true, force: true });
18637
18754
  } catch {
18638
18755
  }
18639
18756
  throw error51;
18640
18757
  }
18758
+ try {
18759
+ rmSync2(stage, { recursive: true, force: true });
18760
+ } catch {
18761
+ }
18641
18762
  }
18642
18763
  function moveStoreAside(file2, backup) {
18643
18764
  const undo = [];
@@ -19365,9 +19486,10 @@ function safeParseStringArray(raw) {
19365
19486
  const parsed = safeJson(raw, null);
19366
19487
  return Array.isArray(parsed) ? parsed : [];
19367
19488
  }
19489
+ var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19368
19490
  function toHarness(raw) {
19369
19491
  const parsed = Harness.safeParse(raw);
19370
- return parsed.success ? parsed.data : "claudecode";
19492
+ return parsed.success ? parsed.data : DEFAULT_HARNESS;
19371
19493
  }
19372
19494
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19373
19495
  if (row.status) {
@@ -19498,7 +19620,7 @@ var SqliteActivityRepository = class {
19498
19620
  const params = [];
19499
19621
  if (query.harness && query.harness.length > 0) {
19500
19622
  conditions.push(
19501
- `coalesce(json_extract(attributes, '$.harness'), 'claudecode') IN (${placeholders(query.harness.length)})`
19623
+ `coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
19502
19624
  );
19503
19625
  params.push(...query.harness);
19504
19626
  }
@@ -19705,13 +19827,13 @@ var SqliteActivityRepository = class {
19705
19827
  * The DISTINCT harnesses that actually have sessions (optionally within a
19706
19828
  * `started_at >= fromMs` window), so the filter can offer only the harnesses
19707
19829
  * present rather than the full enum. Each stored value is normalized through
19708
- * the SAME `toHarness` default the list uses (missing → 'claudecode'), so a
19709
- * store of bare (harness-less) roots surfaces exactly `['claudecode']`.
19830
+ * the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
19831
+ * a store of bare (harness-less) roots surfaces exactly that one harness.
19710
19832
  */
19711
19833
  harnessFacets(fromMs) {
19712
19834
  const where = fromMs === void 0 ? "" : " AND started_at >= ?";
19713
19835
  const stmt = this.db.prepare(
19714
- `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), 'claudecode') AS harness
19836
+ `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
19715
19837
  FROM audit_events WHERE ${SESSION_ROOT}${where}`
19716
19838
  );
19717
19839
  const rows = allRows(
@@ -19925,8 +20047,8 @@ var SqliteAuditEventsRepository = class {
19925
20047
  }
19926
20048
  // Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
19927
20049
  // (which takes a caller-supplied random id), the id here is MINTED internally
19928
- // from the natural key — `llmCallId(sessionId, messageId)` — tenant-free like the
19929
- // sibling local-store ids (the local store is single-tenant). The deterministic
20050
+ // from the natural key — `llmCallId(sessionId, messageId)` — derived from the
20051
+ // session and message alone, like the sibling local-store ids. The deterministic
19930
20052
  // id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
19931
20053
  // AND converge a streaming partial/final split across two incremental passes:
19932
20054
  // a whole-file re-read no-ops (equal output), a lagging final replaces a
@@ -20891,6 +21013,42 @@ var SqliteFindingsRepository = class {
20891
21013
  this.db = db;
20892
21014
  }
20893
21015
  db;
21016
+ /**
21017
+ * The newest `limit` findings, newest first.
21018
+ *
21019
+ * THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
21020
+ * pin it. The natural spelling — drive from `inspection_findings`, order by the
21021
+ * JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
21022
+ * not on the driving table: SQLite sorts every finding in the store through a
21023
+ * temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
21024
+ * against 0.9 ms for the form below, and the gap is a ratio of the store size
21025
+ * rather than a constant.
21026
+ *
21027
+ * What it takes to make `started_at` order come out of an index instead:
21028
+ *
21029
+ * - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
21030
+ * planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
21031
+ * index cannot serve the ORDER BY: the predicate spans four event types, so
21032
+ * satisfying a global `started_at` order across them needs a range merge
21033
+ * SQLite will not do, and it sorts instead. Freed of it, the planner scans
21034
+ * `idx_audit_started_at` — a bare `started_at` index — in DESC order and
21035
+ * filters the type per row, which lets the LIMIT stop the scan early.
21036
+ * - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
21037
+ * purely to stop the tables being reordered. With plain JOINs the planner
21038
+ * drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
21039
+ * unary plus ALONE recovers almost none of the win. Both are needed.
21040
+ *
21041
+ * Neither is a micro-optimisation that a later reader should tidy away, and
21042
+ * `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
21043
+ * the temp B-tree comes back.
21044
+ *
21045
+ * Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
21046
+ * identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
21047
+ * renaming the index turns this read into an ERROR, where `+` turns it into a
21048
+ * scan-and-sort — slower, still correct. The worst case for the chosen form is
21049
+ * a store whose recent captures carry no findings at all, where the scan walks
21050
+ * the whole index; that is still no worse than the full sort it replaced.
21051
+ */
20894
21052
  recentFindings(opts) {
20895
21053
  const limit = opts?.limit ?? 50;
20896
21054
  const rows = allRows(
@@ -20899,10 +21057,10 @@ var SqliteFindingsRepository = class {
20899
21057
  f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
20900
21058
  json_extract(e.attributes, '$.source_tool') AS source_tool,
20901
21059
  e.event_type AS kind
20902
- FROM inspection_findings f
20903
- JOIN audit_events e ON e.id = f.audit_event_id
20904
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20905
- WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
21060
+ FROM audit_events e
21061
+ CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
21062
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21063
+ WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
20906
21064
  ORDER BY e.started_at DESC, f.rowid DESC
20907
21065
  LIMIT :limit`
20908
21066
  ),
@@ -21546,7 +21704,8 @@ var SqliteInspectionDefinitionsRepository = class {
21546
21704
  }
21547
21705
  db;
21548
21706
  insertStmt;
21549
- // Idempotent upsert; returns the content-addressed definition id.
21707
+ // Insert-if-absent; returns the content-addressed definition id. An id already
21708
+ // present keeps the stored row untouched — see the class doc.
21550
21709
  upsert(input) {
21551
21710
  const id = inspectionDefinitionId(input.ruleId, input.version);
21552
21711
  const row = toInspectionDefinitionRow(input, id);
@@ -21690,11 +21849,23 @@ function isParseableBinaryVersion(version2) {
21690
21849
 
21691
21850
  // ../../packages/persistence/src/repositories/installed-packs.ts
21692
21851
  var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
21852
+ function printableRuleId(entry) {
21853
+ if (typeof entry !== "object" || entry === null) return null;
21854
+ const candidate = entry.id;
21855
+ return Rule.shape.id.safeParse(candidate).success ? candidate : null;
21856
+ }
21857
+ function firstIssueReason(error51) {
21858
+ const issue2 = error51.issues[0];
21859
+ if (!issue2) return "unknown";
21860
+ const path = issue2.path.map((segment) => String(segment)).join(".");
21861
+ return path ? `${path}: ${issue2.code}` : issue2.code;
21862
+ }
21863
+ var REJECTED_RULE_DETAIL_CAP = 10;
21693
21864
  function inventorySignature(packs2) {
21694
21865
  return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
21695
21866
  }
21696
21867
  function hashRules(rulesJson) {
21697
- return createHash2("sha1").update(rulesJson).digest("hex");
21868
+ return createHash2("sha256").update(rulesJson).digest("hex");
21698
21869
  }
21699
21870
  function parseVersion(v) {
21700
21871
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -21906,10 +22077,21 @@ var SqliteInstalledPacksRepository = class {
21906
22077
  * (all detection off) instead of falling back to the bundled packs. Every
21907
22078
  * JSON-level failure therefore counts as invalid.
21908
22079
  */
22080
+ /**
22081
+ * ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
22082
+ * index is (namespace, pack_id) — so two enabled packs may contribute the same
22083
+ * id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
22084
+ * winner is whatever order SQLite happens to return, which makes a collision
22085
+ * resolve differently on two machines holding identical stores. Ordering by
22086
+ * (namespace, pack_id) makes the loser deterministic and therefore testable.
22087
+ */
21909
22088
  installedRuleset() {
21910
22089
  const rows = allRows(
21911
22090
  this.db.prepare(
21912
- `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version FROM installed_packs`
22091
+ `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
22092
+ namespace, pack_id AS packId
22093
+ FROM installed_packs
22094
+ ORDER BY namespace, pack_id`
21913
22095
  )
21914
22096
  );
21915
22097
  const out = {
@@ -21917,22 +22099,32 @@ var SqliteInstalledPacksRepository = class {
21917
22099
  enabledPacks: 0,
21918
22100
  rules: [],
21919
22101
  invalidRules: 0,
22102
+ rejectedRules: [],
21920
22103
  ruleActions: /* @__PURE__ */ new Map(),
21921
- ruleVersions: /* @__PURE__ */ new Map()
22104
+ ruleVersions: /* @__PURE__ */ new Map(),
22105
+ reversibleRules: /* @__PURE__ */ new Set()
22106
+ };
22107
+ const reject = (pack, ruleId, reason) => {
22108
+ if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
22109
+ out.rejectedRules.push({ pack, ruleId, reason });
21922
22110
  };
21923
22111
  for (const row of rows) {
21924
22112
  if (!intToBool(row.enabled)) continue;
21925
22113
  out.enabledPacks += 1;
21926
22114
  const action = policyIdToAction(row.policyId);
22115
+ const reversible = policyIdIsReversible(row.policyId);
22116
+ const pack = `${row.namespace}/${row.packId}`;
21927
22117
  let raw;
21928
22118
  try {
21929
22119
  raw = JSON.parse(row.rulesJson);
21930
22120
  } catch {
21931
22121
  out.invalidRules += 1;
22122
+ reject(pack, null, "rules_json: malformed JSON");
21932
22123
  continue;
21933
22124
  }
21934
22125
  if (!Array.isArray(raw)) {
21935
22126
  out.invalidRules += 1;
22127
+ reject(pack, null, "rules_json: not an array");
21936
22128
  continue;
21937
22129
  }
21938
22130
  for (const entry of raw) {
@@ -21941,7 +22133,12 @@ var SqliteInstalledPacksRepository = class {
21941
22133
  out.rules.push(parsed.data);
21942
22134
  out.ruleActions.set(parsed.data.id, action);
21943
22135
  out.ruleVersions.set(parsed.data.id, row.version);
21944
- } else out.invalidRules += 1;
22136
+ if (reversible) out.reversibleRules.add(parsed.data.id);
22137
+ else out.reversibleRules.delete(parsed.data.id);
22138
+ } else {
22139
+ out.invalidRules += 1;
22140
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22141
+ }
21945
22142
  }
21946
22143
  }
21947
22144
  return out;
@@ -22135,31 +22332,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
22135
22332
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
22136
22333
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
22137
22334
  var HARNESS_LABELS = {
22138
- claudecode: "Claude Code",
22139
- cursor: "Cursor",
22140
- codex: "Codex",
22141
- antigravity: "Antigravity"
22335
+ [HARNESS.ClaudeCode]: "Claude Code",
22336
+ [HARNESS.Cursor]: "Cursor",
22337
+ [HARNESS.Codex]: "Codex",
22338
+ [HARNESS.Antigravity]: "Antigravity"
22142
22339
  };
22143
22340
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
22144
22341
  var EMPTY_PROJECT_AGG = {
22145
22342
  accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
22146
22343
  findingsCount: 0
22147
22344
  };
22345
+ var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
22346
+ var TITLE_NEEDLES = {
22347
+ ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
22348
+ Cursor: stripSeparators(SOURCE_TOOL.Cursor),
22349
+ Codex: stripSeparators(SOURCE_TOOL.Codex),
22350
+ Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
22351
+ };
22148
22352
  function resolveHarnessId(attrs, row) {
22149
22353
  if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
22150
22354
  return attrs.provider;
22151
22355
  }
22152
- const t = (row.title ?? "").toLowerCase().replace(/[\s-]/g, "");
22153
- if (t.includes("claudecode") || t === "claude") return "claudecode";
22154
- if (t.includes("cursor")) return "cursor";
22155
- if (t.includes("codex")) return "codex";
22156
- if (t.includes("antigravity")) return "antigravity";
22356
+ const t = stripSeparators(row.title ?? "");
22357
+ if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
22358
+ if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
22359
+ if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
22360
+ if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
22157
22361
  return null;
22158
22362
  }
22159
22363
  function isLiveRealClaudeCode(rows) {
22160
22364
  return rows.some((r) => {
22161
22365
  const attrs = safeJson(r.attributes, {});
22162
- return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === "claudecode";
22366
+ return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
22163
22367
  });
22164
22368
  }
22165
22369
  function toAssetSummary(row) {
@@ -22427,7 +22631,7 @@ var SqliteInventoryAssetsRepository = class {
22427
22631
  const isRealHarness = rows.some(
22428
22632
  (r) => safeJson(r.attributes, {}).provenance !== "sample"
22429
22633
  );
22430
- const attachConfig = isRealHarness && harnessId === "claudecode" && configAssets.length > 0;
22634
+ const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
22431
22635
  const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
22432
22636
  if (q && assets.length === 0) continue;
22433
22637
  const firstRow = rows[0];
@@ -23101,9 +23305,10 @@ var SqliteProjectFilesRepository = class {
23101
23305
  // ../../packages/persistence/src/repositories/resolutions.ts
23102
23306
  import { randomUUID as randomUUID7 } from "crypto";
23103
23307
  var SqliteResolutionsRepository = class {
23104
- constructor(db, now = () => Date.now()) {
23308
+ constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
23105
23309
  this.db = db;
23106
23310
  this.now = now;
23311
+ this.newId = newId;
23107
23312
  this.insertStmt = db.prepare(
23108
23313
  `INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
23109
23314
  VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
@@ -23136,12 +23341,14 @@ var SqliteResolutionsRepository = class {
23136
23341
  }
23137
23342
  db;
23138
23343
  now;
23344
+ newId;
23139
23345
  insertStmt;
23140
23346
  latestStmt;
23141
23347
  openAtRestStmt;
23142
23348
  resolvedAtRestStmt;
23143
23349
  /**
23144
- * Insert one disposition row. The repo mints the id and stamps created_at.
23350
+ * Insert one disposition row. The repo mints the id and stamps created_at,
23351
+ * both through the constructor's injectable seams.
23145
23352
  * `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
23146
23353
  * FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
23147
23354
  * from the schema enums. NOTE for future manual-resolution writers: this is
@@ -23153,7 +23360,7 @@ var SqliteResolutionsRepository = class {
23153
23360
  */
23154
23361
  insertResolution(r) {
23155
23362
  this.insertStmt.run({
23156
- id: randomUUID7(),
23363
+ id: this.newId(),
23157
23364
  findingKey: r.findingKey,
23158
23365
  status: FindingStatus.parse(r.status),
23159
23366
  method: ResolutionMethod.parse(r.method),
@@ -23732,16 +23939,16 @@ var ACTION_TO_KIND = {
23732
23939
  warn: "warned"
23733
23940
  };
23734
23941
  var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
23735
- var SCAN_COVERAGE = [
23736
- { provider: "claudecode", coverage: 100, supported: true },
23737
- { provider: "cursor", coverage: 0, supported: false },
23738
- { provider: "codex", coverage: 80, supported: true },
23739
- { provider: "antigravity", coverage: 60, supported: true },
23740
- { provider: "claudeai", coverage: 0, supported: false },
23741
- { provider: "chatgpt", coverage: 0, supported: false },
23742
- { provider: "copilot", coverage: 0, supported: false },
23743
- { provider: "api", coverage: 0, supported: false }
23744
- ];
23942
+ var SCAN_COVERAGE = {
23943
+ [HARNESS.Antigravity]: { coverage: 60, supported: true },
23944
+ [HARNESS.Api]: { coverage: 0, supported: false },
23945
+ [HARNESS.ChatGpt]: { coverage: 40, supported: true },
23946
+ [HARNESS.ClaudeAi]: { coverage: 40, supported: true },
23947
+ [HARNESS.ClaudeCode]: { coverage: 100, supported: true },
23948
+ [HARNESS.Codex]: { coverage: 80, supported: true },
23949
+ [HARNESS.Copilot]: { coverage: 0, supported: false },
23950
+ [HARNESS.Cursor]: { coverage: 0, supported: false }
23951
+ };
23745
23952
  var GRANULARITY = {
23746
23953
  "7d": "day",
23747
23954
  "30d": "day",
@@ -23840,9 +24047,22 @@ var SqliteSecurityRepository = class {
23840
24047
  return Promise.resolve({ total, needsRemediation, bySeverity });
23841
24048
  }
23842
24049
  // Range is echoed but does not change the result today — coverage is a constant
23843
- // business fact (see SCAN_COVERAGE), not a measured per-window metric.
24050
+ // business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
24051
+ // comes from Provider.options (the enum's declaration order), not from
24052
+ // SCAN_COVERAGE's own key order — deliberately, not because object literals
24053
+ // leave key order unspecified (ES2015 guarantees insertion order for these
24054
+ // non-integer string keys, so iterating SCAN_COVERAGE directly would be
24055
+ // reliable too). The reason is the schema comment's promise: the returned
24056
+ // order must mirror the generated OpenAPI enum list, which is Provider's
24057
+ // contract, not this table's.
23844
24058
  scanCoverage(range) {
23845
- return Promise.resolve({ range, providers: SCAN_COVERAGE.map((p) => ({ ...p })) });
24059
+ return Promise.resolve({
24060
+ range,
24061
+ providers: Provider.options.map((provider) => ({
24062
+ provider,
24063
+ ...SCAN_COVERAGE[provider]
24064
+ }))
24065
+ });
23846
24066
  }
23847
24067
  enforcementActions(range) {
23848
24068
  const lenMs = RANGE_DAYS[range] * DAY_MS4;
@@ -23900,10 +24120,10 @@ var SqliteSecurityRepository = class {
23900
24120
  // count; a superseding open/redetected row means the finding is not
23901
24121
  // remediated and is excluded, same invariant as severitySummary. Legacy
23902
24122
  // at-rest findings with finding_key IS NULL can never have a resolution row
23903
- // (the lifecycle is keyed by finding_key), so the SQL guard excludes them
23904
- // outright. One raw-row query (fetch every trackable finding + its latest
23905
- // resolution's status/method/resolved_at) + pure-JS filter/bucket/mean,
23906
- // mirroring this file's other methods.
24123
+ // (the lifecycle is keyed by finding_key), so they cannot reach the driving
24124
+ // set below. One raw-row query (fetch the findings with resolution activity in
24125
+ // the window + each one's latest resolution status/method/resolved_at) +
24126
+ // pure-JS filter/bucket/mean, mirroring this file's other methods.
23907
24127
  mttrTrend(range) {
23908
24128
  const granularity = granularityFor(range);
23909
24129
  const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
@@ -23919,29 +24139,80 @@ var SqliteSecurityRepository = class {
23919
24139
  // started_at the upsert overwrites onto inspection_findings.audit_event_id.
23920
24140
  // COALESCE onto the parent event's started_at defends against any
23921
24141
  // legacy/edge row the backfill left null.
23922
- `SELECT COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
24142
+ `SELECT DISTINCT f.finding_key AS finding_key,
24143
+ COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
23923
24144
  latest.status AS latest_status,
23924
24145
  latest.method AS latest_method,
23925
24146
  latest.resolved_at AS latest_resolved_at
23926
- FROM inspection_findings f
23927
- JOIN audit_events e ON e.id = f.audit_event_id
23928
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24147
+ FROM finding_resolution fr
24148
+ CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
24149
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24150
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
23929
24151
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
23930
24152
  ON latest.finding_key = f.finding_key
23931
- WHERE f.finding_key IS NOT NULL
23932
- AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
23933
- AND EXISTS (
23934
- SELECT 1 FROM finding_resolution fr
23935
- WHERE fr.finding_key = f.finding_key
23936
- AND fr.resolved_at >= :windowStart
23937
- )`
23938
- // The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
23939
- // any resolution activity at/after the window start — a row this method
24153
+ WHERE fr.resolved_at >= :windowStart
24154
+ AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
24155
+ // `fr` is a SUPERSET prefilter, not the answer: a finding this method
23940
24156
  // ultimately counts has its LATEST resolution inside the window, which
23941
- // implies such a row exists, so nothing wanted is dropped. The exact
23942
- // latest-wins + status/method + window gate stays in JS below,
23943
- // dialect-agnostic. Without this, a
23944
- // 7d request evaluated the store's entire trackable-findings history.
24157
+ // implies a resolution row at/after the window start exists, so nothing
24158
+ // wanted is dropped. The exact latest-wins + status/method + window gate
24159
+ // stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
24160
+ // implied rather than dropped — the join key comes from
24161
+ // finding_resolution, whose finding_key is NOT NULL.
24162
+ //
24163
+ // IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
24164
+ // the correction this replaced. Spelled as an `EXISTS` in the WHERE it
24165
+ // READ as a bound and was not one: SQLite drove from `audit_events` on
24166
+ // event_type, joined every capture event to its findings, and evaluated
24167
+ // the EXISTS last — bounding the RESULT and not the scan, so a 7d request
24168
+ // still cost the store's whole trackable history. Measured at 44.6 ms on
24169
+ // 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
24170
+ // both cases returning rows for a window holding a fraction of it.
24171
+ //
24172
+ // Two things carry it, and they answer DIFFERENT halves — which is worth
24173
+ // stating precisely, because the obvious reading (both are needed for the
24174
+ // speed) is wrong and was measured to be wrong:
24175
+ //
24176
+ // - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
24177
+ // keyword is semantically identical to JOIN and exists only to stop the
24178
+ // tables being reordered; with plain JOINs the planner puts `e` back on
24179
+ // the outside, because with no ANALYZE statistics it prices
24180
+ // `event_type IN (...)` as a selective probe. Reverting it alone takes
24181
+ // the 2k->20k flatness ratio from 1.32 to 16.87.
24182
+ // - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
24183
+ // `resolved_at >= :windowStart` a range SEARCH instead of a bare
24184
+ // `SCAN fr` — finding_key was this table's only index before it, so the
24185
+ // range had none. It buys NO flatness in store size: remove it and the
24186
+ // ratio above does not move, because the latest-resolution derived
24187
+ // table already passes over the whole of finding_resolution, so this
24188
+ // read is O(resolutions) either way and resolutions are not the store.
24189
+ // What it buys is the criterion `hot-read-query-plans.test.ts` enforces
24190
+ // — no hot read may pass over a table with no index — and that is the
24191
+ // guard that goes red when it is dropped. Neither test catches the
24192
+ // other's defect.
24193
+ //
24194
+ // SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
24195
+ // tidy-up. finding_resolution is append-only, so a key that was fixed,
24196
+ // redetected and fixed again carries several rows inside one window and
24197
+ // matches once per row — and the value below is a MEAN, so a key matched
24198
+ // three times is a key weighted three times.
24199
+ //
24200
+ // The skew is easy to argue away and the argument is wrong, so it is worth
24201
+ // recording. Duplicate rows for ONE key are identical (every projected
24202
+ // column is per-key: `latest.*` is latest-wins, `first_detected_at` is
24203
+ // preserved), so sums and counts scale together and that key's own mean
24204
+ // does not move. What moves is a bucket holding TWO findings that duplicate
24205
+ // UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
24206
+ // 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
24207
+ // `security.test.ts`'s "weights a finding ONCE however many resolution rows
24208
+ // it has inside the window" — which needed a fixture built for it, since no
24209
+ // single-key case can show it.
24210
+ //
24211
+ // `finding_key` is selected to make the DISTINCT dedup by KEY rather than
24212
+ // by value tuple. On the other columns alone, two genuinely different
24213
+ // findings sharing a severity, a first-detection event and a resolution
24214
+ // instant — one commit fixing two secrets in one file — are one tuple, and
24215
+ // collapsing them would under-count in the other direction.
23945
24216
  ),
23946
24217
  { windowStart }
23947
24218
  );
@@ -23999,16 +24270,47 @@ var SqliteSecurityRepository = class {
23999
24270
  }
24000
24271
  // Recently-resolved activity feed: findings whose finding_key's LATEST
24001
24272
  // finding_resolution row is status:'resolved'/method:'fixed-at-source' —
24002
- // same latest-resolution-wins correlated subquery as severitySummary /
24003
- // mttrTrend (NOT a plain JOIN, which would surface every historical
24004
- // resolution row for a key rather than just its current disposition). A key
24005
- // whose latest row is a superseding 'open'/'redetected' row (the same
24006
- // secret came back) is excluded — it is not currently resolved. Legacy
24007
- // at-rest findings with finding_key IS NULL are excluded outright (the
24008
- // resolution lifecycle can never attach to them). Path comes from the
24009
- // finding's parent event (event_type 'code_change', attributes.file_path) —
24010
- // mirrors resolutions.ts's openAtRestStmt accessor. Ordered by resolved_at
24011
- // DESC, capped at `limit`.
24273
+ // same latest-resolution-wins derived table as severitySummary / mttrTrend
24274
+ // (NOT a plain JOIN, which would surface every historical resolution row for
24275
+ // a key rather than just its current disposition). A key whose latest row is
24276
+ // a superseding 'open'/'redetected' row (the same secret came back) is
24277
+ // excluded — it is not currently resolved. Legacy at-rest findings with
24278
+ // finding_key IS NULL are excluded outright (the resolution lifecycle can
24279
+ // never attach to them). Path comes from the finding's parent event
24280
+ // (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
24281
+ // openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
24282
+ //
24283
+ // THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
24284
+ // the plan rather than a preference. Written the other way round — driving
24285
+ // from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
24286
+ // cannot use the join key: `f` is reached FROM `latest` by finding_key, so
24287
+ // `latest` gets probed on (rn, status, method) instead and the plan enumerates
24288
+ // every (code_change event x resolved key) pair before `f` can reject it. That
24289
+ // is a cross product, and it is quadratic in the store: measured at 10,966 ms
24290
+ // on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
24291
+ // returned. It was invisible for as long as it was, and reported at 8 ms,
24292
+ // because an empty finding_resolution table makes the inner side empty and the
24293
+ // cross product collapses to nothing — so the shape is only observable on a
24294
+ // corpus that seeds resolutions.
24295
+ //
24296
+ // Driving from `latest` instead makes every step below it a unique-index or
24297
+ // primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
24298
+ // so the cost is the derived table's own — linear in resolutions, which is
24299
+ // what this feed is legitimately about.
24300
+ //
24301
+ // CROSS JOIN is what actually pins that, and it is load-bearing rather than
24302
+ // decorative: in SQLite the keyword is semantically identical to JOIN and
24303
+ // exists only to stop the optimizer reordering the tables. Written as plain
24304
+ // JOINs in this order the planner puts `e` back on the outside — it has no
24305
+ // ANALYZE statistics to price the alternatives with, so it takes
24306
+ // `event_type = 'code_change'` for a selective index probe and rebuilds the
24307
+ // cross product. The FROM order alone was measured to change the plan not at
24308
+ // all.
24309
+ //
24310
+ // The LEFT JOIN it replaced was already an inner join in effect: three
24311
+ // `latest.*` predicates sit in the WHERE, and each of them is false for a
24312
+ // null-extended row. Spelling it JOIN changes no row and stops the plan
24313
+ // reading as though the findings side could drive.
24012
24314
  recentlyResolved(limit = 20) {
24013
24315
  const rows = allRows(
24014
24316
  this.db.prepare(
@@ -24018,17 +24320,15 @@ var SqliteSecurityRepository = class {
24018
24320
  json_extract(e.attributes, '$.file_path') AS path,
24019
24321
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
24020
24322
  latest.resolved_at AS latest_resolved_at
24021
- FROM inspection_findings f
24022
- JOIN audit_events e ON e.id = f.audit_event_id
24023
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24024
- LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24025
- ON latest.finding_key = f.finding_key
24026
- WHERE e.event_type = 'code_change'
24027
- AND f.finding_key IS NOT NULL
24028
- AND latest.status = 'resolved'
24323
+ FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24324
+ CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
24325
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24326
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24327
+ WHERE latest.status = 'resolved'
24029
24328
  AND latest.method = 'fixed-at-source'
24030
24329
  AND latest.resolved_at IS NOT NULL
24031
- ORDER BY latest_resolved_at DESC
24330
+ AND e.event_type = 'code_change'
24331
+ ORDER BY latest.resolved_at DESC
24032
24332
  LIMIT :limit`
24033
24333
  ),
24034
24334
  { limit }
@@ -24965,6 +25265,7 @@ function openAndInitialize(file2) {
24965
25265
  function openLocalDatabase(dir) {
24966
25266
  ensureDataDirSync(dir);
24967
25267
  const file2 = join2(dir, DB_FILENAME);
25268
+ reapStalePartials(file2);
24968
25269
  const {
24969
25270
  db,
24970
25271
  events,
@@ -25399,11 +25700,79 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25399
25700
  }
25400
25701
  }
25401
25702
 
25402
- // ../../packages/persistence/src/settings.ts
25703
+ // ../../packages/persistence/src/managed-settings.ts
25403
25704
  import { readFileSync as readFileSync3 } from "fs";
25705
+ import { posix, win32 } from "path";
25706
+ function managedSettingsPaths(platform2 = process.platform) {
25707
+ if (platform2 === "darwin") {
25708
+ return [
25709
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
25710
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
25711
+ ];
25712
+ }
25713
+ if (platform2 === "win32") {
25714
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
25715
+ }
25716
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
25717
+ }
25718
+ function readManagedSettings(paths = managedSettingsPaths()) {
25719
+ for (const path of paths) {
25720
+ let text;
25721
+ try {
25722
+ text = readFileSync3(path, "utf8");
25723
+ } catch {
25724
+ continue;
25725
+ }
25726
+ const record2 = parseJsonObject(text);
25727
+ if (!record2) continue;
25728
+ const parsed = ManagedSettings.safeParse(record2);
25729
+ if (parsed.success) return parsed.data;
25730
+ }
25731
+ return null;
25732
+ }
25733
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
25734
+ if (!managed) return settings;
25735
+ const { values } = managed;
25736
+ const merged = { ...settings };
25737
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
25738
+ if (values.controlPlane !== void 0) {
25739
+ merged.controlPlane = {
25740
+ ...values.controlPlane,
25741
+ // The administrator pinned WHICH deployment, not WHEN this machine
25742
+ // joined it. Keep the user's own attach time when the endpoint is
25743
+ // unchanged, so a managed machine does not appear to re-attach on every
25744
+ // read; stamp a fresh one when the administrator moved it.
25745
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
25746
+ };
25747
+ }
25748
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
25749
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
25750
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
25751
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
25752
+ if (values.vaultConsent !== void 0) {
25753
+ merged.vaultConsent = values.vaultConsent ? (
25754
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
25755
+ // at the current version otherwise.
25756
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
25757
+ ) : void 0;
25758
+ }
25759
+ if (values.modelJudgeConsent !== void 0) {
25760
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
25761
+ acknowledgedAt: now().toISOString(),
25762
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
25763
+ } : void 0;
25764
+ }
25765
+ return merged;
25766
+ }
25767
+
25768
+ // ../../packages/persistence/src/settings.ts
25769
+ import { readFileSync as readFileSync4 } from "fs";
25404
25770
  import { join as join5 } from "path";
25405
25771
  var SETTINGS_FILENAME = "settings.json";
25406
25772
  function readWorkspaceSettings(base = defaultDataDir()) {
25773
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25774
+ }
25775
+ function readUserSettings(base) {
25407
25776
  const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25408
25777
  if (!record2) return defaultWorkspaceSettings();
25409
25778
  try {
@@ -25415,7 +25784,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
25415
25784
  function readJson(file2) {
25416
25785
  let text;
25417
25786
  try {
25418
- text = readFileSync3(file2, "utf8");
25787
+ text = readFileSync4(file2, "utf8");
25419
25788
  } catch {
25420
25789
  return null;
25421
25790
  }
@@ -25533,15 +25902,7 @@ function formatPointer(category, keyVersion, pointerId, tag) {
25533
25902
  // ../../packages/persistence/src/vault/key-provider.ts
25534
25903
  import { execFileSync } from "child_process";
25535
25904
  import { randomBytes as randomBytes2 } from "crypto";
25536
- import {
25537
- chmodSync as chmodSync2,
25538
- mkdirSync as mkdirSync2,
25539
- readFileSync as readFileSync4,
25540
- renameSync as renameSync4,
25541
- rmSync as rmSync4,
25542
- statSync as statSync3,
25543
- writeFileSync as writeFileSync3
25544
- } from "fs";
25905
+ import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25545
25906
  import { join as join6 } from "path";
25546
25907
  var VAULT_OCCUPANT_REASON = {
25547
25908
  symlink: "the path is a symlink; remove it so a keyring can be created",
@@ -25634,7 +25995,8 @@ var LOCK_OWNER_FILE = "owner";
25634
25995
  var ROTATION_IN_PROGRESS = "vault: a key rotation is already in progress";
25635
25996
  function claimRotationLock(lock, owner) {
25636
25997
  try {
25637
- mkdirSync2(lock);
25998
+ mkdirOwnerOnlySync(lock);
25999
+ tightenDir(lock);
25638
26000
  } catch (err) {
25639
26001
  if (err.code === "EEXIST") return false;
25640
26002
  throw asError(err);
@@ -25676,7 +26038,7 @@ function acquireRotationLock(keysDir2) {
25676
26038
  }
25677
26039
  function releaseRotationLock(lease) {
25678
26040
  try {
25679
- if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26041
+ if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
25680
26042
  } catch {
25681
26043
  return;
25682
26044
  }
@@ -25727,7 +26089,7 @@ var FileKeyProvider = class {
25727
26089
  #read() {
25728
26090
  let raw;
25729
26091
  try {
25730
- raw = readFileSync4(this.filePath, "utf8");
26092
+ raw = readFileSync5(this.filePath, "utf8");
25731
26093
  } catch (err) {
25732
26094
  if (err.code === "ENOENT") return null;
25733
26095
  throw err instanceof Error ? err : new Error(String(err));
@@ -25766,15 +26128,19 @@ var FileKeyProvider = class {
25766
26128
  * Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
25767
26129
  * Used only for rotation, under the rotation lock — first creation goes
25768
26130
  * through the creation-exclusive path instead.
26131
+ *
26132
+ * Delegated to the shared owner-only write rather than spelled here, so the
26133
+ * create mode this file is published at is the one paths.ts owns and tests
26134
+ * directly. A local copy of the pair was a second place the mode could be
26135
+ * dropped with the trailing tighten still repairing the end state, which is
26136
+ * the shape no assertion on a published file can see. It also picks up that
26137
+ * primitive's per-process tmp name, its stale-tmp sweep, and an exclusive
26138
+ * create that refuses to follow a symlink planted at the tmp path.
25769
26139
  */
25770
26140
  #write(keyring) {
25771
26141
  ensureDataDirSync(this.#keysDir);
25772
- const file2 = this.filePath;
25773
- const tmp = `${file2}.tmp`;
25774
- writeFileSync3(tmp, `${serializeKeyring(keyring)}
25775
- `, { mode: DATA_FILE_MODE });
25776
- renameSync4(tmp, file2);
25777
- tightenFileMode(file2);
26142
+ writeOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
26143
+ `);
25778
26144
  return keyring;
25779
26145
  }
25780
26146
  };
@@ -25784,15 +26150,73 @@ function tightenFileMode(file2) {
25784
26150
  } catch {
25785
26151
  }
25786
26152
  }
25787
- var runSecurity = (args) => execFileSync("/usr/bin/security", args, {
26153
+ var SECURITY_TIMEOUT_MS = 5e3;
26154
+ var runSecurity = (args, stdin) => execFileSync("/usr/bin/security", args, {
25788
26155
  encoding: "utf8",
25789
- stdio: ["ignore", "pipe", "ignore"]
26156
+ input: stdin,
26157
+ timeout: SECURITY_TIMEOUT_MS,
26158
+ // stderr is discarded rather than captured, and that is deliberate: a
26159
+ // captured stream rides out on an execFileSync error's `.stderr`, and the
26160
+ // write paths here carry the keyring. Exit status is the only thing any
26161
+ // branch below reads.
26162
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
25790
26163
  });
25791
26164
  var SECURITY_ITEM_NOT_FOUND = 44;
26165
+ function corruptReason(err) {
26166
+ if (err instanceof SyntaxError) return "malformed JSON";
26167
+ return err instanceof Error ? err.message : "unknown";
26168
+ }
26169
+ function securityFailureMeta(err) {
26170
+ const e = err;
26171
+ const parts = [];
26172
+ if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
26173
+ if (typeof e.signal === "string" && e.signal) parts.push(`signal ${e.signal}`);
26174
+ if (typeof e.code === "string" && e.code) parts.push(e.code);
26175
+ return parts.length > 0 ? parts.join(", ") : "unknown error";
26176
+ }
26177
+ function writeCommand(keyring, update, keychain) {
26178
+ const hex3 = Buffer.from(serializeKeyring(keyring), "utf8").toString("hex");
26179
+ const parts = [
26180
+ "add-generic-password",
26181
+ ...update ? ["-U"] : [],
26182
+ "-s",
26183
+ KEYCHAIN_SERVICE,
26184
+ "-a",
26185
+ KEYCHAIN_ACCOUNT,
26186
+ "-X",
26187
+ hex3
26188
+ ];
26189
+ if (keychain !== void 0) {
26190
+ if (/['\\\n\r\0]/.test(keychain)) {
26191
+ throw new Error(
26192
+ "vault: keychain path contains a quote, backslash, line break or NUL, which security -i cannot carry intact"
26193
+ );
26194
+ }
26195
+ parts.push(`'${keychain}'`);
26196
+ }
26197
+ return `${parts.join(" ")}
26198
+ `;
26199
+ }
25792
26200
  var KeychainKeyProvider = class {
25793
26201
  #keysDir;
25794
26202
  #exec;
25795
- constructor(keysDir2, exec = runSecurity) {
26203
+ #keychain;
26204
+ /**
26205
+ * The trailing keychain argument, or nothing. Every subcommand used here
26206
+ * takes it last (`add-generic-password [keychain]`,
26207
+ * `find-generic-password [keychain...]`), and omitting it means the default
26208
+ * search list. Fixed at construction, so it is built once rather than per
26209
+ * call on the capture path.
26210
+ */
26211
+ #target;
26212
+ /**
26213
+ * `keychain` names the keychain to operate on, as `security`'s trailing
26214
+ * argument. Production passes nothing and gets the user's default keychain,
26215
+ * which is the whole point of the backend. A test driving the REAL binary
26216
+ * passes a throwaway one, because the alternative is writing vault key
26217
+ * material into the developer's own login keychain and leaving it there.
26218
+ */
26219
+ constructor(keysDir2, exec = runSecurity, keychain) {
25796
26220
  if (exec === runSecurity && process.platform !== "darwin") {
25797
26221
  throw new Error(
25798
26222
  `keychain custody is not available on this platform (${process.platform}); use file custody`
@@ -25800,6 +26224,8 @@ var KeychainKeyProvider = class {
25800
26224
  }
25801
26225
  this.#keysDir = keysDir2;
25802
26226
  this.#exec = exec;
26227
+ this.#keychain = keychain;
26228
+ this.#target = keychain === void 0 ? [] : [keychain];
25803
26229
  }
25804
26230
  /** Where a fallback file provider for the same vault would keep its keyring. */
25805
26231
  get keysDir() {
@@ -25838,18 +26264,22 @@ var KeychainKeyProvider = class {
25838
26264
  KEYCHAIN_SERVICE,
25839
26265
  "-a",
25840
26266
  KEYCHAIN_ACCOUNT,
25841
- "-w"
26267
+ "-w",
26268
+ ...this.#target
25842
26269
  ]);
25843
26270
  } catch (err) {
25844
26271
  if (err.status === SECURITY_ITEM_NOT_FOUND) return null;
25845
26272
  throw new Error(
25846
- `vault: keychain read failed (${err instanceof Error ? err.message : String(err)}); refusing to treat the failure as an absent keyring`,
25847
- { cause: err }
26273
+ `vault: keychain read failed (${securityFailureMeta(err)}); refusing to treat the failure as an absent keyring`
25848
26274
  );
25849
26275
  }
25850
26276
  const body = raw.trim();
25851
26277
  if (body.length === 0) return null;
25852
- return parseKeyring(body);
26278
+ try {
26279
+ return parseKeyring(body);
26280
+ } catch (err) {
26281
+ throw new Error(`vault: keychain item is not a usable keyring (${corruptReason(err)})`);
26282
+ }
25853
26283
  }
25854
26284
  /**
25855
26285
  * First mint: a plain `add-generic-password` (no `-U`) fails when an item
@@ -25857,37 +26287,25 @@ var KeychainKeyProvider = class {
25857
26287
  * keyring — the loser re-reads and adopts it instead.
25858
26288
  */
25859
26289
  #create(keyring) {
25860
- const args = [
25861
- "add-generic-password",
25862
- "-s",
25863
- KEYCHAIN_SERVICE,
25864
- "-a",
25865
- KEYCHAIN_ACCOUNT,
25866
- "-w",
25867
- serializeKeyring(keyring)
25868
- ];
26290
+ const line = writeCommand(keyring, false, this.#keychain);
25869
26291
  try {
25870
- this.#exec(args);
26292
+ this.#exec(["-i"], line);
25871
26293
  } catch (err) {
25872
26294
  const winner = this.#read();
25873
26295
  if (winner) return winner;
25874
- throw asError(err);
26296
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
25875
26297
  }
25876
26298
  return keyring;
25877
26299
  }
25878
26300
  // `-U` updates the item in place, deliberately replacing the stored map with
25879
26301
  // one that contains it — used only for rotation, under the rotation lock.
25880
26302
  #replace(keyring) {
25881
- this.#exec([
25882
- "add-generic-password",
25883
- "-U",
25884
- "-s",
25885
- KEYCHAIN_SERVICE,
25886
- "-a",
25887
- KEYCHAIN_ACCOUNT,
25888
- "-w",
25889
- serializeKeyring(keyring)
25890
- ]);
26303
+ const line = writeCommand(keyring, true, this.#keychain);
26304
+ try {
26305
+ this.#exec(["-i"], line);
26306
+ } catch (err) {
26307
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
26308
+ }
25891
26309
  return keyring;
25892
26310
  }
25893
26311
  };
@@ -26397,7 +26815,7 @@ function resolveProviderSafe(resolveProviderFn) {
26397
26815
  }
26398
26816
 
26399
26817
  // ../../packages/plugin-sdk/src/config-inventory.ts
26400
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
26818
+ import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
26401
26819
  import { homedir as homedir2 } from "os";
26402
26820
  import { basename as basename3, join as join10 } from "path";
26403
26821
 
@@ -27105,6 +27523,10 @@ function luhnCheck(digits) {
27105
27523
  // ../../packages/detections/src/engine.ts
27106
27524
  var keywordMatcher = new KeywordMatcher2();
27107
27525
  var regexMatcher = new RegexMatcher2();
27526
+ var MATCHERS = {
27527
+ keyword: (text, rule) => keywordMatcher.match(text, rule),
27528
+ regex: (text, rule) => regexMatcher.match(text, rule)
27529
+ };
27108
27530
  var packs = /* @__PURE__ */ new Map();
27109
27531
  var POST_VALIDATORS = {
27110
27532
  entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
@@ -27120,8 +27542,7 @@ function passesPostValidators(rule, value) {
27120
27542
  for (const ref of validators) {
27121
27543
  const name = typeof ref === "string" ? ref : ref.name;
27122
27544
  const config2 = typeof ref === "string" ? void 0 : ref.config;
27123
- const validate = POST_VALIDATORS[name];
27124
- if (validate && !validate(value, config2)) return false;
27545
+ if (!POST_VALIDATORS[name](value, config2)) return false;
27125
27546
  }
27126
27547
  return true;
27127
27548
  }
@@ -27182,14 +27603,7 @@ function scan(text, rules, context) {
27182
27603
  const candidates = [];
27183
27604
  for (const rule of ruleset) {
27184
27605
  if (!ruleApplies(rule, extension)) continue;
27185
- let spans;
27186
- if (rule.matcher.type === "keyword") {
27187
- spans = keywordMatcher.match(text, rule);
27188
- } else if (rule.matcher.type === "regex") {
27189
- spans = regexMatcher.match(text, rule);
27190
- } else {
27191
- continue;
27192
- }
27606
+ const spans = MATCHERS[rule.matcher.type](text, rule);
27193
27607
  for (const span of spans) {
27194
27608
  const rawMatch = text.slice(span.start, span.end);
27195
27609
  if (!passesPostValidators(rule, rawMatch)) continue;
@@ -27431,24 +27845,32 @@ function probesFor(rule) {
27431
27845
  return [...derived, ...EXPONENTIAL_PROBES, ...POLYNOMIAL_PROBES];
27432
27846
  }
27433
27847
  var wallClock = () => performance.now();
27434
- function worstProbeMs(rule, now = wallClock) {
27848
+ function worstProbeMs(rule, now = wallClock, corroborate) {
27435
27849
  let ms = 0;
27436
27850
  let probe = "";
27851
+ let corroboratedMs;
27437
27852
  for (const text of probesFor(rule)) {
27438
27853
  const start = now();
27854
+ const corroborateStart = corroborate?.();
27439
27855
  scan(text, [rule]);
27440
27856
  const elapsed = now() - start;
27857
+ const corroborateEnd = corroborate?.();
27441
27858
  if (elapsed > ms) {
27442
27859
  ms = elapsed;
27443
27860
  probe = text;
27861
+ corroboratedMs = corroborateStart === void 0 || corroborateEnd === void 0 ? void 0 : corroborateEnd - corroborateStart;
27444
27862
  }
27445
27863
  if (ms >= BUDGET_MS) break;
27446
27864
  }
27447
- return { ms, probe };
27865
+ return { ms, probe, corroboratedMs };
27448
27866
  }
27449
- function checkRuleTiming(rule) {
27450
- const { ms, probe } = worstProbeMs(rule);
27451
- return { safe: ms < BUDGET_MS, worstMs: ms, probe };
27867
+ var CPU_CORROBORATION_SHARE = 0.2;
27868
+ var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
27869
+ function checkRuleTiming(rule, corroborate) {
27870
+ const { ms, probe, corroboratedMs } = worstProbeMs(rule, wallClock, corroborate);
27871
+ const work = corroboratedMs ?? 0;
27872
+ const verdict = ms < BUDGET_MS ? "safe" : work >= CORROBORATION_FLOOR_MS ? "over-budget" : "uncorroborated";
27873
+ return { verdict, worstMs: ms, corroboratedMs: work, probe };
27452
27874
  }
27453
27875
 
27454
27876
  // ../../rules/code-flaws/auth-jwt-no-verify.json
@@ -29526,7 +29948,7 @@ function scanText(text, ruleVersions) {
29526
29948
  }
29527
29949
 
29528
29950
  // ../../packages/plugin-sdk/src/repo.ts
29529
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
29951
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
29530
29952
  import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
29531
29953
  function resolveRepoIdentity(cwd) {
29532
29954
  try {
@@ -29589,7 +30011,7 @@ function resolveGitContext(root) {
29589
30011
  }
29590
30012
  function safeRead(path) {
29591
30013
  try {
29592
- return readFileSync5(path, "utf8");
30014
+ return readFileSync6(path, "utf8");
29593
30015
  } catch {
29594
30016
  return void 0;
29595
30017
  }
@@ -29903,7 +30325,12 @@ function createIsolatedScanner(data, opts = {}) {
29903
30325
  build: (id) => ({ kind: "probe", id, rule }),
29904
30326
  reply: (message) => {
29905
30327
  if (message.kind !== "probed") return false;
29906
- resolve2({ status: "ok", safe: message.safe, worstMs: message.worstMs });
30328
+ resolve2({
30329
+ status: "ok",
30330
+ verdict: message.verdict,
30331
+ worstMs: message.worstMs,
30332
+ corroboratedMs: message.corroboratedMs
30333
+ });
29907
30334
  return true;
29908
30335
  }
29909
30336
  },
@@ -29923,6 +30350,12 @@ function createIsolatedScanner(data, opts = {}) {
29923
30350
  };
29924
30351
  }
29925
30352
 
30353
+ // ../../packages/plugin-sdk/src/work-clock.ts
30354
+ function workClockMs() {
30355
+ const usage = typeof process.threadCpuUsage === "function" ? process.threadCpuUsage() : process.cpuUsage();
30356
+ return (usage.user + usage.system) / 1e3;
30357
+ }
30358
+
29926
30359
  // ../../packages/plugin-sdk/src/rule-quarantine.ts
29927
30360
  var PASS_BUDGET_MS = 2e3;
29928
30361
  var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
@@ -29951,6 +30384,14 @@ function warnUnmeasured(rule) {
29951
30384
  false
29952
30385
  );
29953
30386
  }
30387
+ function warnUncorroborated(rule, worstMs, corroboratedMs) {
30388
+ warn(
30389
+ rule,
30390
+ "deferred",
30391
+ `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.`,
30392
+ false
30393
+ );
30394
+ }
29954
30395
  function warnUnmeasurable(reason, count) {
29955
30396
  process.stderr.write(
29956
30397
  `[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.
@@ -29997,24 +30438,31 @@ async function filterUnsafeRules(rules, gateway, opts) {
29997
30438
  warnUnmeasured(rule);
29998
30439
  continue;
29999
30440
  }
30000
- let isSafe;
30441
+ let verdict;
30001
30442
  let worstMs;
30443
+ let corroboratedMs = 0;
30002
30444
  if (prober) {
30003
30445
  const outcome = await prober.probe(rule);
30004
30446
  if (outcome.status === "unavailable") {
30005
30447
  unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
30006
30448
  continue;
30007
30449
  }
30008
- isSafe = outcome.status === "ok" ? outcome.safe : false;
30450
+ verdict = outcome.status === "ok" ? outcome.verdict : "over-budget";
30009
30451
  worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
30452
+ if (outcome.status === "ok") corroboratedMs = outcome.corroboratedMs;
30010
30453
  } else {
30011
30454
  try {
30012
- ({ safe: isSafe, worstMs } = checkRuleTiming(rule));
30455
+ ({ verdict, worstMs, corroboratedMs } = checkRuleTiming(rule, workClockMs));
30013
30456
  } catch {
30014
- isSafe = false;
30457
+ verdict = "over-budget";
30015
30458
  worstMs = Number.POSITIVE_INFINITY;
30016
30459
  }
30017
30460
  }
30461
+ if (verdict === "uncorroborated") {
30462
+ warnUncorroborated(rule, worstMs, corroboratedMs);
30463
+ continue;
30464
+ }
30465
+ const isSafe = verdict === "safe";
30018
30466
  let persisted = false;
30019
30467
  try {
30020
30468
  await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
@@ -30114,6 +30562,11 @@ function createGuardedScanner(partition, gateway, opts) {
30114
30562
  };
30115
30563
  }
30116
30564
 
30565
+ // ../../packages/plugin-sdk/src/ignore-layers.ts
30566
+ var import_ignore = __toESM(require_ignore(), 1);
30567
+ import { readFileSync as readFileSync8 } from "fs";
30568
+ import { join as join11 } from "path";
30569
+
30117
30570
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
30118
30571
  import { arch, hostname as hostname4, platform, release } from "os";
30119
30572
  function resolveInventoryContext(input) {
@@ -30144,17 +30597,16 @@ function resolveInventoryContext(input) {
30144
30597
  }
30145
30598
 
30146
30599
  // ../../packages/plugin-sdk/src/nudge.ts
30147
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
30148
- import { join as join11 } from "path";
30600
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
30601
+ import { join as join12 } from "path";
30149
30602
 
30150
30603
  // ../../packages/plugin-sdk/src/paths.ts
30151
30604
  import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
30152
30605
  import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
30153
30606
 
30154
30607
  // ../../packages/plugin-sdk/src/project-files.ts
30155
- var import_ignore = __toESM(require_ignore(), 1);
30156
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
30157
- import { basename as basename5, join as join12 } from "path";
30608
+ import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
30609
+ import { basename as basename5, join as join13 } from "path";
30158
30610
 
30159
30611
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
30160
30612
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -30253,6 +30705,7 @@ function createPluginRuntime(gateway, settings, opts) {
30253
30705
  let initialized = false;
30254
30706
  const ruleActionIndex = /* @__PURE__ */ new Map();
30255
30707
  const categoryActionIndex = /* @__PURE__ */ new Map();
30708
+ let reversibleRuleIndex = /* @__PURE__ */ new Set();
30256
30709
  async function ensureInitialized() {
30257
30710
  if (initialized) return;
30258
30711
  const bundle = await gateway.getPolicyBundle();
@@ -30265,6 +30718,7 @@ function createPluginRuntime(gateway, settings, opts) {
30265
30718
  categoryActionIndex.set(p.target.category, p.action);
30266
30719
  }
30267
30720
  }
30721
+ reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
30268
30722
  const bundledProbeKeys = new Set(
30269
30723
  getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
30270
30724
  );
@@ -30343,11 +30797,13 @@ function createPluginRuntime(gateway, settings, opts) {
30343
30797
  if (worst === "block") return { action: "block", text: null, findings };
30344
30798
  if (worst === "redact") {
30345
30799
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
30800
+ const reversibleFindings = redactFindings.filter((f) => reversibleRuleIndex.has(f.ruleId));
30346
30801
  return {
30347
30802
  action: "redact",
30348
30803
  text: redact(text, redactFindings),
30349
30804
  findings,
30350
- enforcedFindings: redactFindings
30805
+ enforcedFindings: redactFindings,
30806
+ reversibleFindings
30351
30807
  };
30352
30808
  }
30353
30809
  return { action: worst, text, findings };
@@ -30561,8 +31017,8 @@ function createPluginRuntime(gateway, settings, opts) {
30561
31017
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
30562
31018
 
30563
31019
  // ../../packages/plugin-sdk/src/throttle.ts
30564
- import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30565
- import { join as join13 } from "path";
31020
+ import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
31021
+ import { join as join14 } from "path";
30566
31022
 
30567
31023
  // ../../packages/plugin-sdk/src/tokenize.ts
30568
31024
  function redactedPlaceholder(category) {
@@ -30625,6 +31081,8 @@ var SecretVaultGlue = class {
30625
31081
  async tokenizeText(text, opts) {
30626
31082
  try {
30627
31083
  const findings = opts?.findings ?? this.#selfScan(text);
31084
+ const reversible = opts?.reversible;
31085
+ const keeps = (finding) => reversible === void 0 || reversible.has(finding);
30628
31086
  if (findings === null) return { text: "[REDACTED]", pointers: [], degraded: [] };
30629
31087
  if (findings.length === 0) return { text, pointers: [], degraded: [] };
30630
31088
  const groups = groupSpans(text, findings);
@@ -30641,6 +31099,8 @@ var SecretVaultGlue = class {
30641
31099
  } else if (original !== finding.rawMatch) {
30642
31100
  replacement = redactedPlaceholder(group.category);
30643
31101
  degraded.unshift({ category: group.category });
31102
+ } else if (!keeps(finding)) {
31103
+ replacement = redactedPlaceholder(finding.category);
30644
31104
  } else {
30645
31105
  replacement = await this.tokenizeValue(finding.rawMatch, {
30646
31106
  ruleId: finding.ruleId,
@@ -30862,12 +31322,17 @@ var UNOPENABLE_VAULT = {
30862
31322
 
30863
31323
  // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
30864
31324
  import { writeFileSync as writeFileSync7 } from "fs";
30865
- import { join as join14 } from "path";
31325
+ import { join as join15 } from "path";
31326
+
31327
+ // ../../packages/setup-wizard/src/triage/merge.ts
31328
+ var RANK = Object.fromEntries(
31329
+ KNOWN_BUILTIN_IDS.map((id, i) => [id, i])
31330
+ );
30866
31331
 
30867
31332
  // ../../packages/setup-wizard/src/triage/plan-file.ts
30868
- import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
31333
+ import { mkdtempSync, readFileSync as readFileSync10, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
30869
31334
  import { tmpdir } from "os";
30870
- import { basename as basename6, dirname as dirname4, join as join15 } from "path";
31335
+ import { basename as basename6, dirname as dirname4, join as join16 } from "path";
30871
31336
  var SuppressionEntrySchema = external_exports.object({
30872
31337
  ruleId: external_exports.string(),
30873
31338
  category: DetectionCategory,
@@ -30922,6 +31387,8 @@ var StandaloneDataGateway = class {
30922
31387
  db;
30923
31388
  // Kept for the fingerprint key lookup (exception.key lives beside the store).
30924
31389
  dataDir;
31390
+ // One notice per gateway — see warnRulesetDiscarded.
31391
+ warnedRulesetDiscarded = false;
30925
31392
  constructor(dataDir2, detections = [], meta3) {
30926
31393
  this.db = openLocalDatabase(dataDir2);
30927
31394
  this.dataDir = dataDir2;
@@ -31041,28 +31508,69 @@ var StandaloneDataGateway = class {
31041
31508
  // - ANY invalid rule among enabled packs (all-invalid, partial corruption,
31042
31509
  // or a single malformed entry) → undefined → bundled fallback. Serving a
31043
31510
  // reduced "complete" set would silently drop exactly the corrupted rules
31044
- // with no fallback; the bundled packs are a superset, so falling back
31045
- // never loses coverage. Steady-state installed rules are all valid
31046
- // (generated + Zod-checked), so this only fires on a genuinely
31047
- // malformed/foreign store;
31511
+ // with no fallback. The bundled packs are a superset of AKA's OWN packs,
31512
+ // so falling back never loses coverage there but they contain no
31513
+ // pulled or custom pack, so for those this trades a partial ruleset for
31514
+ // none of them plus the loss of every pack's per-detection enforcement
31515
+ // action. That is deliberate (a store this machine cannot fully validate
31516
+ // is not authoritative), and it is why the cost of REJECTING a rule
31517
+ // matters: `Rule` is strict, so one unrecognized key in one custom rule
31518
+ // reaches this branch, not just a genuinely malformed or foreign store.
31519
+ // `installed-packs.test.ts` pins that per-rule counting;
31048
31520
  // - enabled packs that produce ZERO rules with no invalids (e.g. every
31049
31521
  // enabled pack's rules_json is `[]`) → undefined → bundled fallback: an
31050
31522
  // enabled pack contributing nothing is untrustworthy, not a real
31051
31523
  // "detect nothing" (that is expressed by disabling packs, handled above);
31052
31524
  // - otherwise → the enabled packs' validated rules, marked complete.
31525
+ /**
31526
+ * The discard above is the one ruleset decision this gateway reaches on its
31527
+ * own, and it is the most expensive one here: ONE rejected entry costs the
31528
+ * user every custom rule and every per-detection enforcement action, replaced
31529
+ * by bundled packs that contain neither. Nothing else reports it — a hook is a
31530
+ * short-lived process whose stderr is the only channel it has — so name what
31531
+ * was rejected and where the rest of the list lives.
31532
+ *
31533
+ * Unlike a quarantine verdict this caches nothing: the rejection is re-derived
31534
+ * from the store on every run, so the recovery is to fix or reinstall the pack,
31535
+ * and no line here may offer a command that clears a stored verdict.
31536
+ *
31537
+ * Written at most once per gateway — a second getPolicyBundle() in the same
31538
+ * process would re-report the same finding.
31539
+ */
31540
+ warnRulesetDiscarded(snapshot) {
31541
+ if (this.warnedRulesetDiscarded) return;
31542
+ this.warnedRulesetDiscarded = true;
31543
+ const listed = snapshot.rejectedRules.map((r) => `${r.pack}${r.ruleId === null ? "" : ` "${r.ruleId}"`} (${r.reason})`).join(", ");
31544
+ const undisclosed = snapshot.invalidRules - snapshot.rejectedRules.length;
31545
+ const more = undisclosed > 0 ? `, and ${String(undisclosed)} more` : "";
31546
+ process.stderr.write(
31547
+ `[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\`
31548
+ `
31549
+ );
31550
+ }
31053
31551
  installedScanRules() {
31054
31552
  try {
31055
31553
  const snapshot = this.db.installedPacks.installedRuleset();
31056
31554
  if (snapshot.installedPacks === 0) return void 0;
31057
31555
  if (snapshot.enabledPacks === 0) {
31058
- return { rules: [], ruleActions: /* @__PURE__ */ new Map(), ruleVersions: /* @__PURE__ */ new Map(), complete: true };
31556
+ return {
31557
+ rules: [],
31558
+ ruleActions: /* @__PURE__ */ new Map(),
31559
+ ruleVersions: /* @__PURE__ */ new Map(),
31560
+ reversibleRules: /* @__PURE__ */ new Set(),
31561
+ complete: true
31562
+ };
31563
+ }
31564
+ if (snapshot.invalidRules > 0) {
31565
+ this.warnRulesetDiscarded(snapshot);
31566
+ return void 0;
31059
31567
  }
31060
- if (snapshot.invalidRules > 0) return void 0;
31061
31568
  if (snapshot.rules.length === 0) return void 0;
31062
31569
  return {
31063
31570
  rules: snapshot.rules,
31064
31571
  ruleActions: snapshot.ruleActions,
31065
31572
  ruleVersions: snapshot.ruleVersions,
31573
+ reversibleRules: snapshot.reversibleRules,
31066
31574
  complete: true
31067
31575
  };
31068
31576
  } catch {
@@ -31090,6 +31598,11 @@ var StandaloneDataGateway = class {
31090
31598
  return {
31091
31599
  version: "local",
31092
31600
  policies: [...policies, ...rulePolicies],
31601
+ // The reversibility half of each pack's assignment. Emitted only under the
31602
+ // authoritative installed snapshot, exactly like rulePolicies above: the
31603
+ // bundled-packs fallback carries no per-pack assignment, so it carries no
31604
+ // reversibility either and every redaction there stays one-way.
31605
+ reversibleRuleIds: installed ? [...installed.reversibleRules] : [],
31093
31606
  rules: installed ? installed.rules : [],
31094
31607
  ...installed ? { rulesComplete: true } : {},
31095
31608
  ...installed ? { ruleVersions: Object.fromEntries(installed.ruleVersions) } : {},
@@ -31108,29 +31621,32 @@ var StandaloneDataGateway = class {
31108
31621
  return this.db.exceptions.recordBlocked(entry);
31109
31622
  }
31110
31623
  // Retention sweep over TERMINAL exception rows (revoked / expired / budget
31111
- // exhausted) — standalone-only store maintenance, invoked from SessionStart,
31112
- // not part of the DataGateway port. Active grants are never touched.
31624
+ // exhausted) — local-store maintenance, invoked from SessionStart through the
31625
+ // LocalStoreMaintenance capability rather than the DataGateway port. Active
31626
+ // grants are never touched.
31113
31627
  sweepTerminalExceptions(retentionMs) {
31114
31628
  return this.db.exceptions.sweepTerminal(retentionMs);
31115
31629
  }
31116
- // The warn-era enforcement cap, standalone-only store maintenance invoked
31117
- // from SessionStart, not part of the DataGateway port. Returns the number
31118
- // of block/redact rows capped to warn (0 for a redact-policy store or an
31119
- // already-capped one).
31630
+ // The warn-era enforcement cap — local-store maintenance, invoked from
31631
+ // SessionStart through the LocalStoreMaintenance capability rather than
31632
+ // the DataGateway port. Returns the number of block/redact rows capped to
31633
+ // warn (0 for a redact-policy store or an already-capped one).
31120
31634
  capWarnEraEnforcement(policyMode) {
31121
31635
  const { capped } = capWarnEraEnforcementOnce(this.db, policyMode, this.dataDir);
31122
31636
  return { capped };
31123
31637
  }
31124
31638
  // One project-file scan → the local project_file tree (one transaction inside
31125
- // the LocalDatabase, fail-open there). Like the sweep above, this is
31126
- // NOT part of the DataGateway port: the file tree is a local-store read model.
31639
+ // the LocalDatabase, fail-open there). Like the sweep above, this is reached
31640
+ // through the LocalStoreMaintenance capability rather than the DataGateway
31641
+ // port: the file tree is a local-store read model.
31127
31642
  recordProjectFiles(projectId, scan2) {
31128
31643
  this.db.recordProjectFiles(projectId, scan2);
31129
31644
  return Promise.resolve();
31130
31645
  }
31131
31646
  // Fold ghost source_project rows minted by the pre-worktree-fix resolver
31132
- // (checkout-path identities) into the repo's canonical row. Standalone-only
31133
- // store maintenance, invoked from SessionStart. Fail-open in the store.
31647
+ // (checkout-path identities) into the repo's canonical row. Local-store
31648
+ // maintenance, invoked from SessionStart through the LocalStoreMaintenance
31649
+ // capability. Fail-open in the store.
31134
31650
  reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
31135
31651
  this.db.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
31136
31652
  return Promise.resolve();
@@ -31141,10 +31657,10 @@ var StandaloneDataGateway = class {
31141
31657
  * executing the plugin generation they started with (Claude Code caches
31142
31658
  * plugin versions), and the write gate makes their installed-pack writes
31143
31659
  * silent no-ops — this is the one-line nudge telling the user WHY, and that
31144
- * a restart picks the newer plugin up. Standalone-only, invoked from
31145
- * SessionStart, not part of the DataGateway port. Fail-open: any error →
31146
- * null (no notice), and unparseable versions compare equal so garbage can
31147
- * never fire it.
31660
+ * a restart picks the newer plugin up. Local-store maintenance, invoked
31661
+ * from SessionStart through the LocalStoreMaintenance capability rather
31662
+ * than the DataGateway port. Fail-open: any error → null (no notice), and
31663
+ * unparseable versions compare equal so garbage can never fire it.
31148
31664
  */
31149
31665
  staleBinaryNotice(currentVersion) {
31150
31666
  try {
@@ -31222,7 +31738,8 @@ var StandaloneDataGateway = class {
31222
31738
 
31223
31739
  // ../../packages/plugin-runtime/src/resolve.ts
31224
31740
  var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
31225
- function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFactory) {
31741
+ var defaultGatewayFactory = standaloneGatewayFactory;
31742
+ function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
31226
31743
  return gatewayFactory(config2, meta3);
31227
31744
  }
31228
31745
 
@@ -31231,11 +31748,11 @@ import { randomUUID as randomUUID16 } from "crypto";
31231
31748
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
31232
31749
 
31233
31750
  // src/history/transcripts.ts
31234
- import { readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
31751
+ import { readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
31235
31752
  import { homedir as homedir3 } from "os";
31236
- import { join as join16 } from "path";
31753
+ import { join as join17 } from "path";
31237
31754
  function transcriptsDir(home) {
31238
- return join16(home ?? homedir3(), ".claude", "projects");
31755
+ return join17(home ?? homedir3(), ".claude", "projects");
31239
31756
  }
31240
31757
  function isRecord(value) {
31241
31758
  return typeof value === "object" && value !== null;
@@ -31483,7 +32000,7 @@ function* iterateFileContents(dir, excludeSessionId) {
31483
32000
  return;
31484
32001
  }
31485
32002
  for (const project of projects) {
31486
- const projectDir = join16(dir, project);
32003
+ const projectDir = join17(dir, project);
31487
32004
  let files;
31488
32005
  try {
31489
32006
  files = readdirSync5(projectDir).filter((name) => name.endsWith(".jsonl"));
@@ -31493,10 +32010,10 @@ function* iterateFileContents(dir, excludeSessionId) {
31493
32010
  for (const file2 of files) {
31494
32011
  if (excludeSessionId !== void 0 && file2.slice(0, -".jsonl".length) === excludeSessionId)
31495
32012
  continue;
31496
- const filePath = join16(projectDir, file2);
32013
+ const filePath = join17(projectDir, file2);
31497
32014
  let content;
31498
32015
  try {
31499
- content = readFileSync10(filePath, "utf8");
32016
+ content = readFileSync11(filePath, "utf8");
31500
32017
  } catch {
31501
32018
  continue;
31502
32019
  }
@@ -31591,7 +32108,7 @@ async function scanHistory(config2, opts = {}, onHit) {
31591
32108
  const result = await runtime.capture(
31592
32109
  {
31593
32110
  kind: message.kind,
31594
- sourceTool: "claude-code",
32111
+ sourceTool: SOURCE_TOOL.ClaudeCode,
31595
32112
  text: message.text,
31596
32113
  occurredAt: message.occurredAt
31597
32114
  },
@@ -31628,10 +32145,10 @@ async function scanHistory(config2, opts = {}, onHit) {
31628
32145
  }
31629
32146
 
31630
32147
  // src/history/tail-scrub.ts
31631
- import { readFileSync as readFileSync12, renameSync as renameSync6, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync10 } from "fs";
32148
+ import { readFileSync as readFileSync13, renameSync as renameSync6, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync10 } from "fs";
31632
32149
 
31633
32150
  // src/remediation/redact.ts
31634
- import { readFileSync as readFileSync11, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
32151
+ import { readFileSync as readFileSync12, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
31635
32152
  import { isAbsolute as isAbsolute2, relative, resolve } from "path";
31636
32153
  function platformRedactionScope(home) {
31637
32154
  return { artifactRoots: [transcriptsDir(home)] };
@@ -31663,7 +32180,7 @@ async function scrubTranscriptTail(filePath, deps) {
31663
32180
  if (realPath === null) return null;
31664
32181
  const statBefore = statSync7(realPath);
31665
32182
  if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
31666
- const content = readFileSync12(realPath, "utf8");
32183
+ const content = readFileSync13(realPath, "utf8");
31667
32184
  const lines = content.split("\n");
31668
32185
  let rewritten = 0;
31669
32186
  for (const [i, line] of lines.entries()) {
@@ -31702,13 +32219,13 @@ import { createHash as createHash5 } from "crypto";
31702
32219
  import {
31703
32220
  closeSync as closeSync2,
31704
32221
  fstatSync,
31705
- mkdirSync as mkdirSync5,
32222
+ mkdirSync as mkdirSync4,
31706
32223
  openSync as openSync2,
31707
- readFileSync as readFileSync13,
32224
+ readFileSync as readFileSync14,
31708
32225
  readSync,
31709
32226
  writeFileSync as writeFileSync11
31710
32227
  } from "fs";
31711
- import { join as join17 } from "path";
32228
+ import { join as join18 } from "path";
31712
32229
 
31713
32230
  // src/history/usage.ts
31714
32231
  var NO_PROJECT_CWD = "/nonexistent/aka-reconciler/no-project";
@@ -31727,7 +32244,7 @@ async function reconcileSession(gateway, sessionId, records, opts = {}) {
31727
32244
  if (anchor === void 0) return { llmCalls: 0, skipped: 0, lastPromptId };
31728
32245
  const ctx = resolveInventoryContext({
31729
32246
  cwd: anchor.cwd ?? NO_PROJECT_CWD,
31730
- tool: "claude-code",
32247
+ tool: SOURCE_TOOL.ClaudeCode,
31731
32248
  harnessVersion: anchor.version,
31732
32249
  harnessInterface: anchor.entrypoint
31733
32250
  });
@@ -31864,7 +32381,7 @@ function buildSessionRoot(sessionId, ctx, resolved, anchor, provider) {
31864
32381
  if (typeof osVersion === "string") attributes.os_version = osVersion;
31865
32382
  if (anchor.version !== void 0) attributes.harness_version = anchor.version;
31866
32383
  attributes.provider = provider;
31867
- attributes.harness = harnessFromTool("claude-code");
32384
+ attributes.harness = harnessFromTool(SOURCE_TOOL.ClaudeCode);
31868
32385
  if (anchor.cwd !== void 0) attributes.cwd = anchor.cwd;
31869
32386
  if (anchor.version !== void 0) attributes.version = anchor.version;
31870
32387
  const hostName = ctx.host?.attributes.host_name;