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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -585,6 +585,10 @@ var SQLITE_MIGRATIONS = [
585
585
  {
586
586
  tag: "0020_secret_vault_pagination_indexes",
587
587
  sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
588
+ },
589
+ {
590
+ tag: "0021_finding_resolution_resolved_at_index",
591
+ sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
588
592
  }
589
593
  ];
590
594
 
@@ -15300,6 +15304,50 @@ function date4(params) {
15300
15304
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
15301
15305
  config(en_default());
15302
15306
 
15307
+ // ../../packages/schema/src/zod/harness-map.ts
15308
+ var HARNESS = {
15309
+ ClaudeCode: "claudecode",
15310
+ Cursor: "cursor",
15311
+ Copilot: "copilot",
15312
+ Codex: "codex",
15313
+ Antigravity: "antigravity",
15314
+ Windsurf: "windsurf",
15315
+ ClaudeDesktop: "claudedesktop",
15316
+ ChatGpt: "chatgpt",
15317
+ ClaudeAi: "claudeai",
15318
+ Api: "api"
15319
+ };
15320
+ var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
15321
+ var SOURCE_TOOL = {
15322
+ ClaudeCode: "claude-code",
15323
+ ClaudeDesktop: "claude-desktop",
15324
+ Cursor: "cursor",
15325
+ ChatGpt: "chatgpt",
15326
+ ClaudeAi: "claude-ai",
15327
+ Copilot: "github-copilot",
15328
+ Codex: "codex",
15329
+ Antigravity: "antigravity",
15330
+ // No harness counterpart, deliberately: the CLI's own captures and a capture
15331
+ // whose tool could not be identified both render through the read side's
15332
+ // miss path rather than as a harness of their own.
15333
+ Cli: "cli",
15334
+ Unknown: "unknown"
15335
+ };
15336
+ var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
15337
+ var TOOL_TO_HARNESS = {
15338
+ [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
15339
+ [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
15340
+ [SOURCE_TOOL.Copilot]: HARNESS.Copilot,
15341
+ [SOURCE_TOOL.Cursor]: HARNESS.Cursor,
15342
+ [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
15343
+ [SOURCE_TOOL.Codex]: HARNESS.Codex,
15344
+ [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
15345
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
15346
+ };
15347
+ function harnessFromTool(tool) {
15348
+ return TOOL_TO_HARNESS[tool] ?? tool;
15349
+ }
15350
+
15303
15351
  // ../../packages/schema/src/zod/finding.ts
15304
15352
  var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
15305
15353
  var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
@@ -15322,21 +15370,22 @@ var Finding = external_exports.object({
15322
15370
  }).meta({ id: "Finding" });
15323
15371
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15324
15372
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15325
- var FindingProvider = external_exports.enum([
15326
- "claudecode",
15327
- "claudedesktop",
15328
- "cursor",
15329
- "copilot",
15330
- "chatgpt",
15331
- "claudeai",
15332
- "codex",
15333
- "antigravity",
15334
- "api"
15373
+ var FindingProvider = Harness.extract([
15374
+ "ClaudeCode",
15375
+ "ClaudeDesktop",
15376
+ "Cursor",
15377
+ "Copilot",
15378
+ "ChatGpt",
15379
+ "ClaudeAi",
15380
+ "Codex",
15381
+ "Antigravity",
15382
+ "Api"
15335
15383
  ]).meta({ id: "FindingProvider" });
15336
15384
  var FindingCategory = external_exports.enum([
15337
15385
  "secret",
15338
15386
  "pii",
15339
15387
  "source_code",
15388
+ "code_flaw",
15340
15389
  "external_share",
15341
15390
  "mcp_server",
15342
15391
  "customer_data",
@@ -15516,6 +15565,7 @@ var FindingInstanceDetail = FindingInstance.extend({
15516
15565
  policy: FindingPolicyRef
15517
15566
  }).meta({ id: "FindingInstanceDetail" });
15518
15567
  var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15568
+ var MAX_FLAT_FINDINGS_LIMIT = 200;
15519
15569
  var ListFindingInstancesQuery = external_exports.object({
15520
15570
  severity: external_exports.array(Severity).optional(),
15521
15571
  // Rule ids, the same vocabulary the grouped list's `subtype` carries.
@@ -15535,7 +15585,7 @@ var ListFindingInstancesQuery = external_exports.object({
15535
15585
  q: external_exports.string().optional(),
15536
15586
  sessionId: external_exports.string().optional(),
15537
15587
  from: external_exports.iso.datetime().optional(),
15538
- limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15588
+ limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
15539
15589
  cursor: external_exports.string().optional()
15540
15590
  });
15541
15591
  var ListFindingInstancesResponse = external_exports.object({
@@ -15597,33 +15647,6 @@ var ListFindingLocationsResponse = external_exports.object({
15597
15647
  hasMore: external_exports.boolean()
15598
15648
  }).meta({ id: "ListFindingLocationsResponse" });
15599
15649
 
15600
- // ../../packages/schema/src/zod/harness-map.ts
15601
- var Harness = external_exports.enum([
15602
- "claudecode",
15603
- "cursor",
15604
- "copilot",
15605
- "codex",
15606
- "antigravity",
15607
- "windsurf",
15608
- "claudedesktop",
15609
- "chatgpt",
15610
- "claudeai",
15611
- "api"
15612
- ]).meta({ id: "Harness" });
15613
- var TOOL_TO_HARNESS = {
15614
- "claude-code": "claudecode",
15615
- "claude-desktop": "claudedesktop",
15616
- "github-copilot": "copilot",
15617
- cursor: "cursor",
15618
- chatgpt: "chatgpt",
15619
- codex: "codex",
15620
- antigravity: "antigravity",
15621
- "claude-ai": "claudeai"
15622
- };
15623
- function harnessFromTool(tool) {
15624
- return TOOL_TO_HARNESS[tool] ?? tool;
15625
- }
15626
-
15627
15650
  // ../../packages/schema/src/zod/meta.ts
15628
15651
  var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
15629
15652
  var AuditEventType = external_exports.enum([
@@ -16076,686 +16099,37 @@ var ActivityOverviewResponse = external_exports.object({
16076
16099
  sessions: ListActivitySessionsResponse
16077
16100
  }).meta({ id: "ActivityOverviewResponse" });
16078
16101
 
16079
- // ../../packages/schema/src/zod/event.ts
16080
- var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
16081
- var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
16082
- var SourceTool = external_exports.enum([
16083
- "claude-code",
16084
- "claude-desktop",
16085
- "cursor",
16086
- "chatgpt",
16087
- "claude-ai",
16088
- "github-copilot",
16089
- "codex",
16090
- "antigravity",
16091
- "cli",
16092
- "unknown"
16093
- ]).meta({ id: "SourceTool" });
16094
- var EventMetadata = external_exports.object({
16095
- sessionId: external_exports.string().optional(),
16096
- repo: external_exports.string().optional(),
16097
- filePath: external_exports.string().optional(),
16098
- // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
16099
- // set by the tool-scanning hooks. The tool NAME only — never the tool's
16100
- // arguments or output, which can carry the very value a finding masked
16101
- // (metadata is stored unredacted). Gives findings on non-file captures a
16102
- // display location ("via Bash") when no filePath exists.
16103
- toolName: external_exports.string().optional(),
16104
- // Set (true) by the worktree scanner when the file is excluded by the
16105
- // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
16106
- // generated code can leak real secrets — but the provenance is recorded so
16107
- // policy/dashboards can treat those findings as informational rather than
16108
- // blocking. Omitted (not false) for tracked files and non-scan events.
16109
- gitignored: external_exports.boolean().optional(),
16110
- // Set (true) ONLY when the event's `content` is the COMPLETE file at
16111
- // capture time (a worktree scan reading from disk). Hook-captured edits
16112
- // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
16113
- // this. The resolver-on-ingest keys its fixed-at-source dropout
16114
- // diff on this marker: only a whole-file snapshot can prove a previously
16115
- // open finding is gone; a fragment's absence proves nothing (the secret
16116
- // may live outside the hunk). Omitted (not false) for fragments and
16117
- // non-scan events, so pre-marker clients safely default to the
16118
- // non-authoritative path.
16119
- wholeFile: external_exports.boolean().optional(),
16120
- model: external_exports.string().optional(),
16121
- turnIndex: external_exports.number().int().nonnegative().optional(),
16122
- // Distributed-tracing correlation. `correlationId` ties a recorded event back
16123
- // to the request that captured/ingested it (a UUID, generated independently of
16124
- // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
16125
- // originating span when telemetry is enabled. Both optional + backward
16126
- // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
16127
- correlationId: external_exports.uuid().optional(),
16128
- traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
16129
- // Ids of the detection exceptions that downgraded findings in this capture
16130
- // to 'allow' — the enforcement audit trail's link back to the grant that
16131
- // authorized the bypass. Absent on captures where no exception applied.
16132
- exceptionIds: external_exports.array(external_exports.guid()).optional()
16133
- }).meta({ id: "EventMetadata" });
16134
- var Event = external_exports.object({
16135
- id: external_exports.guid(),
16136
- sourceTool: SourceTool,
16137
- kind: EventKind,
16138
- occurredAt: external_exports.iso.datetime(),
16139
- contentHash: external_exports.string(),
16140
- content: external_exports.string(),
16141
- metadata: EventMetadata.optional()
16142
- }).meta({ id: "Event" });
16143
- var IngestEvent = Event.meta({ id: "IngestEvent" });
16144
- var IngestBatch = external_exports.object({
16145
- events: external_exports.array(IngestEvent).min(1).max(100),
16146
- // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
16147
- // additionally rejects any event whose contentHash the store has already
16148
- // recorded — for re-runnable bulk ingest (worktree scan, transcript
16149
- // backfill), where a re-run mints fresh event ids for identical content and
16150
- // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
16151
- // two genuinely separate prompts can be byte-identical and both belong on
16152
- // the timeline.
16153
- dedupe: external_exports.literal("content-hash").optional()
16154
- }).meta({ id: "IngestBatch" });
16155
-
16156
- // ../../packages/schema/src/zod/inventory.ts
16157
- var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16158
- var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16159
- var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16160
- var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16161
- var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16162
- var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16163
- var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16164
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16165
- var AccessCounts = external_exports.object({
16166
- open: external_exports.number().int().nonnegative(),
16167
- approved: external_exports.number().int().nonnegative(),
16168
- blocked: external_exports.number().int().nonnegative(),
16169
- total: external_exports.number().int().nonnegative()
16170
- }).meta({ id: "AccessCounts" });
16171
- var AssetSummary = external_exports.object({
16172
- id: external_exports.string(),
16173
- type: AssetType,
16174
- name: external_exports.string(),
16175
- sub: external_exports.string(),
16176
- flags: external_exports.array(Flag),
16177
- /** MCP servers only — omitted for all other types. */
16178
- trust: TrustLevel.optional()
16179
- }).meta({ id: "AssetSummary" });
16180
- var ProjectSummary = external_exports.object({
16181
- id: external_exports.string(),
16182
- name: external_exports.string(),
16183
- repo: external_exports.string(),
16184
- visibility: Visibility,
16185
- language: external_exports.string(),
16186
- policyDefault: AccessLevel,
16187
- updatedAt: external_exports.iso.datetime(),
16188
- accessCounts: AccessCounts,
16189
- findingsCount: external_exports.number().int().nonnegative()
16190
- }).meta({ id: "ProjectSummary" });
16191
- var HarnessCategory = external_exports.object({
16192
- /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16193
- type: AssetType,
16194
- assets: external_exports.array(AssetSummary)
16102
+ // ../../packages/schema/src/zod/config-inventory.ts
16103
+ var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16104
+ var SkillScanEntry = external_exports.object({
16105
+ name: external_exports.string().min(1),
16106
+ // The identity source: a marketplace repo for plugin skills (e.g.
16107
+ // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16108
+ // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16109
+ // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16110
+ source: external_exports.string().min(1),
16111
+ scope: ConfigScope,
16112
+ pluginName: external_exports.string().optional(),
16113
+ // Volatile — rides the attribute bag, never the identity hash.
16114
+ version: external_exports.string().optional(),
16115
+ description: external_exports.string().optional(),
16116
+ // Skill directory mtime (ISO) the "updated Nd ago" freshness signal.
16117
+ updatedAt: external_exports.iso.datetime().optional(),
16118
+ // Filesystem path — the promoted inventory `location` column.
16119
+ location: external_exports.string().optional()
16195
16120
  });
16196
- var HarnessSummary = external_exports.object({
16197
- id: HarnessId,
16198
- label: external_exports.string(),
16199
- kind: external_exports.string(),
16200
- version: external_exports.string(),
16201
- sessions: external_exports.number().int().nonnegative(),
16202
- assetCount: external_exports.number().int().nonnegative(),
16203
- flagCount: external_exports.number().int().nonnegative(),
16204
- projects: external_exports.array(ProjectSummary),
16205
- categories: external_exports.array(HarnessCategory)
16206
- }).meta({ id: "HarnessSummary" });
16207
- var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16208
- var AssetGroup = external_exports.object({
16209
- /** Group key — never project (enforced at service layer). */
16210
- type: AssetType,
16211
- total: external_exports.number().int().nonnegative(),
16212
- /**
16213
- * MCP group only — omitted for all other types.
16214
- * Partial: only TrustLevel keys with non-zero counts are included.
16215
- * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16216
- */
16217
- trustRollup: external_exports.object({
16218
- "known-good": external_exports.number().int().nonnegative(),
16219
- risky: external_exports.number().int().nonnegative(),
16220
- unapproved: external_exports.number().int().nonnegative()
16221
- }).partial().strict().optional(),
16222
- /**
16223
- * Partial: only Flag keys with non-zero counts are included.
16224
- * Strict: unknown keys are rejected — only Flag values are valid keys.
16225
- */
16226
- flagRollup: external_exports.object({
16227
- update: external_exports.number().int().nonnegative(),
16228
- stale: external_exports.number().int().nonnegative(),
16229
- conflict: external_exports.number().int().nonnegative(),
16230
- unknown: external_exports.number().int().nonnegative(),
16231
- change: external_exports.number().int().nonnegative(),
16232
- untracked: external_exports.number().int().nonnegative(),
16233
- risk: external_exports.number().int().nonnegative(),
16234
- findings: external_exports.number().int().nonnegative()
16235
- }).partial().strict(),
16236
- items: external_exports.array(AssetSummary)
16237
- }).meta({ id: "AssetGroup" });
16238
- var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16239
- var McpTool = external_exports.object({
16240
- name: external_exports.string(),
16241
- signature: external_exports.string(),
16242
- description: external_exports.string(),
16243
- write: external_exports.boolean(),
16244
- /** Non-null string when tool is dangerous / blocked; null otherwise. */
16245
- risk: external_exports.string().nullable()
16246
- }).meta({ id: "McpTool" });
16247
- var AssetFindingRef = external_exports.object({
16248
- id: external_exports.string(),
16249
- title: external_exports.string(),
16250
- note: external_exports.string()
16251
- });
16252
- var AssetDetail = AssetSummary.extend({
16253
- /** string | null — null when no description is available. */
16254
- description: external_exports.string().nullable(),
16255
- /** trustLevel | null — null for non-MCP assets. */
16256
- trust: TrustLevel.nullable(),
16257
- /** Type-specific raw key/values — FE renders the grid. */
16258
- meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16259
- /** always present — object when there is an active finding, null when absent. */
16260
- finding: AssetFindingRef.nullable(),
16261
- /** MCP exposed-tools list — omitted for non-mcp. */
16262
- tools: external_exports.array(McpTool).optional()
16263
- }).meta({ id: "AssetDetail" });
16264
- var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16265
- var InventoryStats = external_exports.object({
16266
- /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16267
- attention: external_exports.number().int().nonnegative(),
16268
- byType: external_exports.object({
16269
- project: external_exports.number().int().nonnegative(),
16270
- skill: external_exports.number().int().nonnegative(),
16271
- mcp: external_exports.number().int().nonnegative(),
16272
- hook: external_exports.number().int().nonnegative(),
16273
- config: external_exports.number().int().nonnegative()
16274
- }),
16275
- harnesses: external_exports.number().int().nonnegative(),
16276
- mcpTrust: external_exports.object({
16277
- "known-good": external_exports.number().int().nonnegative(),
16278
- risky: external_exports.number().int().nonnegative(),
16279
- unapproved: external_exports.number().int().nonnegative()
16280
- })
16281
- }).meta({ id: "InventoryStats" });
16282
- var FileSummary = external_exports.object({
16283
- path: external_exports.string(),
16284
- name: external_exports.string(),
16285
- origin: Origin,
16286
- /** Effective access (override applied). */
16287
- access: AccessLevel,
16288
- /** True when a file_access_override differs from the computed default. */
16289
- isCustom: external_exports.boolean(),
16290
- findings: external_exports.number().int().nonnegative(),
16291
- /** When the file was auto-blocked by a detection; null when not blocked. */
16292
- blockedAt: external_exports.iso.datetime().nullable().optional(),
16293
- /** Why the file was blocked; null when absent. */
16294
- note: external_exports.string().nullable().optional()
16295
- }).meta({ id: "FileSummary" });
16296
- var FolderSummary = external_exports.object({
16297
- name: external_exports.string(),
16298
- path: external_exports.string(),
16299
- /** Rollup of effective access across all descendants. */
16300
- accessCounts: AccessCounts
16301
- }).meta({ id: "FolderSummary" });
16302
- var ProjectTreeResponse = external_exports.object({
16303
- project: external_exports.object({
16304
- id: external_exports.string(),
16305
- repo: external_exports.string(),
16306
- visibility: Visibility
16307
- }),
16308
- path: external_exports.string(),
16309
- /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16310
- folders: external_exports.array(FolderSummary).optional(),
16311
- files: external_exports.array(FileSummary)
16312
- }).meta({ id: "ProjectTreeResponse" });
16313
- var FileDetail = FileSummary.extend({
16314
- project: external_exports.object({
16315
- repo: external_exports.string(),
16316
- visibility: Visibility,
16317
- language: external_exports.string(),
16318
- policyDefault: AccessLevel,
16319
- updatedAt: external_exports.iso.datetime()
16320
- }),
16321
- findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16322
- }).meta({ id: "FileDetail" });
16323
- var SetFileAccessBody = external_exports.object({
16324
- path: external_exports.string(),
16325
- access: AccessLevel
16326
- }).meta({ id: "SetFileAccessBody" });
16327
- var SetFileAccessResponse = external_exports.object({
16328
- file: FileSummary,
16329
- accessCounts: AccessCounts
16330
- }).meta({ id: "SetFileAccessResponse" });
16331
- var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16332
- var HarnessEventItem = external_exports.object({
16333
- kind: HarnessEventKind,
16334
- title: external_exports.string(),
16335
- detail: external_exports.string(),
16336
- occurredAt: external_exports.iso.datetime(),
16337
- findingId: external_exports.string().nullable().optional()
16338
- }).meta({ id: "HarnessEventItem" });
16339
- var HarnessEventsResponse = external_exports.object({
16340
- counts: external_exports.object({
16341
- block: external_exports.number().int().nonnegative(),
16342
- redact: external_exports.number().int().nonnegative(),
16343
- warn: external_exports.number().int().nonnegative()
16344
- }),
16345
- items: external_exports.array(HarnessEventItem)
16346
- }).meta({ id: "HarnessEventsResponse" });
16347
- var RescanResponse = external_exports.object({
16348
- jobId: external_exports.string(),
16349
- startedAt: external_exports.iso.datetime()
16350
- }).meta({ id: "RescanResponse" });
16351
- var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16352
- var ListAssetsQuery = external_exports.object({
16353
- /** Filter by one or more AssetType values; absent means all types. */
16354
- type: external_exports.array(AssetType).optional(),
16355
- /** Free-text search term. */
16356
- q: external_exports.string().optional()
16357
- });
16358
- var GetProjectTreeQuery = external_exports.object({
16359
- /** Subtree root path; defaults to repository root when absent. */
16360
- path: external_exports.string().optional(),
16361
- /** Free-text filter applied to file paths. */
16362
- q: external_exports.string().optional(),
16363
- /**
16364
- * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16365
- * file across the whole repo (folders omitted, most-recent first), ignoring
16366
- * `path`/`q` — powers the project-wide "recently blocked" strip.
16367
- */
16368
- filter: external_exports.enum(["blocked"]).optional()
16369
- });
16370
- var GetProjectFileQuery = external_exports.object({
16371
- /** Repository-relative file path; absent or empty → 400. */
16372
- path: external_exports.string()
16373
- });
16374
- var GetHarnessEventsQuery = external_exports.object({
16375
- /** Maximum number of events to return. Range: 1–50; default: 7. */
16376
- limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16377
- });
16378
-
16379
- // ../../packages/schema/src/zod/exception.ts
16380
- var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
16381
- var ExceptionConditions = external_exports.object({
16382
- repo: external_exports.string().optional(),
16383
- sourceTool: external_exports.string().optional(),
16384
- provider: external_exports.string().optional()
16385
- }).strict();
16386
- var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16387
- var DetectionException = external_exports.object({
16388
- id: external_exports.guid(),
16389
- ruleId: external_exports.string(),
16390
- // Denormalized from the rule, for reporting — never matched on.
16391
- category: DetectionCategory,
16392
- // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
16393
- // fingerprint, never the raw value, and never reversible. Matching recomputes
16394
- // the fingerprint from a fresh capture; the value itself is never stored.
16395
- // Shape-constrained so a malformed — or accidentally raw — value is rejected
16396
- // at the boundary rather than persisted.
16397
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
16398
- // Version of the fingerprint key the grant was written under; a rotated key
16399
- // invalidates old grants rather than silently mismatching them.
16400
- keyVersion: external_exports.number().int().positive(),
16401
- // maskMatch() preview of the approved value — never the raw value.
16402
- maskedValue: external_exports.string(),
16403
- capability: ExceptionCapability.default("suppress"),
16404
- scope: ExceptionScope,
16405
- expiresAt: external_exports.iso.datetime().nullable(),
16406
- maxUses: external_exports.number().int().positive().nullable(),
16407
- useCount: external_exports.number().int().nonnegative(),
16408
- lastUsedAt: external_exports.iso.datetime().nullable(),
16409
- // Mandatory: every grant carries the human reason it exists.
16410
- justification: external_exports.string().min(1),
16411
- conditions: ExceptionConditions.nullable(),
16412
- createdBy: external_exports.string(),
16413
- createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
16414
- createdAt: external_exports.iso.datetime(),
16415
- updatedAt: external_exports.iso.datetime(),
16416
- // Revocation is terminal and retained — consumed/expired/revoked rows are
16417
- // audit evidence; nothing in the exception lifecycle hard-deletes.
16418
- revokedAt: external_exports.iso.datetime().nullable(),
16419
- revokedBy: external_exports.string().nullable(),
16420
- revokeReason: external_exports.string().nullable()
16421
- });
16422
- var ExceptionBundleEntry = DetectionException.pick({
16423
- id: true,
16424
- ruleId: true,
16425
- valueFingerprint: true,
16426
- keyVersion: true,
16427
- capability: true,
16428
- expiresAt: true,
16429
- maxUses: true,
16430
- useCount: true,
16431
- conditions: true
16432
- });
16433
- var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16434
-
16435
- // ../../packages/schema/src/zod/rule.ts
16436
- var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16437
- var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16438
- var KeywordMatcher = external_exports.object({
16439
- type: external_exports.literal("keyword"),
16440
- // An empty keyword matches at every position, yielding one zero-length span
16441
- // per character. Rejected here because a keyword that matches everything is
16442
- // never intentional.
16443
- keywords: external_exports.array(external_exports.string().min(1)).min(1),
16444
- caseSensitive: external_exports.boolean().default(false)
16445
- });
16446
- function isValidRegex(pattern, flags) {
16447
- try {
16448
- new RegExp(pattern, flags);
16449
- return true;
16450
- } catch {
16451
- return false;
16452
- }
16453
- }
16454
- function matchesEmptyString(pattern, flags) {
16455
- try {
16456
- const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
16457
- return re.exec("")?.[0].length === 0;
16458
- } catch {
16459
- return false;
16460
- }
16461
- }
16462
- var MAX_PATTERN_LENGTH = 2e3;
16463
- var RegexMatcher = external_exports.object({
16464
- type: external_exports.literal("regex"),
16465
- pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16466
- flags: external_exports.string().default("gi"),
16467
- captureGroup: external_exports.number().int().nonnegative().optional()
16468
- }).refine((v) => isValidRegex(v.pattern, v.flags), {
16469
- message: "pattern/flags do not form a valid JavaScript regular expression",
16470
- path: ["pattern"]
16471
- }).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
16472
- 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',
16473
- path: ["pattern"]
16474
- });
16475
- var ValidatorMatcher = external_exports.object({
16476
- type: external_exports.literal("validator"),
16477
- name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
16478
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16479
- });
16480
- var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
16481
- var AppliesTo = external_exports.object({
16482
- // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16483
- extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16484
- }).meta({ id: "AppliesTo" });
16485
- var PostValidatorRef = external_exports.union([
16486
- external_exports.string(),
16487
- external_exports.object({
16488
- name: external_exports.string(),
16489
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16490
- })
16491
- ]).meta({ id: "PostValidatorRef" });
16492
- var RequiresNearby = external_exports.object({
16493
- // Each array, when present, must be non-empty and contain non-empty strings —
16494
- // an empty/blank criterion would either never fire or (for labels) match
16495
- // everything.
16496
- categories: external_exports.array(DetectionCategory).min(1).optional(),
16497
- ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16498
- labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16499
- windowChars: external_exports.number().int().positive().default(160),
16500
- // Optional confidence bump applied when a gated match is corroborated. Capped
16501
- // small: it nudges confidence, it does not assert certainty.
16502
- confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16503
- }).refine(
16504
- (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16505
- { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16506
- ).meta({ id: "RequiresNearby" });
16507
- var RuleFixture = external_exports.object({
16508
- label: external_exports.string(),
16509
- text: external_exports.string().max(5e4),
16510
- shouldMatch: external_exports.boolean(),
16511
- // Simulated file context for the scan, so fixtures can assert `appliesTo`
16512
- // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16513
- filePath: external_exports.string().optional(),
16514
- expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
16515
- }).meta({ id: "RuleFixture" });
16516
- var Rule = external_exports.object({
16517
- specVersion: external_exports.literal(1),
16518
- // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16519
- // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
16520
- // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16521
- // therefore carries no namespace and is not globally unique across publishers;
16522
- // never feed one to splitDetectionId. `category` below (per-rule) is the
16523
- // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16524
- id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16525
- name: external_exports.string(),
16526
- category: DetectionCategory,
16527
- severity: Severity,
16528
- matcher: Matcher,
16529
- appliesTo: AppliesTo.optional(),
16530
- postValidators: external_exports.array(PostValidatorRef).optional(),
16531
- requiresNearby: RequiresNearby.optional(),
16532
- examples: external_exports.array(external_exports.string()).optional()
16533
- }).meta({ id: "Rule" });
16534
- var Author = external_exports.object({
16535
- name: external_exports.string(),
16536
- email: external_exports.email().optional(),
16537
- url: external_exports.url().optional()
16538
- }).meta({ id: "Author" });
16539
- var PackManifest = external_exports.object({
16540
- specVersion: external_exports.literal(1),
16541
- id: external_exports.string(),
16542
- name: external_exports.string(),
16543
- version: external_exports.string(),
16544
- rules: external_exports.array(external_exports.string()),
16545
- // Optional attribution/provenance — consumed by the rule marketplace.
16546
- description: external_exports.string().optional(),
16547
- author: Author.optional(),
16548
- license: external_exports.string().optional(),
16549
- sourceUrl: external_exports.url().optional()
16550
- }).meta({ id: "PackManifest" });
16551
-
16552
- // ../../packages/schema/src/zod/policy.ts
16553
- var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
16554
- var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
16555
- var Policy = external_exports.object({
16556
- id: external_exports.guid(),
16557
- scope: PolicyScope,
16558
- target: PolicyTarget,
16559
- action: ActionTaken,
16560
- enabled: external_exports.boolean().default(true),
16561
- customKeywords: external_exports.array(external_exports.string()).optional(),
16562
- // Display name — optional so older policy rows without name still parse.
16563
- // Added for the findings API (policy.name column migration).
16564
- name: external_exports.string().optional()
16565
- }).meta({ id: "Policy" });
16566
- var PolicyBundle = external_exports.object({
16567
- version: external_exports.string(),
16568
- policies: external_exports.array(Policy),
16569
- // Rules from the installed marketplace packs (snapshotted by the
16570
- // control plane). The plugin registers these in addition to its bundled
16571
- // packs. Optional so older backends — and older on-disk caches — that omit
16572
- // the field still parse; consumers read `bundle.rules ?? []`.
16573
- rules: external_exports.array(Rule).optional(),
16574
- // When true, `rules` IS the complete effective ruleset and the runtime must
16575
- // NOT merge its compiled-in bundled packs — the standalone gateway sets this
16576
- // after reading the user's installed snapshot (installed_packs, enabled
16577
- // packs only), which is how detection updates stay manual: new bundled
16578
- // rules run only after the user applies the pack update. Absent/false keeps
16579
- // the historical composition (bundled packs + rules) — older caches.
16580
- rulesComplete: external_exports.boolean().optional(),
16581
- // Active detection exceptions, evaluation subset only (see
16582
- // ExceptionBundleEntry). Optional so older bundle producers — and older
16583
- // on-disk caches — that omit the field still parse; consumers read
16584
- // `bundle.exceptions ?? []`.
16585
- exceptions: external_exports.array(ExceptionBundleEntry).optional(),
16586
- // Installed pack version, keyed by ruleId, for rules in `rules` that came
16587
- // from a versioned installed pack. Optional so older backends — and older
16588
- // on-disk caches — that omit the field still parse; consumers fall back to
16589
- // the rule's own spec version. NOT the bundle version above — see
16590
- // installedRuleset's ruleVersions for the source of truth.
16591
- ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
16592
- customKeywords: external_exports.array(external_exports.string()),
16593
- fetchedAt: external_exports.iso.datetime()
16594
- }).meta({ id: "PolicyBundle" });
16595
- var OBSERVE_ONLY_CATEGORIES = ["config"];
16596
- var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
16597
- var CATEGORY_PEAK_SEVERITY = {
16598
- secret: "critical",
16599
- financial: "critical",
16600
- // core-financial/credit-card
16601
- code_flaw: "critical",
16602
- pii: "high",
16603
- phi: "high",
16604
- custom: "high",
16605
- // user-defined; conservative
16606
- code_context: "low",
16607
- config: "low"
16608
- // observe-only; floors to monitor regardless
16609
- };
16610
- function severityFloorPolicy(category) {
16611
- if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
16612
- const peak = CATEGORY_PEAK_SEVERITY[category];
16613
- return peak === "critical" || peak === "high" ? "warn" : "monitor";
16614
- }
16615
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
16616
- var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
16617
- var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
16618
- var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
16619
- var BUILTIN_POLICY_SPECS = {
16620
- monitor: {
16621
- name: "Monitor",
16622
- action: "log",
16623
- description: "Log every match for audit. The request is allowed through untouched."
16624
- },
16625
- warn: {
16626
- name: "Warn",
16627
- action: "warn",
16628
- description: "Allow the request, but warn the user inline before it is sent."
16629
- },
16630
- redact: {
16631
- name: "Redact",
16632
- action: "redact",
16633
- description: "Automatically strip the matched value from the request, then continue."
16634
- },
16635
- block: {
16636
- name: "Block",
16637
- action: "block",
16638
- description: "Refuse the request entirely whenever any rule in this detection matches."
16639
- }
16640
- };
16641
- function builtinPolicyToAction(id) {
16642
- return BUILTIN_POLICY_SPECS[id].action;
16643
- }
16644
- var DEFAULT_ACTIONS = Object.fromEntries(
16645
- DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
16646
- );
16647
- var BUILTIN_POLICIES = Object.fromEntries(
16648
- KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
16649
- );
16650
- var DEFAULT_PACK_POLICY_ID = "monitor";
16651
- function policyIdToAction(policyId) {
16652
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
16653
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
16654
- return BUILTIN_POLICIES[id].action;
16655
- }
16656
- var UsedByItem = external_exports.object({
16657
- id: external_exports.string(),
16658
- name: external_exports.string(),
16659
- ruleCount: external_exports.number().int().nonnegative(),
16660
- enabled: external_exports.boolean()
16661
- }).meta({ id: "UsedByItem" });
16662
- var PolicyListItem = external_exports.object({
16663
- id: external_exports.string(),
16664
- kind: PolicyKind,
16665
- name: external_exports.string(),
16666
- enabled: external_exports.boolean(),
16667
- usedByCount: external_exports.number().int().nonnegative()
16668
- }).meta({ id: "PolicyListItem" });
16669
- var PolicyDetail = external_exports.object({
16670
- specVersion: external_exports.literal(1),
16671
- id: external_exports.string(),
16672
- kind: PolicyKind,
16673
- name: external_exports.string(),
16674
- enabled: external_exports.boolean(),
16675
- description: external_exports.string(),
16676
- usedBy: external_exports.array(UsedByItem)
16677
- }).meta({ id: "PolicyDetail" });
16678
- var PolicyStatsResponse = external_exports.object({
16679
- policies: external_exports.number().int().nonnegative(),
16680
- builtin: external_exports.number().int().nonnegative(),
16681
- custom: external_exports.number().int().nonnegative(),
16682
- detectionsGoverned: external_exports.number().int().nonnegative()
16683
- }).meta({ id: "PolicyStatsResponse" });
16684
-
16685
- // ../../packages/schema/src/zod/api.ts
16686
- var LIST_QUERY_MAX_LIMIT = 200;
16687
- var ListEventsQuery = external_exports.object({
16688
- cursor: external_exports.string().optional(),
16689
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16690
- sourceTool: external_exports.string().optional(),
16691
- kind: external_exports.string().optional(),
16692
- from: external_exports.iso.datetime().optional(),
16693
- to: external_exports.iso.datetime().optional()
16694
- });
16695
- var ListEventsResponse = external_exports.object({
16696
- items: external_exports.array(Event),
16697
- nextCursor: external_exports.string().nullable()
16698
- }).meta({ id: "ListEventsResponse" });
16699
- var IngestResponse = external_exports.object({
16700
- accepted: external_exports.number().int().nonnegative(),
16701
- duplicates: external_exports.number().int().nonnegative()
16702
- }).meta({ id: "IngestResponse" });
16703
- var ListFindingsQuery = external_exports.object({
16704
- cursor: external_exports.string().optional(),
16705
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16706
- severity: external_exports.string().optional(),
16707
- category: external_exports.string().optional(),
16708
- eventId: external_exports.guid().optional()
16709
- });
16710
- var ListFindingsResponse = external_exports.object({
16711
- items: external_exports.array(Finding),
16712
- nextCursor: external_exports.string().nullable()
16713
- }).meta({ id: "ListFindingsResponse" });
16714
- var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
16715
- var CreatePolicyRequest = Policy.omit({ id: true }).meta({
16716
- id: "CreatePolicyRequest"
16717
- });
16718
- var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
16719
- var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
16720
- var ErrorResponse = external_exports.object({
16721
- error: external_exports.object({
16722
- code: external_exports.string(),
16723
- message: external_exports.string(),
16724
- details: external_exports.unknown().optional()
16725
- })
16726
- }).meta({ id: "ErrorResponse" });
16727
-
16728
- // ../../packages/schema/src/zod/config-inventory.ts
16729
- var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16730
- var SkillScanEntry = external_exports.object({
16731
- name: external_exports.string().min(1),
16732
- // The identity source: a marketplace repo for plugin skills (e.g.
16733
- // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16734
- // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16735
- // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16736
- source: external_exports.string().min(1),
16737
- scope: ConfigScope,
16738
- pluginName: external_exports.string().optional(),
16739
- // Volatile — rides the attribute bag, never the identity hash.
16740
- version: external_exports.string().optional(),
16741
- description: external_exports.string().optional(),
16742
- // Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
16743
- updatedAt: external_exports.iso.datetime().optional(),
16744
- // Filesystem path — the promoted inventory `location` column.
16745
- location: external_exports.string().optional()
16746
- });
16747
- var HookScanEntry = external_exports.object({
16748
- // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16749
- // set is harness-defined and grows without a schema change.
16750
- event: external_exports.string().min(1),
16751
- // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16752
- matcher: external_exports.string().optional(),
16753
- command: external_exports.string().min(1),
16754
- timeout: external_exports.number().optional(),
16755
- scope: ConfigScope,
16756
- pluginName: external_exports.string().optional(),
16757
- // The settings file / hooks.json the entry came from.
16758
- location: external_exports.string().optional()
16121
+ var HookScanEntry = external_exports.object({
16122
+ // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16123
+ // set is harness-defined and grows without a schema change.
16124
+ event: external_exports.string().min(1),
16125
+ // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16126
+ matcher: external_exports.string().optional(),
16127
+ command: external_exports.string().min(1),
16128
+ timeout: external_exports.number().optional(),
16129
+ scope: ConfigScope,
16130
+ pluginName: external_exports.string().optional(),
16131
+ // The settings file / hooks.json the entry came from.
16132
+ location: external_exports.string().optional()
16759
16133
  });
16760
16134
  var McpServerScanEntry = external_exports.object({
16761
16135
  // The server's config key ("github", "filesystem", …) — identity, with the
@@ -16830,12 +16204,170 @@ var ConfigScanRecord = external_exports.object({
16830
16204
  definitions: external_exports.array(InspectionDefinitionInput).optional(),
16831
16205
  findings: external_exports.array(ConfigPostureFindingInput).optional()
16832
16206
  });
16833
-
16834
- // ../../packages/schema/src/zod/registry.ts
16835
- var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16836
- var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16837
- var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16838
- var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16207
+
16208
+ // ../../packages/schema/src/zod/registry.ts
16209
+ var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16210
+ var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16211
+ var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16212
+ var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16213
+
16214
+ // ../../packages/schema/src/zod/rule.ts
16215
+ var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
16216
+ var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16217
+ var KeywordMatcher = external_exports.strictObject({
16218
+ type: external_exports.literal("keyword"),
16219
+ // An empty keyword matches at every position, yielding one zero-length span
16220
+ // per character. Rejected here because a keyword that matches everything is
16221
+ // never intentional.
16222
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16223
+ caseSensitive: external_exports.boolean().default(false)
16224
+ });
16225
+ function isValidRegex(pattern, flags) {
16226
+ try {
16227
+ new RegExp(pattern, flags);
16228
+ return true;
16229
+ } catch {
16230
+ return false;
16231
+ }
16232
+ }
16233
+ function probeFlags(flags) {
16234
+ return flags.replace(/[gy]/g, "");
16235
+ }
16236
+ function matchesEmptyString(pattern, flags) {
16237
+ try {
16238
+ const re = new RegExp(pattern, probeFlags(flags));
16239
+ return re.exec("")?.[0].length === 0;
16240
+ } catch {
16241
+ return false;
16242
+ }
16243
+ }
16244
+ function spansWholeMatch(captureGroup) {
16245
+ return captureGroup === void 0 || captureGroup === 0;
16246
+ }
16247
+ function captureGroupCount(pattern, flags) {
16248
+ try {
16249
+ const probe = new RegExp(`${pattern}|`, probeFlags(flags));
16250
+ const result = probe.exec("");
16251
+ return result ? result.length - 1 : void 0;
16252
+ } catch {
16253
+ return void 0;
16254
+ }
16255
+ }
16256
+ var MAX_PATTERN_LENGTH = 2e3;
16257
+ var RegexMatcher = external_exports.strictObject({
16258
+ type: external_exports.literal("regex"),
16259
+ pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16260
+ flags: external_exports.string().default("gi"),
16261
+ captureGroup: external_exports.number().int().nonnegative().optional()
16262
+ }).refine((v) => isValidRegex(v.pattern, v.flags), {
16263
+ message: "pattern/flags do not form a valid JavaScript regular expression",
16264
+ path: ["pattern"]
16265
+ }).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
16266
+ 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',
16267
+ path: ["pattern"]
16268
+ }).superRefine((v, ctx) => {
16269
+ if (v.captureGroup === void 0) return;
16270
+ const groups = captureGroupCount(v.pattern, v.flags);
16271
+ if (groups === void 0 || v.captureGroup <= groups) return;
16272
+ ctx.addIssue({
16273
+ code: "custom",
16274
+ path: ["captureGroup"],
16275
+ 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.`
16276
+ });
16277
+ });
16278
+ var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
16279
+ var MATCHER_TYPES = MatcherType.options;
16280
+ var AppliesTo = external_exports.strictObject({
16281
+ // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16282
+ extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16283
+ }).meta({ id: "AppliesTo" });
16284
+ var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
16285
+ var PostValidatorRef = external_exports.union(
16286
+ [
16287
+ PostValidatorName,
16288
+ external_exports.strictObject({
16289
+ name: PostValidatorName,
16290
+ config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16291
+ })
16292
+ ],
16293
+ {
16294
+ // A union reports one collapsed issue for every way its arms can fail, so
16295
+ // this has to describe the whole shape rather than just the name — it is
16296
+ // what an author sees for a misspelled name AND for a stray key in the
16297
+ // object form. The names come from the enum so the message cannot go
16298
+ // stale. Without it Zod says only "Invalid input", which is precisely the
16299
+ // no-feedback outcome this schema exists to remove.
16300
+ error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
16301
+ " or "
16302
+ )}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
16303
+ }
16304
+ ).meta({ id: "PostValidatorRef" });
16305
+ var RequiresNearby = external_exports.strictObject({
16306
+ // Each array, when present, must be non-empty and contain non-empty strings —
16307
+ // an empty/blank criterion would either never fire or (for labels) match
16308
+ // everything.
16309
+ categories: external_exports.array(DetectionCategory).min(1).optional(),
16310
+ ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16311
+ labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16312
+ windowChars: external_exports.number().int().positive().default(160),
16313
+ // Optional confidence bump applied when a gated match is corroborated. Capped
16314
+ // small: it nudges confidence, it does not assert certainty.
16315
+ confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16316
+ }).refine(
16317
+ (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16318
+ { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16319
+ ).meta({ id: "RequiresNearby" });
16320
+ var RuleFixture = external_exports.strictObject({
16321
+ label: external_exports.string(),
16322
+ text: external_exports.string().max(5e4),
16323
+ shouldMatch: external_exports.boolean(),
16324
+ // Simulated file context for the scan, so fixtures can assert `appliesTo`
16325
+ // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16326
+ filePath: external_exports.string().optional(),
16327
+ expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
16328
+ }).meta({ id: "RuleFixture" });
16329
+ var Rule = external_exports.strictObject({
16330
+ // A pinned literal over a STRICT object, and the two together decide how this
16331
+ // format may grow. A rule carrying a key not listed below is refused with
16332
+ // `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
16333
+ // `invalid_value`. So the only additive path is adding an OPTIONAL field here
16334
+ // — that keeps every rule authored before it valid — and a rule author has no
16335
+ // way to introduce a field of their own or to opt into a later version.
16336
+ // Widening the format means changing this literal and every consumer of it.
16337
+ specVersion: external_exports.literal(1),
16338
+ // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16339
+ // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
16340
+ // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16341
+ // therefore carries no namespace and is not globally unique across publishers;
16342
+ // never feed one to splitDetectionId. `category` below (per-rule) is the
16343
+ // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16344
+ id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16345
+ name: external_exports.string(),
16346
+ category: DetectionCategory,
16347
+ severity: Severity,
16348
+ matcher: Matcher,
16349
+ appliesTo: AppliesTo.optional(),
16350
+ postValidators: external_exports.array(PostValidatorRef).optional(),
16351
+ requiresNearby: RequiresNearby.optional(),
16352
+ examples: external_exports.array(external_exports.string()).optional()
16353
+ }).meta({ id: "Rule" });
16354
+ var Author = external_exports.object({
16355
+ name: external_exports.string(),
16356
+ email: external_exports.email().optional(),
16357
+ url: external_exports.url().optional()
16358
+ }).meta({ id: "Author" });
16359
+ var PackManifest = external_exports.object({
16360
+ specVersion: external_exports.literal(1),
16361
+ id: external_exports.string(),
16362
+ name: external_exports.string(),
16363
+ version: external_exports.string(),
16364
+ rules: external_exports.array(external_exports.string()),
16365
+ // Optional attribution/provenance — consumed by the rule marketplace.
16366
+ description: external_exports.string().optional(),
16367
+ author: Author.optional(),
16368
+ license: external_exports.string().optional(),
16369
+ sourceUrl: external_exports.url().optional()
16370
+ }).meta({ id: "PackManifest" });
16839
16371
 
16840
16372
  // ../../packages/schema/src/zod/detection.ts
16841
16373
  var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
@@ -17035,6 +16567,231 @@ function buildDetectionsList(summaries, query) {
17035
16567
  return { counts, items: filtered.map(summaryToDetectionListItem) };
17036
16568
  }
17037
16569
 
16570
+ // ../../packages/schema/src/zod/inventory.ts
16571
+ var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16572
+ var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16573
+ var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16574
+ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16575
+ var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16576
+ var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16577
+ var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16578
+ var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
16579
+ id: "HarnessId"
16580
+ });
16581
+ var AccessCounts = external_exports.object({
16582
+ open: external_exports.number().int().nonnegative(),
16583
+ approved: external_exports.number().int().nonnegative(),
16584
+ blocked: external_exports.number().int().nonnegative(),
16585
+ total: external_exports.number().int().nonnegative()
16586
+ }).meta({ id: "AccessCounts" });
16587
+ var AssetSummary = external_exports.object({
16588
+ id: external_exports.string(),
16589
+ type: AssetType,
16590
+ name: external_exports.string(),
16591
+ sub: external_exports.string(),
16592
+ flags: external_exports.array(Flag),
16593
+ /** MCP servers only — omitted for all other types. */
16594
+ trust: TrustLevel.optional()
16595
+ }).meta({ id: "AssetSummary" });
16596
+ var ProjectSummary = external_exports.object({
16597
+ id: external_exports.string(),
16598
+ name: external_exports.string(),
16599
+ repo: external_exports.string(),
16600
+ visibility: Visibility,
16601
+ language: external_exports.string(),
16602
+ policyDefault: AccessLevel,
16603
+ updatedAt: external_exports.iso.datetime(),
16604
+ accessCounts: AccessCounts,
16605
+ findingsCount: external_exports.number().int().nonnegative()
16606
+ }).meta({ id: "ProjectSummary" });
16607
+ var HarnessCategory = external_exports.object({
16608
+ /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16609
+ type: AssetType,
16610
+ assets: external_exports.array(AssetSummary)
16611
+ });
16612
+ var HarnessSummary = external_exports.object({
16613
+ id: HarnessId,
16614
+ label: external_exports.string(),
16615
+ kind: external_exports.string(),
16616
+ version: external_exports.string(),
16617
+ sessions: external_exports.number().int().nonnegative(),
16618
+ assetCount: external_exports.number().int().nonnegative(),
16619
+ flagCount: external_exports.number().int().nonnegative(),
16620
+ projects: external_exports.array(ProjectSummary),
16621
+ categories: external_exports.array(HarnessCategory)
16622
+ }).meta({ id: "HarnessSummary" });
16623
+ var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16624
+ var AssetGroup = external_exports.object({
16625
+ /** Group key — never project (enforced at service layer). */
16626
+ type: AssetType,
16627
+ total: external_exports.number().int().nonnegative(),
16628
+ /**
16629
+ * MCP group only — omitted for all other types.
16630
+ * Partial: only TrustLevel keys with non-zero counts are included.
16631
+ * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16632
+ */
16633
+ trustRollup: external_exports.object({
16634
+ "known-good": external_exports.number().int().nonnegative(),
16635
+ risky: external_exports.number().int().nonnegative(),
16636
+ unapproved: external_exports.number().int().nonnegative()
16637
+ }).partial().strict().optional(),
16638
+ /**
16639
+ * Partial: only Flag keys with non-zero counts are included.
16640
+ * Strict: unknown keys are rejected — only Flag values are valid keys.
16641
+ */
16642
+ flagRollup: external_exports.object({
16643
+ update: external_exports.number().int().nonnegative(),
16644
+ stale: external_exports.number().int().nonnegative(),
16645
+ conflict: external_exports.number().int().nonnegative(),
16646
+ unknown: external_exports.number().int().nonnegative(),
16647
+ change: external_exports.number().int().nonnegative(),
16648
+ untracked: external_exports.number().int().nonnegative(),
16649
+ risk: external_exports.number().int().nonnegative(),
16650
+ findings: external_exports.number().int().nonnegative()
16651
+ }).partial().strict(),
16652
+ items: external_exports.array(AssetSummary)
16653
+ }).meta({ id: "AssetGroup" });
16654
+ var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16655
+ var McpTool = external_exports.object({
16656
+ name: external_exports.string(),
16657
+ signature: external_exports.string(),
16658
+ description: external_exports.string(),
16659
+ write: external_exports.boolean(),
16660
+ /** Non-null string when tool is dangerous / blocked; null otherwise. */
16661
+ risk: external_exports.string().nullable()
16662
+ }).meta({ id: "McpTool" });
16663
+ var AssetFindingRef = external_exports.object({
16664
+ id: external_exports.string(),
16665
+ title: external_exports.string(),
16666
+ note: external_exports.string()
16667
+ });
16668
+ var AssetDetail = AssetSummary.extend({
16669
+ /** string | null — null when no description is available. */
16670
+ description: external_exports.string().nullable(),
16671
+ /** trustLevel | null — null for non-MCP assets. */
16672
+ trust: TrustLevel.nullable(),
16673
+ /** Type-specific raw key/values — FE renders the grid. */
16674
+ meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16675
+ /** always present — object when there is an active finding, null when absent. */
16676
+ finding: AssetFindingRef.nullable(),
16677
+ /** MCP exposed-tools list — omitted for non-mcp. */
16678
+ tools: external_exports.array(McpTool).optional()
16679
+ }).meta({ id: "AssetDetail" });
16680
+ var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16681
+ var InventoryStats = external_exports.object({
16682
+ /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16683
+ attention: external_exports.number().int().nonnegative(),
16684
+ byType: external_exports.object({
16685
+ project: external_exports.number().int().nonnegative(),
16686
+ skill: external_exports.number().int().nonnegative(),
16687
+ mcp: external_exports.number().int().nonnegative(),
16688
+ hook: external_exports.number().int().nonnegative(),
16689
+ config: external_exports.number().int().nonnegative()
16690
+ }),
16691
+ harnesses: external_exports.number().int().nonnegative(),
16692
+ mcpTrust: external_exports.object({
16693
+ "known-good": external_exports.number().int().nonnegative(),
16694
+ risky: external_exports.number().int().nonnegative(),
16695
+ unapproved: external_exports.number().int().nonnegative()
16696
+ })
16697
+ }).meta({ id: "InventoryStats" });
16698
+ var FileSummary = external_exports.object({
16699
+ path: external_exports.string(),
16700
+ name: external_exports.string(),
16701
+ origin: Origin,
16702
+ /** Effective access (override applied). */
16703
+ access: AccessLevel,
16704
+ /** True when a file_access_override differs from the computed default. */
16705
+ isCustom: external_exports.boolean(),
16706
+ findings: external_exports.number().int().nonnegative(),
16707
+ /** When the file was auto-blocked by a detection; null when not blocked. */
16708
+ blockedAt: external_exports.iso.datetime().nullable().optional(),
16709
+ /** Why the file was blocked; null when absent. */
16710
+ note: external_exports.string().nullable().optional()
16711
+ }).meta({ id: "FileSummary" });
16712
+ var FolderSummary = external_exports.object({
16713
+ name: external_exports.string(),
16714
+ path: external_exports.string(),
16715
+ /** Rollup of effective access across all descendants. */
16716
+ accessCounts: AccessCounts
16717
+ }).meta({ id: "FolderSummary" });
16718
+ var ProjectTreeResponse = external_exports.object({
16719
+ project: external_exports.object({
16720
+ id: external_exports.string(),
16721
+ repo: external_exports.string(),
16722
+ visibility: Visibility
16723
+ }),
16724
+ path: external_exports.string(),
16725
+ /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16726
+ folders: external_exports.array(FolderSummary).optional(),
16727
+ files: external_exports.array(FileSummary)
16728
+ }).meta({ id: "ProjectTreeResponse" });
16729
+ var FileDetail = FileSummary.extend({
16730
+ project: external_exports.object({
16731
+ repo: external_exports.string(),
16732
+ visibility: Visibility,
16733
+ language: external_exports.string(),
16734
+ policyDefault: AccessLevel,
16735
+ updatedAt: external_exports.iso.datetime()
16736
+ }),
16737
+ findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16738
+ }).meta({ id: "FileDetail" });
16739
+ var SetFileAccessBody = external_exports.object({
16740
+ path: external_exports.string(),
16741
+ access: AccessLevel
16742
+ }).meta({ id: "SetFileAccessBody" });
16743
+ var SetFileAccessResponse = external_exports.object({
16744
+ file: FileSummary,
16745
+ accessCounts: AccessCounts
16746
+ }).meta({ id: "SetFileAccessResponse" });
16747
+ var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16748
+ var HarnessEventItem = external_exports.object({
16749
+ kind: HarnessEventKind,
16750
+ title: external_exports.string(),
16751
+ detail: external_exports.string(),
16752
+ occurredAt: external_exports.iso.datetime(),
16753
+ findingId: external_exports.string().nullable().optional()
16754
+ }).meta({ id: "HarnessEventItem" });
16755
+ var HarnessEventsResponse = external_exports.object({
16756
+ counts: external_exports.object({
16757
+ block: external_exports.number().int().nonnegative(),
16758
+ redact: external_exports.number().int().nonnegative(),
16759
+ warn: external_exports.number().int().nonnegative()
16760
+ }),
16761
+ items: external_exports.array(HarnessEventItem)
16762
+ }).meta({ id: "HarnessEventsResponse" });
16763
+ var RescanResponse = external_exports.object({
16764
+ jobId: external_exports.string(),
16765
+ startedAt: external_exports.iso.datetime()
16766
+ }).meta({ id: "RescanResponse" });
16767
+ var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16768
+ var ListAssetsQuery = external_exports.object({
16769
+ /** Filter by one or more AssetType values; absent means all types. */
16770
+ type: external_exports.array(AssetType).optional(),
16771
+ /** Free-text search term. */
16772
+ q: external_exports.string().optional()
16773
+ });
16774
+ var GetProjectTreeQuery = external_exports.object({
16775
+ /** Subtree root path; defaults to repository root when absent. */
16776
+ path: external_exports.string().optional(),
16777
+ /** Free-text filter applied to file paths. */
16778
+ q: external_exports.string().optional(),
16779
+ /**
16780
+ * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16781
+ * file across the whole repo (folders omitted, most-recent first), ignoring
16782
+ * `path`/`q` — powers the project-wide "recently blocked" strip.
16783
+ */
16784
+ filter: external_exports.enum(["blocked"]).optional()
16785
+ });
16786
+ var GetProjectFileQuery = external_exports.object({
16787
+ /** Repository-relative file path; absent or empty → 400. */
16788
+ path: external_exports.string()
16789
+ });
16790
+ var GetHarnessEventsQuery = external_exports.object({
16791
+ /** Maximum number of events to return. Range: 1–50; default: 7. */
16792
+ limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16793
+ });
16794
+
17038
16795
  // ../../packages/schema/src/zod/shares.ts
17039
16796
  var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
17040
16797
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
@@ -17241,6 +16998,127 @@ var EgressWriteSummary = external_exports.object({
17241
16998
  droppedFiles: external_exports.array(external_exports.string()).default([])
17242
16999
  }).meta({ id: "EgressWriteSummary" });
17243
17000
 
17001
+ // ../../packages/schema/src/zod/event.ts
17002
+ var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
17003
+ var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
17004
+ var EventMetadata = external_exports.object({
17005
+ sessionId: external_exports.string().optional(),
17006
+ repo: external_exports.string().optional(),
17007
+ filePath: external_exports.string().optional(),
17008
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
17009
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
17010
+ // arguments or output, which can carry the very value a finding masked
17011
+ // (metadata is stored unredacted). Gives findings on non-file captures a
17012
+ // display location ("via Bash") when no filePath exists.
17013
+ toolName: external_exports.string().optional(),
17014
+ // Set (true) by the worktree scanner when the file is excluded by the
17015
+ // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
17016
+ // generated code can leak real secrets — but the provenance is recorded so
17017
+ // policy/dashboards can treat those findings as informational rather than
17018
+ // blocking. Omitted (not false) for tracked files and non-scan events.
17019
+ gitignored: external_exports.boolean().optional(),
17020
+ // Set (true) ONLY when the event's `content` is the COMPLETE file at
17021
+ // capture time (a worktree scan reading from disk). Hook-captured edits
17022
+ // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
17023
+ // this. The resolver-on-ingest keys its fixed-at-source dropout
17024
+ // diff on this marker: only a whole-file snapshot can prove a previously
17025
+ // open finding is gone; a fragment's absence proves nothing (the secret
17026
+ // may live outside the hunk). Omitted (not false) for fragments and
17027
+ // non-scan events, so pre-marker clients safely default to the
17028
+ // non-authoritative path.
17029
+ wholeFile: external_exports.boolean().optional(),
17030
+ model: external_exports.string().optional(),
17031
+ turnIndex: external_exports.number().int().nonnegative().optional(),
17032
+ // Distributed-tracing correlation. `correlationId` ties a recorded event back
17033
+ // to the request that captured/ingested it (a UUID, generated independently of
17034
+ // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
17035
+ // originating span when telemetry is enabled. Both optional + backward
17036
+ // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
17037
+ correlationId: external_exports.uuid().optional(),
17038
+ traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
17039
+ // Ids of the detection exceptions that downgraded findings in this capture
17040
+ // to 'allow' — the enforcement audit trail's link back to the grant that
17041
+ // authorized the bypass. Absent on captures where no exception applied.
17042
+ exceptionIds: external_exports.array(external_exports.guid()).optional()
17043
+ }).meta({ id: "EventMetadata" });
17044
+ var Event = external_exports.object({
17045
+ id: external_exports.guid(),
17046
+ sourceTool: SourceTool,
17047
+ kind: EventKind,
17048
+ occurredAt: external_exports.iso.datetime(),
17049
+ contentHash: external_exports.string(),
17050
+ content: external_exports.string(),
17051
+ metadata: EventMetadata.optional()
17052
+ }).meta({ id: "Event" });
17053
+ var IngestEvent = Event.meta({ id: "IngestEvent" });
17054
+ var IngestBatch = external_exports.object({
17055
+ events: external_exports.array(IngestEvent).min(1).max(100),
17056
+ // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
17057
+ // additionally rejects any event whose contentHash the store has already
17058
+ // recorded — for re-runnable bulk ingest (worktree scan, transcript
17059
+ // backfill), where a re-run mints fresh event ids for identical content and
17060
+ // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
17061
+ // two genuinely separate prompts can be byte-identical and both belong on
17062
+ // the timeline.
17063
+ dedupe: external_exports.literal("content-hash").optional()
17064
+ }).meta({ id: "IngestBatch" });
17065
+
17066
+ // ../../packages/schema/src/zod/exception.ts
17067
+ var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
17068
+ var ExceptionConditions = external_exports.object({
17069
+ repo: external_exports.string().optional(),
17070
+ sourceTool: external_exports.string().optional(),
17071
+ provider: external_exports.string().optional()
17072
+ }).strict();
17073
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
17074
+ var DetectionException = external_exports.object({
17075
+ id: external_exports.guid(),
17076
+ ruleId: external_exports.string(),
17077
+ // Denormalized from the rule, for reporting — never matched on.
17078
+ category: DetectionCategory,
17079
+ // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
17080
+ // fingerprint, never the raw value, and never reversible. Matching recomputes
17081
+ // the fingerprint from a fresh capture; the value itself is never stored.
17082
+ // Shape-constrained so a malformed — or accidentally raw — value is rejected
17083
+ // at the boundary rather than persisted.
17084
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17085
+ // Version of the fingerprint key the grant was written under; a rotated key
17086
+ // invalidates old grants rather than silently mismatching them.
17087
+ keyVersion: external_exports.number().int().positive(),
17088
+ // maskMatch() preview of the approved value — never the raw value.
17089
+ maskedValue: external_exports.string(),
17090
+ capability: ExceptionCapability.default("suppress"),
17091
+ scope: ExceptionScope,
17092
+ expiresAt: external_exports.iso.datetime().nullable(),
17093
+ maxUses: external_exports.number().int().positive().nullable(),
17094
+ useCount: external_exports.number().int().nonnegative(),
17095
+ lastUsedAt: external_exports.iso.datetime().nullable(),
17096
+ // Mandatory: every grant carries the human reason it exists.
17097
+ justification: external_exports.string().min(1),
17098
+ conditions: ExceptionConditions.nullable(),
17099
+ createdBy: external_exports.string(),
17100
+ createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
17101
+ createdAt: external_exports.iso.datetime(),
17102
+ updatedAt: external_exports.iso.datetime(),
17103
+ // Revocation is terminal and retained — consumed/expired/revoked rows are
17104
+ // audit evidence; nothing in the exception lifecycle hard-deletes.
17105
+ revokedAt: external_exports.iso.datetime().nullable(),
17106
+ revokedBy: external_exports.string().nullable(),
17107
+ revokeReason: external_exports.string().nullable()
17108
+ });
17109
+ var ExceptionBundleEntry = DetectionException.pick({
17110
+ id: true,
17111
+ ruleId: true,
17112
+ valueFingerprint: true,
17113
+ keyVersion: true,
17114
+ capability: true,
17115
+ expiresAt: true,
17116
+ maxUses: true,
17117
+ useCount: true,
17118
+ conditions: true
17119
+ });
17120
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
17121
+
17244
17122
  // ../../packages/schema/src/zod/exception-action.ts
17245
17123
  var confirmation = external_exports.string().optional();
17246
17124
  var ApproveBlockedInput = external_exports.object({
@@ -17283,10 +17161,11 @@ function toApiAction(dbVal) {
17283
17161
  }
17284
17162
  function toApiCategory(dbVal) {
17285
17163
  if (dbVal === "code_context") return "source_code";
17286
- return dbVal;
17164
+ const parsed = FindingCategory.safeParse(dbVal);
17165
+ return parsed.success ? parsed.data : "custom";
17287
17166
  }
17288
17167
  function toApiProvider(sourceTool) {
17289
- return TOOL_TO_HARNESS[sourceTool] ?? "api";
17168
+ return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
17290
17169
  }
17291
17170
  var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
17292
17171
  function foldGroupStatus(instanceStatuses) {
@@ -17873,7 +17752,14 @@ function isVaultConsentValid(consent) {
17873
17752
 
17874
17753
  // ../../packages/schema/src/zod/local.ts
17875
17754
  var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17876
- var RunMode = external_exports.enum(["standalone"]);
17755
+ var MODEL_JUDGE_PAYLOAD_VERSION = 1;
17756
+ var RunMode = external_exports.enum(["standalone", "attached"]);
17757
+ var ControlPlaneConnection = external_exports.object({
17758
+ endpoint: external_exports.string().min(1),
17759
+ // Display name for the deployment, shown instead of the raw endpoint.
17760
+ label: external_exports.string().min(1).optional(),
17761
+ attachedAt: external_exports.iso.datetime()
17762
+ }).meta({ id: "ControlPlaneConnection" });
17877
17763
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17878
17764
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
17879
17765
  var ModelJudgeConsent = external_exports.object({
@@ -17882,12 +17768,10 @@ var ModelJudgeConsent = external_exports.object({
17882
17768
  });
17883
17769
  var WorkspaceSettings = external_exports.object({
17884
17770
  specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
17885
- // Settings files written by earlier releases may carry the retired 'attached'
17886
- // value; it parses as 'standalone' so those files keep loading.
17887
- runMode: external_exports.preprocess(
17888
- (v) => v === "attached" ? "standalone" : v,
17889
- RunMode.default("standalone")
17890
- ),
17771
+ runMode: RunMode.default("standalone"),
17772
+ // Present only while attached; a detach clears it. Its presence is what makes
17773
+ // `runMode: 'attached'` mean anything — see isAttached.
17774
+ controlPlane: ControlPlaneConnection.optional(),
17891
17775
  policy: SimpleDetectionPolicy.default("redact"),
17892
17776
  // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
17893
17777
  historicalAccess: HistoricalAccess.default("session-only"),
@@ -17989,39 +17873,244 @@ function toInspectionFindingRow(input) {
17989
17873
  firstDetectedAt: input.firstDetectedAt ? isoToEpochMillis(input.firstDetectedAt) : null
17990
17874
  };
17991
17875
  }
17992
- function toCaptureAttributes(event) {
17993
- const metadata = event.metadata;
17994
- return {
17995
- source_tool: event.sourceTool,
17996
- ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
17997
- ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
17998
- ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
17999
- ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
18000
- ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
18001
- ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
18002
- ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
18003
- ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
18004
- // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
18005
- // has ever populated either), but every legacy metadata key still rides
18006
- // the bag rather than being silently dropped — CaptureAttributes'
18007
- // `.catchall(z.unknown())` carries the long tail.
18008
- ...metadata?.model !== void 0 ? { model: metadata.model } : {},
18009
- ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
18010
- };
17876
+ function toCaptureAttributes(event) {
17877
+ const metadata = event.metadata;
17878
+ return {
17879
+ source_tool: event.sourceTool,
17880
+ ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
17881
+ ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
17882
+ ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
17883
+ ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
17884
+ ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
17885
+ ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
17886
+ ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
17887
+ ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
17888
+ // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
17889
+ // has ever populated either), but every legacy metadata key still rides
17890
+ // the bag rather than being silently dropped — CaptureAttributes'
17891
+ // `.catchall(z.unknown())` carries the long tail.
17892
+ ...metadata?.model !== void 0 ? { model: metadata.model } : {},
17893
+ ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
17894
+ };
17895
+ }
17896
+ function captureDefinitionVersion(finding) {
17897
+ return `capture/${finding.category}/${finding.severity}`;
17898
+ }
17899
+ function toCaptureDefinitionInput(finding) {
17900
+ return {
17901
+ ruleId: finding.ruleId,
17902
+ version: captureDefinitionVersion(finding),
17903
+ name: finding.ruleId,
17904
+ category: finding.category,
17905
+ severity: finding.severity,
17906
+ definition: JSON.stringify({ ruleId: finding.ruleId })
17907
+ };
17908
+ }
17909
+
17910
+ // ../../packages/schema/src/zod/managed.ts
17911
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
17912
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
17913
+ var ManagedSettingKey = external_exports.enum([
17914
+ "runMode",
17915
+ "historicalAccess",
17916
+ "vaultConsent",
17917
+ "vaultKeyCustody",
17918
+ "vaultInlineReveal",
17919
+ "modelJudgeConsent",
17920
+ "dataSharesInPlace"
17921
+ ]).meta({ id: "ManagedSettingKey" });
17922
+ var ManagedSettingsValues = external_exports.object({
17923
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
17924
+ controlPlane: external_exports.object({
17925
+ endpoint: external_exports.string().min(1),
17926
+ label: external_exports.string().min(1).optional()
17927
+ }).optional(),
17928
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
17929
+ vaultConsent: external_exports.boolean().optional(),
17930
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
17931
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
17932
+ modelJudgeConsent: external_exports.boolean().optional(),
17933
+ dataSharesInPlace: external_exports.boolean().optional()
17934
+ }).meta({ id: "ManagedSettingsValues" });
17935
+ var ManagedSettings = external_exports.object({
17936
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
17937
+ // Shown on every locked control, so the user can tell an administrative
17938
+ // decision from a bug. Absent renders as a generic "your organization".
17939
+ organization: external_exports.string().min(1).optional(),
17940
+ // What the administrator pinned.
17941
+ values: ManagedSettingsValues.default({}),
17942
+ // Which of those the user may not change. A key here with no matching value
17943
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
17944
+ // the user may still override. The two are separable on purpose.
17945
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
17946
+ }).meta({ id: "ManagedSettings" });
17947
+
17948
+ // ../../packages/schema/src/zod/policy.ts
17949
+ var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
17950
+ var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
17951
+ var Policy = external_exports.object({
17952
+ id: external_exports.guid(),
17953
+ scope: PolicyScope,
17954
+ target: PolicyTarget,
17955
+ action: ActionTaken,
17956
+ enabled: external_exports.boolean().default(true),
17957
+ customKeywords: external_exports.array(external_exports.string()).optional(),
17958
+ // Display name — optional so older policy rows without name still parse.
17959
+ // Added for the findings API (policy.name column migration).
17960
+ name: external_exports.string().optional()
17961
+ }).meta({ id: "Policy" });
17962
+ var PolicyBundle = external_exports.object({
17963
+ version: external_exports.string(),
17964
+ policies: external_exports.array(Policy),
17965
+ // Rules from the installed marketplace packs (snapshotted by the
17966
+ // control plane). The plugin registers these in addition to its bundled
17967
+ // packs. Optional so older backends — and older on-disk caches — that omit
17968
+ // the field still parse; consumers read `bundle.rules ?? []`.
17969
+ rules: external_exports.array(Rule).optional(),
17970
+ // When true, `rules` IS the complete effective ruleset and the runtime must
17971
+ // NOT merge its compiled-in bundled packs — the standalone gateway sets this
17972
+ // after reading the user's installed snapshot (installed_packs, enabled
17973
+ // packs only), which is how detection updates stay manual: new bundled
17974
+ // rules run only after the user applies the pack update. Absent/false keeps
17975
+ // the historical composition (bundled packs + rules) — older caches.
17976
+ rulesComplete: external_exports.boolean().optional(),
17977
+ // Active detection exceptions, evaluation subset only (see
17978
+ // ExceptionBundleEntry). Optional so older bundle producers — and older
17979
+ // on-disk caches — that omit the field still parse; consumers read
17980
+ // `bundle.exceptions ?? []`.
17981
+ exceptions: external_exports.array(ExceptionBundleEntry).optional(),
17982
+ // Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
17983
+ // A second axis over the same `redact` action, carried beside the policies
17984
+ // rather than on them: nothing writes ruleId-targeted policies to disk, so
17985
+ // widening Policy itself would change a persisted shape to express something
17986
+ // only the in-memory bundle needs. Optional so an older producer — or an
17987
+ // older on-disk cache — still parses; consumers read `?? []` and get the
17988
+ // pre-existing one-way behaviour, which is the safe direction to default.
17989
+ reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
17990
+ // Installed pack version, keyed by ruleId, for rules in `rules` that came
17991
+ // from a versioned installed pack. Optional so older backends — and older
17992
+ // on-disk caches — that omit the field still parse; consumers fall back to
17993
+ // the rule's own spec version. NOT the bundle version above — see
17994
+ // installedRuleset's ruleVersions for the source of truth.
17995
+ ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
17996
+ customKeywords: external_exports.array(external_exports.string()),
17997
+ fetchedAt: external_exports.iso.datetime()
17998
+ }).meta({ id: "PolicyBundle" });
17999
+ var OBSERVE_ONLY_CATEGORIES = ["config"];
18000
+ var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
18001
+ var CATEGORY_PEAK_SEVERITY = {
18002
+ secret: "critical",
18003
+ financial: "critical",
18004
+ // core-financial/credit-card
18005
+ code_flaw: "critical",
18006
+ pii: "high",
18007
+ phi: "high",
18008
+ custom: "high",
18009
+ // user-defined; conservative
18010
+ code_context: "low",
18011
+ config: "low"
18012
+ // observe-only; floors to monitor regardless
18013
+ };
18014
+ function severityFloorPolicy(category) {
18015
+ if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
18016
+ const peak = CATEGORY_PEAK_SEVERITY[category];
18017
+ return peak === "critical" || peak === "high" ? "warn" : "monitor";
18018
+ }
18019
+ var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
18020
+ var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
18021
+ var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
18022
+ var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
18023
+ var BUILTIN_POLICY_SPECS = {
18024
+ monitor: {
18025
+ name: "Monitor",
18026
+ action: "log",
18027
+ reversible: false,
18028
+ description: "Log every match for audit. The request is allowed through untouched."
18029
+ },
18030
+ warn: {
18031
+ name: "Warn",
18032
+ action: "warn",
18033
+ reversible: false,
18034
+ description: "Allow the request, but warn the user inline before it is sent."
18035
+ },
18036
+ redact: {
18037
+ name: "Redact",
18038
+ action: "redact",
18039
+ reversible: false,
18040
+ description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
18041
+ },
18042
+ vault: {
18043
+ name: "Redact & Vault",
18044
+ action: "redact",
18045
+ reversible: true,
18046
+ 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."
18047
+ },
18048
+ block: {
18049
+ name: "Block",
18050
+ action: "block",
18051
+ reversible: false,
18052
+ description: "Refuse the request entirely whenever any rule in this detection matches."
18053
+ }
18054
+ };
18055
+ function builtinPolicyToAction(id) {
18056
+ return BUILTIN_POLICY_SPECS[id].action;
18057
+ }
18058
+ var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18059
+ (id) => !BUILTIN_POLICY_SPECS[id].reversible
18060
+ );
18061
+ var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18062
+ (id) => BUILTIN_POLICY_SPECS[id].reversible
18063
+ );
18064
+ var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
18065
+ function builtinPolicyIsReversible(id) {
18066
+ return BUILTIN_POLICY_SPECS[id].reversible;
18011
18067
  }
18012
- function captureDefinitionVersion(finding) {
18013
- return `capture/${finding.category}/${finding.severity}`;
18068
+ function policyIdIsReversible(policyId) {
18069
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18070
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18071
+ return builtinPolicyIsReversible(id);
18014
18072
  }
18015
- function toCaptureDefinitionInput(finding) {
18016
- return {
18017
- ruleId: finding.ruleId,
18018
- version: captureDefinitionVersion(finding),
18019
- name: finding.ruleId,
18020
- category: finding.category,
18021
- severity: finding.severity,
18022
- definition: JSON.stringify({ ruleId: finding.ruleId })
18023
- };
18073
+ var DEFAULT_ACTIONS = Object.fromEntries(
18074
+ DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
18075
+ );
18076
+ var BUILTIN_POLICIES = Object.fromEntries(
18077
+ KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
18078
+ );
18079
+ var DEFAULT_PACK_POLICY_ID = "monitor";
18080
+ function policyIdToAction(policyId) {
18081
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18082
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18083
+ return BUILTIN_POLICIES[id].action;
18024
18084
  }
18085
+ var UsedByItem = external_exports.object({
18086
+ id: external_exports.string(),
18087
+ name: external_exports.string(),
18088
+ ruleCount: external_exports.number().int().nonnegative(),
18089
+ enabled: external_exports.boolean()
18090
+ }).meta({ id: "UsedByItem" });
18091
+ var PolicyListItem = external_exports.object({
18092
+ id: external_exports.string(),
18093
+ kind: PolicyKind,
18094
+ name: external_exports.string(),
18095
+ enabled: external_exports.boolean(),
18096
+ usedByCount: external_exports.number().int().nonnegative()
18097
+ }).meta({ id: "PolicyListItem" });
18098
+ var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
18099
+ var PolicyDetail = external_exports.object({
18100
+ specVersion: external_exports.literal(1),
18101
+ id: external_exports.string(),
18102
+ kind: PolicyKind,
18103
+ name: external_exports.string(),
18104
+ enabled: external_exports.boolean(),
18105
+ description: external_exports.string(),
18106
+ usedBy: external_exports.array(UsedByItem)
18107
+ }).meta({ id: "PolicyDetail" });
18108
+ var PolicyStatsResponse = external_exports.object({
18109
+ policies: external_exports.number().int().nonnegative(),
18110
+ builtin: external_exports.number().int().nonnegative(),
18111
+ custom: external_exports.number().int().nonnegative(),
18112
+ detectionsGoverned: external_exports.number().int().nonnegative()
18113
+ }).meta({ id: "PolicyStatsResponse" });
18025
18114
 
18026
18115
  // ../../packages/schema/src/zod/project-files.ts
18027
18116
  var ProjectFileInput = external_exports.object({
@@ -18101,44 +18190,6 @@ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
18101
18190
  NoRemediationDecision
18102
18191
  ]);
18103
18192
 
18104
- // ../../packages/schema/src/zod/rule-test.ts
18105
- var TestRulesRequest = external_exports.object({
18106
- rules: external_exports.array(Rule).min(1).max(100),
18107
- text: external_exports.string().max(5e4).optional(),
18108
- fixtures: external_exports.array(RuleFixture).max(200).optional()
18109
- }).refine((v) => v.text !== void 0 || (v.fixtures?.length ?? 0) > 0, {
18110
- message: "Provide `text`, `fixtures`, or both \u2014 there must be something to test"
18111
- }).meta({ id: "TestRulesRequest" });
18112
- var RuleTestMatch = external_exports.object({
18113
- ruleId: external_exports.string(),
18114
- category: DetectionCategory,
18115
- severity: Severity,
18116
- span: Span,
18117
- confidence: external_exports.number().min(0).max(1),
18118
- match: external_exports.string()
18119
- }).meta({ id: "RuleTestMatch" });
18120
- var FixtureResult = external_exports.object({
18121
- label: external_exports.string(),
18122
- shouldMatch: external_exports.boolean(),
18123
- didMatch: external_exports.boolean(),
18124
- passed: external_exports.boolean(),
18125
- matches: external_exports.array(RuleTestMatch)
18126
- }).meta({ id: "FixtureResult" });
18127
- var TestRulesResponse = external_exports.object({
18128
- // Present only when the request supplied `text`.
18129
- adhoc: external_exports.object({ matches: external_exports.array(RuleTestMatch) }).optional(),
18130
- fixtures: external_exports.array(FixtureResult),
18131
- summary: external_exports.object({
18132
- total: external_exports.number().int().nonnegative(),
18133
- passed: external_exports.number().int().nonnegative(),
18134
- failed: external_exports.number().int().nonnegative()
18135
- }),
18136
- // Ids of rules whose matcher type the engine cannot evaluate today (e.g.
18137
- // `validator`), so they silently never match. Surfaced so an author is not
18138
- // misled by a green run that actually skipped a rule.
18139
- unsupportedRuleIds: external_exports.array(external_exports.string())
18140
- }).meta({ id: "TestRulesResponse" });
18141
-
18142
18193
  // ../../packages/schema/src/zod/security.ts
18143
18194
  var SeveritySummaryItem = external_exports.object({
18144
18195
  severity: Severity,
@@ -18236,10 +18287,22 @@ var TopSourcesQuery = external_exports.object({
18236
18287
  // Omit for both kinds.
18237
18288
  kind: external_exports.enum(SOURCE_KINDS).optional()
18238
18289
  });
18239
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18290
+ var Provider = Harness.extract([
18291
+ "ClaudeCode",
18292
+ "Cursor",
18293
+ "Codex",
18294
+ "Antigravity",
18295
+ "ClaudeAi",
18296
+ "ChatGpt",
18297
+ "Copilot",
18298
+ "Api"
18299
+ ]).meta({ id: "Provider" });
18240
18300
  var ScanCoverageProvider = external_exports.object({
18241
18301
  provider: Provider,
18242
- // Percent of that provider's traffic scanned in the window. 0 when unsupported.
18302
+ // Percent of that provider's traffic the shipped capture surface reaches.
18303
+ // A curated business fact, constant across every `range` — not a measured
18304
+ // per-window metric. 0 exactly when `supported` is false. See the comment
18305
+ // above the block for where these numbers are decided.
18243
18306
  coverage: external_exports.number().int().min(0).max(100),
18244
18307
  supported: external_exports.boolean()
18245
18308
  }).meta({ id: "ScanCoverageProvider" });
@@ -18293,6 +18356,18 @@ var ApplyRecommendedActionResponse = external_exports.object({
18293
18356
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
18294
18357
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
18295
18358
 
18359
+ // ../../packages/schema/src/zod/settings-action.ts
18360
+ var SaveSettingsInput = external_exports.object({
18361
+ historicalAccess: external_exports.string(),
18362
+ modelJudgeConsent: external_exports.boolean(),
18363
+ vaultConsent: external_exports.string(),
18364
+ vaultInlineReveal: external_exports.string()
18365
+ });
18366
+ var AttachInput = external_exports.object({
18367
+ endpoint: external_exports.string(),
18368
+ label: external_exports.string().optional()
18369
+ });
18370
+
18296
18371
  // ../../packages/schema/src/zod/triage.ts
18297
18372
  var TriageHit = external_exports.object({
18298
18373
  ruleId: external_exports.string(),
@@ -18307,7 +18382,7 @@ var TriageHit = external_exports.object({
18307
18382
  valueFingerprint: external_exports.string().optional(),
18308
18383
  keyVersion: external_exports.number().int().nonnegative().optional()
18309
18384
  });
18310
- var TriagePolicy = BuiltinPolicyId;
18385
+ var TriagePolicy = CategoryPolicyId;
18311
18386
  var TriageCategoryRec = external_exports.object({
18312
18387
  category: DetectionCategory,
18313
18388
  action: TriagePolicy,
@@ -18525,8 +18600,11 @@ function chmodBestEffort(path, mode) {
18525
18600
  function tightenDir(dir) {
18526
18601
  chmodBestEffort(dir, DATA_DIR_MODE);
18527
18602
  }
18603
+ function mkdirOwnerOnlySync(dir, recursive = false) {
18604
+ mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
18605
+ }
18528
18606
  function ensureDataDirSync(dir) {
18529
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18607
+ mkdirOwnerOnlySync(dir, true);
18530
18608
  tightenDir(dir);
18531
18609
  }
18532
18610
  function dbSidecars(file2) {
@@ -18538,6 +18616,26 @@ function tightenFile(file2) {
18538
18616
  function tightenPerms(file2) {
18539
18617
  for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18540
18618
  }
18619
+ function writeExclusiveOwnerOnlySync(file2, data) {
18620
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18621
+ }
18622
+ function writeOwnerOnlyFileSync(file2, data) {
18623
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18624
+ try {
18625
+ rmSync(tmp, { force: true });
18626
+ } catch {
18627
+ }
18628
+ try {
18629
+ writeExclusiveOwnerOnlySync(tmp, data);
18630
+ renameSync(tmp, file2);
18631
+ } finally {
18632
+ try {
18633
+ rmSync(tmp, { force: true });
18634
+ } catch {
18635
+ }
18636
+ }
18637
+ tightenFile(file2);
18638
+ }
18541
18639
  function classifyOccupant(file2) {
18542
18640
  try {
18543
18641
  if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
@@ -18566,7 +18664,7 @@ function createOwnerOnlyFileSync(file2, data) {
18566
18664
  }
18567
18665
  let created;
18568
18666
  try {
18569
- writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18667
+ writeExclusiveOwnerOnlySync(tmp, data);
18570
18668
  created = publishByLink(tmp, file2, data);
18571
18669
  } finally {
18572
18670
  try {
@@ -18588,7 +18686,7 @@ function publishByLink(tmp, file2, data) {
18588
18686
  if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18589
18687
  }
18590
18688
  try {
18591
- writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18689
+ writeExclusiveOwnerOnlySync(file2, data);
18592
18690
  return true;
18593
18691
  } catch (err) {
18594
18692
  if (err.code === "EEXIST") return false;
@@ -18601,6 +18699,25 @@ function backupPath(file2, tag) {
18601
18699
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18602
18700
  }
18603
18701
  var STALE_PARTIAL_MS = 5 * 6e4;
18702
+ var SNAPSHOT_STAGING_SUFFIX = ".partial";
18703
+ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18704
+ var SNAPSHOT_STAGING_COPY = "copy";
18705
+ function createSnapshotStaging(backup) {
18706
+ const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18707
+ rmSync2(stage, { recursive: true, force: true });
18708
+ mkdirOwnerOnlySync(stage);
18709
+ tightenDir(stage);
18710
+ return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18711
+ }
18712
+ function idleMs(entry) {
18713
+ for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18714
+ try {
18715
+ return Date.now() - statSync(candidate).mtimeMs;
18716
+ } catch {
18717
+ }
18718
+ }
18719
+ return null;
18720
+ }
18604
18721
  function reapStalePartials(file2) {
18605
18722
  const dir = dirname(file2);
18606
18723
  const prefix = `${basename(file2)}.`;
@@ -18611,30 +18728,34 @@ function reapStalePartials(file2) {
18611
18728
  return;
18612
18729
  }
18613
18730
  for (const name of entries) {
18614
- if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18615
- const partial2 = join(dir, name);
18731
+ if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18732
+ const staging = join(dir, name);
18616
18733
  try {
18617
- if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18618
- rmSync2(partial2, { force: true });
18734
+ const idle = idleMs(staging);
18735
+ if (idle !== null && idle > STALE_PARTIAL_MS) {
18736
+ rmSync2(staging, { recursive: true, force: true });
18619
18737
  }
18620
18738
  } catch {
18621
18739
  }
18622
18740
  }
18623
18741
  }
18624
18742
  function snapshotStore(db, backup) {
18625
- const partial2 = `${backup}.partial`;
18743
+ const { stage, copy } = createSnapshotStaging(backup);
18626
18744
  try {
18627
- rmSync2(partial2, { force: true });
18628
- db.prepare("VACUUM INTO ?").run(partial2);
18629
- tightenFile(partial2);
18630
- renameSync2(partial2, backup);
18745
+ db.prepare("VACUUM INTO ?").run(copy);
18746
+ tightenFile(copy);
18747
+ renameSync2(copy, backup);
18631
18748
  } catch (error51) {
18632
18749
  try {
18633
- rmSync2(partial2, { force: true });
18750
+ rmSync2(stage, { recursive: true, force: true });
18634
18751
  } catch {
18635
18752
  }
18636
18753
  throw error51;
18637
18754
  }
18755
+ try {
18756
+ rmSync2(stage, { recursive: true, force: true });
18757
+ } catch {
18758
+ }
18638
18759
  }
18639
18760
  function moveStoreAside(file2, backup) {
18640
18761
  const undo = [];
@@ -19362,9 +19483,10 @@ function safeParseStringArray(raw) {
19362
19483
  const parsed = safeJson(raw, null);
19363
19484
  return Array.isArray(parsed) ? parsed : [];
19364
19485
  }
19486
+ var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19365
19487
  function toHarness(raw) {
19366
19488
  const parsed = Harness.safeParse(raw);
19367
- return parsed.success ? parsed.data : "claudecode";
19489
+ return parsed.success ? parsed.data : DEFAULT_HARNESS;
19368
19490
  }
19369
19491
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19370
19492
  if (row.status) {
@@ -19495,7 +19617,7 @@ var SqliteActivityRepository = class {
19495
19617
  const params = [];
19496
19618
  if (query.harness && query.harness.length > 0) {
19497
19619
  conditions.push(
19498
- `coalesce(json_extract(attributes, '$.harness'), 'claudecode') IN (${placeholders(query.harness.length)})`
19620
+ `coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
19499
19621
  );
19500
19622
  params.push(...query.harness);
19501
19623
  }
@@ -19702,13 +19824,13 @@ var SqliteActivityRepository = class {
19702
19824
  * The DISTINCT harnesses that actually have sessions (optionally within a
19703
19825
  * `started_at >= fromMs` window), so the filter can offer only the harnesses
19704
19826
  * present rather than the full enum. Each stored value is normalized through
19705
- * the SAME `toHarness` default the list uses (missing → 'claudecode'), so a
19706
- * store of bare (harness-less) roots surfaces exactly `['claudecode']`.
19827
+ * the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
19828
+ * a store of bare (harness-less) roots surfaces exactly that one harness.
19707
19829
  */
19708
19830
  harnessFacets(fromMs) {
19709
19831
  const where = fromMs === void 0 ? "" : " AND started_at >= ?";
19710
19832
  const stmt = this.db.prepare(
19711
- `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), 'claudecode') AS harness
19833
+ `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
19712
19834
  FROM audit_events WHERE ${SESSION_ROOT}${where}`
19713
19835
  );
19714
19836
  const rows = allRows(
@@ -19922,8 +20044,8 @@ var SqliteAuditEventsRepository = class {
19922
20044
  }
19923
20045
  // Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
19924
20046
  // (which takes a caller-supplied random id), the id here is MINTED internally
19925
- // from the natural key — `llmCallId(sessionId, messageId)` — tenant-free like the
19926
- // sibling local-store ids (the local store is single-tenant). The deterministic
20047
+ // from the natural key — `llmCallId(sessionId, messageId)` — derived from the
20048
+ // session and message alone, like the sibling local-store ids. The deterministic
19927
20049
  // id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
19928
20050
  // AND converge a streaming partial/final split across two incremental passes:
19929
20051
  // a whole-file re-read no-ops (equal output), a lagging final replaces a
@@ -20888,6 +21010,42 @@ var SqliteFindingsRepository = class {
20888
21010
  this.db = db;
20889
21011
  }
20890
21012
  db;
21013
+ /**
21014
+ * The newest `limit` findings, newest first.
21015
+ *
21016
+ * THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
21017
+ * pin it. The natural spelling — drive from `inspection_findings`, order by the
21018
+ * JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
21019
+ * not on the driving table: SQLite sorts every finding in the store through a
21020
+ * temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
21021
+ * against 0.9 ms for the form below, and the gap is a ratio of the store size
21022
+ * rather than a constant.
21023
+ *
21024
+ * What it takes to make `started_at` order come out of an index instead:
21025
+ *
21026
+ * - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
21027
+ * planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
21028
+ * index cannot serve the ORDER BY: the predicate spans four event types, so
21029
+ * satisfying a global `started_at` order across them needs a range merge
21030
+ * SQLite will not do, and it sorts instead. Freed of it, the planner scans
21031
+ * `idx_audit_started_at` — a bare `started_at` index — in DESC order and
21032
+ * filters the type per row, which lets the LIMIT stop the scan early.
21033
+ * - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
21034
+ * purely to stop the tables being reordered. With plain JOINs the planner
21035
+ * drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
21036
+ * unary plus ALONE recovers almost none of the win. Both are needed.
21037
+ *
21038
+ * Neither is a micro-optimisation that a later reader should tidy away, and
21039
+ * `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
21040
+ * the temp B-tree comes back.
21041
+ *
21042
+ * Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
21043
+ * identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
21044
+ * renaming the index turns this read into an ERROR, where `+` turns it into a
21045
+ * scan-and-sort — slower, still correct. The worst case for the chosen form is
21046
+ * a store whose recent captures carry no findings at all, where the scan walks
21047
+ * the whole index; that is still no worse than the full sort it replaced.
21048
+ */
20891
21049
  recentFindings(opts) {
20892
21050
  const limit = opts?.limit ?? 50;
20893
21051
  const rows = allRows(
@@ -20896,10 +21054,10 @@ var SqliteFindingsRepository = class {
20896
21054
  f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
20897
21055
  json_extract(e.attributes, '$.source_tool') AS source_tool,
20898
21056
  e.event_type AS kind
20899
- FROM inspection_findings f
20900
- JOIN audit_events e ON e.id = f.audit_event_id
20901
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20902
- WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
21057
+ FROM audit_events e
21058
+ CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
21059
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21060
+ WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
20903
21061
  ORDER BY e.started_at DESC, f.rowid DESC
20904
21062
  LIMIT :limit`
20905
21063
  ),
@@ -21543,7 +21701,8 @@ var SqliteInspectionDefinitionsRepository = class {
21543
21701
  }
21544
21702
  db;
21545
21703
  insertStmt;
21546
- // Idempotent upsert; returns the content-addressed definition id.
21704
+ // Insert-if-absent; returns the content-addressed definition id. An id already
21705
+ // present keeps the stored row untouched — see the class doc.
21547
21706
  upsert(input) {
21548
21707
  const id = inspectionDefinitionId(input.ruleId, input.version);
21549
21708
  const row = toInspectionDefinitionRow(input, id);
@@ -21687,11 +21846,23 @@ function isParseableBinaryVersion(version2) {
21687
21846
 
21688
21847
  // ../../packages/persistence/src/repositories/installed-packs.ts
21689
21848
  var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
21849
+ function printableRuleId(entry) {
21850
+ if (typeof entry !== "object" || entry === null) return null;
21851
+ const candidate = entry.id;
21852
+ return Rule.shape.id.safeParse(candidate).success ? candidate : null;
21853
+ }
21854
+ function firstIssueReason(error51) {
21855
+ const issue2 = error51.issues[0];
21856
+ if (!issue2) return "unknown";
21857
+ const path = issue2.path.map((segment) => String(segment)).join(".");
21858
+ return path ? `${path}: ${issue2.code}` : issue2.code;
21859
+ }
21860
+ var REJECTED_RULE_DETAIL_CAP = 10;
21690
21861
  function inventorySignature(packs2) {
21691
21862
  return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
21692
21863
  }
21693
21864
  function hashRules(rulesJson) {
21694
- return createHash2("sha1").update(rulesJson).digest("hex");
21865
+ return createHash2("sha256").update(rulesJson).digest("hex");
21695
21866
  }
21696
21867
  function parseVersion(v) {
21697
21868
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -21903,10 +22074,21 @@ var SqliteInstalledPacksRepository = class {
21903
22074
  * (all detection off) instead of falling back to the bundled packs. Every
21904
22075
  * JSON-level failure therefore counts as invalid.
21905
22076
  */
22077
+ /**
22078
+ * ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
22079
+ * index is (namespace, pack_id) — so two enabled packs may contribute the same
22080
+ * id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
22081
+ * winner is whatever order SQLite happens to return, which makes a collision
22082
+ * resolve differently on two machines holding identical stores. Ordering by
22083
+ * (namespace, pack_id) makes the loser deterministic and therefore testable.
22084
+ */
21906
22085
  installedRuleset() {
21907
22086
  const rows = allRows(
21908
22087
  this.db.prepare(
21909
- `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version FROM installed_packs`
22088
+ `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
22089
+ namespace, pack_id AS packId
22090
+ FROM installed_packs
22091
+ ORDER BY namespace, pack_id`
21910
22092
  )
21911
22093
  );
21912
22094
  const out = {
@@ -21914,22 +22096,32 @@ var SqliteInstalledPacksRepository = class {
21914
22096
  enabledPacks: 0,
21915
22097
  rules: [],
21916
22098
  invalidRules: 0,
22099
+ rejectedRules: [],
21917
22100
  ruleActions: /* @__PURE__ */ new Map(),
21918
- ruleVersions: /* @__PURE__ */ new Map()
22101
+ ruleVersions: /* @__PURE__ */ new Map(),
22102
+ reversibleRules: /* @__PURE__ */ new Set()
22103
+ };
22104
+ const reject = (pack, ruleId, reason) => {
22105
+ if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
22106
+ out.rejectedRules.push({ pack, ruleId, reason });
21919
22107
  };
21920
22108
  for (const row of rows) {
21921
22109
  if (!intToBool(row.enabled)) continue;
21922
22110
  out.enabledPacks += 1;
21923
22111
  const action = policyIdToAction(row.policyId);
22112
+ const reversible = policyIdIsReversible(row.policyId);
22113
+ const pack = `${row.namespace}/${row.packId}`;
21924
22114
  let raw;
21925
22115
  try {
21926
22116
  raw = JSON.parse(row.rulesJson);
21927
22117
  } catch {
21928
22118
  out.invalidRules += 1;
22119
+ reject(pack, null, "rules_json: malformed JSON");
21929
22120
  continue;
21930
22121
  }
21931
22122
  if (!Array.isArray(raw)) {
21932
22123
  out.invalidRules += 1;
22124
+ reject(pack, null, "rules_json: not an array");
21933
22125
  continue;
21934
22126
  }
21935
22127
  for (const entry of raw) {
@@ -21938,7 +22130,12 @@ var SqliteInstalledPacksRepository = class {
21938
22130
  out.rules.push(parsed.data);
21939
22131
  out.ruleActions.set(parsed.data.id, action);
21940
22132
  out.ruleVersions.set(parsed.data.id, row.version);
21941
- } else out.invalidRules += 1;
22133
+ if (reversible) out.reversibleRules.add(parsed.data.id);
22134
+ else out.reversibleRules.delete(parsed.data.id);
22135
+ } else {
22136
+ out.invalidRules += 1;
22137
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22138
+ }
21942
22139
  }
21943
22140
  }
21944
22141
  return out;
@@ -22132,31 +22329,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
22132
22329
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
22133
22330
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
22134
22331
  var HARNESS_LABELS = {
22135
- claudecode: "Claude Code",
22136
- cursor: "Cursor",
22137
- codex: "Codex",
22138
- antigravity: "Antigravity"
22332
+ [HARNESS.ClaudeCode]: "Claude Code",
22333
+ [HARNESS.Cursor]: "Cursor",
22334
+ [HARNESS.Codex]: "Codex",
22335
+ [HARNESS.Antigravity]: "Antigravity"
22139
22336
  };
22140
22337
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
22141
22338
  var EMPTY_PROJECT_AGG = {
22142
22339
  accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
22143
22340
  findingsCount: 0
22144
22341
  };
22342
+ var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
22343
+ var TITLE_NEEDLES = {
22344
+ ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
22345
+ Cursor: stripSeparators(SOURCE_TOOL.Cursor),
22346
+ Codex: stripSeparators(SOURCE_TOOL.Codex),
22347
+ Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
22348
+ };
22145
22349
  function resolveHarnessId(attrs, row) {
22146
22350
  if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
22147
22351
  return attrs.provider;
22148
22352
  }
22149
- const t = (row.title ?? "").toLowerCase().replace(/[\s-]/g, "");
22150
- if (t.includes("claudecode") || t === "claude") return "claudecode";
22151
- if (t.includes("cursor")) return "cursor";
22152
- if (t.includes("codex")) return "codex";
22153
- if (t.includes("antigravity")) return "antigravity";
22353
+ const t = stripSeparators(row.title ?? "");
22354
+ if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
22355
+ if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
22356
+ if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
22357
+ if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
22154
22358
  return null;
22155
22359
  }
22156
22360
  function isLiveRealClaudeCode(rows) {
22157
22361
  return rows.some((r) => {
22158
22362
  const attrs = safeJson(r.attributes, {});
22159
- return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === "claudecode";
22363
+ return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
22160
22364
  });
22161
22365
  }
22162
22366
  function toAssetSummary(row) {
@@ -22424,7 +22628,7 @@ var SqliteInventoryAssetsRepository = class {
22424
22628
  const isRealHarness = rows.some(
22425
22629
  (r) => safeJson(r.attributes, {}).provenance !== "sample"
22426
22630
  );
22427
- const attachConfig = isRealHarness && harnessId === "claudecode" && configAssets.length > 0;
22631
+ const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
22428
22632
  const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
22429
22633
  if (q && assets.length === 0) continue;
22430
22634
  const firstRow = rows[0];
@@ -23098,9 +23302,10 @@ var SqliteProjectFilesRepository = class {
23098
23302
  // ../../packages/persistence/src/repositories/resolutions.ts
23099
23303
  import { randomUUID as randomUUID7 } from "crypto";
23100
23304
  var SqliteResolutionsRepository = class {
23101
- constructor(db, now = () => Date.now()) {
23305
+ constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
23102
23306
  this.db = db;
23103
23307
  this.now = now;
23308
+ this.newId = newId;
23104
23309
  this.insertStmt = db.prepare(
23105
23310
  `INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
23106
23311
  VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
@@ -23133,12 +23338,14 @@ var SqliteResolutionsRepository = class {
23133
23338
  }
23134
23339
  db;
23135
23340
  now;
23341
+ newId;
23136
23342
  insertStmt;
23137
23343
  latestStmt;
23138
23344
  openAtRestStmt;
23139
23345
  resolvedAtRestStmt;
23140
23346
  /**
23141
- * Insert one disposition row. The repo mints the id and stamps created_at.
23347
+ * Insert one disposition row. The repo mints the id and stamps created_at,
23348
+ * both through the constructor's injectable seams.
23142
23349
  * `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
23143
23350
  * FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
23144
23351
  * from the schema enums. NOTE for future manual-resolution writers: this is
@@ -23150,7 +23357,7 @@ var SqliteResolutionsRepository = class {
23150
23357
  */
23151
23358
  insertResolution(r) {
23152
23359
  this.insertStmt.run({
23153
- id: randomUUID7(),
23360
+ id: this.newId(),
23154
23361
  findingKey: r.findingKey,
23155
23362
  status: FindingStatus.parse(r.status),
23156
23363
  method: ResolutionMethod.parse(r.method),
@@ -23729,16 +23936,16 @@ var ACTION_TO_KIND = {
23729
23936
  warn: "warned"
23730
23937
  };
23731
23938
  var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
23732
- var SCAN_COVERAGE = [
23733
- { provider: "claudecode", coverage: 100, supported: true },
23734
- { provider: "cursor", coverage: 0, supported: false },
23735
- { provider: "codex", coverage: 80, supported: true },
23736
- { provider: "antigravity", coverage: 60, supported: true },
23737
- { provider: "claudeai", coverage: 0, supported: false },
23738
- { provider: "chatgpt", coverage: 0, supported: false },
23739
- { provider: "copilot", coverage: 0, supported: false },
23740
- { provider: "api", coverage: 0, supported: false }
23741
- ];
23939
+ var SCAN_COVERAGE = {
23940
+ [HARNESS.Antigravity]: { coverage: 60, supported: true },
23941
+ [HARNESS.Api]: { coverage: 0, supported: false },
23942
+ [HARNESS.ChatGpt]: { coverage: 40, supported: true },
23943
+ [HARNESS.ClaudeAi]: { coverage: 40, supported: true },
23944
+ [HARNESS.ClaudeCode]: { coverage: 100, supported: true },
23945
+ [HARNESS.Codex]: { coverage: 80, supported: true },
23946
+ [HARNESS.Copilot]: { coverage: 0, supported: false },
23947
+ [HARNESS.Cursor]: { coverage: 0, supported: false }
23948
+ };
23742
23949
  var GRANULARITY = {
23743
23950
  "7d": "day",
23744
23951
  "30d": "day",
@@ -23837,9 +24044,22 @@ var SqliteSecurityRepository = class {
23837
24044
  return Promise.resolve({ total, needsRemediation, bySeverity });
23838
24045
  }
23839
24046
  // Range is echoed but does not change the result today — coverage is a constant
23840
- // business fact (see SCAN_COVERAGE), not a measured per-window metric.
24047
+ // business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
24048
+ // comes from Provider.options (the enum's declaration order), not from
24049
+ // SCAN_COVERAGE's own key order — deliberately, not because object literals
24050
+ // leave key order unspecified (ES2015 guarantees insertion order for these
24051
+ // non-integer string keys, so iterating SCAN_COVERAGE directly would be
24052
+ // reliable too). The reason is the schema comment's promise: the returned
24053
+ // order must mirror the generated OpenAPI enum list, which is Provider's
24054
+ // contract, not this table's.
23841
24055
  scanCoverage(range) {
23842
- return Promise.resolve({ range, providers: SCAN_COVERAGE.map((p) => ({ ...p })) });
24056
+ return Promise.resolve({
24057
+ range,
24058
+ providers: Provider.options.map((provider) => ({
24059
+ provider,
24060
+ ...SCAN_COVERAGE[provider]
24061
+ }))
24062
+ });
23843
24063
  }
23844
24064
  enforcementActions(range) {
23845
24065
  const lenMs = RANGE_DAYS[range] * DAY_MS4;
@@ -23897,10 +24117,10 @@ var SqliteSecurityRepository = class {
23897
24117
  // count; a superseding open/redetected row means the finding is not
23898
24118
  // remediated and is excluded, same invariant as severitySummary. Legacy
23899
24119
  // at-rest findings with finding_key IS NULL can never have a resolution row
23900
- // (the lifecycle is keyed by finding_key), so the SQL guard excludes them
23901
- // outright. One raw-row query (fetch every trackable finding + its latest
23902
- // resolution's status/method/resolved_at) + pure-JS filter/bucket/mean,
23903
- // mirroring this file's other methods.
24120
+ // (the lifecycle is keyed by finding_key), so they cannot reach the driving
24121
+ // set below. One raw-row query (fetch the findings with resolution activity in
24122
+ // the window + each one's latest resolution status/method/resolved_at) +
24123
+ // pure-JS filter/bucket/mean, mirroring this file's other methods.
23904
24124
  mttrTrend(range) {
23905
24125
  const granularity = granularityFor(range);
23906
24126
  const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
@@ -23916,29 +24136,80 @@ var SqliteSecurityRepository = class {
23916
24136
  // started_at the upsert overwrites onto inspection_findings.audit_event_id.
23917
24137
  // COALESCE onto the parent event's started_at defends against any
23918
24138
  // legacy/edge row the backfill left null.
23919
- `SELECT COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
24139
+ `SELECT DISTINCT f.finding_key AS finding_key,
24140
+ COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
23920
24141
  latest.status AS latest_status,
23921
24142
  latest.method AS latest_method,
23922
24143
  latest.resolved_at AS latest_resolved_at
23923
- FROM inspection_findings f
23924
- JOIN audit_events e ON e.id = f.audit_event_id
23925
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24144
+ FROM finding_resolution fr
24145
+ CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
24146
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24147
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
23926
24148
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
23927
24149
  ON latest.finding_key = f.finding_key
23928
- WHERE f.finding_key IS NOT NULL
23929
- AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
23930
- AND EXISTS (
23931
- SELECT 1 FROM finding_resolution fr
23932
- WHERE fr.finding_key = f.finding_key
23933
- AND fr.resolved_at >= :windowStart
23934
- )`
23935
- // The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
23936
- // any resolution activity at/after the window start — a row this method
24150
+ WHERE fr.resolved_at >= :windowStart
24151
+ AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
24152
+ // `fr` is a SUPERSET prefilter, not the answer: a finding this method
23937
24153
  // ultimately counts has its LATEST resolution inside the window, which
23938
- // implies such a row exists, so nothing wanted is dropped. The exact
23939
- // latest-wins + status/method + window gate stays in JS below,
23940
- // dialect-agnostic. Without this, a
23941
- // 7d request evaluated the store's entire trackable-findings history.
24154
+ // implies a resolution row at/after the window start exists, so nothing
24155
+ // wanted is dropped. The exact latest-wins + status/method + window gate
24156
+ // stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
24157
+ // implied rather than dropped — the join key comes from
24158
+ // finding_resolution, whose finding_key is NOT NULL.
24159
+ //
24160
+ // IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
24161
+ // the correction this replaced. Spelled as an `EXISTS` in the WHERE it
24162
+ // READ as a bound and was not one: SQLite drove from `audit_events` on
24163
+ // event_type, joined every capture event to its findings, and evaluated
24164
+ // the EXISTS last — bounding the RESULT and not the scan, so a 7d request
24165
+ // still cost the store's whole trackable history. Measured at 44.6 ms on
24166
+ // 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
24167
+ // both cases returning rows for a window holding a fraction of it.
24168
+ //
24169
+ // Two things carry it, and they answer DIFFERENT halves — which is worth
24170
+ // stating precisely, because the obvious reading (both are needed for the
24171
+ // speed) is wrong and was measured to be wrong:
24172
+ //
24173
+ // - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
24174
+ // keyword is semantically identical to JOIN and exists only to stop the
24175
+ // tables being reordered; with plain JOINs the planner puts `e` back on
24176
+ // the outside, because with no ANALYZE statistics it prices
24177
+ // `event_type IN (...)` as a selective probe. Reverting it alone takes
24178
+ // the 2k->20k flatness ratio from 1.32 to 16.87.
24179
+ // - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
24180
+ // `resolved_at >= :windowStart` a range SEARCH instead of a bare
24181
+ // `SCAN fr` — finding_key was this table's only index before it, so the
24182
+ // range had none. It buys NO flatness in store size: remove it and the
24183
+ // ratio above does not move, because the latest-resolution derived
24184
+ // table already passes over the whole of finding_resolution, so this
24185
+ // read is O(resolutions) either way and resolutions are not the store.
24186
+ // What it buys is the criterion `hot-read-query-plans.test.ts` enforces
24187
+ // — no hot read may pass over a table with no index — and that is the
24188
+ // guard that goes red when it is dropped. Neither test catches the
24189
+ // other's defect.
24190
+ //
24191
+ // SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
24192
+ // tidy-up. finding_resolution is append-only, so a key that was fixed,
24193
+ // redetected and fixed again carries several rows inside one window and
24194
+ // matches once per row — and the value below is a MEAN, so a key matched
24195
+ // three times is a key weighted three times.
24196
+ //
24197
+ // The skew is easy to argue away and the argument is wrong, so it is worth
24198
+ // recording. Duplicate rows for ONE key are identical (every projected
24199
+ // column is per-key: `latest.*` is latest-wins, `first_detected_at` is
24200
+ // preserved), so sums and counts scale together and that key's own mean
24201
+ // does not move. What moves is a bucket holding TWO findings that duplicate
24202
+ // UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
24203
+ // 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
24204
+ // `security.test.ts`'s "weights a finding ONCE however many resolution rows
24205
+ // it has inside the window" — which needed a fixture built for it, since no
24206
+ // single-key case can show it.
24207
+ //
24208
+ // `finding_key` is selected to make the DISTINCT dedup by KEY rather than
24209
+ // by value tuple. On the other columns alone, two genuinely different
24210
+ // findings sharing a severity, a first-detection event and a resolution
24211
+ // instant — one commit fixing two secrets in one file — are one tuple, and
24212
+ // collapsing them would under-count in the other direction.
23942
24213
  ),
23943
24214
  { windowStart }
23944
24215
  );
@@ -23996,16 +24267,47 @@ var SqliteSecurityRepository = class {
23996
24267
  }
23997
24268
  // Recently-resolved activity feed: findings whose finding_key's LATEST
23998
24269
  // finding_resolution row is status:'resolved'/method:'fixed-at-source' —
23999
- // same latest-resolution-wins correlated subquery as severitySummary /
24000
- // mttrTrend (NOT a plain JOIN, which would surface every historical
24001
- // resolution row for a key rather than just its current disposition). A key
24002
- // whose latest row is a superseding 'open'/'redetected' row (the same
24003
- // secret came back) is excluded — it is not currently resolved. Legacy
24004
- // at-rest findings with finding_key IS NULL are excluded outright (the
24005
- // resolution lifecycle can never attach to them). Path comes from the
24006
- // finding's parent event (event_type 'code_change', attributes.file_path) —
24007
- // mirrors resolutions.ts's openAtRestStmt accessor. Ordered by resolved_at
24008
- // DESC, capped at `limit`.
24270
+ // same latest-resolution-wins derived table as severitySummary / mttrTrend
24271
+ // (NOT a plain JOIN, which would surface every historical resolution row for
24272
+ // a key rather than just its current disposition). A key whose latest row is
24273
+ // a superseding 'open'/'redetected' row (the same secret came back) is
24274
+ // excluded — it is not currently resolved. Legacy at-rest findings with
24275
+ // finding_key IS NULL are excluded outright (the resolution lifecycle can
24276
+ // never attach to them). Path comes from the finding's parent event
24277
+ // (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
24278
+ // openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
24279
+ //
24280
+ // THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
24281
+ // the plan rather than a preference. Written the other way round — driving
24282
+ // from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
24283
+ // cannot use the join key: `f` is reached FROM `latest` by finding_key, so
24284
+ // `latest` gets probed on (rn, status, method) instead and the plan enumerates
24285
+ // every (code_change event x resolved key) pair before `f` can reject it. That
24286
+ // is a cross product, and it is quadratic in the store: measured at 10,966 ms
24287
+ // on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
24288
+ // returned. It was invisible for as long as it was, and reported at 8 ms,
24289
+ // because an empty finding_resolution table makes the inner side empty and the
24290
+ // cross product collapses to nothing — so the shape is only observable on a
24291
+ // corpus that seeds resolutions.
24292
+ //
24293
+ // Driving from `latest` instead makes every step below it a unique-index or
24294
+ // primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
24295
+ // so the cost is the derived table's own — linear in resolutions, which is
24296
+ // what this feed is legitimately about.
24297
+ //
24298
+ // CROSS JOIN is what actually pins that, and it is load-bearing rather than
24299
+ // decorative: in SQLite the keyword is semantically identical to JOIN and
24300
+ // exists only to stop the optimizer reordering the tables. Written as plain
24301
+ // JOINs in this order the planner puts `e` back on the outside — it has no
24302
+ // ANALYZE statistics to price the alternatives with, so it takes
24303
+ // `event_type = 'code_change'` for a selective index probe and rebuilds the
24304
+ // cross product. The FROM order alone was measured to change the plan not at
24305
+ // all.
24306
+ //
24307
+ // The LEFT JOIN it replaced was already an inner join in effect: three
24308
+ // `latest.*` predicates sit in the WHERE, and each of them is false for a
24309
+ // null-extended row. Spelling it JOIN changes no row and stops the plan
24310
+ // reading as though the findings side could drive.
24009
24311
  recentlyResolved(limit = 20) {
24010
24312
  const rows = allRows(
24011
24313
  this.db.prepare(
@@ -24015,17 +24317,15 @@ var SqliteSecurityRepository = class {
24015
24317
  json_extract(e.attributes, '$.file_path') AS path,
24016
24318
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
24017
24319
  latest.resolved_at AS latest_resolved_at
24018
- FROM inspection_findings f
24019
- JOIN audit_events e ON e.id = f.audit_event_id
24020
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24021
- LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24022
- ON latest.finding_key = f.finding_key
24023
- WHERE e.event_type = 'code_change'
24024
- AND f.finding_key IS NOT NULL
24025
- AND latest.status = 'resolved'
24320
+ FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24321
+ CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
24322
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24323
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24324
+ WHERE latest.status = 'resolved'
24026
24325
  AND latest.method = 'fixed-at-source'
24027
24326
  AND latest.resolved_at IS NOT NULL
24028
- ORDER BY latest_resolved_at DESC
24327
+ AND e.event_type = 'code_change'
24328
+ ORDER BY latest.resolved_at DESC
24029
24329
  LIMIT :limit`
24030
24330
  ),
24031
24331
  { limit }
@@ -24962,6 +25262,7 @@ function openAndInitialize(file2) {
24962
25262
  function openLocalDatabase(dir) {
24963
25263
  ensureDataDirSync(dir);
24964
25264
  const file2 = join2(dir, DB_FILENAME);
25265
+ reapStalePartials(file2);
24965
25266
  const {
24966
25267
  db,
24967
25268
  events,
@@ -25389,11 +25690,79 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25389
25690
  }
25390
25691
  }
25391
25692
 
25392
- // ../../packages/persistence/src/settings.ts
25693
+ // ../../packages/persistence/src/managed-settings.ts
25393
25694
  import { readFileSync as readFileSync3 } from "fs";
25695
+ import { posix, win32 } from "path";
25696
+ function managedSettingsPaths(platform2 = process.platform) {
25697
+ if (platform2 === "darwin") {
25698
+ return [
25699
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
25700
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
25701
+ ];
25702
+ }
25703
+ if (platform2 === "win32") {
25704
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
25705
+ }
25706
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
25707
+ }
25708
+ function readManagedSettings(paths = managedSettingsPaths()) {
25709
+ for (const path of paths) {
25710
+ let text;
25711
+ try {
25712
+ text = readFileSync3(path, "utf8");
25713
+ } catch {
25714
+ continue;
25715
+ }
25716
+ const record2 = parseJsonObject(text);
25717
+ if (!record2) continue;
25718
+ const parsed = ManagedSettings.safeParse(record2);
25719
+ if (parsed.success) return parsed.data;
25720
+ }
25721
+ return null;
25722
+ }
25723
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
25724
+ if (!managed) return settings;
25725
+ const { values } = managed;
25726
+ const merged = { ...settings };
25727
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
25728
+ if (values.controlPlane !== void 0) {
25729
+ merged.controlPlane = {
25730
+ ...values.controlPlane,
25731
+ // The administrator pinned WHICH deployment, not WHEN this machine
25732
+ // joined it. Keep the user's own attach time when the endpoint is
25733
+ // unchanged, so a managed machine does not appear to re-attach on every
25734
+ // read; stamp a fresh one when the administrator moved it.
25735
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
25736
+ };
25737
+ }
25738
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
25739
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
25740
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
25741
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
25742
+ if (values.vaultConsent !== void 0) {
25743
+ merged.vaultConsent = values.vaultConsent ? (
25744
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
25745
+ // at the current version otherwise.
25746
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
25747
+ ) : void 0;
25748
+ }
25749
+ if (values.modelJudgeConsent !== void 0) {
25750
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
25751
+ acknowledgedAt: now().toISOString(),
25752
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
25753
+ } : void 0;
25754
+ }
25755
+ return merged;
25756
+ }
25757
+
25758
+ // ../../packages/persistence/src/settings.ts
25759
+ import { readFileSync as readFileSync4 } from "fs";
25394
25760
  import { join as join5 } from "path";
25395
25761
  var SETTINGS_FILENAME = "settings.json";
25396
25762
  function readWorkspaceSettings(base = defaultDataDir()) {
25763
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25764
+ }
25765
+ function readUserSettings(base) {
25397
25766
  const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25398
25767
  if (!record2) return defaultWorkspaceSettings();
25399
25768
  try {
@@ -25405,7 +25774,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
25405
25774
  function readJson(file2) {
25406
25775
  let text;
25407
25776
  try {
25408
- text = readFileSync3(file2, "utf8");
25777
+ text = readFileSync4(file2, "utf8");
25409
25778
  } catch {
25410
25779
  return null;
25411
25780
  }
@@ -25523,15 +25892,7 @@ function formatPointer(category, keyVersion, pointerId, tag) {
25523
25892
  // ../../packages/persistence/src/vault/key-provider.ts
25524
25893
  import { execFileSync } from "child_process";
25525
25894
  import { randomBytes as randomBytes2 } from "crypto";
25526
- import {
25527
- chmodSync as chmodSync2,
25528
- mkdirSync as mkdirSync2,
25529
- readFileSync as readFileSync4,
25530
- renameSync as renameSync4,
25531
- rmSync as rmSync4,
25532
- statSync as statSync3,
25533
- writeFileSync as writeFileSync3
25534
- } from "fs";
25895
+ import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25535
25896
  import { join as join6 } from "path";
25536
25897
  var VAULT_OCCUPANT_REASON = {
25537
25898
  symlink: "the path is a symlink; remove it so a keyring can be created",
@@ -25624,7 +25985,8 @@ var LOCK_OWNER_FILE = "owner";
25624
25985
  var ROTATION_IN_PROGRESS = "vault: a key rotation is already in progress";
25625
25986
  function claimRotationLock(lock, owner) {
25626
25987
  try {
25627
- mkdirSync2(lock);
25988
+ mkdirOwnerOnlySync(lock);
25989
+ tightenDir(lock);
25628
25990
  } catch (err) {
25629
25991
  if (err.code === "EEXIST") return false;
25630
25992
  throw asError(err);
@@ -25666,7 +26028,7 @@ function acquireRotationLock(keysDir2) {
25666
26028
  }
25667
26029
  function releaseRotationLock(lease) {
25668
26030
  try {
25669
- if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26031
+ if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
25670
26032
  } catch {
25671
26033
  return;
25672
26034
  }
@@ -25717,7 +26079,7 @@ var FileKeyProvider = class {
25717
26079
  #read() {
25718
26080
  let raw;
25719
26081
  try {
25720
- raw = readFileSync4(this.filePath, "utf8");
26082
+ raw = readFileSync5(this.filePath, "utf8");
25721
26083
  } catch (err) {
25722
26084
  if (err.code === "ENOENT") return null;
25723
26085
  throw err instanceof Error ? err : new Error(String(err));
@@ -25756,15 +26118,19 @@ var FileKeyProvider = class {
25756
26118
  * Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
25757
26119
  * Used only for rotation, under the rotation lock — first creation goes
25758
26120
  * through the creation-exclusive path instead.
26121
+ *
26122
+ * Delegated to the shared owner-only write rather than spelled here, so the
26123
+ * create mode this file is published at is the one paths.ts owns and tests
26124
+ * directly. A local copy of the pair was a second place the mode could be
26125
+ * dropped with the trailing tighten still repairing the end state, which is
26126
+ * the shape no assertion on a published file can see. It also picks up that
26127
+ * primitive's per-process tmp name, its stale-tmp sweep, and an exclusive
26128
+ * create that refuses to follow a symlink planted at the tmp path.
25759
26129
  */
25760
26130
  #write(keyring) {
25761
26131
  ensureDataDirSync(this.#keysDir);
25762
- const file2 = this.filePath;
25763
- const tmp = `${file2}.tmp`;
25764
- writeFileSync3(tmp, `${serializeKeyring(keyring)}
25765
- `, { mode: DATA_FILE_MODE });
25766
- renameSync4(tmp, file2);
25767
- tightenFileMode(file2);
26132
+ writeOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
26133
+ `);
25768
26134
  return keyring;
25769
26135
  }
25770
26136
  };
@@ -25774,15 +26140,73 @@ function tightenFileMode(file2) {
25774
26140
  } catch {
25775
26141
  }
25776
26142
  }
25777
- var runSecurity = (args) => execFileSync("/usr/bin/security", args, {
26143
+ var SECURITY_TIMEOUT_MS = 5e3;
26144
+ var runSecurity = (args, stdin) => execFileSync("/usr/bin/security", args, {
25778
26145
  encoding: "utf8",
25779
- stdio: ["ignore", "pipe", "ignore"]
26146
+ input: stdin,
26147
+ timeout: SECURITY_TIMEOUT_MS,
26148
+ // stderr is discarded rather than captured, and that is deliberate: a
26149
+ // captured stream rides out on an execFileSync error's `.stderr`, and the
26150
+ // write paths here carry the keyring. Exit status is the only thing any
26151
+ // branch below reads.
26152
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
25780
26153
  });
25781
26154
  var SECURITY_ITEM_NOT_FOUND = 44;
26155
+ function corruptReason(err) {
26156
+ if (err instanceof SyntaxError) return "malformed JSON";
26157
+ return err instanceof Error ? err.message : "unknown";
26158
+ }
26159
+ function securityFailureMeta(err) {
26160
+ const e = err;
26161
+ const parts = [];
26162
+ if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
26163
+ if (typeof e.signal === "string" && e.signal) parts.push(`signal ${e.signal}`);
26164
+ if (typeof e.code === "string" && e.code) parts.push(e.code);
26165
+ return parts.length > 0 ? parts.join(", ") : "unknown error";
26166
+ }
26167
+ function writeCommand(keyring, update, keychain) {
26168
+ const hex3 = Buffer.from(serializeKeyring(keyring), "utf8").toString("hex");
26169
+ const parts = [
26170
+ "add-generic-password",
26171
+ ...update ? ["-U"] : [],
26172
+ "-s",
26173
+ KEYCHAIN_SERVICE,
26174
+ "-a",
26175
+ KEYCHAIN_ACCOUNT,
26176
+ "-X",
26177
+ hex3
26178
+ ];
26179
+ if (keychain !== void 0) {
26180
+ if (/['\\\n\r\0]/.test(keychain)) {
26181
+ throw new Error(
26182
+ "vault: keychain path contains a quote, backslash, line break or NUL, which security -i cannot carry intact"
26183
+ );
26184
+ }
26185
+ parts.push(`'${keychain}'`);
26186
+ }
26187
+ return `${parts.join(" ")}
26188
+ `;
26189
+ }
25782
26190
  var KeychainKeyProvider = class {
25783
26191
  #keysDir;
25784
26192
  #exec;
25785
- constructor(keysDir2, exec = runSecurity) {
26193
+ #keychain;
26194
+ /**
26195
+ * The trailing keychain argument, or nothing. Every subcommand used here
26196
+ * takes it last (`add-generic-password [keychain]`,
26197
+ * `find-generic-password [keychain...]`), and omitting it means the default
26198
+ * search list. Fixed at construction, so it is built once rather than per
26199
+ * call on the capture path.
26200
+ */
26201
+ #target;
26202
+ /**
26203
+ * `keychain` names the keychain to operate on, as `security`'s trailing
26204
+ * argument. Production passes nothing and gets the user's default keychain,
26205
+ * which is the whole point of the backend. A test driving the REAL binary
26206
+ * passes a throwaway one, because the alternative is writing vault key
26207
+ * material into the developer's own login keychain and leaving it there.
26208
+ */
26209
+ constructor(keysDir2, exec = runSecurity, keychain) {
25786
26210
  if (exec === runSecurity && process.platform !== "darwin") {
25787
26211
  throw new Error(
25788
26212
  `keychain custody is not available on this platform (${process.platform}); use file custody`
@@ -25790,6 +26214,8 @@ var KeychainKeyProvider = class {
25790
26214
  }
25791
26215
  this.#keysDir = keysDir2;
25792
26216
  this.#exec = exec;
26217
+ this.#keychain = keychain;
26218
+ this.#target = keychain === void 0 ? [] : [keychain];
25793
26219
  }
25794
26220
  /** Where a fallback file provider for the same vault would keep its keyring. */
25795
26221
  get keysDir() {
@@ -25828,18 +26254,22 @@ var KeychainKeyProvider = class {
25828
26254
  KEYCHAIN_SERVICE,
25829
26255
  "-a",
25830
26256
  KEYCHAIN_ACCOUNT,
25831
- "-w"
26257
+ "-w",
26258
+ ...this.#target
25832
26259
  ]);
25833
26260
  } catch (err) {
25834
26261
  if (err.status === SECURITY_ITEM_NOT_FOUND) return null;
25835
26262
  throw new Error(
25836
- `vault: keychain read failed (${err instanceof Error ? err.message : String(err)}); refusing to treat the failure as an absent keyring`,
25837
- { cause: err }
26263
+ `vault: keychain read failed (${securityFailureMeta(err)}); refusing to treat the failure as an absent keyring`
25838
26264
  );
25839
26265
  }
25840
26266
  const body = raw.trim();
25841
26267
  if (body.length === 0) return null;
25842
- return parseKeyring(body);
26268
+ try {
26269
+ return parseKeyring(body);
26270
+ } catch (err) {
26271
+ throw new Error(`vault: keychain item is not a usable keyring (${corruptReason(err)})`);
26272
+ }
25843
26273
  }
25844
26274
  /**
25845
26275
  * First mint: a plain `add-generic-password` (no `-U`) fails when an item
@@ -25847,37 +26277,25 @@ var KeychainKeyProvider = class {
25847
26277
  * keyring — the loser re-reads and adopts it instead.
25848
26278
  */
25849
26279
  #create(keyring) {
25850
- const args = [
25851
- "add-generic-password",
25852
- "-s",
25853
- KEYCHAIN_SERVICE,
25854
- "-a",
25855
- KEYCHAIN_ACCOUNT,
25856
- "-w",
25857
- serializeKeyring(keyring)
25858
- ];
26280
+ const line = writeCommand(keyring, false, this.#keychain);
25859
26281
  try {
25860
- this.#exec(args);
26282
+ this.#exec(["-i"], line);
25861
26283
  } catch (err) {
25862
26284
  const winner = this.#read();
25863
26285
  if (winner) return winner;
25864
- throw asError(err);
26286
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
25865
26287
  }
25866
26288
  return keyring;
25867
26289
  }
25868
26290
  // `-U` updates the item in place, deliberately replacing the stored map with
25869
26291
  // one that contains it — used only for rotation, under the rotation lock.
25870
26292
  #replace(keyring) {
25871
- this.#exec([
25872
- "add-generic-password",
25873
- "-U",
25874
- "-s",
25875
- KEYCHAIN_SERVICE,
25876
- "-a",
25877
- KEYCHAIN_ACCOUNT,
25878
- "-w",
25879
- serializeKeyring(keyring)
25880
- ]);
26293
+ const line = writeCommand(keyring, true, this.#keychain);
26294
+ try {
26295
+ this.#exec(["-i"], line);
26296
+ } catch (err) {
26297
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
26298
+ }
25881
26299
  return keyring;
25882
26300
  }
25883
26301
  };
@@ -26387,7 +26805,7 @@ function resolveProviderSafe(resolveProviderFn) {
26387
26805
  }
26388
26806
 
26389
26807
  // ../../packages/plugin-sdk/src/config-inventory.ts
26390
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
26808
+ import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
26391
26809
  import { homedir as homedir2 } from "os";
26392
26810
  import { basename as basename3, join as join10 } from "path";
26393
26811
 
@@ -26963,6 +27381,40 @@ function escapeRegExp2(value) {
26963
27381
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26964
27382
  }
26965
27383
 
27384
+ // ../../packages/detections/src/regex-cache.ts
27385
+ var singles = /* @__PURE__ */ new WeakMap();
27386
+ var keywordLists = /* @__PURE__ */ new WeakMap();
27387
+ var labelLists = /* @__PURE__ */ new WeakMap();
27388
+ function listCache(kind) {
27389
+ return kind === "keyword" ? keywordLists : labelLists;
27390
+ }
27391
+ function memoizedRegExp(owner, build) {
27392
+ const cached2 = singles.get(owner);
27393
+ if (cached2 !== void 0) {
27394
+ cached2.lastIndex = 0;
27395
+ return cached2;
27396
+ }
27397
+ const compiled = build();
27398
+ singles.set(owner, compiled);
27399
+ return compiled;
27400
+ }
27401
+ function memoizedRegExpList(kind, owner, build) {
27402
+ const cache = listCache(kind);
27403
+ const cached2 = cache.get(owner);
27404
+ if (cached2 !== void 0) {
27405
+ if (cached2.stateful) {
27406
+ for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
27407
+ }
27408
+ return cached2.entries;
27409
+ }
27410
+ const entries = build();
27411
+ cache.set(owner, {
27412
+ entries,
27413
+ stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
27414
+ });
27415
+ return entries;
27416
+ }
27417
+
26966
27418
  // ../../packages/detections/src/matchers/limits.ts
26967
27419
  var MAX_MATCHES_PER_RULE = 1e4;
26968
27420
  var MAX_REGEX_INPUT_LENGTH = 2e5;
@@ -26973,10 +27425,17 @@ var KeywordMatcher2 = class {
26973
27425
  if (rule.matcher.type !== "keyword") return [];
26974
27426
  const { keywords, caseSensitive } = rule.matcher;
26975
27427
  const spans = [];
26976
- for (const kw of keywords) {
26977
- if (kw.length === 0) continue;
27428
+ const compiled = memoizedRegExpList(
27429
+ "keyword",
27430
+ rule.matcher,
27431
+ () => keywords.map((kw) => {
27432
+ if (kw.length === 0) return void 0;
27433
+ return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
27434
+ })
27435
+ );
27436
+ for (const re of compiled) {
27437
+ if (re === void 0) continue;
26978
27438
  if (spans.length >= MAX_MATCHES_PER_RULE) break;
26979
- const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
26980
27439
  let m;
26981
27440
  while ((m = re.exec(text)) !== null) {
26982
27441
  spans.push({ start: m.index, end: m.index + m[0].length });
@@ -26992,7 +27451,10 @@ var RegexMatcher2 = class {
26992
27451
  match(text, rule) {
26993
27452
  if (rule.matcher.type !== "regex") return [];
26994
27453
  const { pattern, flags, captureGroup } = rule.matcher;
26995
- const re = new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`);
27454
+ const re = memoizedRegExp(
27455
+ rule.matcher,
27456
+ () => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
27457
+ );
26996
27458
  const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
26997
27459
  const spans = [];
26998
27460
  let m;
@@ -27051,6 +27513,10 @@ function luhnCheck(digits) {
27051
27513
  // ../../packages/detections/src/engine.ts
27052
27514
  var keywordMatcher = new KeywordMatcher2();
27053
27515
  var regexMatcher = new RegexMatcher2();
27516
+ var MATCHERS = {
27517
+ keyword: (text, rule) => keywordMatcher.match(text, rule),
27518
+ regex: (text, rule) => regexMatcher.match(text, rule)
27519
+ };
27054
27520
  var packs = /* @__PURE__ */ new Map();
27055
27521
  var POST_VALIDATORS = {
27056
27522
  entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
@@ -27066,8 +27532,7 @@ function passesPostValidators(rule, value) {
27066
27532
  for (const ref of validators) {
27067
27533
  const name = typeof ref === "string" ? ref : ref.name;
27068
27534
  const config2 = typeof ref === "string" ? void 0 : ref.config;
27069
- const validate = POST_VALIDATORS[name];
27070
- if (validate && !validate(value, config2)) return false;
27535
+ if (!POST_VALIDATORS[name](value, config2)) return false;
27071
27536
  }
27072
27537
  return true;
27073
27538
  }
@@ -27100,11 +27565,15 @@ function isCorroborated(candidate, candidates, text) {
27100
27565
  const labels = req.labels;
27101
27566
  if (labels && labels.length > 0) {
27102
27567
  const haystack = text.slice(Math.max(0, winStart), winEnd);
27103
- for (const label of labels) {
27104
- const trimmed = label.trim();
27105
- if (trimmed.length === 0) continue;
27106
- const re = new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
27107
- if (re.test(haystack)) return true;
27568
+ for (const re of memoizedRegExpList(
27569
+ "label",
27570
+ req,
27571
+ () => labels.map((label) => {
27572
+ const trimmed = label.trim();
27573
+ return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
27574
+ })
27575
+ )) {
27576
+ if (re?.test(haystack)) return true;
27108
27577
  }
27109
27578
  }
27110
27579
  return false;
@@ -27124,14 +27593,7 @@ function scan(text, rules, context) {
27124
27593
  const candidates = [];
27125
27594
  for (const rule of ruleset) {
27126
27595
  if (!ruleApplies(rule, extension)) continue;
27127
- let spans;
27128
- if (rule.matcher.type === "keyword") {
27129
- spans = keywordMatcher.match(text, rule);
27130
- } else if (rule.matcher.type === "regex") {
27131
- spans = regexMatcher.match(text, rule);
27132
- } else {
27133
- continue;
27134
- }
27596
+ const spans = MATCHERS[rule.matcher.type](text, rule);
27135
27597
  for (const span of spans) {
27136
27598
  const rawMatch = text.slice(span.start, span.end);
27137
27599
  if (!passesPostValidators(rule, rawMatch)) continue;
@@ -27287,6 +27749,7 @@ var CONFIG_POSTURE_RULES = [
27287
27749
  ];
27288
27750
 
27289
27751
  // ../../packages/detections/src/security/redos-probe.ts
27752
+ var BUDGET_MS = 100;
27290
27753
  var EXPONENTIAL_UNITS = [
27291
27754
  "a",
27292
27755
  "0",
@@ -27310,6 +27773,8 @@ var EXPONENTIAL_PROBES = EXPONENTIAL_UNITS.flatMap(
27310
27773
  var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].map(
27311
27774
  (unit) => unit.repeat(1e4).slice(0, 4e4) + "!"
27312
27775
  );
27776
+ var CPU_CORROBORATION_SHARE = 0.2;
27777
+ var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
27313
27778
 
27314
27779
  // ../../rules/code-flaws/auth-jwt-no-verify.json
27315
27780
  var auth_jwt_no_verify_default = {
@@ -29386,7 +29851,7 @@ function scanText(text, ruleVersions) {
29386
29851
  }
29387
29852
 
29388
29853
  // ../../packages/plugin-sdk/src/repo.ts
29389
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
29854
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
29390
29855
  import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
29391
29856
  function resolveRepoIdentity(cwd) {
29392
29857
  try {
@@ -29449,7 +29914,7 @@ function resolveGitContext(root) {
29449
29914
  }
29450
29915
  function safeRead(path) {
29451
29916
  try {
29452
- return readFileSync5(path, "utf8");
29917
+ return readFileSync6(path, "utf8");
29453
29918
  } catch {
29454
29919
  return void 0;
29455
29920
  }
@@ -29507,6 +29972,11 @@ import { existsSync as existsSync7 } from "fs";
29507
29972
  import { fileURLToPath } from "url";
29508
29973
  import { Worker } from "worker_threads";
29509
29974
 
29975
+ // ../../packages/plugin-sdk/src/ignore-layers.ts
29976
+ var import_ignore = __toESM(require_ignore(), 1);
29977
+ import { readFileSync as readFileSync8 } from "fs";
29978
+ import { join as join11 } from "path";
29979
+
29510
29980
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
29511
29981
  import { arch, hostname as hostname4, platform, release } from "os";
29512
29982
  function resolveInventoryContext(input) {
@@ -29537,17 +30007,16 @@ function resolveInventoryContext(input) {
29537
30007
  }
29538
30008
 
29539
30009
  // ../../packages/plugin-sdk/src/nudge.ts
29540
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
29541
- import { join as join11 } from "path";
30010
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
30011
+ import { join as join12 } from "path";
29542
30012
 
29543
30013
  // ../../packages/plugin-sdk/src/paths.ts
29544
30014
  import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
29545
30015
  import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
29546
30016
 
29547
30017
  // ../../packages/plugin-sdk/src/project-files.ts
29548
- var import_ignore = __toESM(require_ignore(), 1);
29549
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
29550
- import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
30018
+ import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
30019
+ import { basename as basename5, join as join13 } from "path";
29551
30020
 
29552
30021
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
29553
30022
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -29582,8 +30051,8 @@ import { randomUUID as randomUUID14 } from "crypto";
29582
30051
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
29583
30052
 
29584
30053
  // ../../packages/plugin-sdk/src/throttle.ts
29585
- import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
29586
- import { join as join13 } from "path";
30054
+ import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30055
+ import { join as join14 } from "path";
29587
30056
 
29588
30057
  // ../../packages/plugin-sdk/src/tokenize.ts
29589
30058
  function redactedPlaceholder(category) {
@@ -29646,6 +30115,8 @@ var SecretVaultGlue = class {
29646
30115
  async tokenizeText(text, opts) {
29647
30116
  try {
29648
30117
  const findings = opts?.findings ?? this.#selfScan(text);
30118
+ const reversible = opts?.reversible;
30119
+ const keeps = (finding) => reversible === void 0 || reversible.has(finding);
29649
30120
  if (findings === null) return { text: "[REDACTED]", pointers: [], degraded: [] };
29650
30121
  if (findings.length === 0) return { text, pointers: [], degraded: [] };
29651
30122
  const groups = groupSpans(text, findings);
@@ -29662,6 +30133,8 @@ var SecretVaultGlue = class {
29662
30133
  } else if (original !== finding.rawMatch) {
29663
30134
  replacement = redactedPlaceholder(group.category);
29664
30135
  degraded.unshift({ category: group.category });
30136
+ } else if (!keeps(finding)) {
30137
+ replacement = redactedPlaceholder(finding.category);
29665
30138
  } else {
29666
30139
  replacement = await this.tokenizeValue(finding.rawMatch, {
29667
30140
  ruleId: finding.ruleId,
@@ -29892,6 +30365,8 @@ var StandaloneDataGateway = class {
29892
30365
  db;
29893
30366
  // Kept for the fingerprint key lookup (exception.key lives beside the store).
29894
30367
  dataDir;
30368
+ // One notice per gateway — see warnRulesetDiscarded.
30369
+ warnedRulesetDiscarded = false;
29895
30370
  constructor(dataDir2, detections = [], meta3) {
29896
30371
  this.db = openLocalDatabase(dataDir2);
29897
30372
  this.dataDir = dataDir2;
@@ -30011,28 +30486,69 @@ var StandaloneDataGateway = class {
30011
30486
  // - ANY invalid rule among enabled packs (all-invalid, partial corruption,
30012
30487
  // or a single malformed entry) → undefined → bundled fallback. Serving a
30013
30488
  // reduced "complete" set would silently drop exactly the corrupted rules
30014
- // with no fallback; the bundled packs are a superset, so falling back
30015
- // never loses coverage. Steady-state installed rules are all valid
30016
- // (generated + Zod-checked), so this only fires on a genuinely
30017
- // malformed/foreign store;
30489
+ // with no fallback. The bundled packs are a superset of AKA's OWN packs,
30490
+ // so falling back never loses coverage there but they contain no
30491
+ // pulled or custom pack, so for those this trades a partial ruleset for
30492
+ // none of them plus the loss of every pack's per-detection enforcement
30493
+ // action. That is deliberate (a store this machine cannot fully validate
30494
+ // is not authoritative), and it is why the cost of REJECTING a rule
30495
+ // matters: `Rule` is strict, so one unrecognized key in one custom rule
30496
+ // reaches this branch, not just a genuinely malformed or foreign store.
30497
+ // `installed-packs.test.ts` pins that per-rule counting;
30018
30498
  // - enabled packs that produce ZERO rules with no invalids (e.g. every
30019
30499
  // enabled pack's rules_json is `[]`) → undefined → bundled fallback: an
30020
30500
  // enabled pack contributing nothing is untrustworthy, not a real
30021
30501
  // "detect nothing" (that is expressed by disabling packs, handled above);
30022
30502
  // - otherwise → the enabled packs' validated rules, marked complete.
30503
+ /**
30504
+ * The discard above is the one ruleset decision this gateway reaches on its
30505
+ * own, and it is the most expensive one here: ONE rejected entry costs the
30506
+ * user every custom rule and every per-detection enforcement action, replaced
30507
+ * by bundled packs that contain neither. Nothing else reports it — a hook is a
30508
+ * short-lived process whose stderr is the only channel it has — so name what
30509
+ * was rejected and where the rest of the list lives.
30510
+ *
30511
+ * Unlike a quarantine verdict this caches nothing: the rejection is re-derived
30512
+ * from the store on every run, so the recovery is to fix or reinstall the pack,
30513
+ * and no line here may offer a command that clears a stored verdict.
30514
+ *
30515
+ * Written at most once per gateway — a second getPolicyBundle() in the same
30516
+ * process would re-report the same finding.
30517
+ */
30518
+ warnRulesetDiscarded(snapshot) {
30519
+ if (this.warnedRulesetDiscarded) return;
30520
+ this.warnedRulesetDiscarded = true;
30521
+ const listed = snapshot.rejectedRules.map((r) => `${r.pack}${r.ruleId === null ? "" : ` "${r.ruleId}"`} (${r.reason})`).join(", ");
30522
+ const undisclosed = snapshot.invalidRules - snapshot.rejectedRules.length;
30523
+ const more = undisclosed > 0 ? `, and ${String(undisclosed)} more` : "";
30524
+ process.stderr.write(
30525
+ `[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\`
30526
+ `
30527
+ );
30528
+ }
30023
30529
  installedScanRules() {
30024
30530
  try {
30025
30531
  const snapshot = this.db.installedPacks.installedRuleset();
30026
30532
  if (snapshot.installedPacks === 0) return void 0;
30027
30533
  if (snapshot.enabledPacks === 0) {
30028
- return { rules: [], ruleActions: /* @__PURE__ */ new Map(), ruleVersions: /* @__PURE__ */ new Map(), complete: true };
30534
+ return {
30535
+ rules: [],
30536
+ ruleActions: /* @__PURE__ */ new Map(),
30537
+ ruleVersions: /* @__PURE__ */ new Map(),
30538
+ reversibleRules: /* @__PURE__ */ new Set(),
30539
+ complete: true
30540
+ };
30541
+ }
30542
+ if (snapshot.invalidRules > 0) {
30543
+ this.warnRulesetDiscarded(snapshot);
30544
+ return void 0;
30029
30545
  }
30030
- if (snapshot.invalidRules > 0) return void 0;
30031
30546
  if (snapshot.rules.length === 0) return void 0;
30032
30547
  return {
30033
30548
  rules: snapshot.rules,
30034
30549
  ruleActions: snapshot.ruleActions,
30035
30550
  ruleVersions: snapshot.ruleVersions,
30551
+ reversibleRules: snapshot.reversibleRules,
30036
30552
  complete: true
30037
30553
  };
30038
30554
  } catch {
@@ -30060,6 +30576,11 @@ var StandaloneDataGateway = class {
30060
30576
  return {
30061
30577
  version: "local",
30062
30578
  policies: [...policies, ...rulePolicies],
30579
+ // The reversibility half of each pack's assignment. Emitted only under the
30580
+ // authoritative installed snapshot, exactly like rulePolicies above: the
30581
+ // bundled-packs fallback carries no per-pack assignment, so it carries no
30582
+ // reversibility either and every redaction there stays one-way.
30583
+ reversibleRuleIds: installed ? [...installed.reversibleRules] : [],
30063
30584
  rules: installed ? installed.rules : [],
30064
30585
  ...installed ? { rulesComplete: true } : {},
30065
30586
  ...installed ? { ruleVersions: Object.fromEntries(installed.ruleVersions) } : {},
@@ -30078,29 +30599,32 @@ var StandaloneDataGateway = class {
30078
30599
  return this.db.exceptions.recordBlocked(entry);
30079
30600
  }
30080
30601
  // Retention sweep over TERMINAL exception rows (revoked / expired / budget
30081
- // exhausted) — standalone-only store maintenance, invoked from SessionStart,
30082
- // not part of the DataGateway port. Active grants are never touched.
30602
+ // exhausted) — local-store maintenance, invoked from SessionStart through the
30603
+ // LocalStoreMaintenance capability rather than the DataGateway port. Active
30604
+ // grants are never touched.
30083
30605
  sweepTerminalExceptions(retentionMs) {
30084
30606
  return this.db.exceptions.sweepTerminal(retentionMs);
30085
30607
  }
30086
- // The warn-era enforcement cap, standalone-only store maintenance invoked
30087
- // from SessionStart, not part of the DataGateway port. Returns the number
30088
- // of block/redact rows capped to warn (0 for a redact-policy store or an
30089
- // already-capped one).
30608
+ // The warn-era enforcement cap — local-store maintenance, invoked from
30609
+ // SessionStart through the LocalStoreMaintenance capability rather than
30610
+ // the DataGateway port. Returns the number of block/redact rows capped to
30611
+ // warn (0 for a redact-policy store or an already-capped one).
30090
30612
  capWarnEraEnforcement(policyMode) {
30091
30613
  const { capped } = capWarnEraEnforcementOnce(this.db, policyMode, this.dataDir);
30092
30614
  return { capped };
30093
30615
  }
30094
30616
  // One project-file scan → the local project_file tree (one transaction inside
30095
- // the LocalDatabase, fail-open there). Like the sweep above, this is
30096
- // NOT part of the DataGateway port: the file tree is a local-store read model.
30617
+ // the LocalDatabase, fail-open there). Like the sweep above, this is reached
30618
+ // through the LocalStoreMaintenance capability rather than the DataGateway
30619
+ // port: the file tree is a local-store read model.
30097
30620
  recordProjectFiles(projectId, scan2) {
30098
30621
  this.db.recordProjectFiles(projectId, scan2);
30099
30622
  return Promise.resolve();
30100
30623
  }
30101
30624
  // Fold ghost source_project rows minted by the pre-worktree-fix resolver
30102
- // (checkout-path identities) into the repo's canonical row. Standalone-only
30103
- // store maintenance, invoked from SessionStart. Fail-open in the store.
30625
+ // (checkout-path identities) into the repo's canonical row. Local-store
30626
+ // maintenance, invoked from SessionStart through the LocalStoreMaintenance
30627
+ // capability. Fail-open in the store.
30104
30628
  reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
30105
30629
  this.db.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
30106
30630
  return Promise.resolve();
@@ -30111,10 +30635,10 @@ var StandaloneDataGateway = class {
30111
30635
  * executing the plugin generation they started with (Claude Code caches
30112
30636
  * plugin versions), and the write gate makes their installed-pack writes
30113
30637
  * silent no-ops — this is the one-line nudge telling the user WHY, and that
30114
- * a restart picks the newer plugin up. Standalone-only, invoked from
30115
- * SessionStart, not part of the DataGateway port. Fail-open: any error →
30116
- * null (no notice), and unparseable versions compare equal so garbage can
30117
- * never fire it.
30638
+ * a restart picks the newer plugin up. Local-store maintenance, invoked
30639
+ * from SessionStart through the LocalStoreMaintenance capability rather
30640
+ * than the DataGateway port. Fail-open: any error → null (no notice), and
30641
+ * unparseable versions compare equal so garbage can never fire it.
30118
30642
  */
30119
30643
  staleBinaryNotice(currentVersion) {
30120
30644
  try {
@@ -30192,7 +30716,8 @@ var StandaloneDataGateway = class {
30192
30716
 
30193
30717
  // ../../packages/plugin-runtime/src/resolve.ts
30194
30718
  var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
30195
- function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFactory) {
30719
+ var defaultGatewayFactory = standaloneGatewayFactory;
30720
+ function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
30196
30721
  return gatewayFactory(config2, meta3);
30197
30722
  }
30198
30723
 
@@ -30201,15 +30726,15 @@ import { randomUUID as randomUUID16 } from "crypto";
30201
30726
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
30202
30727
 
30203
30728
  // src/remediation/redact.ts
30204
- import { readFileSync as readFileSync10, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
30205
- import { isAbsolute as isAbsolute2, relative as relative2, resolve } from "path";
30729
+ import { readFileSync as readFileSync11, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
30730
+ import { isAbsolute as isAbsolute2, relative, resolve } from "path";
30206
30731
 
30207
30732
  // src/history/transcripts.ts
30208
- import { readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
30733
+ import { readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
30209
30734
  import { homedir as homedir3 } from "os";
30210
- import { join as join14 } from "path";
30735
+ import { join as join15 } from "path";
30211
30736
  function transcriptsDir(home) {
30212
- return join14(home ?? homedir3(), ".claude", "projects");
30737
+ return join15(home ?? homedir3(), ".claude", "projects");
30213
30738
  }
30214
30739
  function isRecord(value) {
30215
30740
  return typeof value === "object" && value !== null;
@@ -30426,7 +30951,7 @@ function realPathOrNull(path) {
30426
30951
  function isWithinRoot(realTarget, root) {
30427
30952
  const realRoot = realPathOrNull(root);
30428
30953
  if (realRoot === null) return false;
30429
- const rel = relative2(realRoot, realTarget);
30954
+ const rel = relative(realRoot, realTarget);
30430
30955
  return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
30431
30956
  }
30432
30957
  function resolveRedactableArtifact(filePath, scope) {
@@ -30440,15 +30965,15 @@ import { createHash as createHash5 } from "crypto";
30440
30965
  import {
30441
30966
  closeSync as closeSync2,
30442
30967
  fstatSync,
30443
- mkdirSync as mkdirSync5,
30968
+ mkdirSync as mkdirSync4,
30444
30969
  openSync as openSync2,
30445
- readFileSync as readFileSync11,
30970
+ readFileSync as readFileSync12,
30446
30971
  readSync,
30447
30972
  writeFileSync as writeFileSync8
30448
30973
  } from "fs";
30449
- import { join as join15 } from "path";
30974
+ import { join as join16 } from "path";
30450
30975
  function offsetsDir(dataDir2) {
30451
- return join15(dataDir2, "usage-offsets");
30976
+ return join16(dataDir2, "usage-offsets");
30452
30977
  }
30453
30978
  var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
30454
30979
  function safeSessionId(sessionId) {
@@ -30458,11 +30983,11 @@ function safeSessionId(sessionId) {
30458
30983
  return createHash5("sha256").update(sessionId).digest("hex");
30459
30984
  }
30460
30985
  function offsetPath(dataDir2, sessionId) {
30461
- return join15(offsetsDir(dataDir2), safeSessionId(sessionId));
30986
+ return join16(offsetsDir(dataDir2), safeSessionId(sessionId));
30462
30987
  }
30463
30988
  function readOffset(dataDir2, sessionId) {
30464
30989
  try {
30465
- const raw = readFileSync11(offsetPath(dataDir2, sessionId), "utf8");
30990
+ const raw = readFileSync12(offsetPath(dataDir2, sessionId), "utf8");
30466
30991
  const parsed = JSON.parse(raw);
30467
30992
  if (typeof parsed === "object" && parsed !== null) {
30468
30993
  const rec = parsed;
@@ -30476,7 +31001,7 @@ function readOffset(dataDir2, sessionId) {
30476
31001
  }
30477
31002
  function writeOffset(dataDir2, sessionId, value) {
30478
31003
  try {
30479
- mkdirSync5(offsetsDir(dataDir2), { recursive: true, mode: DATA_DIR_MODE });
31004
+ mkdirSync4(offsetsDir(dataDir2), { recursive: true, mode: DATA_DIR_MODE });
30480
31005
  const payload = value.lastPromptId !== void 0 ? { offset: value.offset, lastPromptId: value.lastPromptId } : { offset: value.offset };
30481
31006
  writeFileSync8(offsetPath(dataDir2, sessionId), JSON.stringify(payload), {
30482
31007
  mode: DATA_FILE_MODE
@@ -30520,7 +31045,7 @@ function readTail(transcriptPath, startOffset) {
30520
31045
  }
30521
31046
 
30522
31047
  // src/history/tail-scrub.ts
30523
- import { readFileSync as readFileSync12, renameSync as renameSync6, rmSync as rmSync6, statSync as statSync7, writeFileSync as writeFileSync9 } from "fs";
31048
+ import { readFileSync as readFileSync13, renameSync as renameSync6, rmSync as rmSync6, statSync as statSync7, writeFileSync as writeFileSync9 } from "fs";
30524
31049
  var DEFAULT_MAX_SCRUB_BYTES = 32 * 1024 * 1024;
30525
31050
  async function scrubTranscriptTail(filePath, deps) {
30526
31051
  try {
@@ -30528,7 +31053,7 @@ async function scrubTranscriptTail(filePath, deps) {
30528
31053
  if (realPath === null) return null;
30529
31054
  const statBefore = statSync7(realPath);
30530
31055
  if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
30531
- const content = readFileSync12(realPath, "utf8");
31056
+ const content = readFileSync13(realPath, "utf8");
30532
31057
  const lines = content.split("\n");
30533
31058
  let rewritten = 0;
30534
31059
  for (const [i, line] of lines.entries()) {
@@ -30579,7 +31104,7 @@ async function reconcileSession(gateway, sessionId, records, opts = {}) {
30579
31104
  if (anchor === void 0) return { llmCalls: 0, skipped: 0, lastPromptId };
30580
31105
  const ctx = resolveInventoryContext({
30581
31106
  cwd: anchor.cwd ?? NO_PROJECT_CWD,
30582
- tool: "claude-code",
31107
+ tool: SOURCE_TOOL.ClaudeCode,
30583
31108
  harnessVersion: anchor.version,
30584
31109
  harnessInterface: anchor.entrypoint
30585
31110
  });
@@ -30720,7 +31245,7 @@ function buildSessionRoot(sessionId, ctx, resolved, anchor, provider) {
30720
31245
  if (typeof osVersion === "string") attributes.os_version = osVersion;
30721
31246
  if (anchor.version !== void 0) attributes.harness_version = anchor.version;
30722
31247
  attributes.provider = provider;
30723
- attributes.harness = harnessFromTool("claude-code");
31248
+ attributes.harness = harnessFromTool(SOURCE_TOOL.ClaudeCode);
30724
31249
  if (anchor.cwd !== void 0) attributes.cwd = anchor.cwd;
30725
31250
  if (anchor.version !== void 0) attributes.version = anchor.version;
30726
31251
  const hostName = ctx.host?.attributes.host_name;