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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -492,9 +492,9 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // src/apply-suppressions.ts
495
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
495
+ import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
496
496
  import { userInfo } from "os";
497
- import { dirname as dirname6, join as join17 } from "path";
497
+ import { dirname as dirname6, join as join18 } from "path";
498
498
  import { fileURLToPath as fileURLToPath4 } from "url";
499
499
 
500
500
  // ../../packages/persistence/src/database.ts
@@ -587,6 +587,10 @@ var SQLITE_MIGRATIONS = [
587
587
  {
588
588
  tag: "0020_secret_vault_pagination_indexes",
589
589
  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');"
590
+ },
591
+ {
592
+ tag: "0021_finding_resolution_resolved_at_index",
593
+ sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
590
594
  }
591
595
  ];
592
596
 
@@ -15302,6 +15306,47 @@ function date4(params) {
15302
15306
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
15303
15307
  config(en_default());
15304
15308
 
15309
+ // ../../packages/schema/src/zod/harness-map.ts
15310
+ var HARNESS = {
15311
+ ClaudeCode: "claudecode",
15312
+ Cursor: "cursor",
15313
+ Copilot: "copilot",
15314
+ Codex: "codex",
15315
+ Antigravity: "antigravity",
15316
+ Windsurf: "windsurf",
15317
+ ClaudeDesktop: "claudedesktop",
15318
+ ChatGpt: "chatgpt",
15319
+ ClaudeAi: "claudeai",
15320
+ Api: "api"
15321
+ };
15322
+ var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
15323
+ var SOURCE_TOOL = {
15324
+ ClaudeCode: "claude-code",
15325
+ ClaudeDesktop: "claude-desktop",
15326
+ Cursor: "cursor",
15327
+ ChatGpt: "chatgpt",
15328
+ ClaudeAi: "claude-ai",
15329
+ Copilot: "github-copilot",
15330
+ Codex: "codex",
15331
+ Antigravity: "antigravity",
15332
+ // No harness counterpart, deliberately: the CLI's own captures and a capture
15333
+ // whose tool could not be identified both render through the read side's
15334
+ // miss path rather than as a harness of their own.
15335
+ Cli: "cli",
15336
+ Unknown: "unknown"
15337
+ };
15338
+ var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
15339
+ var TOOL_TO_HARNESS = {
15340
+ [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
15341
+ [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
15342
+ [SOURCE_TOOL.Copilot]: HARNESS.Copilot,
15343
+ [SOURCE_TOOL.Cursor]: HARNESS.Cursor,
15344
+ [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
15345
+ [SOURCE_TOOL.Codex]: HARNESS.Codex,
15346
+ [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
15347
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
15348
+ };
15349
+
15305
15350
  // ../../packages/schema/src/zod/finding.ts
15306
15351
  var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
15307
15352
  var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
@@ -15324,21 +15369,22 @@ var Finding = external_exports.object({
15324
15369
  }).meta({ id: "Finding" });
15325
15370
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15326
15371
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15327
- var FindingProvider = external_exports.enum([
15328
- "claudecode",
15329
- "claudedesktop",
15330
- "cursor",
15331
- "copilot",
15332
- "chatgpt",
15333
- "claudeai",
15334
- "codex",
15335
- "antigravity",
15336
- "api"
15372
+ var FindingProvider = Harness.extract([
15373
+ "ClaudeCode",
15374
+ "ClaudeDesktop",
15375
+ "Cursor",
15376
+ "Copilot",
15377
+ "ChatGpt",
15378
+ "ClaudeAi",
15379
+ "Codex",
15380
+ "Antigravity",
15381
+ "Api"
15337
15382
  ]).meta({ id: "FindingProvider" });
15338
15383
  var FindingCategory = external_exports.enum([
15339
15384
  "secret",
15340
15385
  "pii",
15341
15386
  "source_code",
15387
+ "code_flaw",
15342
15388
  "external_share",
15343
15389
  "mcp_server",
15344
15390
  "customer_data",
@@ -15518,6 +15564,7 @@ var FindingInstanceDetail = FindingInstance.extend({
15518
15564
  policy: FindingPolicyRef
15519
15565
  }).meta({ id: "FindingInstanceDetail" });
15520
15566
  var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15567
+ var MAX_FLAT_FINDINGS_LIMIT = 200;
15521
15568
  var ListFindingInstancesQuery = external_exports.object({
15522
15569
  severity: external_exports.array(Severity).optional(),
15523
15570
  // Rule ids, the same vocabulary the grouped list's `subtype` carries.
@@ -15537,7 +15584,7 @@ var ListFindingInstancesQuery = external_exports.object({
15537
15584
  q: external_exports.string().optional(),
15538
15585
  sessionId: external_exports.string().optional(),
15539
15586
  from: external_exports.iso.datetime().optional(),
15540
- limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15587
+ limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
15541
15588
  cursor: external_exports.string().optional()
15542
15589
  });
15543
15590
  var ListFindingInstancesResponse = external_exports.object({
@@ -15599,30 +15646,6 @@ var ListFindingLocationsResponse = external_exports.object({
15599
15646
  hasMore: external_exports.boolean()
15600
15647
  }).meta({ id: "ListFindingLocationsResponse" });
15601
15648
 
15602
- // ../../packages/schema/src/zod/harness-map.ts
15603
- var Harness = external_exports.enum([
15604
- "claudecode",
15605
- "cursor",
15606
- "copilot",
15607
- "codex",
15608
- "antigravity",
15609
- "windsurf",
15610
- "claudedesktop",
15611
- "chatgpt",
15612
- "claudeai",
15613
- "api"
15614
- ]).meta({ id: "Harness" });
15615
- var TOOL_TO_HARNESS = {
15616
- "claude-code": "claudecode",
15617
- "claude-desktop": "claudedesktop",
15618
- "github-copilot": "copilot",
15619
- cursor: "cursor",
15620
- chatgpt: "chatgpt",
15621
- codex: "codex",
15622
- antigravity: "antigravity",
15623
- "claude-ai": "claudeai"
15624
- };
15625
-
15626
15649
  // ../../packages/schema/src/zod/meta.ts
15627
15650
  var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
15628
15651
  var AuditEventType = external_exports.enum([
@@ -16075,691 +16098,37 @@ var ActivityOverviewResponse = external_exports.object({
16075
16098
  sessions: ListActivitySessionsResponse
16076
16099
  }).meta({ id: "ActivityOverviewResponse" });
16077
16100
 
16078
- // ../../packages/schema/src/zod/event.ts
16079
- var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
16080
- var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
16081
- var SourceTool = external_exports.enum([
16082
- "claude-code",
16083
- "claude-desktop",
16084
- "cursor",
16085
- "chatgpt",
16086
- "claude-ai",
16087
- "github-copilot",
16088
- "codex",
16089
- "antigravity",
16090
- "cli",
16091
- "unknown"
16092
- ]).meta({ id: "SourceTool" });
16093
- var EventMetadata = external_exports.object({
16094
- sessionId: external_exports.string().optional(),
16095
- repo: external_exports.string().optional(),
16096
- filePath: external_exports.string().optional(),
16097
- // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
16098
- // set by the tool-scanning hooks. The tool NAME only — never the tool's
16099
- // arguments or output, which can carry the very value a finding masked
16100
- // (metadata is stored unredacted). Gives findings on non-file captures a
16101
- // display location ("via Bash") when no filePath exists.
16102
- toolName: external_exports.string().optional(),
16103
- // Set (true) by the worktree scanner when the file is excluded by the
16104
- // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
16105
- // generated code can leak real secrets — but the provenance is recorded so
16106
- // policy/dashboards can treat those findings as informational rather than
16107
- // blocking. Omitted (not false) for tracked files and non-scan events.
16108
- gitignored: external_exports.boolean().optional(),
16109
- // Set (true) ONLY when the event's `content` is the COMPLETE file at
16110
- // capture time (a worktree scan reading from disk). Hook-captured edits
16111
- // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
16112
- // this. The resolver-on-ingest keys its fixed-at-source dropout
16113
- // diff on this marker: only a whole-file snapshot can prove a previously
16114
- // open finding is gone; a fragment's absence proves nothing (the secret
16115
- // may live outside the hunk). Omitted (not false) for fragments and
16116
- // non-scan events, so pre-marker clients safely default to the
16117
- // non-authoritative path.
16118
- wholeFile: external_exports.boolean().optional(),
16119
- model: external_exports.string().optional(),
16120
- turnIndex: external_exports.number().int().nonnegative().optional(),
16121
- // Distributed-tracing correlation. `correlationId` ties a recorded event back
16122
- // to the request that captured/ingested it (a UUID, generated independently of
16123
- // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
16124
- // originating span when telemetry is enabled. Both optional + backward
16125
- // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
16126
- correlationId: external_exports.uuid().optional(),
16127
- traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
16128
- // Ids of the detection exceptions that downgraded findings in this capture
16129
- // to 'allow' — the enforcement audit trail's link back to the grant that
16130
- // authorized the bypass. Absent on captures where no exception applied.
16131
- exceptionIds: external_exports.array(external_exports.guid()).optional()
16132
- }).meta({ id: "EventMetadata" });
16133
- var Event = external_exports.object({
16134
- id: external_exports.guid(),
16135
- sourceTool: SourceTool,
16136
- kind: EventKind,
16137
- occurredAt: external_exports.iso.datetime(),
16138
- contentHash: external_exports.string(),
16139
- content: external_exports.string(),
16140
- metadata: EventMetadata.optional()
16141
- }).meta({ id: "Event" });
16142
- var IngestEvent = Event.meta({ id: "IngestEvent" });
16143
- var IngestBatch = external_exports.object({
16144
- events: external_exports.array(IngestEvent).min(1).max(100),
16145
- // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
16146
- // additionally rejects any event whose contentHash the store has already
16147
- // recorded — for re-runnable bulk ingest (worktree scan, transcript
16148
- // backfill), where a re-run mints fresh event ids for identical content and
16149
- // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
16150
- // two genuinely separate prompts can be byte-identical and both belong on
16151
- // the timeline.
16152
- dedupe: external_exports.literal("content-hash").optional()
16153
- }).meta({ id: "IngestBatch" });
16154
-
16155
- // ../../packages/schema/src/zod/inventory.ts
16156
- var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16157
- var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16158
- var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16159
- var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16160
- var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16161
- var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16162
- var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16163
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16164
- var AccessCounts = external_exports.object({
16165
- open: external_exports.number().int().nonnegative(),
16166
- approved: external_exports.number().int().nonnegative(),
16167
- blocked: external_exports.number().int().nonnegative(),
16168
- total: external_exports.number().int().nonnegative()
16169
- }).meta({ id: "AccessCounts" });
16170
- var AssetSummary = external_exports.object({
16171
- id: external_exports.string(),
16172
- type: AssetType,
16173
- name: external_exports.string(),
16174
- sub: external_exports.string(),
16175
- flags: external_exports.array(Flag),
16176
- /** MCP servers only — omitted for all other types. */
16177
- trust: TrustLevel.optional()
16178
- }).meta({ id: "AssetSummary" });
16179
- var ProjectSummary = external_exports.object({
16180
- id: external_exports.string(),
16181
- name: external_exports.string(),
16182
- repo: external_exports.string(),
16183
- visibility: Visibility,
16184
- language: external_exports.string(),
16185
- policyDefault: AccessLevel,
16186
- updatedAt: external_exports.iso.datetime(),
16187
- accessCounts: AccessCounts,
16188
- findingsCount: external_exports.number().int().nonnegative()
16189
- }).meta({ id: "ProjectSummary" });
16190
- var HarnessCategory = external_exports.object({
16191
- /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16192
- type: AssetType,
16193
- assets: external_exports.array(AssetSummary)
16101
+ // ../../packages/schema/src/zod/config-inventory.ts
16102
+ var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16103
+ var SkillScanEntry = external_exports.object({
16104
+ name: external_exports.string().min(1),
16105
+ // The identity source: a marketplace repo for plugin skills (e.g.
16106
+ // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16107
+ // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16108
+ // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16109
+ source: external_exports.string().min(1),
16110
+ scope: ConfigScope,
16111
+ pluginName: external_exports.string().optional(),
16112
+ // Volatile — rides the attribute bag, never the identity hash.
16113
+ version: external_exports.string().optional(),
16114
+ description: external_exports.string().optional(),
16115
+ // Skill directory mtime (ISO) the "updated Nd ago" freshness signal.
16116
+ updatedAt: external_exports.iso.datetime().optional(),
16117
+ // Filesystem path — the promoted inventory `location` column.
16118
+ location: external_exports.string().optional()
16194
16119
  });
16195
- var HarnessSummary = external_exports.object({
16196
- id: HarnessId,
16197
- label: external_exports.string(),
16198
- kind: external_exports.string(),
16199
- version: external_exports.string(),
16200
- sessions: external_exports.number().int().nonnegative(),
16201
- assetCount: external_exports.number().int().nonnegative(),
16202
- flagCount: external_exports.number().int().nonnegative(),
16203
- projects: external_exports.array(ProjectSummary),
16204
- categories: external_exports.array(HarnessCategory)
16205
- }).meta({ id: "HarnessSummary" });
16206
- var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16207
- var AssetGroup = external_exports.object({
16208
- /** Group key — never project (enforced at service layer). */
16209
- type: AssetType,
16210
- total: external_exports.number().int().nonnegative(),
16211
- /**
16212
- * MCP group only — omitted for all other types.
16213
- * Partial: only TrustLevel keys with non-zero counts are included.
16214
- * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16215
- */
16216
- trustRollup: external_exports.object({
16217
- "known-good": external_exports.number().int().nonnegative(),
16218
- risky: external_exports.number().int().nonnegative(),
16219
- unapproved: external_exports.number().int().nonnegative()
16220
- }).partial().strict().optional(),
16221
- /**
16222
- * Partial: only Flag keys with non-zero counts are included.
16223
- * Strict: unknown keys are rejected — only Flag values are valid keys.
16224
- */
16225
- flagRollup: external_exports.object({
16226
- update: external_exports.number().int().nonnegative(),
16227
- stale: external_exports.number().int().nonnegative(),
16228
- conflict: external_exports.number().int().nonnegative(),
16229
- unknown: external_exports.number().int().nonnegative(),
16230
- change: external_exports.number().int().nonnegative(),
16231
- untracked: external_exports.number().int().nonnegative(),
16232
- risk: external_exports.number().int().nonnegative(),
16233
- findings: external_exports.number().int().nonnegative()
16234
- }).partial().strict(),
16235
- items: external_exports.array(AssetSummary)
16236
- }).meta({ id: "AssetGroup" });
16237
- var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16238
- var McpTool = external_exports.object({
16239
- name: external_exports.string(),
16240
- signature: external_exports.string(),
16241
- description: external_exports.string(),
16242
- write: external_exports.boolean(),
16243
- /** Non-null string when tool is dangerous / blocked; null otherwise. */
16244
- risk: external_exports.string().nullable()
16245
- }).meta({ id: "McpTool" });
16246
- var AssetFindingRef = external_exports.object({
16247
- id: external_exports.string(),
16248
- title: external_exports.string(),
16249
- note: external_exports.string()
16250
- });
16251
- var AssetDetail = AssetSummary.extend({
16252
- /** string | null — null when no description is available. */
16253
- description: external_exports.string().nullable(),
16254
- /** trustLevel | null — null for non-MCP assets. */
16255
- trust: TrustLevel.nullable(),
16256
- /** Type-specific raw key/values — FE renders the grid. */
16257
- meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16258
- /** always present — object when there is an active finding, null when absent. */
16259
- finding: AssetFindingRef.nullable(),
16260
- /** MCP exposed-tools list — omitted for non-mcp. */
16261
- tools: external_exports.array(McpTool).optional()
16262
- }).meta({ id: "AssetDetail" });
16263
- var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16264
- var InventoryStats = external_exports.object({
16265
- /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16266
- attention: external_exports.number().int().nonnegative(),
16267
- byType: external_exports.object({
16268
- project: external_exports.number().int().nonnegative(),
16269
- skill: external_exports.number().int().nonnegative(),
16270
- mcp: external_exports.number().int().nonnegative(),
16271
- hook: external_exports.number().int().nonnegative(),
16272
- config: external_exports.number().int().nonnegative()
16273
- }),
16274
- harnesses: external_exports.number().int().nonnegative(),
16275
- mcpTrust: external_exports.object({
16276
- "known-good": external_exports.number().int().nonnegative(),
16277
- risky: external_exports.number().int().nonnegative(),
16278
- unapproved: external_exports.number().int().nonnegative()
16279
- })
16280
- }).meta({ id: "InventoryStats" });
16281
- var FileSummary = external_exports.object({
16282
- path: external_exports.string(),
16283
- name: external_exports.string(),
16284
- origin: Origin,
16285
- /** Effective access (override applied). */
16286
- access: AccessLevel,
16287
- /** True when a file_access_override differs from the computed default. */
16288
- isCustom: external_exports.boolean(),
16289
- findings: external_exports.number().int().nonnegative(),
16290
- /** When the file was auto-blocked by a detection; null when not blocked. */
16291
- blockedAt: external_exports.iso.datetime().nullable().optional(),
16292
- /** Why the file was blocked; null when absent. */
16293
- note: external_exports.string().nullable().optional()
16294
- }).meta({ id: "FileSummary" });
16295
- var FolderSummary = external_exports.object({
16296
- name: external_exports.string(),
16297
- path: external_exports.string(),
16298
- /** Rollup of effective access across all descendants. */
16299
- accessCounts: AccessCounts
16300
- }).meta({ id: "FolderSummary" });
16301
- var ProjectTreeResponse = external_exports.object({
16302
- project: external_exports.object({
16303
- id: external_exports.string(),
16304
- repo: external_exports.string(),
16305
- visibility: Visibility
16306
- }),
16307
- path: external_exports.string(),
16308
- /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16309
- folders: external_exports.array(FolderSummary).optional(),
16310
- files: external_exports.array(FileSummary)
16311
- }).meta({ id: "ProjectTreeResponse" });
16312
- var FileDetail = FileSummary.extend({
16313
- project: external_exports.object({
16314
- repo: external_exports.string(),
16315
- visibility: Visibility,
16316
- language: external_exports.string(),
16317
- policyDefault: AccessLevel,
16318
- updatedAt: external_exports.iso.datetime()
16319
- }),
16320
- findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16321
- }).meta({ id: "FileDetail" });
16322
- var SetFileAccessBody = external_exports.object({
16323
- path: external_exports.string(),
16324
- access: AccessLevel
16325
- }).meta({ id: "SetFileAccessBody" });
16326
- var SetFileAccessResponse = external_exports.object({
16327
- file: FileSummary,
16328
- accessCounts: AccessCounts
16329
- }).meta({ id: "SetFileAccessResponse" });
16330
- var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16331
- var HarnessEventItem = external_exports.object({
16332
- kind: HarnessEventKind,
16333
- title: external_exports.string(),
16334
- detail: external_exports.string(),
16335
- occurredAt: external_exports.iso.datetime(),
16336
- findingId: external_exports.string().nullable().optional()
16337
- }).meta({ id: "HarnessEventItem" });
16338
- var HarnessEventsResponse = external_exports.object({
16339
- counts: external_exports.object({
16340
- block: external_exports.number().int().nonnegative(),
16341
- redact: external_exports.number().int().nonnegative(),
16342
- warn: external_exports.number().int().nonnegative()
16343
- }),
16344
- items: external_exports.array(HarnessEventItem)
16345
- }).meta({ id: "HarnessEventsResponse" });
16346
- var RescanResponse = external_exports.object({
16347
- jobId: external_exports.string(),
16348
- startedAt: external_exports.iso.datetime()
16349
- }).meta({ id: "RescanResponse" });
16350
- var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16351
- var ListAssetsQuery = external_exports.object({
16352
- /** Filter by one or more AssetType values; absent means all types. */
16353
- type: external_exports.array(AssetType).optional(),
16354
- /** Free-text search term. */
16355
- q: external_exports.string().optional()
16356
- });
16357
- var GetProjectTreeQuery = external_exports.object({
16358
- /** Subtree root path; defaults to repository root when absent. */
16359
- path: external_exports.string().optional(),
16360
- /** Free-text filter applied to file paths. */
16361
- q: external_exports.string().optional(),
16362
- /**
16363
- * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16364
- * file across the whole repo (folders omitted, most-recent first), ignoring
16365
- * `path`/`q` — powers the project-wide "recently blocked" strip.
16366
- */
16367
- filter: external_exports.enum(["blocked"]).optional()
16368
- });
16369
- var GetProjectFileQuery = external_exports.object({
16370
- /** Repository-relative file path; absent or empty → 400. */
16371
- path: external_exports.string()
16372
- });
16373
- var GetHarnessEventsQuery = external_exports.object({
16374
- /** Maximum number of events to return. Range: 1–50; default: 7. */
16375
- limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16376
- });
16377
-
16378
- // ../../packages/schema/src/zod/exception.ts
16379
- var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
16380
- var ExceptionConditions = external_exports.object({
16381
- repo: external_exports.string().optional(),
16382
- sourceTool: external_exports.string().optional(),
16383
- provider: external_exports.string().optional()
16384
- }).strict();
16385
- var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16386
- var DetectionException = external_exports.object({
16387
- id: external_exports.guid(),
16388
- ruleId: external_exports.string(),
16389
- // Denormalized from the rule, for reporting — never matched on.
16390
- category: DetectionCategory,
16391
- // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
16392
- // fingerprint, never the raw value, and never reversible. Matching recomputes
16393
- // the fingerprint from a fresh capture; the value itself is never stored.
16394
- // Shape-constrained so a malformed — or accidentally raw — value is rejected
16395
- // at the boundary rather than persisted.
16396
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
16397
- // Version of the fingerprint key the grant was written under; a rotated key
16398
- // invalidates old grants rather than silently mismatching them.
16399
- keyVersion: external_exports.number().int().positive(),
16400
- // maskMatch() preview of the approved value — never the raw value.
16401
- maskedValue: external_exports.string(),
16402
- capability: ExceptionCapability.default("suppress"),
16403
- scope: ExceptionScope,
16404
- expiresAt: external_exports.iso.datetime().nullable(),
16405
- maxUses: external_exports.number().int().positive().nullable(),
16406
- useCount: external_exports.number().int().nonnegative(),
16407
- lastUsedAt: external_exports.iso.datetime().nullable(),
16408
- // Mandatory: every grant carries the human reason it exists.
16409
- justification: external_exports.string().min(1),
16410
- conditions: ExceptionConditions.nullable(),
16411
- createdBy: external_exports.string(),
16412
- createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
16413
- createdAt: external_exports.iso.datetime(),
16414
- updatedAt: external_exports.iso.datetime(),
16415
- // Revocation is terminal and retained — consumed/expired/revoked rows are
16416
- // audit evidence; nothing in the exception lifecycle hard-deletes.
16417
- revokedAt: external_exports.iso.datetime().nullable(),
16418
- revokedBy: external_exports.string().nullable(),
16419
- revokeReason: external_exports.string().nullable()
16420
- });
16421
- var ExceptionBundleEntry = DetectionException.pick({
16422
- id: true,
16423
- ruleId: true,
16424
- valueFingerprint: true,
16425
- keyVersion: true,
16426
- capability: true,
16427
- expiresAt: true,
16428
- maxUses: true,
16429
- useCount: true,
16430
- conditions: true
16431
- });
16432
- var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16433
-
16434
- // ../../packages/schema/src/zod/rule.ts
16435
- var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16436
- var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16437
- var KeywordMatcher = external_exports.object({
16438
- type: external_exports.literal("keyword"),
16439
- // An empty keyword matches at every position, yielding one zero-length span
16440
- // per character. Rejected here because a keyword that matches everything is
16441
- // never intentional.
16442
- keywords: external_exports.array(external_exports.string().min(1)).min(1),
16443
- caseSensitive: external_exports.boolean().default(false)
16444
- });
16445
- function isValidRegex(pattern, flags) {
16446
- try {
16447
- new RegExp(pattern, flags);
16448
- return true;
16449
- } catch {
16450
- return false;
16451
- }
16452
- }
16453
- function matchesEmptyString(pattern, flags) {
16454
- try {
16455
- const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
16456
- return re.exec("")?.[0].length === 0;
16457
- } catch {
16458
- return false;
16459
- }
16460
- }
16461
- var MAX_PATTERN_LENGTH = 2e3;
16462
- var RegexMatcher = external_exports.object({
16463
- type: external_exports.literal("regex"),
16464
- pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16465
- flags: external_exports.string().default("gi"),
16466
- captureGroup: external_exports.number().int().nonnegative().optional()
16467
- }).refine((v) => isValidRegex(v.pattern, v.flags), {
16468
- message: "pattern/flags do not form a valid JavaScript regular expression",
16469
- path: ["pattern"]
16470
- }).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
16471
- 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',
16472
- path: ["pattern"]
16473
- });
16474
- var ValidatorMatcher = external_exports.object({
16475
- type: external_exports.literal("validator"),
16476
- name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
16477
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16478
- });
16479
- var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
16480
- var AppliesTo = external_exports.object({
16481
- // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16482
- extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16483
- }).meta({ id: "AppliesTo" });
16484
- var PostValidatorRef = external_exports.union([
16485
- external_exports.string(),
16486
- external_exports.object({
16487
- name: external_exports.string(),
16488
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16489
- })
16490
- ]).meta({ id: "PostValidatorRef" });
16491
- var RequiresNearby = external_exports.object({
16492
- // Each array, when present, must be non-empty and contain non-empty strings —
16493
- // an empty/blank criterion would either never fire or (for labels) match
16494
- // everything.
16495
- categories: external_exports.array(DetectionCategory).min(1).optional(),
16496
- ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16497
- labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16498
- windowChars: external_exports.number().int().positive().default(160),
16499
- // Optional confidence bump applied when a gated match is corroborated. Capped
16500
- // small: it nudges confidence, it does not assert certainty.
16501
- confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16502
- }).refine(
16503
- (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16504
- { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16505
- ).meta({ id: "RequiresNearby" });
16506
- var RuleFixture = external_exports.object({
16507
- label: external_exports.string(),
16508
- text: external_exports.string().max(5e4),
16509
- shouldMatch: external_exports.boolean(),
16510
- // Simulated file context for the scan, so fixtures can assert `appliesTo`
16511
- // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16512
- filePath: external_exports.string().optional(),
16513
- expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
16514
- }).meta({ id: "RuleFixture" });
16515
- var Rule = external_exports.object({
16516
- specVersion: external_exports.literal(1),
16517
- // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16518
- // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
16519
- // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16520
- // therefore carries no namespace and is not globally unique across publishers;
16521
- // never feed one to splitDetectionId. `category` below (per-rule) is the
16522
- // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16523
- id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16524
- name: external_exports.string(),
16525
- category: DetectionCategory,
16526
- severity: Severity,
16527
- matcher: Matcher,
16528
- appliesTo: AppliesTo.optional(),
16529
- postValidators: external_exports.array(PostValidatorRef).optional(),
16530
- requiresNearby: RequiresNearby.optional(),
16531
- examples: external_exports.array(external_exports.string()).optional()
16532
- }).meta({ id: "Rule" });
16533
- var Author = external_exports.object({
16534
- name: external_exports.string(),
16535
- email: external_exports.email().optional(),
16536
- url: external_exports.url().optional()
16537
- }).meta({ id: "Author" });
16538
- var PackManifest = external_exports.object({
16539
- specVersion: external_exports.literal(1),
16540
- id: external_exports.string(),
16541
- name: external_exports.string(),
16542
- version: external_exports.string(),
16543
- rules: external_exports.array(external_exports.string()),
16544
- // Optional attribution/provenance — consumed by the rule marketplace.
16545
- description: external_exports.string().optional(),
16546
- author: Author.optional(),
16547
- license: external_exports.string().optional(),
16548
- sourceUrl: external_exports.url().optional()
16549
- }).meta({ id: "PackManifest" });
16550
-
16551
- // ../../packages/schema/src/zod/policy.ts
16552
- var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
16553
- var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
16554
- var Policy = external_exports.object({
16555
- id: external_exports.guid(),
16556
- scope: PolicyScope,
16557
- target: PolicyTarget,
16558
- action: ActionTaken,
16559
- enabled: external_exports.boolean().default(true),
16560
- customKeywords: external_exports.array(external_exports.string()).optional(),
16561
- // Display name — optional so older policy rows without name still parse.
16562
- // Added for the findings API (policy.name column migration).
16563
- name: external_exports.string().optional()
16564
- }).meta({ id: "Policy" });
16565
- var PolicyBundle = external_exports.object({
16566
- version: external_exports.string(),
16567
- policies: external_exports.array(Policy),
16568
- // Rules from the installed marketplace packs (snapshotted by the
16569
- // control plane). The plugin registers these in addition to its bundled
16570
- // packs. Optional so older backends — and older on-disk caches — that omit
16571
- // the field still parse; consumers read `bundle.rules ?? []`.
16572
- rules: external_exports.array(Rule).optional(),
16573
- // When true, `rules` IS the complete effective ruleset and the runtime must
16574
- // NOT merge its compiled-in bundled packs — the standalone gateway sets this
16575
- // after reading the user's installed snapshot (installed_packs, enabled
16576
- // packs only), which is how detection updates stay manual: new bundled
16577
- // rules run only after the user applies the pack update. Absent/false keeps
16578
- // the historical composition (bundled packs + rules) — older caches.
16579
- rulesComplete: external_exports.boolean().optional(),
16580
- // Active detection exceptions, evaluation subset only (see
16581
- // ExceptionBundleEntry). Optional so older bundle producers — and older
16582
- // on-disk caches — that omit the field still parse; consumers read
16583
- // `bundle.exceptions ?? []`.
16584
- exceptions: external_exports.array(ExceptionBundleEntry).optional(),
16585
- // Installed pack version, keyed by ruleId, for rules in `rules` that came
16586
- // from a versioned installed pack. Optional so older backends — and older
16587
- // on-disk caches — that omit the field still parse; consumers fall back to
16588
- // the rule's own spec version. NOT the bundle version above — see
16589
- // installedRuleset's ruleVersions for the source of truth.
16590
- ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
16591
- customKeywords: external_exports.array(external_exports.string()),
16592
- fetchedAt: external_exports.iso.datetime()
16593
- }).meta({ id: "PolicyBundle" });
16594
- var OBSERVE_ONLY_CATEGORIES = ["config"];
16595
- var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
16596
- var CATEGORY_PEAK_SEVERITY = {
16597
- secret: "critical",
16598
- financial: "critical",
16599
- // core-financial/credit-card
16600
- code_flaw: "critical",
16601
- pii: "high",
16602
- phi: "high",
16603
- custom: "high",
16604
- // user-defined; conservative
16605
- code_context: "low",
16606
- config: "low"
16607
- // observe-only; floors to monitor regardless
16608
- };
16609
- function severityFloorPolicy(category) {
16610
- if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
16611
- const peak = CATEGORY_PEAK_SEVERITY[category];
16612
- return peak === "critical" || peak === "high" ? "warn" : "monitor";
16613
- }
16614
- function severityFloorPosture() {
16615
- const out = {};
16616
- for (const c of DetectionCategory.options) out[c] = severityFloorPolicy(c);
16617
- return out;
16618
- }
16619
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
16620
- var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
16621
- var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
16622
- var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
16623
- var BUILTIN_POLICY_SPECS = {
16624
- monitor: {
16625
- name: "Monitor",
16626
- action: "log",
16627
- description: "Log every match for audit. The request is allowed through untouched."
16628
- },
16629
- warn: {
16630
- name: "Warn",
16631
- action: "warn",
16632
- description: "Allow the request, but warn the user inline before it is sent."
16633
- },
16634
- redact: {
16635
- name: "Redact",
16636
- action: "redact",
16637
- description: "Automatically strip the matched value from the request, then continue."
16638
- },
16639
- block: {
16640
- name: "Block",
16641
- action: "block",
16642
- description: "Refuse the request entirely whenever any rule in this detection matches."
16643
- }
16644
- };
16645
- function builtinPolicyToAction(id) {
16646
- return BUILTIN_POLICY_SPECS[id].action;
16647
- }
16648
- var DEFAULT_ACTIONS = Object.fromEntries(
16649
- DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
16650
- );
16651
- var BUILTIN_POLICIES = Object.fromEntries(
16652
- KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
16653
- );
16654
- var DEFAULT_PACK_POLICY_ID = "monitor";
16655
- function policyIdToAction(policyId) {
16656
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
16657
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
16658
- return BUILTIN_POLICIES[id].action;
16659
- }
16660
- var UsedByItem = external_exports.object({
16661
- id: external_exports.string(),
16662
- name: external_exports.string(),
16663
- ruleCount: external_exports.number().int().nonnegative(),
16664
- enabled: external_exports.boolean()
16665
- }).meta({ id: "UsedByItem" });
16666
- var PolicyListItem = external_exports.object({
16667
- id: external_exports.string(),
16668
- kind: PolicyKind,
16669
- name: external_exports.string(),
16670
- enabled: external_exports.boolean(),
16671
- usedByCount: external_exports.number().int().nonnegative()
16672
- }).meta({ id: "PolicyListItem" });
16673
- var PolicyDetail = external_exports.object({
16674
- specVersion: external_exports.literal(1),
16675
- id: external_exports.string(),
16676
- kind: PolicyKind,
16677
- name: external_exports.string(),
16678
- enabled: external_exports.boolean(),
16679
- description: external_exports.string(),
16680
- usedBy: external_exports.array(UsedByItem)
16681
- }).meta({ id: "PolicyDetail" });
16682
- var PolicyStatsResponse = external_exports.object({
16683
- policies: external_exports.number().int().nonnegative(),
16684
- builtin: external_exports.number().int().nonnegative(),
16685
- custom: external_exports.number().int().nonnegative(),
16686
- detectionsGoverned: external_exports.number().int().nonnegative()
16687
- }).meta({ id: "PolicyStatsResponse" });
16688
-
16689
- // ../../packages/schema/src/zod/api.ts
16690
- var LIST_QUERY_MAX_LIMIT = 200;
16691
- var ListEventsQuery = external_exports.object({
16692
- cursor: external_exports.string().optional(),
16693
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16694
- sourceTool: external_exports.string().optional(),
16695
- kind: external_exports.string().optional(),
16696
- from: external_exports.iso.datetime().optional(),
16697
- to: external_exports.iso.datetime().optional()
16698
- });
16699
- var ListEventsResponse = external_exports.object({
16700
- items: external_exports.array(Event),
16701
- nextCursor: external_exports.string().nullable()
16702
- }).meta({ id: "ListEventsResponse" });
16703
- var IngestResponse = external_exports.object({
16704
- accepted: external_exports.number().int().nonnegative(),
16705
- duplicates: external_exports.number().int().nonnegative()
16706
- }).meta({ id: "IngestResponse" });
16707
- var ListFindingsQuery = external_exports.object({
16708
- cursor: external_exports.string().optional(),
16709
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16710
- severity: external_exports.string().optional(),
16711
- category: external_exports.string().optional(),
16712
- eventId: external_exports.guid().optional()
16713
- });
16714
- var ListFindingsResponse = external_exports.object({
16715
- items: external_exports.array(Finding),
16716
- nextCursor: external_exports.string().nullable()
16717
- }).meta({ id: "ListFindingsResponse" });
16718
- var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
16719
- var CreatePolicyRequest = Policy.omit({ id: true }).meta({
16720
- id: "CreatePolicyRequest"
16721
- });
16722
- var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
16723
- var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
16724
- var ErrorResponse = external_exports.object({
16725
- error: external_exports.object({
16726
- code: external_exports.string(),
16727
- message: external_exports.string(),
16728
- details: external_exports.unknown().optional()
16729
- })
16730
- }).meta({ id: "ErrorResponse" });
16731
-
16732
- // ../../packages/schema/src/zod/config-inventory.ts
16733
- var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16734
- var SkillScanEntry = external_exports.object({
16735
- name: external_exports.string().min(1),
16736
- // The identity source: a marketplace repo for plugin skills (e.g.
16737
- // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16738
- // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16739
- // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16740
- source: external_exports.string().min(1),
16741
- scope: ConfigScope,
16742
- pluginName: external_exports.string().optional(),
16743
- // Volatile — rides the attribute bag, never the identity hash.
16744
- version: external_exports.string().optional(),
16745
- description: external_exports.string().optional(),
16746
- // Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
16747
- updatedAt: external_exports.iso.datetime().optional(),
16748
- // Filesystem path — the promoted inventory `location` column.
16749
- location: external_exports.string().optional()
16750
- });
16751
- var HookScanEntry = external_exports.object({
16752
- // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16753
- // set is harness-defined and grows without a schema change.
16754
- event: external_exports.string().min(1),
16755
- // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16756
- matcher: external_exports.string().optional(),
16757
- command: external_exports.string().min(1),
16758
- timeout: external_exports.number().optional(),
16759
- scope: ConfigScope,
16760
- pluginName: external_exports.string().optional(),
16761
- // The settings file / hooks.json the entry came from.
16762
- location: external_exports.string().optional()
16120
+ var HookScanEntry = external_exports.object({
16121
+ // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16122
+ // set is harness-defined and grows without a schema change.
16123
+ event: external_exports.string().min(1),
16124
+ // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16125
+ matcher: external_exports.string().optional(),
16126
+ command: external_exports.string().min(1),
16127
+ timeout: external_exports.number().optional(),
16128
+ scope: ConfigScope,
16129
+ pluginName: external_exports.string().optional(),
16130
+ // The settings file / hooks.json the entry came from.
16131
+ location: external_exports.string().optional()
16763
16132
  });
16764
16133
  var McpServerScanEntry = external_exports.object({
16765
16134
  // The server's config key ("github", "filesystem", …) — identity, with the
@@ -16841,6 +16210,164 @@ var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16841
16210
  var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16842
16211
  var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16843
16212
 
16213
+ // ../../packages/schema/src/zod/rule.ts
16214
+ var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
16215
+ var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16216
+ var KeywordMatcher = external_exports.strictObject({
16217
+ type: external_exports.literal("keyword"),
16218
+ // An empty keyword matches at every position, yielding one zero-length span
16219
+ // per character. Rejected here because a keyword that matches everything is
16220
+ // never intentional.
16221
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16222
+ caseSensitive: external_exports.boolean().default(false)
16223
+ });
16224
+ function isValidRegex(pattern, flags) {
16225
+ try {
16226
+ new RegExp(pattern, flags);
16227
+ return true;
16228
+ } catch {
16229
+ return false;
16230
+ }
16231
+ }
16232
+ function probeFlags(flags) {
16233
+ return flags.replace(/[gy]/g, "");
16234
+ }
16235
+ function matchesEmptyString(pattern, flags) {
16236
+ try {
16237
+ const re = new RegExp(pattern, probeFlags(flags));
16238
+ return re.exec("")?.[0].length === 0;
16239
+ } catch {
16240
+ return false;
16241
+ }
16242
+ }
16243
+ function spansWholeMatch(captureGroup) {
16244
+ return captureGroup === void 0 || captureGroup === 0;
16245
+ }
16246
+ function captureGroupCount(pattern, flags) {
16247
+ try {
16248
+ const probe = new RegExp(`${pattern}|`, probeFlags(flags));
16249
+ const result = probe.exec("");
16250
+ return result ? result.length - 1 : void 0;
16251
+ } catch {
16252
+ return void 0;
16253
+ }
16254
+ }
16255
+ var MAX_PATTERN_LENGTH = 2e3;
16256
+ var RegexMatcher = external_exports.strictObject({
16257
+ type: external_exports.literal("regex"),
16258
+ pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16259
+ flags: external_exports.string().default("gi"),
16260
+ captureGroup: external_exports.number().int().nonnegative().optional()
16261
+ }).refine((v) => isValidRegex(v.pattern, v.flags), {
16262
+ message: "pattern/flags do not form a valid JavaScript regular expression",
16263
+ path: ["pattern"]
16264
+ }).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
16265
+ 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',
16266
+ path: ["pattern"]
16267
+ }).superRefine((v, ctx) => {
16268
+ if (v.captureGroup === void 0) return;
16269
+ const groups = captureGroupCount(v.pattern, v.flags);
16270
+ if (groups === void 0 || v.captureGroup <= groups) return;
16271
+ ctx.addIssue({
16272
+ code: "custom",
16273
+ path: ["captureGroup"],
16274
+ 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.`
16275
+ });
16276
+ });
16277
+ var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
16278
+ var MATCHER_TYPES = MatcherType.options;
16279
+ var AppliesTo = external_exports.strictObject({
16280
+ // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16281
+ extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16282
+ }).meta({ id: "AppliesTo" });
16283
+ var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
16284
+ var PostValidatorRef = external_exports.union(
16285
+ [
16286
+ PostValidatorName,
16287
+ external_exports.strictObject({
16288
+ name: PostValidatorName,
16289
+ config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16290
+ })
16291
+ ],
16292
+ {
16293
+ // A union reports one collapsed issue for every way its arms can fail, so
16294
+ // this has to describe the whole shape rather than just the name — it is
16295
+ // what an author sees for a misspelled name AND for a stray key in the
16296
+ // object form. The names come from the enum so the message cannot go
16297
+ // stale. Without it Zod says only "Invalid input", which is precisely the
16298
+ // no-feedback outcome this schema exists to remove.
16299
+ error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
16300
+ " or "
16301
+ )}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
16302
+ }
16303
+ ).meta({ id: "PostValidatorRef" });
16304
+ var RequiresNearby = external_exports.strictObject({
16305
+ // Each array, when present, must be non-empty and contain non-empty strings —
16306
+ // an empty/blank criterion would either never fire or (for labels) match
16307
+ // everything.
16308
+ categories: external_exports.array(DetectionCategory).min(1).optional(),
16309
+ ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16310
+ labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16311
+ windowChars: external_exports.number().int().positive().default(160),
16312
+ // Optional confidence bump applied when a gated match is corroborated. Capped
16313
+ // small: it nudges confidence, it does not assert certainty.
16314
+ confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16315
+ }).refine(
16316
+ (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16317
+ { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16318
+ ).meta({ id: "RequiresNearby" });
16319
+ var RuleFixture = external_exports.strictObject({
16320
+ label: external_exports.string(),
16321
+ text: external_exports.string().max(5e4),
16322
+ shouldMatch: external_exports.boolean(),
16323
+ // Simulated file context for the scan, so fixtures can assert `appliesTo`
16324
+ // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16325
+ filePath: external_exports.string().optional(),
16326
+ expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
16327
+ }).meta({ id: "RuleFixture" });
16328
+ var Rule = external_exports.strictObject({
16329
+ // A pinned literal over a STRICT object, and the two together decide how this
16330
+ // format may grow. A rule carrying a key not listed below is refused with
16331
+ // `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
16332
+ // `invalid_value`. So the only additive path is adding an OPTIONAL field here
16333
+ // — that keeps every rule authored before it valid — and a rule author has no
16334
+ // way to introduce a field of their own or to opt into a later version.
16335
+ // Widening the format means changing this literal and every consumer of it.
16336
+ specVersion: external_exports.literal(1),
16337
+ // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16338
+ // the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
16339
+ // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16340
+ // therefore carries no namespace and is not globally unique across publishers;
16341
+ // never feed one to splitDetectionId. `category` below (per-rule) is the
16342
+ // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16343
+ id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16344
+ name: external_exports.string(),
16345
+ category: DetectionCategory,
16346
+ severity: Severity,
16347
+ matcher: Matcher,
16348
+ appliesTo: AppliesTo.optional(),
16349
+ postValidators: external_exports.array(PostValidatorRef).optional(),
16350
+ requiresNearby: RequiresNearby.optional(),
16351
+ examples: external_exports.array(external_exports.string()).optional()
16352
+ }).meta({ id: "Rule" });
16353
+ var Author = external_exports.object({
16354
+ name: external_exports.string(),
16355
+ email: external_exports.email().optional(),
16356
+ url: external_exports.url().optional()
16357
+ }).meta({ id: "Author" });
16358
+ var PackManifest = external_exports.object({
16359
+ specVersion: external_exports.literal(1),
16360
+ id: external_exports.string(),
16361
+ name: external_exports.string(),
16362
+ version: external_exports.string(),
16363
+ rules: external_exports.array(external_exports.string()),
16364
+ // Optional attribution/provenance — consumed by the rule marketplace.
16365
+ description: external_exports.string().optional(),
16366
+ author: Author.optional(),
16367
+ license: external_exports.string().optional(),
16368
+ sourceUrl: external_exports.url().optional()
16369
+ }).meta({ id: "PackManifest" });
16370
+
16844
16371
  // ../../packages/schema/src/zod/detection.ts
16845
16372
  var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
16846
16373
  var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
@@ -17039,6 +16566,231 @@ function buildDetectionsList(summaries, query) {
17039
16566
  return { counts, items: filtered.map(summaryToDetectionListItem) };
17040
16567
  }
17041
16568
 
16569
+ // ../../packages/schema/src/zod/inventory.ts
16570
+ var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16571
+ var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16572
+ var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16573
+ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16574
+ var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16575
+ var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16576
+ var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16577
+ var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
16578
+ id: "HarnessId"
16579
+ });
16580
+ var AccessCounts = external_exports.object({
16581
+ open: external_exports.number().int().nonnegative(),
16582
+ approved: external_exports.number().int().nonnegative(),
16583
+ blocked: external_exports.number().int().nonnegative(),
16584
+ total: external_exports.number().int().nonnegative()
16585
+ }).meta({ id: "AccessCounts" });
16586
+ var AssetSummary = external_exports.object({
16587
+ id: external_exports.string(),
16588
+ type: AssetType,
16589
+ name: external_exports.string(),
16590
+ sub: external_exports.string(),
16591
+ flags: external_exports.array(Flag),
16592
+ /** MCP servers only — omitted for all other types. */
16593
+ trust: TrustLevel.optional()
16594
+ }).meta({ id: "AssetSummary" });
16595
+ var ProjectSummary = external_exports.object({
16596
+ id: external_exports.string(),
16597
+ name: external_exports.string(),
16598
+ repo: external_exports.string(),
16599
+ visibility: Visibility,
16600
+ language: external_exports.string(),
16601
+ policyDefault: AccessLevel,
16602
+ updatedAt: external_exports.iso.datetime(),
16603
+ accessCounts: AccessCounts,
16604
+ findingsCount: external_exports.number().int().nonnegative()
16605
+ }).meta({ id: "ProjectSummary" });
16606
+ var HarnessCategory = external_exports.object({
16607
+ /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16608
+ type: AssetType,
16609
+ assets: external_exports.array(AssetSummary)
16610
+ });
16611
+ var HarnessSummary = external_exports.object({
16612
+ id: HarnessId,
16613
+ label: external_exports.string(),
16614
+ kind: external_exports.string(),
16615
+ version: external_exports.string(),
16616
+ sessions: external_exports.number().int().nonnegative(),
16617
+ assetCount: external_exports.number().int().nonnegative(),
16618
+ flagCount: external_exports.number().int().nonnegative(),
16619
+ projects: external_exports.array(ProjectSummary),
16620
+ categories: external_exports.array(HarnessCategory)
16621
+ }).meta({ id: "HarnessSummary" });
16622
+ var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16623
+ var AssetGroup = external_exports.object({
16624
+ /** Group key — never project (enforced at service layer). */
16625
+ type: AssetType,
16626
+ total: external_exports.number().int().nonnegative(),
16627
+ /**
16628
+ * MCP group only — omitted for all other types.
16629
+ * Partial: only TrustLevel keys with non-zero counts are included.
16630
+ * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16631
+ */
16632
+ trustRollup: external_exports.object({
16633
+ "known-good": external_exports.number().int().nonnegative(),
16634
+ risky: external_exports.number().int().nonnegative(),
16635
+ unapproved: external_exports.number().int().nonnegative()
16636
+ }).partial().strict().optional(),
16637
+ /**
16638
+ * Partial: only Flag keys with non-zero counts are included.
16639
+ * Strict: unknown keys are rejected — only Flag values are valid keys.
16640
+ */
16641
+ flagRollup: external_exports.object({
16642
+ update: external_exports.number().int().nonnegative(),
16643
+ stale: external_exports.number().int().nonnegative(),
16644
+ conflict: external_exports.number().int().nonnegative(),
16645
+ unknown: external_exports.number().int().nonnegative(),
16646
+ change: external_exports.number().int().nonnegative(),
16647
+ untracked: external_exports.number().int().nonnegative(),
16648
+ risk: external_exports.number().int().nonnegative(),
16649
+ findings: external_exports.number().int().nonnegative()
16650
+ }).partial().strict(),
16651
+ items: external_exports.array(AssetSummary)
16652
+ }).meta({ id: "AssetGroup" });
16653
+ var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16654
+ var McpTool = external_exports.object({
16655
+ name: external_exports.string(),
16656
+ signature: external_exports.string(),
16657
+ description: external_exports.string(),
16658
+ write: external_exports.boolean(),
16659
+ /** Non-null string when tool is dangerous / blocked; null otherwise. */
16660
+ risk: external_exports.string().nullable()
16661
+ }).meta({ id: "McpTool" });
16662
+ var AssetFindingRef = external_exports.object({
16663
+ id: external_exports.string(),
16664
+ title: external_exports.string(),
16665
+ note: external_exports.string()
16666
+ });
16667
+ var AssetDetail = AssetSummary.extend({
16668
+ /** string | null — null when no description is available. */
16669
+ description: external_exports.string().nullable(),
16670
+ /** trustLevel | null — null for non-MCP assets. */
16671
+ trust: TrustLevel.nullable(),
16672
+ /** Type-specific raw key/values — FE renders the grid. */
16673
+ meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16674
+ /** always present — object when there is an active finding, null when absent. */
16675
+ finding: AssetFindingRef.nullable(),
16676
+ /** MCP exposed-tools list — omitted for non-mcp. */
16677
+ tools: external_exports.array(McpTool).optional()
16678
+ }).meta({ id: "AssetDetail" });
16679
+ var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16680
+ var InventoryStats = external_exports.object({
16681
+ /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16682
+ attention: external_exports.number().int().nonnegative(),
16683
+ byType: external_exports.object({
16684
+ project: external_exports.number().int().nonnegative(),
16685
+ skill: external_exports.number().int().nonnegative(),
16686
+ mcp: external_exports.number().int().nonnegative(),
16687
+ hook: external_exports.number().int().nonnegative(),
16688
+ config: external_exports.number().int().nonnegative()
16689
+ }),
16690
+ harnesses: external_exports.number().int().nonnegative(),
16691
+ mcpTrust: external_exports.object({
16692
+ "known-good": external_exports.number().int().nonnegative(),
16693
+ risky: external_exports.number().int().nonnegative(),
16694
+ unapproved: external_exports.number().int().nonnegative()
16695
+ })
16696
+ }).meta({ id: "InventoryStats" });
16697
+ var FileSummary = external_exports.object({
16698
+ path: external_exports.string(),
16699
+ name: external_exports.string(),
16700
+ origin: Origin,
16701
+ /** Effective access (override applied). */
16702
+ access: AccessLevel,
16703
+ /** True when a file_access_override differs from the computed default. */
16704
+ isCustom: external_exports.boolean(),
16705
+ findings: external_exports.number().int().nonnegative(),
16706
+ /** When the file was auto-blocked by a detection; null when not blocked. */
16707
+ blockedAt: external_exports.iso.datetime().nullable().optional(),
16708
+ /** Why the file was blocked; null when absent. */
16709
+ note: external_exports.string().nullable().optional()
16710
+ }).meta({ id: "FileSummary" });
16711
+ var FolderSummary = external_exports.object({
16712
+ name: external_exports.string(),
16713
+ path: external_exports.string(),
16714
+ /** Rollup of effective access across all descendants. */
16715
+ accessCounts: AccessCounts
16716
+ }).meta({ id: "FolderSummary" });
16717
+ var ProjectTreeResponse = external_exports.object({
16718
+ project: external_exports.object({
16719
+ id: external_exports.string(),
16720
+ repo: external_exports.string(),
16721
+ visibility: Visibility
16722
+ }),
16723
+ path: external_exports.string(),
16724
+ /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16725
+ folders: external_exports.array(FolderSummary).optional(),
16726
+ files: external_exports.array(FileSummary)
16727
+ }).meta({ id: "ProjectTreeResponse" });
16728
+ var FileDetail = FileSummary.extend({
16729
+ project: external_exports.object({
16730
+ repo: external_exports.string(),
16731
+ visibility: Visibility,
16732
+ language: external_exports.string(),
16733
+ policyDefault: AccessLevel,
16734
+ updatedAt: external_exports.iso.datetime()
16735
+ }),
16736
+ findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16737
+ }).meta({ id: "FileDetail" });
16738
+ var SetFileAccessBody = external_exports.object({
16739
+ path: external_exports.string(),
16740
+ access: AccessLevel
16741
+ }).meta({ id: "SetFileAccessBody" });
16742
+ var SetFileAccessResponse = external_exports.object({
16743
+ file: FileSummary,
16744
+ accessCounts: AccessCounts
16745
+ }).meta({ id: "SetFileAccessResponse" });
16746
+ var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16747
+ var HarnessEventItem = external_exports.object({
16748
+ kind: HarnessEventKind,
16749
+ title: external_exports.string(),
16750
+ detail: external_exports.string(),
16751
+ occurredAt: external_exports.iso.datetime(),
16752
+ findingId: external_exports.string().nullable().optional()
16753
+ }).meta({ id: "HarnessEventItem" });
16754
+ var HarnessEventsResponse = external_exports.object({
16755
+ counts: external_exports.object({
16756
+ block: external_exports.number().int().nonnegative(),
16757
+ redact: external_exports.number().int().nonnegative(),
16758
+ warn: external_exports.number().int().nonnegative()
16759
+ }),
16760
+ items: external_exports.array(HarnessEventItem)
16761
+ }).meta({ id: "HarnessEventsResponse" });
16762
+ var RescanResponse = external_exports.object({
16763
+ jobId: external_exports.string(),
16764
+ startedAt: external_exports.iso.datetime()
16765
+ }).meta({ id: "RescanResponse" });
16766
+ var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16767
+ var ListAssetsQuery = external_exports.object({
16768
+ /** Filter by one or more AssetType values; absent means all types. */
16769
+ type: external_exports.array(AssetType).optional(),
16770
+ /** Free-text search term. */
16771
+ q: external_exports.string().optional()
16772
+ });
16773
+ var GetProjectTreeQuery = external_exports.object({
16774
+ /** Subtree root path; defaults to repository root when absent. */
16775
+ path: external_exports.string().optional(),
16776
+ /** Free-text filter applied to file paths. */
16777
+ q: external_exports.string().optional(),
16778
+ /**
16779
+ * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16780
+ * file across the whole repo (folders omitted, most-recent first), ignoring
16781
+ * `path`/`q` — powers the project-wide "recently blocked" strip.
16782
+ */
16783
+ filter: external_exports.enum(["blocked"]).optional()
16784
+ });
16785
+ var GetProjectFileQuery = external_exports.object({
16786
+ /** Repository-relative file path; absent or empty → 400. */
16787
+ path: external_exports.string()
16788
+ });
16789
+ var GetHarnessEventsQuery = external_exports.object({
16790
+ /** Maximum number of events to return. Range: 1–50; default: 7. */
16791
+ limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16792
+ });
16793
+
17042
16794
  // ../../packages/schema/src/zod/shares.ts
17043
16795
  var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
17044
16796
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
@@ -17245,6 +16997,127 @@ var EgressWriteSummary = external_exports.object({
17245
16997
  droppedFiles: external_exports.array(external_exports.string()).default([])
17246
16998
  }).meta({ id: "EgressWriteSummary" });
17247
16999
 
17000
+ // ../../packages/schema/src/zod/event.ts
17001
+ var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
17002
+ var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
17003
+ var EventMetadata = external_exports.object({
17004
+ sessionId: external_exports.string().optional(),
17005
+ repo: external_exports.string().optional(),
17006
+ filePath: external_exports.string().optional(),
17007
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
17008
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
17009
+ // arguments or output, which can carry the very value a finding masked
17010
+ // (metadata is stored unredacted). Gives findings on non-file captures a
17011
+ // display location ("via Bash") when no filePath exists.
17012
+ toolName: external_exports.string().optional(),
17013
+ // Set (true) by the worktree scanner when the file is excluded by the
17014
+ // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
17015
+ // generated code can leak real secrets — but the provenance is recorded so
17016
+ // policy/dashboards can treat those findings as informational rather than
17017
+ // blocking. Omitted (not false) for tracked files and non-scan events.
17018
+ gitignored: external_exports.boolean().optional(),
17019
+ // Set (true) ONLY when the event's `content` is the COMPLETE file at
17020
+ // capture time (a worktree scan reading from disk). Hook-captured edits
17021
+ // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
17022
+ // this. The resolver-on-ingest keys its fixed-at-source dropout
17023
+ // diff on this marker: only a whole-file snapshot can prove a previously
17024
+ // open finding is gone; a fragment's absence proves nothing (the secret
17025
+ // may live outside the hunk). Omitted (not false) for fragments and
17026
+ // non-scan events, so pre-marker clients safely default to the
17027
+ // non-authoritative path.
17028
+ wholeFile: external_exports.boolean().optional(),
17029
+ model: external_exports.string().optional(),
17030
+ turnIndex: external_exports.number().int().nonnegative().optional(),
17031
+ // Distributed-tracing correlation. `correlationId` ties a recorded event back
17032
+ // to the request that captured/ingested it (a UUID, generated independently of
17033
+ // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
17034
+ // originating span when telemetry is enabled. Both optional + backward
17035
+ // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
17036
+ correlationId: external_exports.uuid().optional(),
17037
+ traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
17038
+ // Ids of the detection exceptions that downgraded findings in this capture
17039
+ // to 'allow' — the enforcement audit trail's link back to the grant that
17040
+ // authorized the bypass. Absent on captures where no exception applied.
17041
+ exceptionIds: external_exports.array(external_exports.guid()).optional()
17042
+ }).meta({ id: "EventMetadata" });
17043
+ var Event = external_exports.object({
17044
+ id: external_exports.guid(),
17045
+ sourceTool: SourceTool,
17046
+ kind: EventKind,
17047
+ occurredAt: external_exports.iso.datetime(),
17048
+ contentHash: external_exports.string(),
17049
+ content: external_exports.string(),
17050
+ metadata: EventMetadata.optional()
17051
+ }).meta({ id: "Event" });
17052
+ var IngestEvent = Event.meta({ id: "IngestEvent" });
17053
+ var IngestBatch = external_exports.object({
17054
+ events: external_exports.array(IngestEvent).min(1).max(100),
17055
+ // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
17056
+ // additionally rejects any event whose contentHash the store has already
17057
+ // recorded — for re-runnable bulk ingest (worktree scan, transcript
17058
+ // backfill), where a re-run mints fresh event ids for identical content and
17059
+ // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
17060
+ // two genuinely separate prompts can be byte-identical and both belong on
17061
+ // the timeline.
17062
+ dedupe: external_exports.literal("content-hash").optional()
17063
+ }).meta({ id: "IngestBatch" });
17064
+
17065
+ // ../../packages/schema/src/zod/exception.ts
17066
+ var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
17067
+ var ExceptionConditions = external_exports.object({
17068
+ repo: external_exports.string().optional(),
17069
+ sourceTool: external_exports.string().optional(),
17070
+ provider: external_exports.string().optional()
17071
+ }).strict();
17072
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
17073
+ var DetectionException = external_exports.object({
17074
+ id: external_exports.guid(),
17075
+ ruleId: external_exports.string(),
17076
+ // Denormalized from the rule, for reporting — never matched on.
17077
+ category: DetectionCategory,
17078
+ // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
17079
+ // fingerprint, never the raw value, and never reversible. Matching recomputes
17080
+ // the fingerprint from a fresh capture; the value itself is never stored.
17081
+ // Shape-constrained so a malformed — or accidentally raw — value is rejected
17082
+ // at the boundary rather than persisted.
17083
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17084
+ // Version of the fingerprint key the grant was written under; a rotated key
17085
+ // invalidates old grants rather than silently mismatching them.
17086
+ keyVersion: external_exports.number().int().positive(),
17087
+ // maskMatch() preview of the approved value — never the raw value.
17088
+ maskedValue: external_exports.string(),
17089
+ capability: ExceptionCapability.default("suppress"),
17090
+ scope: ExceptionScope,
17091
+ expiresAt: external_exports.iso.datetime().nullable(),
17092
+ maxUses: external_exports.number().int().positive().nullable(),
17093
+ useCount: external_exports.number().int().nonnegative(),
17094
+ lastUsedAt: external_exports.iso.datetime().nullable(),
17095
+ // Mandatory: every grant carries the human reason it exists.
17096
+ justification: external_exports.string().min(1),
17097
+ conditions: ExceptionConditions.nullable(),
17098
+ createdBy: external_exports.string(),
17099
+ createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
17100
+ createdAt: external_exports.iso.datetime(),
17101
+ updatedAt: external_exports.iso.datetime(),
17102
+ // Revocation is terminal and retained — consumed/expired/revoked rows are
17103
+ // audit evidence; nothing in the exception lifecycle hard-deletes.
17104
+ revokedAt: external_exports.iso.datetime().nullable(),
17105
+ revokedBy: external_exports.string().nullable(),
17106
+ revokeReason: external_exports.string().nullable()
17107
+ });
17108
+ var ExceptionBundleEntry = DetectionException.pick({
17109
+ id: true,
17110
+ ruleId: true,
17111
+ valueFingerprint: true,
17112
+ keyVersion: true,
17113
+ capability: true,
17114
+ expiresAt: true,
17115
+ maxUses: true,
17116
+ useCount: true,
17117
+ conditions: true
17118
+ });
17119
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
17120
+
17248
17121
  // ../../packages/schema/src/zod/exception-action.ts
17249
17122
  var confirmation = external_exports.string().optional();
17250
17123
  var ApproveBlockedInput = external_exports.object({
@@ -17287,10 +17160,11 @@ function toApiAction(dbVal) {
17287
17160
  }
17288
17161
  function toApiCategory(dbVal) {
17289
17162
  if (dbVal === "code_context") return "source_code";
17290
- return dbVal;
17163
+ const parsed = FindingCategory.safeParse(dbVal);
17164
+ return parsed.success ? parsed.data : "custom";
17291
17165
  }
17292
17166
  function toApiProvider(sourceTool) {
17293
- return TOOL_TO_HARNESS[sourceTool] ?? "api";
17167
+ return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
17294
17168
  }
17295
17169
  var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
17296
17170
  function foldGroupStatus(instanceStatuses) {
@@ -17862,6 +17736,7 @@ var ListVaultDerefsResponse = external_exports.object({
17862
17736
  });
17863
17737
  var VaultKeyCustody = external_exports.string();
17864
17738
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17739
+ var VAULT_CONSENT_VERSION = 1;
17865
17740
  var VaultConsent = external_exports.object({
17866
17741
  acknowledgedAt: external_exports.iso.datetime(),
17867
17742
  version: external_exports.number().int().positive()
@@ -17870,7 +17745,13 @@ var VaultConsent = external_exports.object({
17870
17745
  // ../../packages/schema/src/zod/local.ts
17871
17746
  var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17872
17747
  var MODEL_JUDGE_PAYLOAD_VERSION = 1;
17873
- var RunMode = external_exports.enum(["standalone"]);
17748
+ var RunMode = external_exports.enum(["standalone", "attached"]);
17749
+ var ControlPlaneConnection = external_exports.object({
17750
+ endpoint: external_exports.string().min(1),
17751
+ // Display name for the deployment, shown instead of the raw endpoint.
17752
+ label: external_exports.string().min(1).optional(),
17753
+ attachedAt: external_exports.iso.datetime()
17754
+ }).meta({ id: "ControlPlaneConnection" });
17874
17755
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17875
17756
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
17876
17757
  var ModelJudgeConsent = external_exports.object({
@@ -17882,12 +17763,10 @@ function isModelJudgeConsentValid(consent) {
17882
17763
  }
17883
17764
  var WorkspaceSettings = external_exports.object({
17884
17765
  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
- ),
17766
+ runMode: RunMode.default("standalone"),
17767
+ // Present only while attached; a detach clears it. Its presence is what makes
17768
+ // `runMode: 'attached'` mean anything — see isAttached.
17769
+ controlPlane: ControlPlaneConnection.optional(),
17891
17770
  policy: SimpleDetectionPolicy.default("redact"),
17892
17771
  // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
17893
17772
  historicalAccess: HistoricalAccess.default("session-only"),
@@ -17974,54 +17853,264 @@ function toInspectionDefinitionRow(input, id) {
17974
17853
  version: input.version
17975
17854
  };
17976
17855
  }
17977
- function toInspectionFindingRow(input) {
17978
- return {
17979
- id: input.id,
17980
- auditEventId: input.auditEventId,
17981
- inspectionDefinitionId: input.inspectionDefinitionId,
17982
- classifiedDataId: input.classifiedDataId ?? null,
17983
- spanStart: input.span.start,
17984
- spanEnd: input.span.end,
17985
- maskedMatch: input.maskedMatch,
17986
- actionTaken: input.actionTaken,
17987
- confidence: input.confidence,
17988
- findingKey: input.findingKey ?? null,
17989
- firstDetectedAt: input.firstDetectedAt ? isoToEpochMillis(input.firstDetectedAt) : null
17990
- };
17856
+ function toInspectionFindingRow(input) {
17857
+ return {
17858
+ id: input.id,
17859
+ auditEventId: input.auditEventId,
17860
+ inspectionDefinitionId: input.inspectionDefinitionId,
17861
+ classifiedDataId: input.classifiedDataId ?? null,
17862
+ spanStart: input.span.start,
17863
+ spanEnd: input.span.end,
17864
+ maskedMatch: input.maskedMatch,
17865
+ actionTaken: input.actionTaken,
17866
+ confidence: input.confidence,
17867
+ findingKey: input.findingKey ?? null,
17868
+ firstDetectedAt: input.firstDetectedAt ? isoToEpochMillis(input.firstDetectedAt) : null
17869
+ };
17870
+ }
17871
+ function toCaptureAttributes(event) {
17872
+ const metadata = event.metadata;
17873
+ return {
17874
+ source_tool: event.sourceTool,
17875
+ ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
17876
+ ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
17877
+ ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
17878
+ ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
17879
+ ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
17880
+ ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
17881
+ ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
17882
+ ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
17883
+ // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
17884
+ // has ever populated either), but every legacy metadata key still rides
17885
+ // the bag rather than being silently dropped — CaptureAttributes'
17886
+ // `.catchall(z.unknown())` carries the long tail.
17887
+ ...metadata?.model !== void 0 ? { model: metadata.model } : {},
17888
+ ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
17889
+ };
17890
+ }
17891
+ function captureDefinitionVersion(finding) {
17892
+ return `capture/${finding.category}/${finding.severity}`;
17893
+ }
17894
+ function toCaptureDefinitionInput(finding) {
17895
+ return {
17896
+ ruleId: finding.ruleId,
17897
+ version: captureDefinitionVersion(finding),
17898
+ name: finding.ruleId,
17899
+ category: finding.category,
17900
+ severity: finding.severity,
17901
+ definition: JSON.stringify({ ruleId: finding.ruleId })
17902
+ };
17903
+ }
17904
+
17905
+ // ../../packages/schema/src/zod/managed.ts
17906
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
17907
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
17908
+ var ManagedSettingKey = external_exports.enum([
17909
+ "runMode",
17910
+ "historicalAccess",
17911
+ "vaultConsent",
17912
+ "vaultKeyCustody",
17913
+ "vaultInlineReveal",
17914
+ "modelJudgeConsent",
17915
+ "dataSharesInPlace"
17916
+ ]).meta({ id: "ManagedSettingKey" });
17917
+ var ManagedSettingsValues = external_exports.object({
17918
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
17919
+ controlPlane: external_exports.object({
17920
+ endpoint: external_exports.string().min(1),
17921
+ label: external_exports.string().min(1).optional()
17922
+ }).optional(),
17923
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
17924
+ vaultConsent: external_exports.boolean().optional(),
17925
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
17926
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
17927
+ modelJudgeConsent: external_exports.boolean().optional(),
17928
+ dataSharesInPlace: external_exports.boolean().optional()
17929
+ }).meta({ id: "ManagedSettingsValues" });
17930
+ var ManagedSettings = external_exports.object({
17931
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
17932
+ // Shown on every locked control, so the user can tell an administrative
17933
+ // decision from a bug. Absent renders as a generic "your organization".
17934
+ organization: external_exports.string().min(1).optional(),
17935
+ // What the administrator pinned.
17936
+ values: ManagedSettingsValues.default({}),
17937
+ // Which of those the user may not change. A key here with no matching value
17938
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
17939
+ // the user may still override. The two are separable on purpose.
17940
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
17941
+ }).meta({ id: "ManagedSettings" });
17942
+
17943
+ // ../../packages/schema/src/zod/policy.ts
17944
+ var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
17945
+ var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
17946
+ var Policy = external_exports.object({
17947
+ id: external_exports.guid(),
17948
+ scope: PolicyScope,
17949
+ target: PolicyTarget,
17950
+ action: ActionTaken,
17951
+ enabled: external_exports.boolean().default(true),
17952
+ customKeywords: external_exports.array(external_exports.string()).optional(),
17953
+ // Display name — optional so older policy rows without name still parse.
17954
+ // Added for the findings API (policy.name column migration).
17955
+ name: external_exports.string().optional()
17956
+ }).meta({ id: "Policy" });
17957
+ var PolicyBundle = external_exports.object({
17958
+ version: external_exports.string(),
17959
+ policies: external_exports.array(Policy),
17960
+ // Rules from the installed marketplace packs (snapshotted by the
17961
+ // control plane). The plugin registers these in addition to its bundled
17962
+ // packs. Optional so older backends — and older on-disk caches — that omit
17963
+ // the field still parse; consumers read `bundle.rules ?? []`.
17964
+ rules: external_exports.array(Rule).optional(),
17965
+ // When true, `rules` IS the complete effective ruleset and the runtime must
17966
+ // NOT merge its compiled-in bundled packs — the standalone gateway sets this
17967
+ // after reading the user's installed snapshot (installed_packs, enabled
17968
+ // packs only), which is how detection updates stay manual: new bundled
17969
+ // rules run only after the user applies the pack update. Absent/false keeps
17970
+ // the historical composition (bundled packs + rules) — older caches.
17971
+ rulesComplete: external_exports.boolean().optional(),
17972
+ // Active detection exceptions, evaluation subset only (see
17973
+ // ExceptionBundleEntry). Optional so older bundle producers — and older
17974
+ // on-disk caches — that omit the field still parse; consumers read
17975
+ // `bundle.exceptions ?? []`.
17976
+ exceptions: external_exports.array(ExceptionBundleEntry).optional(),
17977
+ // Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
17978
+ // A second axis over the same `redact` action, carried beside the policies
17979
+ // rather than on them: nothing writes ruleId-targeted policies to disk, so
17980
+ // widening Policy itself would change a persisted shape to express something
17981
+ // only the in-memory bundle needs. Optional so an older producer — or an
17982
+ // older on-disk cache — still parses; consumers read `?? []` and get the
17983
+ // pre-existing one-way behaviour, which is the safe direction to default.
17984
+ reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
17985
+ // Installed pack version, keyed by ruleId, for rules in `rules` that came
17986
+ // from a versioned installed pack. Optional so older backends — and older
17987
+ // on-disk caches — that omit the field still parse; consumers fall back to
17988
+ // the rule's own spec version. NOT the bundle version above — see
17989
+ // installedRuleset's ruleVersions for the source of truth.
17990
+ ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
17991
+ customKeywords: external_exports.array(external_exports.string()),
17992
+ fetchedAt: external_exports.iso.datetime()
17993
+ }).meta({ id: "PolicyBundle" });
17994
+ var OBSERVE_ONLY_CATEGORIES = ["config"];
17995
+ var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
17996
+ var CATEGORY_PEAK_SEVERITY = {
17997
+ secret: "critical",
17998
+ financial: "critical",
17999
+ // core-financial/credit-card
18000
+ code_flaw: "critical",
18001
+ pii: "high",
18002
+ phi: "high",
18003
+ custom: "high",
18004
+ // user-defined; conservative
18005
+ code_context: "low",
18006
+ config: "low"
18007
+ // observe-only; floors to monitor regardless
18008
+ };
18009
+ function severityFloorPolicy(category) {
18010
+ if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
18011
+ const peak = CATEGORY_PEAK_SEVERITY[category];
18012
+ return peak === "critical" || peak === "high" ? "warn" : "monitor";
18013
+ }
18014
+ function severityFloorPosture() {
18015
+ const out = {};
18016
+ for (const c of DetectionCategory.options) out[c] = severityFloorPolicy(c);
18017
+ return out;
17991
18018
  }
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
- };
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;
18011
18057
  }
18012
- function captureDefinitionVersion(finding) {
18013
- return `capture/${finding.category}/${finding.severity}`;
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;
18014
18067
  }
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
- };
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);
18072
+ }
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,
@@ -18514,8 +18589,11 @@ function chmodBestEffort(path, mode) {
18514
18589
  function tightenDir(dir) {
18515
18590
  chmodBestEffort(dir, DATA_DIR_MODE);
18516
18591
  }
18592
+ function mkdirOwnerOnlySync(dir, recursive = false) {
18593
+ mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
18594
+ }
18517
18595
  function ensureDataDirSync(dir) {
18518
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18596
+ mkdirOwnerOnlySync(dir, true);
18519
18597
  tightenDir(dir);
18520
18598
  }
18521
18599
  function dbSidecars(file2) {
@@ -18533,6 +18611,25 @@ function backupPath(file2, tag) {
18533
18611
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18534
18612
  }
18535
18613
  var STALE_PARTIAL_MS = 5 * 6e4;
18614
+ var SNAPSHOT_STAGING_SUFFIX = ".partial";
18615
+ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18616
+ var SNAPSHOT_STAGING_COPY = "copy";
18617
+ function createSnapshotStaging(backup) {
18618
+ const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18619
+ rmSync2(stage, { recursive: true, force: true });
18620
+ mkdirOwnerOnlySync(stage);
18621
+ tightenDir(stage);
18622
+ return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18623
+ }
18624
+ function idleMs(entry) {
18625
+ for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18626
+ try {
18627
+ return Date.now() - statSync(candidate).mtimeMs;
18628
+ } catch {
18629
+ }
18630
+ }
18631
+ return null;
18632
+ }
18536
18633
  function reapStalePartials(file2) {
18537
18634
  const dir = dirname(file2);
18538
18635
  const prefix = `${basename(file2)}.`;
@@ -18543,30 +18640,34 @@ function reapStalePartials(file2) {
18543
18640
  return;
18544
18641
  }
18545
18642
  for (const name of entries) {
18546
- if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18547
- const partial2 = join(dir, name);
18643
+ if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18644
+ const staging = join(dir, name);
18548
18645
  try {
18549
- if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18550
- rmSync2(partial2, { force: true });
18646
+ const idle = idleMs(staging);
18647
+ if (idle !== null && idle > STALE_PARTIAL_MS) {
18648
+ rmSync2(staging, { recursive: true, force: true });
18551
18649
  }
18552
18650
  } catch {
18553
18651
  }
18554
18652
  }
18555
18653
  }
18556
18654
  function snapshotStore(db, backup) {
18557
- const partial2 = `${backup}.partial`;
18655
+ const { stage, copy } = createSnapshotStaging(backup);
18558
18656
  try {
18559
- rmSync2(partial2, { force: true });
18560
- db.prepare("VACUUM INTO ?").run(partial2);
18561
- tightenFile(partial2);
18562
- renameSync2(partial2, backup);
18657
+ db.prepare("VACUUM INTO ?").run(copy);
18658
+ tightenFile(copy);
18659
+ renameSync2(copy, backup);
18563
18660
  } catch (error51) {
18564
18661
  try {
18565
- rmSync2(partial2, { force: true });
18662
+ rmSync2(stage, { recursive: true, force: true });
18566
18663
  } catch {
18567
18664
  }
18568
18665
  throw error51;
18569
18666
  }
18667
+ try {
18668
+ rmSync2(stage, { recursive: true, force: true });
18669
+ } catch {
18670
+ }
18570
18671
  }
18571
18672
  function moveStoreAside(file2, backup) {
18572
18673
  const undo = [];
@@ -19294,9 +19395,10 @@ function safeParseStringArray(raw) {
19294
19395
  const parsed = safeJson(raw, null);
19295
19396
  return Array.isArray(parsed) ? parsed : [];
19296
19397
  }
19398
+ var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19297
19399
  function toHarness(raw) {
19298
19400
  const parsed = Harness.safeParse(raw);
19299
- return parsed.success ? parsed.data : "claudecode";
19401
+ return parsed.success ? parsed.data : DEFAULT_HARNESS;
19300
19402
  }
19301
19403
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19302
19404
  if (row.status) {
@@ -19427,7 +19529,7 @@ var SqliteActivityRepository = class {
19427
19529
  const params = [];
19428
19530
  if (query.harness && query.harness.length > 0) {
19429
19531
  conditions.push(
19430
- `coalesce(json_extract(attributes, '$.harness'), 'claudecode') IN (${placeholders(query.harness.length)})`
19532
+ `coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
19431
19533
  );
19432
19534
  params.push(...query.harness);
19433
19535
  }
@@ -19634,13 +19736,13 @@ var SqliteActivityRepository = class {
19634
19736
  * The DISTINCT harnesses that actually have sessions (optionally within a
19635
19737
  * `started_at >= fromMs` window), so the filter can offer only the harnesses
19636
19738
  * present rather than the full enum. Each stored value is normalized through
19637
- * the SAME `toHarness` default the list uses (missing → 'claudecode'), so a
19638
- * store of bare (harness-less) roots surfaces exactly `['claudecode']`.
19739
+ * the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
19740
+ * a store of bare (harness-less) roots surfaces exactly that one harness.
19639
19741
  */
19640
19742
  harnessFacets(fromMs) {
19641
19743
  const where = fromMs === void 0 ? "" : " AND started_at >= ?";
19642
19744
  const stmt = this.db.prepare(
19643
- `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), 'claudecode') AS harness
19745
+ `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
19644
19746
  FROM audit_events WHERE ${SESSION_ROOT}${where}`
19645
19747
  );
19646
19748
  const rows = allRows(
@@ -19854,8 +19956,8 @@ var SqliteAuditEventsRepository = class {
19854
19956
  }
19855
19957
  // Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
19856
19958
  // (which takes a caller-supplied random id), the id here is MINTED internally
19857
- // from the natural key — `llmCallId(sessionId, messageId)` — tenant-free like the
19858
- // sibling local-store ids (the local store is single-tenant). The deterministic
19959
+ // from the natural key — `llmCallId(sessionId, messageId)` — derived from the
19960
+ // session and message alone, like the sibling local-store ids. The deterministic
19859
19961
  // id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
19860
19962
  // AND converge a streaming partial/final split across two incremental passes:
19861
19963
  // a whole-file re-read no-ops (equal output), a lagging final replaces a
@@ -20820,6 +20922,42 @@ var SqliteFindingsRepository = class {
20820
20922
  this.db = db;
20821
20923
  }
20822
20924
  db;
20925
+ /**
20926
+ * The newest `limit` findings, newest first.
20927
+ *
20928
+ * THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
20929
+ * pin it. The natural spelling — drive from `inspection_findings`, order by the
20930
+ * JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
20931
+ * not on the driving table: SQLite sorts every finding in the store through a
20932
+ * temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
20933
+ * against 0.9 ms for the form below, and the gap is a ratio of the store size
20934
+ * rather than a constant.
20935
+ *
20936
+ * What it takes to make `started_at` order come out of an index instead:
20937
+ *
20938
+ * - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
20939
+ * planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
20940
+ * index cannot serve the ORDER BY: the predicate spans four event types, so
20941
+ * satisfying a global `started_at` order across them needs a range merge
20942
+ * SQLite will not do, and it sorts instead. Freed of it, the planner scans
20943
+ * `idx_audit_started_at` — a bare `started_at` index — in DESC order and
20944
+ * filters the type per row, which lets the LIMIT stop the scan early.
20945
+ * - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
20946
+ * purely to stop the tables being reordered. With plain JOINs the planner
20947
+ * drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
20948
+ * unary plus ALONE recovers almost none of the win. Both are needed.
20949
+ *
20950
+ * Neither is a micro-optimisation that a later reader should tidy away, and
20951
+ * `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
20952
+ * the temp B-tree comes back.
20953
+ *
20954
+ * Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
20955
+ * identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
20956
+ * renaming the index turns this read into an ERROR, where `+` turns it into a
20957
+ * scan-and-sort — slower, still correct. The worst case for the chosen form is
20958
+ * a store whose recent captures carry no findings at all, where the scan walks
20959
+ * the whole index; that is still no worse than the full sort it replaced.
20960
+ */
20823
20961
  recentFindings(opts) {
20824
20962
  const limit = opts?.limit ?? 50;
20825
20963
  const rows = allRows(
@@ -20828,10 +20966,10 @@ var SqliteFindingsRepository = class {
20828
20966
  f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
20829
20967
  json_extract(e.attributes, '$.source_tool') AS source_tool,
20830
20968
  e.event_type AS kind
20831
- FROM inspection_findings f
20832
- JOIN audit_events e ON e.id = f.audit_event_id
20833
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20834
- WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
20969
+ FROM audit_events e
20970
+ CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
20971
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20972
+ WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
20835
20973
  ORDER BY e.started_at DESC, f.rowid DESC
20836
20974
  LIMIT :limit`
20837
20975
  ),
@@ -21475,7 +21613,8 @@ var SqliteInspectionDefinitionsRepository = class {
21475
21613
  }
21476
21614
  db;
21477
21615
  insertStmt;
21478
- // Idempotent upsert; returns the content-addressed definition id.
21616
+ // Insert-if-absent; returns the content-addressed definition id. An id already
21617
+ // present keeps the stored row untouched — see the class doc.
21479
21618
  upsert(input) {
21480
21619
  const id = inspectionDefinitionId(input.ruleId, input.version);
21481
21620
  const row = toInspectionDefinitionRow(input, id);
@@ -21619,11 +21758,23 @@ function isParseableBinaryVersion(version2) {
21619
21758
 
21620
21759
  // ../../packages/persistence/src/repositories/installed-packs.ts
21621
21760
  var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
21761
+ function printableRuleId(entry) {
21762
+ if (typeof entry !== "object" || entry === null) return null;
21763
+ const candidate = entry.id;
21764
+ return Rule.shape.id.safeParse(candidate).success ? candidate : null;
21765
+ }
21766
+ function firstIssueReason(error51) {
21767
+ const issue2 = error51.issues[0];
21768
+ if (!issue2) return "unknown";
21769
+ const path = issue2.path.map((segment) => String(segment)).join(".");
21770
+ return path ? `${path}: ${issue2.code}` : issue2.code;
21771
+ }
21772
+ var REJECTED_RULE_DETAIL_CAP = 10;
21622
21773
  function inventorySignature(packs2) {
21623
21774
  return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
21624
21775
  }
21625
21776
  function hashRules(rulesJson) {
21626
- return createHash2("sha1").update(rulesJson).digest("hex");
21777
+ return createHash2("sha256").update(rulesJson).digest("hex");
21627
21778
  }
21628
21779
  function parseVersion(v) {
21629
21780
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -21835,10 +21986,21 @@ var SqliteInstalledPacksRepository = class {
21835
21986
  * (all detection off) instead of falling back to the bundled packs. Every
21836
21987
  * JSON-level failure therefore counts as invalid.
21837
21988
  */
21989
+ /**
21990
+ * ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
21991
+ * index is (namespace, pack_id) — so two enabled packs may contribute the same
21992
+ * id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
21993
+ * winner is whatever order SQLite happens to return, which makes a collision
21994
+ * resolve differently on two machines holding identical stores. Ordering by
21995
+ * (namespace, pack_id) makes the loser deterministic and therefore testable.
21996
+ */
21838
21997
  installedRuleset() {
21839
21998
  const rows = allRows(
21840
21999
  this.db.prepare(
21841
- `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version FROM installed_packs`
22000
+ `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
22001
+ namespace, pack_id AS packId
22002
+ FROM installed_packs
22003
+ ORDER BY namespace, pack_id`
21842
22004
  )
21843
22005
  );
21844
22006
  const out = {
@@ -21846,22 +22008,32 @@ var SqliteInstalledPacksRepository = class {
21846
22008
  enabledPacks: 0,
21847
22009
  rules: [],
21848
22010
  invalidRules: 0,
22011
+ rejectedRules: [],
21849
22012
  ruleActions: /* @__PURE__ */ new Map(),
21850
- ruleVersions: /* @__PURE__ */ new Map()
22013
+ ruleVersions: /* @__PURE__ */ new Map(),
22014
+ reversibleRules: /* @__PURE__ */ new Set()
22015
+ };
22016
+ const reject = (pack, ruleId, reason) => {
22017
+ if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
22018
+ out.rejectedRules.push({ pack, ruleId, reason });
21851
22019
  };
21852
22020
  for (const row of rows) {
21853
22021
  if (!intToBool(row.enabled)) continue;
21854
22022
  out.enabledPacks += 1;
21855
22023
  const action = policyIdToAction(row.policyId);
22024
+ const reversible = policyIdIsReversible(row.policyId);
22025
+ const pack = `${row.namespace}/${row.packId}`;
21856
22026
  let raw;
21857
22027
  try {
21858
22028
  raw = JSON.parse(row.rulesJson);
21859
22029
  } catch {
21860
22030
  out.invalidRules += 1;
22031
+ reject(pack, null, "rules_json: malformed JSON");
21861
22032
  continue;
21862
22033
  }
21863
22034
  if (!Array.isArray(raw)) {
21864
22035
  out.invalidRules += 1;
22036
+ reject(pack, null, "rules_json: not an array");
21865
22037
  continue;
21866
22038
  }
21867
22039
  for (const entry of raw) {
@@ -21870,7 +22042,12 @@ var SqliteInstalledPacksRepository = class {
21870
22042
  out.rules.push(parsed.data);
21871
22043
  out.ruleActions.set(parsed.data.id, action);
21872
22044
  out.ruleVersions.set(parsed.data.id, row.version);
21873
- } else out.invalidRules += 1;
22045
+ if (reversible) out.reversibleRules.add(parsed.data.id);
22046
+ else out.reversibleRules.delete(parsed.data.id);
22047
+ } else {
22048
+ out.invalidRules += 1;
22049
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22050
+ }
21874
22051
  }
21875
22052
  }
21876
22053
  return out;
@@ -22064,31 +22241,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
22064
22241
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
22065
22242
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
22066
22243
  var HARNESS_LABELS = {
22067
- claudecode: "Claude Code",
22068
- cursor: "Cursor",
22069
- codex: "Codex",
22070
- antigravity: "Antigravity"
22244
+ [HARNESS.ClaudeCode]: "Claude Code",
22245
+ [HARNESS.Cursor]: "Cursor",
22246
+ [HARNESS.Codex]: "Codex",
22247
+ [HARNESS.Antigravity]: "Antigravity"
22071
22248
  };
22072
22249
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
22073
22250
  var EMPTY_PROJECT_AGG = {
22074
22251
  accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
22075
22252
  findingsCount: 0
22076
22253
  };
22254
+ var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
22255
+ var TITLE_NEEDLES = {
22256
+ ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
22257
+ Cursor: stripSeparators(SOURCE_TOOL.Cursor),
22258
+ Codex: stripSeparators(SOURCE_TOOL.Codex),
22259
+ Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
22260
+ };
22077
22261
  function resolveHarnessId(attrs, row) {
22078
22262
  if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
22079
22263
  return attrs.provider;
22080
22264
  }
22081
- const t = (row.title ?? "").toLowerCase().replace(/[\s-]/g, "");
22082
- if (t.includes("claudecode") || t === "claude") return "claudecode";
22083
- if (t.includes("cursor")) return "cursor";
22084
- if (t.includes("codex")) return "codex";
22085
- if (t.includes("antigravity")) return "antigravity";
22265
+ const t = stripSeparators(row.title ?? "");
22266
+ if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
22267
+ if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
22268
+ if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
22269
+ if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
22086
22270
  return null;
22087
22271
  }
22088
22272
  function isLiveRealClaudeCode(rows) {
22089
22273
  return rows.some((r) => {
22090
22274
  const attrs = safeJson(r.attributes, {});
22091
- return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === "claudecode";
22275
+ return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
22092
22276
  });
22093
22277
  }
22094
22278
  function toAssetSummary(row) {
@@ -22356,7 +22540,7 @@ var SqliteInventoryAssetsRepository = class {
22356
22540
  const isRealHarness = rows.some(
22357
22541
  (r) => safeJson(r.attributes, {}).provenance !== "sample"
22358
22542
  );
22359
- const attachConfig = isRealHarness && harnessId === "claudecode" && configAssets.length > 0;
22543
+ const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
22360
22544
  const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
22361
22545
  if (q && assets.length === 0) continue;
22362
22546
  const firstRow = rows[0];
@@ -23030,9 +23214,10 @@ var SqliteProjectFilesRepository = class {
23030
23214
  // ../../packages/persistence/src/repositories/resolutions.ts
23031
23215
  import { randomUUID as randomUUID7 } from "crypto";
23032
23216
  var SqliteResolutionsRepository = class {
23033
- constructor(db, now = () => Date.now()) {
23217
+ constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
23034
23218
  this.db = db;
23035
23219
  this.now = now;
23220
+ this.newId = newId;
23036
23221
  this.insertStmt = db.prepare(
23037
23222
  `INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
23038
23223
  VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
@@ -23065,12 +23250,14 @@ var SqliteResolutionsRepository = class {
23065
23250
  }
23066
23251
  db;
23067
23252
  now;
23253
+ newId;
23068
23254
  insertStmt;
23069
23255
  latestStmt;
23070
23256
  openAtRestStmt;
23071
23257
  resolvedAtRestStmt;
23072
23258
  /**
23073
- * Insert one disposition row. The repo mints the id and stamps created_at.
23259
+ * Insert one disposition row. The repo mints the id and stamps created_at,
23260
+ * both through the constructor's injectable seams.
23074
23261
  * `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
23075
23262
  * FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
23076
23263
  * from the schema enums. NOTE for future manual-resolution writers: this is
@@ -23082,7 +23269,7 @@ var SqliteResolutionsRepository = class {
23082
23269
  */
23083
23270
  insertResolution(r) {
23084
23271
  this.insertStmt.run({
23085
- id: randomUUID7(),
23272
+ id: this.newId(),
23086
23273
  findingKey: r.findingKey,
23087
23274
  status: FindingStatus.parse(r.status),
23088
23275
  method: ResolutionMethod.parse(r.method),
@@ -23661,16 +23848,16 @@ var ACTION_TO_KIND = {
23661
23848
  warn: "warned"
23662
23849
  };
23663
23850
  var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
23664
- var SCAN_COVERAGE = [
23665
- { provider: "claudecode", coverage: 100, supported: true },
23666
- { provider: "cursor", coverage: 0, supported: false },
23667
- { provider: "codex", coverage: 80, supported: true },
23668
- { provider: "antigravity", coverage: 60, supported: true },
23669
- { provider: "claudeai", coverage: 0, supported: false },
23670
- { provider: "chatgpt", coverage: 0, supported: false },
23671
- { provider: "copilot", coverage: 0, supported: false },
23672
- { provider: "api", coverage: 0, supported: false }
23673
- ];
23851
+ var SCAN_COVERAGE = {
23852
+ [HARNESS.Antigravity]: { coverage: 60, supported: true },
23853
+ [HARNESS.Api]: { coverage: 0, supported: false },
23854
+ [HARNESS.ChatGpt]: { coverage: 40, supported: true },
23855
+ [HARNESS.ClaudeAi]: { coverage: 40, supported: true },
23856
+ [HARNESS.ClaudeCode]: { coverage: 100, supported: true },
23857
+ [HARNESS.Codex]: { coverage: 80, supported: true },
23858
+ [HARNESS.Copilot]: { coverage: 0, supported: false },
23859
+ [HARNESS.Cursor]: { coverage: 0, supported: false }
23860
+ };
23674
23861
  var GRANULARITY = {
23675
23862
  "7d": "day",
23676
23863
  "30d": "day",
@@ -23769,9 +23956,22 @@ var SqliteSecurityRepository = class {
23769
23956
  return Promise.resolve({ total, needsRemediation, bySeverity });
23770
23957
  }
23771
23958
  // Range is echoed but does not change the result today — coverage is a constant
23772
- // business fact (see SCAN_COVERAGE), not a measured per-window metric.
23959
+ // business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
23960
+ // comes from Provider.options (the enum's declaration order), not from
23961
+ // SCAN_COVERAGE's own key order — deliberately, not because object literals
23962
+ // leave key order unspecified (ES2015 guarantees insertion order for these
23963
+ // non-integer string keys, so iterating SCAN_COVERAGE directly would be
23964
+ // reliable too). The reason is the schema comment's promise: the returned
23965
+ // order must mirror the generated OpenAPI enum list, which is Provider's
23966
+ // contract, not this table's.
23773
23967
  scanCoverage(range) {
23774
- return Promise.resolve({ range, providers: SCAN_COVERAGE.map((p) => ({ ...p })) });
23968
+ return Promise.resolve({
23969
+ range,
23970
+ providers: Provider.options.map((provider) => ({
23971
+ provider,
23972
+ ...SCAN_COVERAGE[provider]
23973
+ }))
23974
+ });
23775
23975
  }
23776
23976
  enforcementActions(range) {
23777
23977
  const lenMs = RANGE_DAYS[range] * DAY_MS4;
@@ -23829,10 +24029,10 @@ var SqliteSecurityRepository = class {
23829
24029
  // count; a superseding open/redetected row means the finding is not
23830
24030
  // remediated and is excluded, same invariant as severitySummary. Legacy
23831
24031
  // at-rest findings with finding_key IS NULL can never have a resolution row
23832
- // (the lifecycle is keyed by finding_key), so the SQL guard excludes them
23833
- // outright. One raw-row query (fetch every trackable finding + its latest
23834
- // resolution's status/method/resolved_at) + pure-JS filter/bucket/mean,
23835
- // mirroring this file's other methods.
24032
+ // (the lifecycle is keyed by finding_key), so they cannot reach the driving
24033
+ // set below. One raw-row query (fetch the findings with resolution activity in
24034
+ // the window + each one's latest resolution status/method/resolved_at) +
24035
+ // pure-JS filter/bucket/mean, mirroring this file's other methods.
23836
24036
  mttrTrend(range) {
23837
24037
  const granularity = granularityFor(range);
23838
24038
  const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
@@ -23848,29 +24048,80 @@ var SqliteSecurityRepository = class {
23848
24048
  // started_at the upsert overwrites onto inspection_findings.audit_event_id.
23849
24049
  // COALESCE onto the parent event's started_at defends against any
23850
24050
  // legacy/edge row the backfill left null.
23851
- `SELECT COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
24051
+ `SELECT DISTINCT f.finding_key AS finding_key,
24052
+ COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
23852
24053
  latest.status AS latest_status,
23853
24054
  latest.method AS latest_method,
23854
24055
  latest.resolved_at AS latest_resolved_at
23855
- FROM inspection_findings f
23856
- JOIN audit_events e ON e.id = f.audit_event_id
23857
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24056
+ FROM finding_resolution fr
24057
+ CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
24058
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24059
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
23858
24060
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
23859
24061
  ON latest.finding_key = f.finding_key
23860
- WHERE f.finding_key IS NOT NULL
23861
- AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
23862
- AND EXISTS (
23863
- SELECT 1 FROM finding_resolution fr
23864
- WHERE fr.finding_key = f.finding_key
23865
- AND fr.resolved_at >= :windowStart
23866
- )`
23867
- // The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
23868
- // any resolution activity at/after the window start — a row this method
24062
+ WHERE fr.resolved_at >= :windowStart
24063
+ AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
24064
+ // `fr` is a SUPERSET prefilter, not the answer: a finding this method
23869
24065
  // ultimately counts has its LATEST resolution inside the window, which
23870
- // implies such a row exists, so nothing wanted is dropped. The exact
23871
- // latest-wins + status/method + window gate stays in JS below,
23872
- // dialect-agnostic. Without this, a
23873
- // 7d request evaluated the store's entire trackable-findings history.
24066
+ // implies a resolution row at/after the window start exists, so nothing
24067
+ // wanted is dropped. The exact latest-wins + status/method + window gate
24068
+ // stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
24069
+ // implied rather than dropped — the join key comes from
24070
+ // finding_resolution, whose finding_key is NOT NULL.
24071
+ //
24072
+ // IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
24073
+ // the correction this replaced. Spelled as an `EXISTS` in the WHERE it
24074
+ // READ as a bound and was not one: SQLite drove from `audit_events` on
24075
+ // event_type, joined every capture event to its findings, and evaluated
24076
+ // the EXISTS last — bounding the RESULT and not the scan, so a 7d request
24077
+ // still cost the store's whole trackable history. Measured at 44.6 ms on
24078
+ // 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
24079
+ // both cases returning rows for a window holding a fraction of it.
24080
+ //
24081
+ // Two things carry it, and they answer DIFFERENT halves — which is worth
24082
+ // stating precisely, because the obvious reading (both are needed for the
24083
+ // speed) is wrong and was measured to be wrong:
24084
+ //
24085
+ // - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
24086
+ // keyword is semantically identical to JOIN and exists only to stop the
24087
+ // tables being reordered; with plain JOINs the planner puts `e` back on
24088
+ // the outside, because with no ANALYZE statistics it prices
24089
+ // `event_type IN (...)` as a selective probe. Reverting it alone takes
24090
+ // the 2k->20k flatness ratio from 1.32 to 16.87.
24091
+ // - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
24092
+ // `resolved_at >= :windowStart` a range SEARCH instead of a bare
24093
+ // `SCAN fr` — finding_key was this table's only index before it, so the
24094
+ // range had none. It buys NO flatness in store size: remove it and the
24095
+ // ratio above does not move, because the latest-resolution derived
24096
+ // table already passes over the whole of finding_resolution, so this
24097
+ // read is O(resolutions) either way and resolutions are not the store.
24098
+ // What it buys is the criterion `hot-read-query-plans.test.ts` enforces
24099
+ // — no hot read may pass over a table with no index — and that is the
24100
+ // guard that goes red when it is dropped. Neither test catches the
24101
+ // other's defect.
24102
+ //
24103
+ // SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
24104
+ // tidy-up. finding_resolution is append-only, so a key that was fixed,
24105
+ // redetected and fixed again carries several rows inside one window and
24106
+ // matches once per row — and the value below is a MEAN, so a key matched
24107
+ // three times is a key weighted three times.
24108
+ //
24109
+ // The skew is easy to argue away and the argument is wrong, so it is worth
24110
+ // recording. Duplicate rows for ONE key are identical (every projected
24111
+ // column is per-key: `latest.*` is latest-wins, `first_detected_at` is
24112
+ // preserved), so sums and counts scale together and that key's own mean
24113
+ // does not move. What moves is a bucket holding TWO findings that duplicate
24114
+ // UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
24115
+ // 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
24116
+ // `security.test.ts`'s "weights a finding ONCE however many resolution rows
24117
+ // it has inside the window" — which needed a fixture built for it, since no
24118
+ // single-key case can show it.
24119
+ //
24120
+ // `finding_key` is selected to make the DISTINCT dedup by KEY rather than
24121
+ // by value tuple. On the other columns alone, two genuinely different
24122
+ // findings sharing a severity, a first-detection event and a resolution
24123
+ // instant — one commit fixing two secrets in one file — are one tuple, and
24124
+ // collapsing them would under-count in the other direction.
23874
24125
  ),
23875
24126
  { windowStart }
23876
24127
  );
@@ -23928,16 +24179,47 @@ var SqliteSecurityRepository = class {
23928
24179
  }
23929
24180
  // Recently-resolved activity feed: findings whose finding_key's LATEST
23930
24181
  // finding_resolution row is status:'resolved'/method:'fixed-at-source' —
23931
- // same latest-resolution-wins correlated subquery as severitySummary /
23932
- // mttrTrend (NOT a plain JOIN, which would surface every historical
23933
- // resolution row for a key rather than just its current disposition). A key
23934
- // whose latest row is a superseding 'open'/'redetected' row (the same
23935
- // secret came back) is excluded — it is not currently resolved. Legacy
23936
- // at-rest findings with finding_key IS NULL are excluded outright (the
23937
- // resolution lifecycle can never attach to them). Path comes from the
23938
- // finding's parent event (event_type 'code_change', attributes.file_path) —
23939
- // mirrors resolutions.ts's openAtRestStmt accessor. Ordered by resolved_at
23940
- // DESC, capped at `limit`.
24182
+ // same latest-resolution-wins derived table as severitySummary / mttrTrend
24183
+ // (NOT a plain JOIN, which would surface every historical resolution row for
24184
+ // a key rather than just its current disposition). A key whose latest row is
24185
+ // a superseding 'open'/'redetected' row (the same secret came back) is
24186
+ // excluded — it is not currently resolved. Legacy at-rest findings with
24187
+ // finding_key IS NULL are excluded outright (the resolution lifecycle can
24188
+ // never attach to them). Path comes from the finding's parent event
24189
+ // (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
24190
+ // openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
24191
+ //
24192
+ // THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
24193
+ // the plan rather than a preference. Written the other way round — driving
24194
+ // from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
24195
+ // cannot use the join key: `f` is reached FROM `latest` by finding_key, so
24196
+ // `latest` gets probed on (rn, status, method) instead and the plan enumerates
24197
+ // every (code_change event x resolved key) pair before `f` can reject it. That
24198
+ // is a cross product, and it is quadratic in the store: measured at 10,966 ms
24199
+ // on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
24200
+ // returned. It was invisible for as long as it was, and reported at 8 ms,
24201
+ // because an empty finding_resolution table makes the inner side empty and the
24202
+ // cross product collapses to nothing — so the shape is only observable on a
24203
+ // corpus that seeds resolutions.
24204
+ //
24205
+ // Driving from `latest` instead makes every step below it a unique-index or
24206
+ // primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
24207
+ // so the cost is the derived table's own — linear in resolutions, which is
24208
+ // what this feed is legitimately about.
24209
+ //
24210
+ // CROSS JOIN is what actually pins that, and it is load-bearing rather than
24211
+ // decorative: in SQLite the keyword is semantically identical to JOIN and
24212
+ // exists only to stop the optimizer reordering the tables. Written as plain
24213
+ // JOINs in this order the planner puts `e` back on the outside — it has no
24214
+ // ANALYZE statistics to price the alternatives with, so it takes
24215
+ // `event_type = 'code_change'` for a selective index probe and rebuilds the
24216
+ // cross product. The FROM order alone was measured to change the plan not at
24217
+ // all.
24218
+ //
24219
+ // The LEFT JOIN it replaced was already an inner join in effect: three
24220
+ // `latest.*` predicates sit in the WHERE, and each of them is false for a
24221
+ // null-extended row. Spelling it JOIN changes no row and stops the plan
24222
+ // reading as though the findings side could drive.
23941
24223
  recentlyResolved(limit = 20) {
23942
24224
  const rows = allRows(
23943
24225
  this.db.prepare(
@@ -23947,17 +24229,15 @@ var SqliteSecurityRepository = class {
23947
24229
  json_extract(e.attributes, '$.file_path') AS path,
23948
24230
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
23949
24231
  latest.resolved_at AS latest_resolved_at
23950
- FROM inspection_findings f
23951
- JOIN audit_events e ON e.id = f.audit_event_id
23952
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
23953
- LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
23954
- ON latest.finding_key = f.finding_key
23955
- WHERE e.event_type = 'code_change'
23956
- AND f.finding_key IS NOT NULL
23957
- AND latest.status = 'resolved'
24232
+ FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24233
+ CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
24234
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24235
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24236
+ WHERE latest.status = 'resolved'
23958
24237
  AND latest.method = 'fixed-at-source'
23959
24238
  AND latest.resolved_at IS NOT NULL
23960
- ORDER BY latest_resolved_at DESC
24239
+ AND e.event_type = 'code_change'
24240
+ ORDER BY latest.resolved_at DESC
23961
24241
  LIMIT :limit`
23962
24242
  ),
23963
24243
  { limit }
@@ -24894,6 +25174,7 @@ function openAndInitialize(file2) {
24894
25174
  function openLocalDatabase(dir) {
24895
25175
  ensureDataDirSync(dir);
24896
25176
  const file2 = join2(dir, DB_FILENAME);
25177
+ reapStalePartials(file2);
24897
25178
  const {
24898
25179
  db,
24899
25180
  events,
@@ -25181,11 +25462,79 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25181
25462
  }
25182
25463
  }
25183
25464
 
25184
- // ../../packages/persistence/src/settings.ts
25465
+ // ../../packages/persistence/src/managed-settings.ts
25185
25466
  import { readFileSync as readFileSync3 } from "fs";
25467
+ import { posix, win32 } from "path";
25468
+ function managedSettingsPaths(platform2 = process.platform) {
25469
+ if (platform2 === "darwin") {
25470
+ return [
25471
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
25472
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
25473
+ ];
25474
+ }
25475
+ if (platform2 === "win32") {
25476
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
25477
+ }
25478
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
25479
+ }
25480
+ function readManagedSettings(paths = managedSettingsPaths()) {
25481
+ for (const path of paths) {
25482
+ let text;
25483
+ try {
25484
+ text = readFileSync3(path, "utf8");
25485
+ } catch {
25486
+ continue;
25487
+ }
25488
+ const record2 = parseJsonObject(text);
25489
+ if (!record2) continue;
25490
+ const parsed = ManagedSettings.safeParse(record2);
25491
+ if (parsed.success) return parsed.data;
25492
+ }
25493
+ return null;
25494
+ }
25495
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
25496
+ if (!managed) return settings;
25497
+ const { values } = managed;
25498
+ const merged = { ...settings };
25499
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
25500
+ if (values.controlPlane !== void 0) {
25501
+ merged.controlPlane = {
25502
+ ...values.controlPlane,
25503
+ // The administrator pinned WHICH deployment, not WHEN this machine
25504
+ // joined it. Keep the user's own attach time when the endpoint is
25505
+ // unchanged, so a managed machine does not appear to re-attach on every
25506
+ // read; stamp a fresh one when the administrator moved it.
25507
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
25508
+ };
25509
+ }
25510
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
25511
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
25512
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
25513
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
25514
+ if (values.vaultConsent !== void 0) {
25515
+ merged.vaultConsent = values.vaultConsent ? (
25516
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
25517
+ // at the current version otherwise.
25518
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
25519
+ ) : void 0;
25520
+ }
25521
+ if (values.modelJudgeConsent !== void 0) {
25522
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
25523
+ acknowledgedAt: now().toISOString(),
25524
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
25525
+ } : void 0;
25526
+ }
25527
+ return merged;
25528
+ }
25529
+
25530
+ // ../../packages/persistence/src/settings.ts
25531
+ import { readFileSync as readFileSync4 } from "fs";
25186
25532
  import { join as join5 } from "path";
25187
25533
  var SETTINGS_FILENAME = "settings.json";
25188
25534
  function readWorkspaceSettings(base = defaultDataDir()) {
25535
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25536
+ }
25537
+ function readUserSettings(base) {
25189
25538
  const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25190
25539
  if (!record2) return defaultWorkspaceSettings();
25191
25540
  try {
@@ -25197,7 +25546,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
25197
25546
  function readJson(file2) {
25198
25547
  let text;
25199
25548
  try {
25200
- text = readFileSync3(file2, "utf8");
25549
+ text = readFileSync4(file2, "utf8");
25201
25550
  } catch {
25202
25551
  return null;
25203
25552
  }
@@ -25216,15 +25565,7 @@ import {
25216
25565
  // ../../packages/persistence/src/vault/key-provider.ts
25217
25566
  import { execFileSync } from "child_process";
25218
25567
  import { randomBytes as randomBytes2 } from "crypto";
25219
- import {
25220
- chmodSync as chmodSync2,
25221
- mkdirSync as mkdirSync2,
25222
- readFileSync as readFileSync4,
25223
- renameSync as renameSync4,
25224
- rmSync as rmSync4,
25225
- statSync as statSync3,
25226
- writeFileSync as writeFileSync3
25227
- } from "fs";
25568
+ import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25228
25569
  import { join as join6 } from "path";
25229
25570
 
25230
25571
  // ../../packages/persistence/src/vault/vault.ts
@@ -25314,7 +25655,7 @@ function resolveProviderSafe(resolveProviderFn) {
25314
25655
  }
25315
25656
 
25316
25657
  // ../../packages/plugin-sdk/src/config-inventory.ts
25317
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
25658
+ import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
25318
25659
  import { homedir as homedir2 } from "os";
25319
25660
  import { basename as basename3, join as join10 } from "path";
25320
25661
 
@@ -26022,6 +26363,10 @@ function luhnCheck(digits) {
26022
26363
  // ../../packages/detections/src/engine.ts
26023
26364
  var keywordMatcher = new KeywordMatcher2();
26024
26365
  var regexMatcher = new RegexMatcher2();
26366
+ var MATCHERS = {
26367
+ keyword: (text, rule) => keywordMatcher.match(text, rule),
26368
+ regex: (text, rule) => regexMatcher.match(text, rule)
26369
+ };
26025
26370
  var packs = /* @__PURE__ */ new Map();
26026
26371
  var POST_VALIDATORS = {
26027
26372
  entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
@@ -26037,8 +26382,7 @@ function passesPostValidators(rule, value) {
26037
26382
  for (const ref of validators) {
26038
26383
  const name = typeof ref === "string" ? ref : ref.name;
26039
26384
  const config2 = typeof ref === "string" ? void 0 : ref.config;
26040
- const validate = POST_VALIDATORS[name];
26041
- if (validate && !validate(value, config2)) return false;
26385
+ if (!POST_VALIDATORS[name](value, config2)) return false;
26042
26386
  }
26043
26387
  return true;
26044
26388
  }
@@ -26099,14 +26443,7 @@ function scan(text, rules, context) {
26099
26443
  const candidates = [];
26100
26444
  for (const rule of ruleset) {
26101
26445
  if (!ruleApplies(rule, extension)) continue;
26102
- let spans;
26103
- if (rule.matcher.type === "keyword") {
26104
- spans = keywordMatcher.match(text, rule);
26105
- } else if (rule.matcher.type === "regex") {
26106
- spans = regexMatcher.match(text, rule);
26107
- } else {
26108
- continue;
26109
- }
26446
+ const spans = MATCHERS[rule.matcher.type](text, rule);
26110
26447
  for (const span of spans) {
26111
26448
  const rawMatch = text.slice(span.start, span.end);
26112
26449
  if (!passesPostValidators(rule, rawMatch)) continue;
@@ -26262,6 +26599,7 @@ var CONFIG_POSTURE_RULES = [
26262
26599
  ];
26263
26600
 
26264
26601
  // ../../packages/detections/src/security/redos-probe.ts
26602
+ var BUDGET_MS = 100;
26265
26603
  var EXPONENTIAL_UNITS = [
26266
26604
  "a",
26267
26605
  "0",
@@ -26285,6 +26623,8 @@ var EXPONENTIAL_PROBES = EXPONENTIAL_UNITS.flatMap(
26285
26623
  var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].map(
26286
26624
  (unit) => unit.repeat(1e4).slice(0, 4e4) + "!"
26287
26625
  );
26626
+ var CPU_CORROBORATION_SHARE = 0.2;
26627
+ var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
26288
26628
 
26289
26629
  // ../../rules/code-flaws/auth-jwt-no-verify.json
26290
26630
  var auth_jwt_no_verify_default = {
@@ -28352,7 +28692,7 @@ function maskText(text) {
28352
28692
  }
28353
28693
 
28354
28694
  // ../../packages/plugin-sdk/src/repo.ts
28355
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
28695
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
28356
28696
  import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
28357
28697
 
28358
28698
  // ../../packages/plugin-sdk/src/events.ts
@@ -28363,12 +28703,17 @@ import { existsSync as existsSync7 } from "fs";
28363
28703
  import { fileURLToPath } from "url";
28364
28704
  import { Worker } from "worker_threads";
28365
28705
 
28706
+ // ../../packages/plugin-sdk/src/ignore-layers.ts
28707
+ var import_ignore = __toESM(require_ignore(), 1);
28708
+ import { readFileSync as readFileSync8 } from "fs";
28709
+ import { join as join11 } from "path";
28710
+
28366
28711
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
28367
28712
  import { arch, hostname as hostname4, platform, release } from "os";
28368
28713
 
28369
28714
  // ../../packages/plugin-sdk/src/nudge.ts
28370
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
28371
- import { join as join11 } from "path";
28715
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
28716
+ import { join as join12 } from "path";
28372
28717
 
28373
28718
  // ../../packages/plugin-sdk/src/paths.ts
28374
28719
  import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
@@ -28385,9 +28730,8 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
28385
28730
  }
28386
28731
 
28387
28732
  // ../../packages/plugin-sdk/src/project-files.ts
28388
- var import_ignore = __toESM(require_ignore(), 1);
28389
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
28390
- import { basename as basename5, join as join12 } from "path";
28733
+ import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
28734
+ import { basename as basename5, join as join13 } from "path";
28391
28735
 
28392
28736
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
28393
28737
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -28504,12 +28848,12 @@ async function applySetupTriageSuppressions(entries, writer, opts) {
28504
28848
  }
28505
28849
 
28506
28850
  // ../../packages/plugin-sdk/src/throttle.ts
28507
- import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
28508
- import { join as join13 } from "path";
28851
+ import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
28852
+ import { join as join14 } from "path";
28509
28853
 
28510
28854
  // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
28511
28855
  import { writeFileSync as writeFileSync7 } from "fs";
28512
- import { join as join14 } from "path";
28856
+ import { join as join15 } from "path";
28513
28857
 
28514
28858
  // ../../packages/setup-wizard/src/triage/dedupe.ts
28515
28859
  function dedupeKey(hit) {
@@ -28564,12 +28908,12 @@ function deriveFalsePositivePatterns(hits, rec, plan) {
28564
28908
  }
28565
28909
 
28566
28910
  // ../../packages/setup-wizard/src/triage/gate-display.ts
28567
- function findContext(entry, join18) {
28568
- const byFingerprint = join18.find(
28911
+ function findContext(entry, join19) {
28912
+ const byFingerprint = join19.find(
28569
28913
  (j) => j.valueFingerprint !== void 0 && j.valueFingerprint === entry.valueFingerprint
28570
28914
  );
28571
28915
  if (byFingerprint) return byFingerprint.maskedContext;
28572
- const byRuleAndMask = join18.find(
28916
+ const byRuleAndMask = join19.find(
28573
28917
  (j) => j.ruleId === entry.ruleId && j.maskedMatch === entry.maskedValue
28574
28918
  );
28575
28919
  return byRuleAndMask?.maskedContext;
@@ -28640,13 +28984,13 @@ function renderShowcase(showcase) {
28640
28984
 
28641
28985
  ${blocks.join("\n\n")}`;
28642
28986
  }
28643
- function renderSuppressionGate(entries, join18) {
28987
+ function renderSuppressionGate(entries, join19) {
28644
28988
  if (entries.length === 0) {
28645
28989
  return "No false-positive suppressions to confirm \u2014 nothing will be written.";
28646
28990
  }
28647
28991
  const header = entries.length === 1 ? "This looks like a false positive \u2014 take a look before I suppress it:" : `These ${String(entries.length)} look like false positives \u2014 take a look before I suppress them:`;
28648
28992
  const blocks = entries.map((entry, i) => {
28649
- const context = findContext(entry, join18);
28993
+ const context = findContext(entry, join19);
28650
28994
  const lines = [
28651
28995
  `${String(i + 1)}. ${entry.ruleId} [${entry.category}]`,
28652
28996
  ` value: ${entry.maskedValue}`,
@@ -28661,7 +29005,9 @@ ${blocks.join("\n\n")}`;
28661
29005
  }
28662
29006
 
28663
29007
  // ../../packages/setup-wizard/src/triage/merge.ts
28664
- var RANK = { monitor: 0, warn: 1, redact: 2, block: 3 };
29008
+ var RANK = Object.fromEntries(
29009
+ KNOWN_BUILTIN_IDS.map((id, i) => [id, i])
29010
+ );
28665
29011
  function chunkForJudge(hits, maxBytes = 262144) {
28666
29012
  const chunks = [];
28667
29013
  let current = [];
@@ -28730,9 +29076,9 @@ function mergeRecommendations(verdicts) {
28730
29076
  }
28731
29077
 
28732
29078
  // ../../packages/setup-wizard/src/triage/plan-file.ts
28733
- import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
29079
+ import { mkdtempSync, readFileSync as readFileSync10, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
28734
29080
  import { tmpdir } from "os";
28735
- import { basename as basename6, dirname as dirname4, join as join15 } from "path";
29081
+ import { basename as basename6, dirname as dirname4, join as join16 } from "path";
28736
29082
  var SuppressionEntrySchema = external_exports.object({
28737
29083
  ruleId: external_exports.string(),
28738
29084
  category: DetectionCategory,
@@ -28787,13 +29133,13 @@ function serializePlan(plan, current) {
28787
29133
  function writePlanFile(plan, current, rawValues, deps = {}) {
28788
29134
  const serialized = serializePlan(plan, current);
28789
29135
  assertRawFree(serialized, rawValues);
28790
- const dir = (deps.mkTempDir ?? (() => mkdtempSync(join15(tmpdir(), "aka-plan-"))))();
28791
- const path = join15(dir, "setup-plan.json");
29136
+ const dir = (deps.mkTempDir ?? (() => mkdtempSync(join16(tmpdir(), "aka-plan-"))))();
29137
+ const path = join16(dir, "setup-plan.json");
28792
29138
  writeFileSync8(path, serialized, { encoding: "utf8", mode: 384 });
28793
29139
  return path;
28794
29140
  }
28795
29141
  function readPlanFile(path) {
28796
- const text = readFileSync9(path, "utf8");
29142
+ const text = readFileSync10(path, "utf8");
28797
29143
  const json2 = JSON.parse(text);
28798
29144
  return PersistedPlanSchema.parse(json2);
28799
29145
  }
@@ -28867,8 +29213,8 @@ function buildJoinEntries(hits) {
28867
29213
  }
28868
29214
 
28869
29215
  // ../../packages/setup-wizard/src/triage/resolve.ts
28870
- function resolveSuppressions(rec, join18) {
28871
- const byId = new Map(join18.map((e) => [e.id, e]));
29216
+ function resolveSuppressions(rec, join19) {
29217
+ const byId = new Map(join19.map((e) => [e.id, e]));
28872
29218
  const entries = [];
28873
29219
  const skipped = [];
28874
29220
  for (const cat of rec.perCategory) {
@@ -28970,7 +29316,7 @@ function parseTriageStream(text) {
28970
29316
  return { hits, status: "complete" };
28971
29317
  }
28972
29318
  function planTriageWriteback(hits, rec) {
28973
- const join18 = buildJoinEntries(hits);
29319
+ const join19 = buildJoinEntries(hits);
28974
29320
  const rawValues = hits.map((h) => h.rawMatch);
28975
29321
  const skipped = [];
28976
29322
  const posture = {};
@@ -29010,7 +29356,7 @@ function planTriageWriteback(hits, rec) {
29010
29356
  }
29011
29357
  const { entries, skipped: resolveSkips } = resolveSuppressions(
29012
29358
  { perCategory: safeCategories, notes: rec.notes },
29013
- join18
29359
+ join19
29014
29360
  );
29015
29361
  skipped.push(...resolveSkips);
29016
29362
  let notes = rec.notes;
@@ -29020,7 +29366,7 @@ function planTriageWriteback(hits, rec) {
29020
29366
  if (err instanceof RawEgressError) notes = SCRUBBED_NOTES;
29021
29367
  else throw err;
29022
29368
  }
29023
- return { entries, posture, showcase, join: join18, notes, skipped };
29369
+ return { entries, posture, showcase, join: join19, notes, skipped };
29024
29370
  }
29025
29371
  function recommendedPosture(evidence) {
29026
29372
  return { ...severityFloorPosture(), ...evidence };
@@ -29289,12 +29635,121 @@ function parseRecommendation(text) {
29289
29635
 
29290
29636
  // src/triage/judge.ts
29291
29637
  import { execFileSync as execFileSync2 } from "child_process";
29292
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync10, rmSync as rmSync6 } from "fs";
29638
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync11, rmSync as rmSync6 } from "fs";
29293
29639
  import { tmpdir as tmpdir2 } from "os";
29294
- import { dirname as dirname5, join as join16 } from "path";
29640
+ import { dirname as dirname5, join as join17 } from "path";
29295
29641
  import { fileURLToPath as fileURLToPath2 } from "url";
29642
+
29643
+ // ../../packages/plugin-sdk/src/bare-command.ts
29644
+ import { spawnSync } from "child_process";
29645
+ import { homedir as homedir3 } from "os";
29646
+ import { win32 as win322 } from "path";
29647
+ var CMD_LINE_MAX = 8191;
29648
+ var CMD_WRAPPER_ALLOWANCE = 64;
29649
+ var DIRECT_EXTENSIONS = /* @__PURE__ */ new Set([".exe", ".com"]);
29650
+ var CMD_HAZARDS = [
29651
+ ['"', "a double quote, which would close the quoted argument"],
29652
+ ["%", "a percent sign, which cmd.exe expands inside quotes and re-parses"],
29653
+ ["!", "an exclamation mark, which cmd.exe expands when delayed expansion is on"],
29654
+ ["\r", "a carriage return, which a Windows command line cannot carry"],
29655
+ ["\n", "a line break, which a Windows command line cannot carry"],
29656
+ ["\0", "a NUL byte"]
29657
+ ];
29658
+ var BACKSLASH = 92;
29659
+ var BARE_COMMAND_ERROR_CODE = "ERR_AKA_WINDOWS_ARGV";
29660
+ var BareCommandUnsupportedError = class extends Error {
29661
+ code = BARE_COMMAND_ERROR_CODE;
29662
+ reason;
29663
+ constructor(reason) {
29664
+ super(`cannot spawn this command on Windows: ${reason}`);
29665
+ this.name = "BareCommandUnsupportedError";
29666
+ this.reason = reason;
29667
+ }
29668
+ };
29669
+ function isBareCommandUnsupported(err) {
29670
+ return typeof err === "object" && err !== null && err.code === BARE_COMMAND_ERROR_CODE && typeof err.reason === "string";
29671
+ }
29672
+ function cmdLineHazard(command, args) {
29673
+ for (const [index, arg] of [command, ...args].entries()) {
29674
+ for (const [char, why] of CMD_HAZARDS) {
29675
+ if (arg.includes(char)) {
29676
+ const where = index === 0 ? "the command name" : `argument ${String(index)}`;
29677
+ return `${where} contains ${why}`;
29678
+ }
29679
+ }
29680
+ }
29681
+ const length = quoteCommandLine(command, args).length + CMD_WRAPPER_ALLOWANCE;
29682
+ if (length > CMD_LINE_MAX) {
29683
+ return `the command line is ${String(length)} characters once cmd.exe's own prefix is counted, over its ${String(CMD_LINE_MAX)}`;
29684
+ }
29685
+ return void 0;
29686
+ }
29687
+ function quoteForCmd(value) {
29688
+ let runStart = value.length;
29689
+ while (runStart > 0 && value.charCodeAt(runStart - 1) === BACKSLASH) runStart -= 1;
29690
+ const trailingBackslashes = value.slice(runStart);
29691
+ const body = value.slice(0, runStart);
29692
+ return `"${body}${trailingBackslashes}${trailingBackslashes}"`;
29693
+ }
29694
+ function quoteCommandLine(command, args) {
29695
+ return [command, ...args].map(quoteForCmd).join(" ");
29696
+ }
29697
+ var RESOLVE_TIMEOUT_MS = 5e3;
29698
+ function systemWhere(env) {
29699
+ for (const [key, value] of Object.entries(env ?? process.env)) {
29700
+ if (key.toLowerCase() !== "systemroot") continue;
29701
+ if (typeof value !== "string" || value.trim() === "") return void 0;
29702
+ return win322.join(value, "System32", "where.exe");
29703
+ }
29704
+ return void 0;
29705
+ }
29706
+ function resolveWindowsCommand(command, env, home) {
29707
+ try {
29708
+ const probe = spawnSync(systemWhere(env) ?? "where", [command], {
29709
+ encoding: "utf8",
29710
+ cwd: home,
29711
+ windowsHide: true,
29712
+ timeout: RESOLVE_TIMEOUT_MS,
29713
+ ...env === void 0 ? {} : { env }
29714
+ });
29715
+ if (probe.status !== 0 || typeof probe.stdout !== "string") return void 0;
29716
+ const first = probe.stdout.split(/\r?\n/).find((line) => line.trim() !== "");
29717
+ return first?.trim();
29718
+ } catch {
29719
+ return void 0;
29720
+ }
29721
+ }
29722
+ function isDirectlyExecutable(file2) {
29723
+ return DIRECT_EXTENSIONS.has(win322.extname(file2).toLowerCase());
29724
+ }
29725
+ function planBareCommand(command, args, deps = {}) {
29726
+ const platform2 = deps.platform ?? process.platform;
29727
+ if (platform2 !== "win32") {
29728
+ return { file: command, args, options: {}, viaShell: false, resolved: void 0 };
29729
+ }
29730
+ const home = deps.home ?? homedir3();
29731
+ const resolve = deps.resolve ?? resolveWindowsCommand;
29732
+ const resolved = resolve(command, deps.env, home);
29733
+ if (resolved !== void 0 && isDirectlyExecutable(resolved)) {
29734
+ return { file: resolved, args, options: { cwd: home }, viaShell: false, resolved };
29735
+ }
29736
+ const hazard = cmdLineHazard(command, args);
29737
+ if (hazard !== void 0) {
29738
+ const why = resolved === void 0 ? `${command} did not resolve to an executable, so it could only be run through cmd.exe` : `${command} resolves only to a batch shim, which must be run through cmd.exe`;
29739
+ throw new BareCommandUnsupportedError(`${why}, and ${hazard}`);
29740
+ }
29741
+ return {
29742
+ file: quoteCommandLine(command, args),
29743
+ args: [],
29744
+ options: { shell: true, cwd: home },
29745
+ viaShell: true,
29746
+ resolved
29747
+ };
29748
+ }
29749
+
29750
+ // src/triage/judge.ts
29296
29751
  var TRIAGE_DIR = dirname5(fileURLToPath2(import.meta.url));
29297
- var DEFAULT_RUBRIC_PATH = join16(
29752
+ var DEFAULT_RUBRIC_PATH = join17(
29298
29753
  TRIAGE_DIR,
29299
29754
  "..",
29300
29755
  "..",
@@ -29331,20 +29786,23 @@ function judgeEnv(platform2 = process.platform) {
29331
29786
  CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
29332
29787
  };
29333
29788
  if (platform2 === "darwin") {
29334
- env.CLAUDE_CONFIG_DIR = mkdtempSync2(join16(tmpdir2(), "aka-judge-cfg-"));
29789
+ env.CLAUDE_CONFIG_DIR = mkdtempSync2(join17(tmpdir2(), "aka-judge-cfg-"));
29335
29790
  }
29336
29791
  return env;
29337
29792
  }
29338
- function spawnClaude(argv, env, stdin) {
29339
- return execFileSync2("claude", [...argv], {
29793
+ function spawnClaude(argv, env, stdin, platform2 = process.platform) {
29794
+ const plan = planBareCommand("claude", argv, { env, platform: platform2 });
29795
+ return execFileSync2(plan.file, [...plan.args], {
29340
29796
  env,
29341
29797
  input: stdin,
29342
29798
  encoding: "utf8",
29343
29799
  timeout: 18e4,
29344
- maxBuffer: 32 * 1024 * 1024
29800
+ maxBuffer: 32 * 1024 * 1024,
29801
+ ...plan.options
29345
29802
  });
29346
29803
  }
29347
29804
  function spawnFailureMeta(err) {
29805
+ if (isBareCommandUnsupported(err)) return err.reason;
29348
29806
  const e = err;
29349
29807
  const parts = [];
29350
29808
  if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
@@ -29363,7 +29821,7 @@ function runJudge(hits, deps) {
29363
29821
  if (typeof deps.spawn !== "function") {
29364
29822
  throw new TypeError("runJudge requires deps.spawn \u2014 there is no live-spawn fallback");
29365
29823
  }
29366
- const rubric = deps.loadRubric?.() ?? readFileSync10(DEFAULT_RUBRIC_PATH, "utf8");
29824
+ const rubric = deps.loadRubric?.() ?? readFileSync11(DEFAULT_RUBRIC_PATH, "utf8");
29367
29825
  const hitsJsonl = hits.map((h) => JSON.stringify(toJudgePayload(h))).join("\n");
29368
29826
  const fullPrompt = `${rubric}
29369
29827
 
@@ -29534,9 +29992,9 @@ function renderPostureGrid(posture) {
29534
29992
  );
29535
29993
  const rows = packs2.map((category) => [
29536
29994
  category,
29537
- ...BUILTIN_ORDER.map((level) => posture[category] === level ? GRID_MARK : "")
29995
+ ...CATEGORY_EXPRESSIBLE_IDS.map((level) => posture[category] === level ? GRID_MARK : "")
29538
29996
  ]);
29539
- return indent(table(["Category", ...BUILTIN_ORDER], rows));
29997
+ return indent(table(["Category", ...CATEGORY_EXPRESSIBLE_IDS], rows));
29540
29998
  }
29541
29999
  var READY_COMMANDS = ["/aka:health", "/aka:findings", "/aka:recommend"];
29542
30000
  function renderCategoriesTuned(categoriesTuned) {
@@ -29633,10 +30091,10 @@ function resolveCreatedBy() {
29633
30091
  }
29634
30092
  function loadRubric() {
29635
30093
  const here = dirname6(fileURLToPath4(import.meta.url));
29636
- const shipped = join17(here, "triage-rubric.md");
29637
- if (existsSync9(shipped)) return readFileSync11(shipped, "utf8");
29638
- return readFileSync11(
29639
- join17(here, "..", "..", "..", "packages", "setup-wizard", "assets", "triage-rubric.md"),
30094
+ const shipped = join18(here, "triage-rubric.md");
30095
+ if (existsSync9(shipped)) return readFileSync12(shipped, "utf8");
30096
+ return readFileSync12(
30097
+ join18(here, "..", "..", "..", "packages", "setup-wizard", "assets", "triage-rubric.md"),
29640
30098
  "utf8"
29641
30099
  );
29642
30100
  }
@@ -29646,7 +30104,7 @@ async function main() {
29646
30104
  argv,
29647
30105
  // fd 0 = stdin; the wizard pipes `backfill.js --triage` into this on preview.
29648
30106
  // Called only on the preview path — the confirm path never reads a stream.
29649
- readStream: (streamPath) => streamPath !== void 0 ? readFileSync11(streamPath, "utf8") : readFileSync11(0, "utf8"),
30107
+ readStream: (streamPath) => streamPath !== void 0 ? readFileSync12(streamPath, "utf8") : readFileSync12(0, "utf8"),
29650
30108
  runJudge: (hits) => runJudge(hits, { spawn: spawnClaude, loadRubric }),
29651
30109
  // The distinct model-judge egress consent, read from settings.json. When it
29652
30110
  // is absent or stale the preview skips the judge instead of sending findings