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

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,13 +492,12 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // ../../packages/plugin-sdk/src/config.ts
495
- import { existsSync as existsSync5 } from "fs";
496
- import { join as join8 } from "path";
495
+ import { existsSync as existsSync6 } from "fs";
496
+ import { join as join10 } from "path";
497
497
 
498
- // ../../packages/persistence/src/database.ts
499
- import { randomUUID as randomUUID10 } from "crypto";
500
- import { join as join2, sep } from "path";
501
- import { DatabaseSync } from "node:sqlite";
498
+ // ../../packages/persistence/src/control-plane-credential.ts
499
+ import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
500
+ import { join } from "path";
502
501
 
503
502
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
504
503
  var SQLITE_MIGRATIONS = [
@@ -585,6 +584,10 @@ var SQLITE_MIGRATIONS = [
585
584
  {
586
585
  tag: "0020_secret_vault_pagination_indexes",
587
586
  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');"
587
+ },
588
+ {
589
+ tag: "0021_finding_resolution_resolved_at_index",
590
+ sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
588
591
  }
589
592
  ];
590
593
 
@@ -15300,6 +15303,47 @@ function date4(params) {
15300
15303
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
15301
15304
  config(en_default());
15302
15305
 
15306
+ // ../../packages/schema/src/zod/harness-map.ts
15307
+ var HARNESS = {
15308
+ ClaudeCode: "claudecode",
15309
+ Cursor: "cursor",
15310
+ Copilot: "copilot",
15311
+ Codex: "codex",
15312
+ Antigravity: "antigravity",
15313
+ Windsurf: "windsurf",
15314
+ ClaudeDesktop: "claudedesktop",
15315
+ ChatGpt: "chatgpt",
15316
+ ClaudeAi: "claudeai",
15317
+ Api: "api"
15318
+ };
15319
+ var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
15320
+ var SOURCE_TOOL = {
15321
+ ClaudeCode: "claude-code",
15322
+ ClaudeDesktop: "claude-desktop",
15323
+ Cursor: "cursor",
15324
+ ChatGpt: "chatgpt",
15325
+ ClaudeAi: "claude-ai",
15326
+ Copilot: "github-copilot",
15327
+ Codex: "codex",
15328
+ Antigravity: "antigravity",
15329
+ // No harness counterpart, deliberately: the CLI's own captures and a capture
15330
+ // whose tool could not be identified both render through the read side's
15331
+ // miss path rather than as a harness of their own.
15332
+ Cli: "cli",
15333
+ Unknown: "unknown"
15334
+ };
15335
+ var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
15336
+ var TOOL_TO_HARNESS = {
15337
+ [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
15338
+ [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
15339
+ [SOURCE_TOOL.Copilot]: HARNESS.Copilot,
15340
+ [SOURCE_TOOL.Cursor]: HARNESS.Cursor,
15341
+ [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
15342
+ [SOURCE_TOOL.Codex]: HARNESS.Codex,
15343
+ [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
15344
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
15345
+ };
15346
+
15303
15347
  // ../../packages/schema/src/zod/finding.ts
15304
15348
  var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
15305
15349
  var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
@@ -15322,21 +15366,22 @@ var Finding = external_exports.object({
15322
15366
  }).meta({ id: "Finding" });
15323
15367
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15324
15368
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15325
- var FindingProvider = external_exports.enum([
15326
- "claudecode",
15327
- "claudedesktop",
15328
- "cursor",
15329
- "copilot",
15330
- "chatgpt",
15331
- "claudeai",
15332
- "codex",
15333
- "antigravity",
15334
- "api"
15369
+ var FindingProvider = Harness.extract([
15370
+ "ClaudeCode",
15371
+ "ClaudeDesktop",
15372
+ "Cursor",
15373
+ "Copilot",
15374
+ "ChatGpt",
15375
+ "ClaudeAi",
15376
+ "Codex",
15377
+ "Antigravity",
15378
+ "Api"
15335
15379
  ]).meta({ id: "FindingProvider" });
15336
15380
  var FindingCategory = external_exports.enum([
15337
15381
  "secret",
15338
15382
  "pii",
15339
15383
  "source_code",
15384
+ "code_flaw",
15340
15385
  "external_share",
15341
15386
  "mcp_server",
15342
15387
  "customer_data",
@@ -15516,6 +15561,7 @@ var FindingInstanceDetail = FindingInstance.extend({
15516
15561
  policy: FindingPolicyRef
15517
15562
  }).meta({ id: "FindingInstanceDetail" });
15518
15563
  var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15564
+ var MAX_FLAT_FINDINGS_LIMIT = 200;
15519
15565
  var ListFindingInstancesQuery = external_exports.object({
15520
15566
  severity: external_exports.array(Severity).optional(),
15521
15567
  // Rule ids, the same vocabulary the grouped list's `subtype` carries.
@@ -15535,7 +15581,7 @@ var ListFindingInstancesQuery = external_exports.object({
15535
15581
  q: external_exports.string().optional(),
15536
15582
  sessionId: external_exports.string().optional(),
15537
15583
  from: external_exports.iso.datetime().optional(),
15538
- limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15584
+ limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
15539
15585
  cursor: external_exports.string().optional()
15540
15586
  });
15541
15587
  var ListFindingInstancesResponse = external_exports.object({
@@ -15597,30 +15643,6 @@ var ListFindingLocationsResponse = external_exports.object({
15597
15643
  hasMore: external_exports.boolean()
15598
15644
  }).meta({ id: "ListFindingLocationsResponse" });
15599
15645
 
15600
- // ../../packages/schema/src/zod/harness-map.ts
15601
- var Harness = external_exports.enum([
15602
- "claudecode",
15603
- "cursor",
15604
- "copilot",
15605
- "codex",
15606
- "antigravity",
15607
- "windsurf",
15608
- "claudedesktop",
15609
- "chatgpt",
15610
- "claudeai",
15611
- "api"
15612
- ]).meta({ id: "Harness" });
15613
- var TOOL_TO_HARNESS = {
15614
- "claude-code": "claudecode",
15615
- "claude-desktop": "claudedesktop",
15616
- "github-copilot": "copilot",
15617
- cursor: "cursor",
15618
- chatgpt: "chatgpt",
15619
- codex: "codex",
15620
- antigravity: "antigravity",
15621
- "claude-ai": "claudeai"
15622
- };
15623
-
15624
15646
  // ../../packages/schema/src/zod/meta.ts
15625
15647
  var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
15626
15648
  var AuditEventType = external_exports.enum([
@@ -16073,366 +16095,240 @@ var ActivityOverviewResponse = external_exports.object({
16073
16095
  sessions: ListActivitySessionsResponse
16074
16096
  }).meta({ id: "ActivityOverviewResponse" });
16075
16097
 
16076
- // ../../packages/schema/src/zod/event.ts
16077
- var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
16078
- var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
16079
- var SourceTool = external_exports.enum([
16080
- "claude-code",
16081
- "claude-desktop",
16082
- "cursor",
16083
- "chatgpt",
16084
- "claude-ai",
16085
- "github-copilot",
16086
- "codex",
16087
- "antigravity",
16088
- "cli",
16089
- "unknown"
16090
- ]).meta({ id: "SourceTool" });
16091
- var EventMetadata = external_exports.object({
16092
- sessionId: external_exports.string().optional(),
16093
- repo: external_exports.string().optional(),
16094
- filePath: external_exports.string().optional(),
16095
- // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
16096
- // set by the tool-scanning hooks. The tool NAME only — never the tool's
16097
- // arguments or output, which can carry the very value a finding masked
16098
- // (metadata is stored unredacted). Gives findings on non-file captures a
16099
- // display location ("via Bash") when no filePath exists.
16100
- toolName: external_exports.string().optional(),
16101
- // Set (true) by the worktree scanner when the file is excluded by the
16102
- // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
16103
- // generated code can leak real secrets — but the provenance is recorded so
16104
- // policy/dashboards can treat those findings as informational rather than
16105
- // blocking. Omitted (not false) for tracked files and non-scan events.
16106
- gitignored: external_exports.boolean().optional(),
16107
- // Set (true) ONLY when the event's `content` is the COMPLETE file at
16108
- // capture time (a worktree scan reading from disk). Hook-captured edits
16109
- // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
16110
- // this. The resolver-on-ingest keys its fixed-at-source dropout
16111
- // diff on this marker: only a whole-file snapshot can prove a previously
16112
- // open finding is gone; a fragment's absence proves nothing (the secret
16113
- // may live outside the hunk). Omitted (not false) for fragments and
16114
- // non-scan events, so pre-marker clients safely default to the
16115
- // non-authoritative path.
16116
- wholeFile: external_exports.boolean().optional(),
16117
- model: external_exports.string().optional(),
16118
- turnIndex: external_exports.number().int().nonnegative().optional(),
16119
- // Distributed-tracing correlation. `correlationId` ties a recorded event back
16120
- // to the request that captured/ingested it (a UUID, generated independently of
16121
- // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
16122
- // originating span when telemetry is enabled. Both optional + backward
16123
- // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
16124
- correlationId: external_exports.uuid().optional(),
16125
- traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
16126
- // Ids of the detection exceptions that downgraded findings in this capture
16127
- // to 'allow' — the enforcement audit trail's link back to the grant that
16128
- // authorized the bypass. Absent on captures where no exception applied.
16129
- exceptionIds: external_exports.array(external_exports.guid()).optional()
16130
- }).meta({ id: "EventMetadata" });
16131
- var Event = external_exports.object({
16132
- id: external_exports.guid(),
16133
- sourceTool: SourceTool,
16134
- kind: EventKind,
16135
- occurredAt: external_exports.iso.datetime(),
16136
- contentHash: external_exports.string(),
16137
- content: external_exports.string(),
16138
- metadata: EventMetadata.optional()
16139
- }).meta({ id: "Event" });
16140
- var IngestEvent = Event.meta({ id: "IngestEvent" });
16141
- var IngestBatch = external_exports.object({
16142
- events: external_exports.array(IngestEvent).min(1).max(100),
16143
- // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
16144
- // additionally rejects any event whose contentHash the store has already
16145
- // recorded — for re-runnable bulk ingest (worktree scan, transcript
16146
- // backfill), where a re-run mints fresh event ids for identical content and
16147
- // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
16148
- // two genuinely separate prompts can be byte-identical and both belong on
16149
- // the timeline.
16150
- dedupe: external_exports.literal("content-hash").optional()
16151
- }).meta({ id: "IngestBatch" });
16152
-
16153
- // ../../packages/schema/src/zod/inventory.ts
16154
- var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16155
- var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16156
- var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16157
- var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16158
- var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16159
- var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16160
- var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16161
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16162
- var AccessCounts = external_exports.object({
16163
- open: external_exports.number().int().nonnegative(),
16164
- approved: external_exports.number().int().nonnegative(),
16165
- blocked: external_exports.number().int().nonnegative(),
16166
- total: external_exports.number().int().nonnegative()
16167
- }).meta({ id: "AccessCounts" });
16168
- var AssetSummary = external_exports.object({
16169
- id: external_exports.string(),
16170
- type: AssetType,
16171
- name: external_exports.string(),
16172
- sub: external_exports.string(),
16173
- flags: external_exports.array(Flag),
16174
- /** MCP servers only — omitted for all other types. */
16175
- trust: TrustLevel.optional()
16176
- }).meta({ id: "AssetSummary" });
16177
- var ProjectSummary = external_exports.object({
16178
- id: external_exports.string(),
16179
- name: external_exports.string(),
16180
- repo: external_exports.string(),
16181
- visibility: Visibility,
16182
- language: external_exports.string(),
16183
- policyDefault: AccessLevel,
16184
- updatedAt: external_exports.iso.datetime(),
16185
- accessCounts: AccessCounts,
16186
- findingsCount: external_exports.number().int().nonnegative()
16187
- }).meta({ id: "ProjectSummary" });
16188
- var HarnessCategory = external_exports.object({
16189
- /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16190
- type: AssetType,
16191
- assets: external_exports.array(AssetSummary)
16098
+ // ../../packages/schema/src/zod/config-inventory.ts
16099
+ var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16100
+ var SkillScanEntry = external_exports.object({
16101
+ name: external_exports.string().min(1),
16102
+ // The identity source: a marketplace repo for plugin skills (e.g.
16103
+ // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16104
+ // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16105
+ // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16106
+ source: external_exports.string().min(1),
16107
+ scope: ConfigScope,
16108
+ pluginName: external_exports.string().optional(),
16109
+ // Volatile — rides the attribute bag, never the identity hash.
16110
+ version: external_exports.string().optional(),
16111
+ description: external_exports.string().optional(),
16112
+ // Skill directory mtime (ISO) the "updated Nd ago" freshness signal.
16113
+ updatedAt: external_exports.iso.datetime().optional(),
16114
+ // Filesystem path — the promoted inventory `location` column.
16115
+ location: external_exports.string().optional()
16192
16116
  });
16193
- var HarnessSummary = external_exports.object({
16194
- id: HarnessId,
16195
- label: external_exports.string(),
16196
- kind: external_exports.string(),
16197
- version: external_exports.string(),
16198
- sessions: external_exports.number().int().nonnegative(),
16199
- assetCount: external_exports.number().int().nonnegative(),
16200
- flagCount: external_exports.number().int().nonnegative(),
16201
- projects: external_exports.array(ProjectSummary),
16202
- categories: external_exports.array(HarnessCategory)
16203
- }).meta({ id: "HarnessSummary" });
16204
- var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16205
- var AssetGroup = external_exports.object({
16206
- /** Group key — never project (enforced at service layer). */
16207
- type: AssetType,
16208
- total: external_exports.number().int().nonnegative(),
16209
- /**
16210
- * MCP group only — omitted for all other types.
16211
- * Partial: only TrustLevel keys with non-zero counts are included.
16212
- * Strict: unknown keys are rejected only TrustLevel values are valid keys.
16213
- */
16214
- trustRollup: external_exports.object({
16215
- "known-good": external_exports.number().int().nonnegative(),
16216
- risky: external_exports.number().int().nonnegative(),
16217
- unapproved: external_exports.number().int().nonnegative()
16218
- }).partial().strict().optional(),
16117
+ var HookScanEntry = external_exports.object({
16118
+ // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16119
+ // set is harness-defined and grows without a schema change.
16120
+ event: external_exports.string().min(1),
16121
+ // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16122
+ matcher: external_exports.string().optional(),
16123
+ command: external_exports.string().min(1),
16124
+ timeout: external_exports.number().optional(),
16125
+ scope: ConfigScope,
16126
+ pluginName: external_exports.string().optional(),
16127
+ // The settings file / hooks.json the entry came from.
16128
+ location: external_exports.string().optional()
16129
+ });
16130
+ var McpServerScanEntry = external_exports.object({
16131
+ // The server's config key ("github", "filesystem", …) — identity, with the
16132
+ // qualified scope (see mcpServerIdentityKey).
16133
+ name: external_exports.string().min(1),
16134
+ scope: ConfigScope,
16135
+ pluginName: external_exports.string().optional(),
16136
+ // The owning plugin's marketplacepart of PLUGIN-scope identity: two
16137
+ // marketplaces can each ship a plugin named `guard`, and without this their
16138
+ // same-named servers would collapse to one row (the second silently dropped,
16139
+ // inheriting the first's trust).
16140
+ marketplace: external_exports.string().optional(),
16141
+ // The repo identity (remote url, or the cwd for un-remoted repos) — part of
16142
+ // PROJECT/LOCAL-scope identity: a server named `github` in repo A and one in
16143
+ // repo B are different servers with different commands, and MUST NOT share a
16144
+ // row — a shared row would let a cloned repo's .mcp.json inherit the trust
16145
+ // the user granted elsewhere.
16146
+ project: external_exports.string().optional(),
16147
+ // 'stdio' when the entry carries a command; otherwise the config's `type`
16148
+ // ('http' / 'sse' / …). Open string — the transport set is harness-defined.
16149
+ transport: external_exports.string().min(1),
16150
+ // Volatile on purpose (unlike hook `command`): a changed command/url is drift
16151
+ // on a stable row — visible across config_scan snapshots and preserving the
16152
+ // user's trust decision — never a quiet new row. One of the two is present.
16153
+ // Secret-masked at collection time (the scanner runs the bundled detection
16154
+ // packs over both — tokens routinely ride command args and URLs).
16155
+ command: external_exports.string().optional(),
16156
+ url: external_exports.string().optional(),
16157
+ // Env var NAMES only, never values (the no-secrets rule).
16158
+ envKeys: external_exports.array(external_exports.string()).optional(),
16159
+ // The config file the entry came from.
16160
+ location: external_exports.string().optional()
16161
+ });
16162
+ var ConfigFileScanEntry = external_exports.object({
16163
+ // Basename (settings.json, CLAUDE.md) or dir name (commands/, agents/).
16164
+ name: external_exports.string().min(1),
16165
+ // The absolute path — identity (with scope) and the promoted `location`.
16166
+ path: external_exports.string().min(1),
16167
+ scope: ConfigScope,
16168
+ // Human label: "User settings", "Project memory", "Slash commands", …
16169
+ kind: external_exports.string().min(1),
16170
+ // Derived SHAPE summary — top-level key names, entry counts, line counts.
16171
+ // Never file content or values (memory files can carry sensitive detail).
16172
+ detail: external_exports.string().optional(),
16173
+ // Dir configs (commands/, agents/) and .mcp.json: how many entries.
16174
+ entryCount: external_exports.number().optional(),
16175
+ // File mtime (ISO) — the freshness signal.
16176
+ updatedAt: external_exports.iso.datetime().optional()
16177
+ });
16178
+ var ConfigScanResult = external_exports.object({
16179
+ scannedAt: external_exports.iso.datetime(),
16180
+ skills: external_exports.array(SkillScanEntry),
16181
+ hooks: external_exports.array(HookScanEntry),
16182
+ mcpServers: external_exports.array(McpServerScanEntry),
16183
+ configFiles: external_exports.array(ConfigFileScanEntry),
16184
+ errors: external_exports.array(external_exports.object({ source: external_exports.string(), reason: external_exports.string() }))
16185
+ });
16186
+ var ConfigPostureFindingInput = external_exports.object({
16187
+ ruleId: external_exports.string().min(1),
16188
+ version: external_exports.string().min(1),
16189
+ span: Span,
16190
+ // For posture rules this is the offending COMMAND (config the user already
16191
+ // holds locally, not captured secret content) — it is also the correlation
16192
+ // key the read surface matches back to a hook row.
16193
+ maskedMatch: external_exports.string(),
16194
+ actionTaken: ActionTaken,
16195
+ confidence: external_exports.number().min(0).max(1)
16196
+ });
16197
+ var ConfigScanRecord = external_exports.object({
16198
+ items: external_exports.array(InventoryInput),
16199
+ scanEvent: AuditEventInput,
16200
+ definitions: external_exports.array(InspectionDefinitionInput).optional(),
16201
+ findings: external_exports.array(ConfigPostureFindingInput).optional()
16202
+ });
16203
+
16204
+ // ../../packages/schema/src/zod/control-plane.ts
16205
+ var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
16206
+ var AttachedCredential = external_exports.object({
16207
+ specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
16208
+ // The control-plane endpoint this credential was minted against.
16209
+ endpoint: external_exports.string().min(1),
16210
+ // The bearer credential itself. Never logged, never rendered — status
16211
+ // surfaces show `keyPrefix` and nothing else.
16212
+ apiKey: external_exports.string().min(1),
16213
+ // First few characters of the key, safe to display so a user can match the
16214
+ // credential against their organization's key list.
16215
+ keyPrefix: external_exports.string().min(1).max(16).optional(),
16216
+ mintedAt: external_exports.iso.datetime().optional()
16217
+ });
16218
+ var MAX_DATE_MS = 253402300799999;
16219
+ var MAX_INT4 = 2147483647;
16220
+ var StorePosturePack = external_exports.object({
16221
+ packId: external_exports.string().min(1),
16222
+ // 'namespace/packId'
16223
+ version: external_exports.string().min(1),
16224
+ enabled: external_exports.boolean(),
16225
+ // Stringified pass-through of the local store's `installed_packs.updated_at`
16226
+ // — the column format is store-version-dependent (epoch millis vs ISO), so
16227
+ // the wire shape assumes neither.
16228
+ updatedAt: external_exports.string().nullable()
16229
+ }).meta({ id: "StorePosturePack" });
16230
+ var StorePosturePolicyCounts = external_exports.object({
16231
+ total: external_exports.number().int().min(0),
16232
+ disabled: external_exports.number().int().min(0),
16233
+ // Exhaustive per-action map; the builder pre-fills every action with 0.
16234
+ //
16235
+ // Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
16236
+ // enforces exhaustiveness either way, but z.record emits `propertyNames` +
16237
+ // `additionalProperties` into a generated schema document, and a type
16238
+ // generator renders THAT with every key optional — a sender built against
16239
+ // the generated type would typecheck and still be rejected at runtime. An
16240
+ // explicit object emits `properties` + `required`, so generated types
16241
+ // demand all five.
16242
+ //
16243
+ // `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
16244
+ // ActionTaken member is a COMPILE error here instead of silent drift.
16245
+ // `.strict()` is load-bearing — it rejects an unknown action key, which a
16246
+ // bare object would silently STRIP, accepting a miscounted map as valid.
16247
+ byAction: external_exports.object({
16248
+ warn: external_exports.number().int().min(0),
16249
+ redact: external_exports.number().int().min(0),
16250
+ block: external_exports.number().int().min(0),
16251
+ allow: external_exports.number().int().min(0),
16252
+ log: external_exports.number().int().min(0)
16253
+ }).strict()
16254
+ }).meta({ id: "StorePosturePolicyCounts" });
16255
+ var StorePosturePlugin = external_exports.object({
16256
+ /** Package name of the reporting plugin. */
16257
+ package: external_exports.string().min(1).max(200),
16258
+ version: external_exports.string().min(1).max(64),
16259
+ /** Version of the bundled core, when the build records one separately. */
16260
+ ossVersion: external_exports.string().max(64).nullable(),
16219
16261
  /**
16220
- * Partial: only Flag keys with non-zero counts are included.
16221
- * Strict: unknown keys are rejected only Flag values are valid keys.
16262
+ * `version` of the policy bundle this machine last fetched. Bounded at 200
16263
+ * rather than the 64 a bare sha256 hex digest needs today, so a later
16264
+ * format with an algorithm prefix does not start rejecting the channel.
16222
16265
  */
16223
- flagRollup: external_exports.object({
16224
- update: external_exports.number().int().nonnegative(),
16225
- stale: external_exports.number().int().nonnegative(),
16226
- conflict: external_exports.number().int().nonnegative(),
16227
- unknown: external_exports.number().int().nonnegative(),
16228
- change: external_exports.number().int().nonnegative(),
16229
- untracked: external_exports.number().int().nonnegative(),
16230
- risk: external_exports.number().int().nonnegative(),
16231
- findings: external_exports.number().int().nonnegative()
16232
- }).partial().strict(),
16233
- items: external_exports.array(AssetSummary)
16234
- }).meta({ id: "AssetGroup" });
16235
- var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16236
- var McpTool = external_exports.object({
16237
- name: external_exports.string(),
16238
- signature: external_exports.string(),
16239
- description: external_exports.string(),
16240
- write: external_exports.boolean(),
16241
- /** Non-null string when tool is dangerous / blocked; null otherwise. */
16242
- risk: external_exports.string().nullable()
16243
- }).meta({ id: "McpTool" });
16244
- var AssetFindingRef = external_exports.object({
16245
- id: external_exports.string(),
16246
- title: external_exports.string(),
16247
- note: external_exports.string()
16248
- });
16249
- var AssetDetail = AssetSummary.extend({
16250
- /** string | null — null when no description is available. */
16251
- description: external_exports.string().nullable(),
16252
- /** trustLevel | null — null for non-MCP assets. */
16253
- trust: TrustLevel.nullable(),
16254
- /** Type-specific raw key/values — FE renders the grid. */
16255
- meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16256
- /** always present object when there is an active finding, null when absent. */
16257
- finding: AssetFindingRef.nullable(),
16258
- /** MCP exposed-tools list — omitted for non-mcp. */
16259
- tools: external_exports.array(McpTool).optional()
16260
- }).meta({ id: "AssetDetail" });
16261
- var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16262
- var InventoryStats = external_exports.object({
16263
- /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16264
- attention: external_exports.number().int().nonnegative(),
16265
- byType: external_exports.object({
16266
- project: external_exports.number().int().nonnegative(),
16267
- skill: external_exports.number().int().nonnegative(),
16268
- mcp: external_exports.number().int().nonnegative(),
16269
- hook: external_exports.number().int().nonnegative(),
16270
- config: external_exports.number().int().nonnegative()
16271
- }),
16272
- harnesses: external_exports.number().int().nonnegative(),
16273
- mcpTrust: external_exports.object({
16274
- "known-good": external_exports.number().int().nonnegative(),
16275
- risky: external_exports.number().int().nonnegative(),
16276
- unapproved: external_exports.number().int().nonnegative()
16277
- })
16278
- }).meta({ id: "InventoryStats" });
16279
- var FileSummary = external_exports.object({
16280
- path: external_exports.string(),
16281
- name: external_exports.string(),
16282
- origin: Origin,
16283
- /** Effective access (override applied). */
16284
- access: AccessLevel,
16285
- /** True when a file_access_override differs from the computed default. */
16286
- isCustom: external_exports.boolean(),
16287
- findings: external_exports.number().int().nonnegative(),
16288
- /** When the file was auto-blocked by a detection; null when not blocked. */
16289
- blockedAt: external_exports.iso.datetime().nullable().optional(),
16290
- /** Why the file was blocked; null when absent. */
16291
- note: external_exports.string().nullable().optional()
16292
- }).meta({ id: "FileSummary" });
16293
- var FolderSummary = external_exports.object({
16294
- name: external_exports.string(),
16295
- path: external_exports.string(),
16296
- /** Rollup of effective access across all descendants. */
16297
- accessCounts: AccessCounts
16298
- }).meta({ id: "FolderSummary" });
16299
- var ProjectTreeResponse = external_exports.object({
16300
- project: external_exports.object({
16301
- id: external_exports.string(),
16302
- repo: external_exports.string(),
16303
- visibility: Visibility
16304
- }),
16305
- path: external_exports.string(),
16306
- /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16307
- folders: external_exports.array(FolderSummary).optional(),
16308
- files: external_exports.array(FileSummary)
16309
- }).meta({ id: "ProjectTreeResponse" });
16310
- var FileDetail = FileSummary.extend({
16311
- project: external_exports.object({
16312
- repo: external_exports.string(),
16313
- visibility: Visibility,
16314
- language: external_exports.string(),
16315
- policyDefault: AccessLevel,
16316
- updatedAt: external_exports.iso.datetime()
16317
- }),
16318
- findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16319
- }).meta({ id: "FileDetail" });
16320
- var SetFileAccessBody = external_exports.object({
16321
- path: external_exports.string(),
16322
- access: AccessLevel
16323
- }).meta({ id: "SetFileAccessBody" });
16324
- var SetFileAccessResponse = external_exports.object({
16325
- file: FileSummary,
16326
- accessCounts: AccessCounts
16327
- }).meta({ id: "SetFileAccessResponse" });
16328
- var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16329
- var HarnessEventItem = external_exports.object({
16330
- kind: HarnessEventKind,
16331
- title: external_exports.string(),
16332
- detail: external_exports.string(),
16333
- occurredAt: external_exports.iso.datetime(),
16334
- findingId: external_exports.string().nullable().optional()
16335
- }).meta({ id: "HarnessEventItem" });
16336
- var HarnessEventsResponse = external_exports.object({
16337
- counts: external_exports.object({
16338
- block: external_exports.number().int().nonnegative(),
16339
- redact: external_exports.number().int().nonnegative(),
16340
- warn: external_exports.number().int().nonnegative()
16341
- }),
16342
- items: external_exports.array(HarnessEventItem)
16343
- }).meta({ id: "HarnessEventsResponse" });
16344
- var RescanResponse = external_exports.object({
16345
- jobId: external_exports.string(),
16346
- startedAt: external_exports.iso.datetime()
16347
- }).meta({ id: "RescanResponse" });
16348
- var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16349
- var ListAssetsQuery = external_exports.object({
16350
- /** Filter by one or more AssetType values; absent means all types. */
16351
- type: external_exports.array(AssetType).optional(),
16352
- /** Free-text search term. */
16353
- q: external_exports.string().optional()
16354
- });
16355
- var GetProjectTreeQuery = external_exports.object({
16356
- /** Subtree root path; defaults to repository root when absent. */
16357
- path: external_exports.string().optional(),
16358
- /** Free-text filter applied to file paths. */
16359
- q: external_exports.string().optional(),
16360
- /**
16361
- * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16362
- * file across the whole repo (folders omitted, most-recent first), ignoring
16363
- * `path`/`q` — powers the project-wide "recently blocked" strip.
16364
- */
16365
- filter: external_exports.enum(["blocked"]).optional()
16266
+ policyBundleVersion: external_exports.string().max(200).nullable(),
16267
+ /** Epoch millis, on the CLIENT clock, of that fetch. */
16268
+ policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
16269
+ }).meta({ id: "StorePosturePlugin" });
16270
+ var StorePostureSnapshot = external_exports.object({
16271
+ deviceId: external_exports.guid(),
16272
+ hostname: external_exports.string().min(1).max(253),
16273
+ // Epoch millis on the CLIENT clock. Bounded by what a receiving store
16274
+ // accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
16275
+ capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
16276
+ // False is a measurement, not an error state: "no local store exists on
16277
+ // this machine".
16278
+ storePresent: external_exports.boolean(),
16279
+ schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
16280
+ // PRAGMA user_version
16281
+ findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
16282
+ // Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
16283
+ // bound does and does not do. Worth stating for these two specifically:
16284
+ // they are read from the local store's own ROWS rather than from this
16285
+ // machine's clock, so a damaged or hand-edited store is enough to produce
16286
+ // an out-of-range value with no clock skew involved.
16287
+ findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16288
+ findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16289
+ packs: external_exports.array(StorePosturePack).max(500),
16290
+ policyCounts: StorePosturePolicyCounts,
16291
+ // OPTIONAL, not nullable: a reporter that predates this member keeps
16292
+ // getting its 200 without a payload change.
16293
+ plugin: StorePosturePlugin.optional()
16294
+ }).meta({ id: "StorePostureSnapshot" });
16295
+ var CAPTURE_VERSION_PREFIX = "capture/";
16296
+ var RecordAuditEventRequest = AuditEventInput.extend({
16297
+ inspections: external_exports.array(ToolCallInspection).default([])
16298
+ }).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
16299
+ message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
16300
+ path: ["inspections"]
16301
+ }).meta({ id: "RecordAuditEventRequest" });
16302
+ var IngestAck = external_exports.object({
16303
+ accepted: external_exports.number().int().nonnegative(),
16304
+ duplicates: external_exports.number().int().nonnegative()
16366
16305
  });
16367
- var GetProjectFileQuery = external_exports.object({
16368
- /** Repository-relative file path; absent or empty 400. */
16369
- path: external_exports.string()
16306
+ var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
16307
+ var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
16308
+ var PluginWhoami = external_exports.object({
16309
+ tenantName: printable(200),
16310
+ userEmail: printable(320),
16311
+ role: printable(64),
16312
+ keyKind: printable(64),
16313
+ serverTime: printable(64)
16370
16314
  });
16371
- var GetHarnessEventsQuery = external_exports.object({
16372
- /** Maximum number of events to return. Range: 1–50; default: 7. */
16373
- limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16315
+ var ControlPlaneErrorBody = external_exports.object({
16316
+ error: external_exports.object({
16317
+ code: external_exports.string().optional(),
16318
+ message: external_exports.string().optional()
16319
+ }).optional()
16374
16320
  });
16375
16321
 
16376
- // ../../packages/schema/src/zod/exception.ts
16377
- var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
16378
- var ExceptionConditions = external_exports.object({
16379
- repo: external_exports.string().optional(),
16380
- sourceTool: external_exports.string().optional(),
16381
- provider: external_exports.string().optional()
16382
- }).strict();
16383
- var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16384
- var DetectionException = external_exports.object({
16385
- id: external_exports.guid(),
16386
- ruleId: external_exports.string(),
16387
- // Denormalized from the rule, for reporting — never matched on.
16388
- category: DetectionCategory,
16389
- // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
16390
- // fingerprint, never the raw value, and never reversible. Matching recomputes
16391
- // the fingerprint from a fresh capture; the value itself is never stored.
16392
- // Shape-constrained so a malformed — or accidentally raw — value is rejected
16393
- // at the boundary rather than persisted.
16394
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
16395
- // Version of the fingerprint key the grant was written under; a rotated key
16396
- // invalidates old grants rather than silently mismatching them.
16397
- keyVersion: external_exports.number().int().positive(),
16398
- // maskMatch() preview of the approved value — never the raw value.
16399
- maskedValue: external_exports.string(),
16400
- capability: ExceptionCapability.default("suppress"),
16401
- scope: ExceptionScope,
16402
- expiresAt: external_exports.iso.datetime().nullable(),
16403
- maxUses: external_exports.number().int().positive().nullable(),
16404
- useCount: external_exports.number().int().nonnegative(),
16405
- lastUsedAt: external_exports.iso.datetime().nullable(),
16406
- // Mandatory: every grant carries the human reason it exists.
16407
- justification: external_exports.string().min(1),
16408
- conditions: ExceptionConditions.nullable(),
16409
- createdBy: external_exports.string(),
16410
- createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
16411
- createdAt: external_exports.iso.datetime(),
16412
- updatedAt: external_exports.iso.datetime(),
16413
- // Revocation is terminal and retained — consumed/expired/revoked rows are
16414
- // audit evidence; nothing in the exception lifecycle hard-deletes.
16415
- revokedAt: external_exports.iso.datetime().nullable(),
16416
- revokedBy: external_exports.string().nullable(),
16417
- revokeReason: external_exports.string().nullable()
16418
- });
16419
- var ExceptionBundleEntry = DetectionException.pick({
16420
- id: true,
16421
- ruleId: true,
16422
- valueFingerprint: true,
16423
- keyVersion: true,
16424
- capability: true,
16425
- expiresAt: true,
16426
- maxUses: true,
16427
- useCount: true,
16428
- conditions: true
16429
- });
16430
- var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16322
+ // ../../packages/schema/src/zod/registry.ts
16323
+ var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16324
+ var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16325
+ var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16326
+ var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16431
16327
 
16432
16328
  // ../../packages/schema/src/zod/rule.ts
16433
- var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16329
+ var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
16434
16330
  var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16435
- var KeywordMatcher = external_exports.object({
16331
+ var KeywordMatcher = external_exports.strictObject({
16436
16332
  type: external_exports.literal("keyword"),
16437
16333
  // An empty keyword matches at every position, yielding one zero-length span
16438
16334
  // per character. Rejected here because a keyword that matches everything is
@@ -16448,16 +16344,31 @@ function isValidRegex(pattern, flags) {
16448
16344
  return false;
16449
16345
  }
16450
16346
  }
16347
+ function probeFlags(flags) {
16348
+ return flags.replace(/[gy]/g, "");
16349
+ }
16451
16350
  function matchesEmptyString(pattern, flags) {
16452
16351
  try {
16453
- const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
16352
+ const re = new RegExp(pattern, probeFlags(flags));
16454
16353
  return re.exec("")?.[0].length === 0;
16455
16354
  } catch {
16456
16355
  return false;
16457
16356
  }
16458
16357
  }
16358
+ function spansWholeMatch(captureGroup) {
16359
+ return captureGroup === void 0 || captureGroup === 0;
16360
+ }
16361
+ function captureGroupCount(pattern, flags) {
16362
+ try {
16363
+ const probe = new RegExp(`${pattern}|`, probeFlags(flags));
16364
+ const result = probe.exec("");
16365
+ return result ? result.length - 1 : void 0;
16366
+ } catch {
16367
+ return void 0;
16368
+ }
16369
+ }
16459
16370
  var MAX_PATTERN_LENGTH = 2e3;
16460
- var RegexMatcher = external_exports.object({
16371
+ var RegexMatcher = external_exports.strictObject({
16461
16372
  type: external_exports.literal("regex"),
16462
16373
  pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16463
16374
  flags: external_exports.string().default("gi"),
@@ -16465,374 +16376,112 @@ var RegexMatcher = external_exports.object({
16465
16376
  }).refine((v) => isValidRegex(v.pattern, v.flags), {
16466
16377
  message: "pattern/flags do not form a valid JavaScript regular expression",
16467
16378
  path: ["pattern"]
16468
- }).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
16379
+ }).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
16469
16380
  message: 'a whole-match regex that can match the empty string (e.g. "\\d*", "a?", "(?:)") can hang the matcher \u2014 scope the quantifier to a captureGroup, or require at least one character',
16470
- path: ["pattern"]
16471
- });
16472
- var ValidatorMatcher = external_exports.object({
16473
- type: external_exports.literal("validator"),
16474
- name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
16475
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16476
- });
16477
- var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
16478
- var AppliesTo = external_exports.object({
16479
- // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16480
- extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16481
- }).meta({ id: "AppliesTo" });
16482
- var PostValidatorRef = external_exports.union([
16483
- external_exports.string(),
16484
- external_exports.object({
16485
- name: external_exports.string(),
16486
- config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16487
- })
16488
- ]).meta({ id: "PostValidatorRef" });
16489
- var RequiresNearby = external_exports.object({
16490
- // Each array, when present, must be non-empty and contain non-empty strings —
16491
- // an empty/blank criterion would either never fire or (for labels) match
16492
- // everything.
16493
- categories: external_exports.array(DetectionCategory).min(1).optional(),
16494
- ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16495
- labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16496
- windowChars: external_exports.number().int().positive().default(160),
16497
- // Optional confidence bump applied when a gated match is corroborated. Capped
16498
- // small: it nudges confidence, it does not assert certainty.
16499
- confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16500
- }).refine(
16501
- (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16502
- { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16503
- ).meta({ id: "RequiresNearby" });
16504
- var RuleFixture = external_exports.object({
16505
- label: external_exports.string(),
16506
- text: external_exports.string().max(5e4),
16507
- shouldMatch: external_exports.boolean(),
16508
- // Simulated file context for the scan, so fixtures can assert `appliesTo`
16509
- // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16510
- filePath: external_exports.string().optional(),
16511
- expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
16512
- }).meta({ id: "RuleFixture" });
16513
- var Rule = external_exports.object({
16514
- specVersion: external_exports.literal(1),
16515
- // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16516
- // the PACK id, NOT a namespace this is a DIFFERENT slug space from a
16517
- // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16518
- // therefore carries no namespace and is not globally unique across publishers;
16519
- // never feed one to splitDetectionId. `category` below (per-rule) is the
16520
- // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16521
- id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16522
- name: external_exports.string(),
16523
- category: DetectionCategory,
16524
- severity: Severity,
16525
- matcher: Matcher,
16526
- appliesTo: AppliesTo.optional(),
16527
- postValidators: external_exports.array(PostValidatorRef).optional(),
16528
- requiresNearby: RequiresNearby.optional(),
16529
- examples: external_exports.array(external_exports.string()).optional()
16530
- }).meta({ id: "Rule" });
16531
- var Author = external_exports.object({
16532
- name: external_exports.string(),
16533
- email: external_exports.email().optional(),
16534
- url: external_exports.url().optional()
16535
- }).meta({ id: "Author" });
16536
- var PackManifest = external_exports.object({
16537
- specVersion: external_exports.literal(1),
16538
- id: external_exports.string(),
16539
- name: external_exports.string(),
16540
- version: external_exports.string(),
16541
- rules: external_exports.array(external_exports.string()),
16542
- // Optional attribution/provenanceconsumed by the rule marketplace.
16543
- description: external_exports.string().optional(),
16544
- author: Author.optional(),
16545
- license: external_exports.string().optional(),
16546
- sourceUrl: external_exports.url().optional()
16547
- }).meta({ id: "PackManifest" });
16548
-
16549
- // ../../packages/schema/src/zod/policy.ts
16550
- var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
16551
- var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
16552
- var Policy = external_exports.object({
16553
- id: external_exports.guid(),
16554
- scope: PolicyScope,
16555
- target: PolicyTarget,
16556
- action: ActionTaken,
16557
- enabled: external_exports.boolean().default(true),
16558
- customKeywords: external_exports.array(external_exports.string()).optional(),
16559
- // Display name — optional so older policy rows without name still parse.
16560
- // Added for the findings API (policy.name column migration).
16561
- name: external_exports.string().optional()
16562
- }).meta({ id: "Policy" });
16563
- var PolicyBundle = external_exports.object({
16564
- version: external_exports.string(),
16565
- policies: external_exports.array(Policy),
16566
- // Rules from the installed marketplace packs (snapshotted by the
16567
- // control plane). The plugin registers these in addition to its bundled
16568
- // packs. Optional so older backends and older on-disk caches — that omit
16569
- // the field still parse; consumers read `bundle.rules ?? []`.
16570
- rules: external_exports.array(Rule).optional(),
16571
- // When true, `rules` IS the complete effective ruleset and the runtime must
16572
- // NOT merge its compiled-in bundled packs — the standalone gateway sets this
16573
- // after reading the user's installed snapshot (installed_packs, enabled
16574
- // packs only), which is how detection updates stay manual: new bundled
16575
- // rules run only after the user applies the pack update. Absent/false keeps
16576
- // the historical composition (bundled packs + rules) — older caches.
16577
- rulesComplete: external_exports.boolean().optional(),
16578
- // Active detection exceptions, evaluation subset only (see
16579
- // ExceptionBundleEntry). Optional so older bundle producers — and older
16580
- // on-disk caches — that omit the field still parse; consumers read
16581
- // `bundle.exceptions ?? []`.
16582
- exceptions: external_exports.array(ExceptionBundleEntry).optional(),
16583
- // Installed pack version, keyed by ruleId, for rules in `rules` that came
16584
- // from a versioned installed pack. Optional so older backends — and older
16585
- // on-disk caches — that omit the field still parse; consumers fall back to
16586
- // the rule's own spec version. NOT the bundle version above — see
16587
- // installedRuleset's ruleVersions for the source of truth.
16588
- ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
16589
- customKeywords: external_exports.array(external_exports.string()),
16590
- fetchedAt: external_exports.iso.datetime()
16591
- }).meta({ id: "PolicyBundle" });
16592
- var OBSERVE_ONLY_CATEGORIES = ["config"];
16593
- var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
16594
- var CATEGORY_PEAK_SEVERITY = {
16595
- secret: "critical",
16596
- financial: "critical",
16597
- // core-financial/credit-card
16598
- code_flaw: "critical",
16599
- pii: "high",
16600
- phi: "high",
16601
- custom: "high",
16602
- // user-defined; conservative
16603
- code_context: "low",
16604
- config: "low"
16605
- // observe-only; floors to monitor regardless
16606
- };
16607
- function severityFloorPolicy(category) {
16608
- if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
16609
- const peak = CATEGORY_PEAK_SEVERITY[category];
16610
- return peak === "critical" || peak === "high" ? "warn" : "monitor";
16611
- }
16612
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
16613
- var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
16614
- var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
16615
- var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
16616
- var BUILTIN_POLICY_SPECS = {
16617
- monitor: {
16618
- name: "Monitor",
16619
- action: "log",
16620
- description: "Log every match for audit. The request is allowed through untouched."
16621
- },
16622
- warn: {
16623
- name: "Warn",
16624
- action: "warn",
16625
- description: "Allow the request, but warn the user inline before it is sent."
16626
- },
16627
- redact: {
16628
- name: "Redact",
16629
- action: "redact",
16630
- description: "Automatically strip the matched value from the request, then continue."
16631
- },
16632
- block: {
16633
- name: "Block",
16634
- action: "block",
16635
- description: "Refuse the request entirely whenever any rule in this detection matches."
16636
- }
16637
- };
16638
- function builtinPolicyToAction(id) {
16639
- return BUILTIN_POLICY_SPECS[id].action;
16640
- }
16641
- var DEFAULT_ACTIONS = Object.fromEntries(
16642
- DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
16643
- );
16644
- var BUILTIN_POLICIES = Object.fromEntries(
16645
- KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
16646
- );
16647
- var DEFAULT_PACK_POLICY_ID = "monitor";
16648
- function policyIdToAction(policyId) {
16649
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
16650
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
16651
- return BUILTIN_POLICIES[id].action;
16652
- }
16653
- var UsedByItem = external_exports.object({
16654
- id: external_exports.string(),
16655
- name: external_exports.string(),
16656
- ruleCount: external_exports.number().int().nonnegative(),
16657
- enabled: external_exports.boolean()
16658
- }).meta({ id: "UsedByItem" });
16659
- var PolicyListItem = external_exports.object({
16660
- id: external_exports.string(),
16661
- kind: PolicyKind,
16662
- name: external_exports.string(),
16663
- enabled: external_exports.boolean(),
16664
- usedByCount: external_exports.number().int().nonnegative()
16665
- }).meta({ id: "PolicyListItem" });
16666
- var PolicyDetail = external_exports.object({
16667
- specVersion: external_exports.literal(1),
16668
- id: external_exports.string(),
16669
- kind: PolicyKind,
16670
- name: external_exports.string(),
16671
- enabled: external_exports.boolean(),
16672
- description: external_exports.string(),
16673
- usedBy: external_exports.array(UsedByItem)
16674
- }).meta({ id: "PolicyDetail" });
16675
- var PolicyStatsResponse = external_exports.object({
16676
- policies: external_exports.number().int().nonnegative(),
16677
- builtin: external_exports.number().int().nonnegative(),
16678
- custom: external_exports.number().int().nonnegative(),
16679
- detectionsGoverned: external_exports.number().int().nonnegative()
16680
- }).meta({ id: "PolicyStatsResponse" });
16681
-
16682
- // ../../packages/schema/src/zod/api.ts
16683
- var LIST_QUERY_MAX_LIMIT = 200;
16684
- var ListEventsQuery = external_exports.object({
16685
- cursor: external_exports.string().optional(),
16686
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16687
- sourceTool: external_exports.string().optional(),
16688
- kind: external_exports.string().optional(),
16689
- from: external_exports.iso.datetime().optional(),
16690
- to: external_exports.iso.datetime().optional()
16691
- });
16692
- var ListEventsResponse = external_exports.object({
16693
- items: external_exports.array(Event),
16694
- nextCursor: external_exports.string().nullable()
16695
- }).meta({ id: "ListEventsResponse" });
16696
- var IngestResponse = external_exports.object({
16697
- accepted: external_exports.number().int().nonnegative(),
16698
- duplicates: external_exports.number().int().nonnegative()
16699
- }).meta({ id: "IngestResponse" });
16700
- var ListFindingsQuery = external_exports.object({
16701
- cursor: external_exports.string().optional(),
16702
- limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
16703
- severity: external_exports.string().optional(),
16704
- category: external_exports.string().optional(),
16705
- eventId: external_exports.guid().optional()
16706
- });
16707
- var ListFindingsResponse = external_exports.object({
16708
- items: external_exports.array(Finding),
16709
- nextCursor: external_exports.string().nullable()
16710
- }).meta({ id: "ListFindingsResponse" });
16711
- var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
16712
- var CreatePolicyRequest = Policy.omit({ id: true }).meta({
16713
- id: "CreatePolicyRequest"
16714
- });
16715
- var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
16716
- var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
16717
- var ErrorResponse = external_exports.object({
16718
- error: external_exports.object({
16719
- code: external_exports.string(),
16720
- message: external_exports.string(),
16721
- details: external_exports.unknown().optional()
16722
- })
16723
- }).meta({ id: "ErrorResponse" });
16724
-
16725
- // ../../packages/schema/src/zod/config-inventory.ts
16726
- var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
16727
- var SkillScanEntry = external_exports.object({
16728
- name: external_exports.string().min(1),
16729
- // The identity source: a marketplace repo for plugin skills (e.g.
16730
- // 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
16731
- // 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
16732
- // a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
16733
- source: external_exports.string().min(1),
16734
- scope: ConfigScope,
16735
- pluginName: external_exports.string().optional(),
16736
- // Volatile — rides the attribute bag, never the identity hash.
16737
- version: external_exports.string().optional(),
16738
- description: external_exports.string().optional(),
16739
- // Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
16740
- updatedAt: external_exports.iso.datetime().optional(),
16741
- // Filesystem path — the promoted inventory `location` column.
16742
- location: external_exports.string().optional()
16743
- });
16744
- var HookScanEntry = external_exports.object({
16745
- // Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
16746
- // set is harness-defined and grows without a schema change.
16747
- event: external_exports.string().min(1),
16748
- // The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
16749
- matcher: external_exports.string().optional(),
16750
- command: external_exports.string().min(1),
16751
- timeout: external_exports.number().optional(),
16752
- scope: ConfigScope,
16753
- pluginName: external_exports.string().optional(),
16754
- // The settings file / hooks.json the entry came from.
16755
- location: external_exports.string().optional()
16756
- });
16757
- var McpServerScanEntry = external_exports.object({
16758
- // The server's config key ("github", "filesystem", …) — identity, with the
16759
- // qualified scope (see mcpServerIdentityKey).
16760
- name: external_exports.string().min(1),
16761
- scope: ConfigScope,
16762
- pluginName: external_exports.string().optional(),
16763
- // The owning plugin's marketplace — part of PLUGIN-scope identity: two
16764
- // marketplaces can each ship a plugin named `guard`, and without this their
16765
- // same-named servers would collapse to one row (the second silently dropped,
16766
- // inheriting the first's trust).
16767
- marketplace: external_exports.string().optional(),
16768
- // The repo identity (remote url, or the cwd for un-remoted repos) — part of
16769
- // PROJECT/LOCAL-scope identity: a server named `github` in repo A and one in
16770
- // repo B are different servers with different commands, and MUST NOT share a
16771
- // row — a shared row would let a cloned repo's .mcp.json inherit the trust
16772
- // the user granted elsewhere.
16773
- project: external_exports.string().optional(),
16774
- // 'stdio' when the entry carries a command; otherwise the config's `type`
16775
- // ('http' / 'sse' / …). Open string — the transport set is harness-defined.
16776
- transport: external_exports.string().min(1),
16777
- // Volatile on purpose (unlike hook `command`): a changed command/url is drift
16778
- // on a stable row — visible across config_scan snapshots and preserving the
16779
- // user's trust decision — never a quiet new row. One of the two is present.
16780
- // Secret-masked at collection time (the scanner runs the bundled detection
16781
- // packs over both — tokens routinely ride command args and URLs).
16782
- command: external_exports.string().optional(),
16783
- url: external_exports.string().optional(),
16784
- // Env var NAMES only, never values (the no-secrets rule).
16785
- envKeys: external_exports.array(external_exports.string()).optional(),
16786
- // The config file the entry came from.
16787
- location: external_exports.string().optional()
16788
- });
16789
- var ConfigFileScanEntry = external_exports.object({
16790
- // Basename (settings.json, CLAUDE.md) or dir name (commands/, agents/).
16791
- name: external_exports.string().min(1),
16792
- // The absolute path — identity (with scope) and the promoted `location`.
16793
- path: external_exports.string().min(1),
16794
- scope: ConfigScope,
16795
- // Human label: "User settings", "Project memory", "Slash commands", …
16796
- kind: external_exports.string().min(1),
16797
- // Derived SHAPE summary — top-level key names, entry counts, line counts.
16798
- // Never file content or values (memory files can carry sensitive detail).
16799
- detail: external_exports.string().optional(),
16800
- // Dir configs (commands/, agents/) and .mcp.json: how many entries.
16801
- entryCount: external_exports.number().optional(),
16802
- // File mtime (ISO) — the freshness signal.
16803
- updatedAt: external_exports.iso.datetime().optional()
16804
- });
16805
- var ConfigScanResult = external_exports.object({
16806
- scannedAt: external_exports.iso.datetime(),
16807
- skills: external_exports.array(SkillScanEntry),
16808
- hooks: external_exports.array(HookScanEntry),
16809
- mcpServers: external_exports.array(McpServerScanEntry),
16810
- configFiles: external_exports.array(ConfigFileScanEntry),
16811
- errors: external_exports.array(external_exports.object({ source: external_exports.string(), reason: external_exports.string() }))
16812
- });
16813
- var ConfigPostureFindingInput = external_exports.object({
16814
- ruleId: external_exports.string().min(1),
16815
- version: external_exports.string().min(1),
16816
- span: Span,
16817
- // For posture rules this is the offending COMMAND (config the user already
16818
- // holds locally, not captured secret content) — it is also the correlation
16819
- // key the read surface matches back to a hook row.
16820
- maskedMatch: external_exports.string(),
16821
- actionTaken: ActionTaken,
16822
- confidence: external_exports.number().min(0).max(1)
16823
- });
16824
- var ConfigScanRecord = external_exports.object({
16825
- items: external_exports.array(InventoryInput),
16826
- scanEvent: AuditEventInput,
16827
- definitions: external_exports.array(InspectionDefinitionInput).optional(),
16828
- findings: external_exports.array(ConfigPostureFindingInput).optional()
16829
- });
16830
-
16831
- // ../../packages/schema/src/zod/registry.ts
16832
- var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16833
- var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16834
- var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
16835
- var PublisherKind = external_exports.enum(["labs", "user", "org"]);
16381
+ path: ["pattern"]
16382
+ }).superRefine((v, ctx) => {
16383
+ if (v.captureGroup === void 0) return;
16384
+ const groups = captureGroupCount(v.pattern, v.flags);
16385
+ if (groups === void 0 || v.captureGroup <= groups) return;
16386
+ ctx.addIssue({
16387
+ code: "custom",
16388
+ path: ["captureGroup"],
16389
+ 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.`
16390
+ });
16391
+ });
16392
+ var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
16393
+ var MATCHER_TYPES = MatcherType.options;
16394
+ var AppliesTo = external_exports.strictObject({
16395
+ // Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
16396
+ extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
16397
+ }).meta({ id: "AppliesTo" });
16398
+ var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
16399
+ var PostValidatorRef = external_exports.union(
16400
+ [
16401
+ PostValidatorName,
16402
+ external_exports.strictObject({
16403
+ name: PostValidatorName,
16404
+ config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16405
+ })
16406
+ ],
16407
+ {
16408
+ // A union reports one collapsed issue for every way its arms can fail, so
16409
+ // this has to describe the whole shape rather than just the name — it is
16410
+ // what an author sees for a misspelled name AND for a stray key in the
16411
+ // object form. The names come from the enum so the message cannot go
16412
+ // stale. Without it Zod says only "Invalid input", which is precisely the
16413
+ // no-feedback outcome this schema exists to remove.
16414
+ error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
16415
+ " or "
16416
+ )}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
16417
+ }
16418
+ ).meta({ id: "PostValidatorRef" });
16419
+ var RequiresNearby = external_exports.strictObject({
16420
+ // Each array, when present, must be non-empty and contain non-empty strings —
16421
+ // an empty/blank criterion would either never fire or (for labels) match
16422
+ // everything.
16423
+ categories: external_exports.array(DetectionCategory).min(1).optional(),
16424
+ ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16425
+ labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
16426
+ windowChars: external_exports.number().int().positive().default(160),
16427
+ // Optional confidence bump applied when a gated match is corroborated. Capped
16428
+ // small: it nudges confidence, it does not assert certainty.
16429
+ confidenceBoost: external_exports.number().min(0).max(0.3).optional()
16430
+ }).refine(
16431
+ (v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
16432
+ { message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
16433
+ ).meta({ id: "RequiresNearby" });
16434
+ var RuleFixture = external_exports.strictObject({
16435
+ label: external_exports.string(),
16436
+ text: external_exports.string().max(5e4),
16437
+ shouldMatch: external_exports.boolean(),
16438
+ // Simulated file context for the scan, so fixtures can assert `appliesTo`
16439
+ // gating (e.g. a Python-only pattern must NOT fire in a .ts file).
16440
+ filePath: external_exports.string().optional(),
16441
+ expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
16442
+ }).meta({ id: "RuleFixture" });
16443
+ var Rule = external_exports.strictObject({
16444
+ // A pinned literal over a STRICT object, and the two together decide how this
16445
+ // format may grow. A rule carrying a key not listed below is refused with
16446
+ // `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
16447
+ // `invalid_value`. So the only additive path is adding an OPTIONAL field here
16448
+ // — that keeps every rule authored before it valid — and a rule author has no
16449
+ // way to introduce a field of their own or to opt into a later version.
16450
+ // Widening the format means changing this literal and every consumer of it.
16451
+ specVersion: external_exports.literal(1),
16452
+ // `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
16453
+ // the PACK id, NOT a namespace this is a DIFFERENT slug space from a
16454
+ // detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
16455
+ // therefore carries no namespace and is not globally unique across publishers;
16456
+ // never feed one to splitDetectionId. `category` below (per-rule) is the
16457
+ // taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
16458
+ id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
16459
+ name: external_exports.string(),
16460
+ category: DetectionCategory,
16461
+ severity: Severity,
16462
+ matcher: Matcher,
16463
+ appliesTo: AppliesTo.optional(),
16464
+ postValidators: external_exports.array(PostValidatorRef).optional(),
16465
+ requiresNearby: RequiresNearby.optional(),
16466
+ examples: external_exports.array(external_exports.string()).optional()
16467
+ }).meta({ id: "Rule" });
16468
+ var Author = external_exports.object({
16469
+ name: external_exports.string(),
16470
+ email: external_exports.email().optional(),
16471
+ url: external_exports.url().optional()
16472
+ }).meta({ id: "Author" });
16473
+ var PackManifest = external_exports.object({
16474
+ specVersion: external_exports.literal(1),
16475
+ id: external_exports.string(),
16476
+ name: external_exports.string(),
16477
+ version: external_exports.string(),
16478
+ rules: external_exports.array(external_exports.string()),
16479
+ // Optional attribution/provenanceconsumed by the rule marketplace.
16480
+ description: external_exports.string().optional(),
16481
+ author: Author.optional(),
16482
+ license: external_exports.string().optional(),
16483
+ sourceUrl: external_exports.url().optional()
16484
+ }).meta({ id: "PackManifest" });
16836
16485
 
16837
16486
  // ../../packages/schema/src/zod/detection.ts
16838
16487
  var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
@@ -17032,6 +16681,231 @@ function buildDetectionsList(summaries, query) {
17032
16681
  return { counts, items: filtered.map(summaryToDetectionListItem) };
17033
16682
  }
17034
16683
 
16684
+ // ../../packages/schema/src/zod/inventory.ts
16685
+ var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
16686
+ var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
16687
+ var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
16688
+ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
16689
+ var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16690
+ var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16691
+ var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16692
+ var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
16693
+ id: "HarnessId"
16694
+ });
16695
+ var AccessCounts = external_exports.object({
16696
+ open: external_exports.number().int().nonnegative(),
16697
+ approved: external_exports.number().int().nonnegative(),
16698
+ blocked: external_exports.number().int().nonnegative(),
16699
+ total: external_exports.number().int().nonnegative()
16700
+ }).meta({ id: "AccessCounts" });
16701
+ var AssetSummary = external_exports.object({
16702
+ id: external_exports.string(),
16703
+ type: AssetType,
16704
+ name: external_exports.string(),
16705
+ sub: external_exports.string(),
16706
+ flags: external_exports.array(Flag),
16707
+ /** MCP servers only — omitted for all other types. */
16708
+ trust: TrustLevel.optional()
16709
+ }).meta({ id: "AssetSummary" });
16710
+ var ProjectSummary = external_exports.object({
16711
+ id: external_exports.string(),
16712
+ name: external_exports.string(),
16713
+ repo: external_exports.string(),
16714
+ visibility: Visibility,
16715
+ language: external_exports.string(),
16716
+ policyDefault: AccessLevel,
16717
+ updatedAt: external_exports.iso.datetime(),
16718
+ accessCounts: AccessCounts,
16719
+ findingsCount: external_exports.number().int().nonnegative()
16720
+ }).meta({ id: "ProjectSummary" });
16721
+ var HarnessCategory = external_exports.object({
16722
+ /** One of config/skill/mcp/hook — never project (enforced at service layer). */
16723
+ type: AssetType,
16724
+ assets: external_exports.array(AssetSummary)
16725
+ });
16726
+ var HarnessSummary = external_exports.object({
16727
+ id: HarnessId,
16728
+ label: external_exports.string(),
16729
+ kind: external_exports.string(),
16730
+ version: external_exports.string(),
16731
+ sessions: external_exports.number().int().nonnegative(),
16732
+ assetCount: external_exports.number().int().nonnegative(),
16733
+ flagCount: external_exports.number().int().nonnegative(),
16734
+ projects: external_exports.array(ProjectSummary),
16735
+ categories: external_exports.array(HarnessCategory)
16736
+ }).meta({ id: "HarnessSummary" });
16737
+ var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
16738
+ var AssetGroup = external_exports.object({
16739
+ /** Group key — never project (enforced at service layer). */
16740
+ type: AssetType,
16741
+ total: external_exports.number().int().nonnegative(),
16742
+ /**
16743
+ * MCP group only — omitted for all other types.
16744
+ * Partial: only TrustLevel keys with non-zero counts are included.
16745
+ * Strict: unknown keys are rejected — only TrustLevel values are valid keys.
16746
+ */
16747
+ trustRollup: external_exports.object({
16748
+ "known-good": external_exports.number().int().nonnegative(),
16749
+ risky: external_exports.number().int().nonnegative(),
16750
+ unapproved: external_exports.number().int().nonnegative()
16751
+ }).partial().strict().optional(),
16752
+ /**
16753
+ * Partial: only Flag keys with non-zero counts are included.
16754
+ * Strict: unknown keys are rejected — only Flag values are valid keys.
16755
+ */
16756
+ flagRollup: external_exports.object({
16757
+ update: external_exports.number().int().nonnegative(),
16758
+ stale: external_exports.number().int().nonnegative(),
16759
+ conflict: external_exports.number().int().nonnegative(),
16760
+ unknown: external_exports.number().int().nonnegative(),
16761
+ change: external_exports.number().int().nonnegative(),
16762
+ untracked: external_exports.number().int().nonnegative(),
16763
+ risk: external_exports.number().int().nonnegative(),
16764
+ findings: external_exports.number().int().nonnegative()
16765
+ }).partial().strict(),
16766
+ items: external_exports.array(AssetSummary)
16767
+ }).meta({ id: "AssetGroup" });
16768
+ var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
16769
+ var McpTool = external_exports.object({
16770
+ name: external_exports.string(),
16771
+ signature: external_exports.string(),
16772
+ description: external_exports.string(),
16773
+ write: external_exports.boolean(),
16774
+ /** Non-null string when tool is dangerous / blocked; null otherwise. */
16775
+ risk: external_exports.string().nullable()
16776
+ }).meta({ id: "McpTool" });
16777
+ var AssetFindingRef = external_exports.object({
16778
+ id: external_exports.string(),
16779
+ title: external_exports.string(),
16780
+ note: external_exports.string()
16781
+ });
16782
+ var AssetDetail = AssetSummary.extend({
16783
+ /** string | null — null when no description is available. */
16784
+ description: external_exports.string().nullable(),
16785
+ /** trustLevel | null — null for non-MCP assets. */
16786
+ trust: TrustLevel.nullable(),
16787
+ /** Type-specific raw key/values — FE renders the grid. */
16788
+ meta: external_exports.record(external_exports.string(), external_exports.unknown()),
16789
+ /** always present — object when there is an active finding, null when absent. */
16790
+ finding: AssetFindingRef.nullable(),
16791
+ /** MCP exposed-tools list — omitted for non-mcp. */
16792
+ tools: external_exports.array(McpTool).optional()
16793
+ }).meta({ id: "AssetDetail" });
16794
+ var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
16795
+ var InventoryStats = external_exports.object({
16796
+ /** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
16797
+ attention: external_exports.number().int().nonnegative(),
16798
+ byType: external_exports.object({
16799
+ project: external_exports.number().int().nonnegative(),
16800
+ skill: external_exports.number().int().nonnegative(),
16801
+ mcp: external_exports.number().int().nonnegative(),
16802
+ hook: external_exports.number().int().nonnegative(),
16803
+ config: external_exports.number().int().nonnegative()
16804
+ }),
16805
+ harnesses: external_exports.number().int().nonnegative(),
16806
+ mcpTrust: external_exports.object({
16807
+ "known-good": external_exports.number().int().nonnegative(),
16808
+ risky: external_exports.number().int().nonnegative(),
16809
+ unapproved: external_exports.number().int().nonnegative()
16810
+ })
16811
+ }).meta({ id: "InventoryStats" });
16812
+ var FileSummary = external_exports.object({
16813
+ path: external_exports.string(),
16814
+ name: external_exports.string(),
16815
+ origin: Origin,
16816
+ /** Effective access (override applied). */
16817
+ access: AccessLevel,
16818
+ /** True when a file_access_override differs from the computed default. */
16819
+ isCustom: external_exports.boolean(),
16820
+ findings: external_exports.number().int().nonnegative(),
16821
+ /** When the file was auto-blocked by a detection; null when not blocked. */
16822
+ blockedAt: external_exports.iso.datetime().nullable().optional(),
16823
+ /** Why the file was blocked; null when absent. */
16824
+ note: external_exports.string().nullable().optional()
16825
+ }).meta({ id: "FileSummary" });
16826
+ var FolderSummary = external_exports.object({
16827
+ name: external_exports.string(),
16828
+ path: external_exports.string(),
16829
+ /** Rollup of effective access across all descendants. */
16830
+ accessCounts: AccessCounts
16831
+ }).meta({ id: "FolderSummary" });
16832
+ var ProjectTreeResponse = external_exports.object({
16833
+ project: external_exports.object({
16834
+ id: external_exports.string(),
16835
+ repo: external_exports.string(),
16836
+ visibility: Visibility
16837
+ }),
16838
+ path: external_exports.string(),
16839
+ /** Browse mode: one-level folders at the current path. Omitted in search mode. */
16840
+ folders: external_exports.array(FolderSummary).optional(),
16841
+ files: external_exports.array(FileSummary)
16842
+ }).meta({ id: "ProjectTreeResponse" });
16843
+ var FileDetail = FileSummary.extend({
16844
+ project: external_exports.object({
16845
+ repo: external_exports.string(),
16846
+ visibility: Visibility,
16847
+ language: external_exports.string(),
16848
+ policyDefault: AccessLevel,
16849
+ updatedAt: external_exports.iso.datetime()
16850
+ }),
16851
+ findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
16852
+ }).meta({ id: "FileDetail" });
16853
+ var SetFileAccessBody = external_exports.object({
16854
+ path: external_exports.string(),
16855
+ access: AccessLevel
16856
+ }).meta({ id: "SetFileAccessBody" });
16857
+ var SetFileAccessResponse = external_exports.object({
16858
+ file: FileSummary,
16859
+ accessCounts: AccessCounts
16860
+ }).meta({ id: "SetFileAccessResponse" });
16861
+ var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
16862
+ var HarnessEventItem = external_exports.object({
16863
+ kind: HarnessEventKind,
16864
+ title: external_exports.string(),
16865
+ detail: external_exports.string(),
16866
+ occurredAt: external_exports.iso.datetime(),
16867
+ findingId: external_exports.string().nullable().optional()
16868
+ }).meta({ id: "HarnessEventItem" });
16869
+ var HarnessEventsResponse = external_exports.object({
16870
+ counts: external_exports.object({
16871
+ block: external_exports.number().int().nonnegative(),
16872
+ redact: external_exports.number().int().nonnegative(),
16873
+ warn: external_exports.number().int().nonnegative()
16874
+ }),
16875
+ items: external_exports.array(HarnessEventItem)
16876
+ }).meta({ id: "HarnessEventsResponse" });
16877
+ var RescanResponse = external_exports.object({
16878
+ jobId: external_exports.string(),
16879
+ startedAt: external_exports.iso.datetime()
16880
+ }).meta({ id: "RescanResponse" });
16881
+ var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
16882
+ var ListAssetsQuery = external_exports.object({
16883
+ /** Filter by one or more AssetType values; absent means all types. */
16884
+ type: external_exports.array(AssetType).optional(),
16885
+ /** Free-text search term. */
16886
+ q: external_exports.string().optional()
16887
+ });
16888
+ var GetProjectTreeQuery = external_exports.object({
16889
+ /** Subtree root path; defaults to repository root when absent. */
16890
+ path: external_exports.string().optional(),
16891
+ /** Free-text filter applied to file paths. */
16892
+ q: external_exports.string().optional(),
16893
+ /**
16894
+ * Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
16895
+ * file across the whole repo (folders omitted, most-recent first), ignoring
16896
+ * `path`/`q` — powers the project-wide "recently blocked" strip.
16897
+ */
16898
+ filter: external_exports.enum(["blocked"]).optional()
16899
+ });
16900
+ var GetProjectFileQuery = external_exports.object({
16901
+ /** Repository-relative file path; absent or empty → 400. */
16902
+ path: external_exports.string()
16903
+ });
16904
+ var GetHarnessEventsQuery = external_exports.object({
16905
+ /** Maximum number of events to return. Range: 1–50; default: 7. */
16906
+ limit: external_exports.coerce.number().int().min(1).max(50).default(7)
16907
+ });
16908
+
17035
16909
  // ../../packages/schema/src/zod/shares.ts
17036
16910
  var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
17037
16911
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
@@ -17238,6 +17112,127 @@ var EgressWriteSummary = external_exports.object({
17238
17112
  droppedFiles: external_exports.array(external_exports.string()).default([])
17239
17113
  }).meta({ id: "EgressWriteSummary" });
17240
17114
 
17115
+ // ../../packages/schema/src/zod/event.ts
17116
+ var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
17117
+ var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
17118
+ var EventMetadata = external_exports.object({
17119
+ sessionId: external_exports.string().optional(),
17120
+ repo: external_exports.string().optional(),
17121
+ filePath: external_exports.string().optional(),
17122
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
17123
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
17124
+ // arguments or output, which can carry the very value a finding masked
17125
+ // (metadata is stored unredacted). Gives findings on non-file captures a
17126
+ // display location ("via Bash") when no filePath exists.
17127
+ toolName: external_exports.string().optional(),
17128
+ // Set (true) by the worktree scanner when the file is excluded by the
17129
+ // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
17130
+ // generated code can leak real secrets — but the provenance is recorded so
17131
+ // policy/dashboards can treat those findings as informational rather than
17132
+ // blocking. Omitted (not false) for tracked files and non-scan events.
17133
+ gitignored: external_exports.boolean().optional(),
17134
+ // Set (true) ONLY when the event's `content` is the COMPLETE file at
17135
+ // capture time (a worktree scan reading from disk). Hook-captured edits
17136
+ // (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
17137
+ // this. The resolver-on-ingest keys its fixed-at-source dropout
17138
+ // diff on this marker: only a whole-file snapshot can prove a previously
17139
+ // open finding is gone; a fragment's absence proves nothing (the secret
17140
+ // may live outside the hunk). Omitted (not false) for fragments and
17141
+ // non-scan events, so pre-marker clients safely default to the
17142
+ // non-authoritative path.
17143
+ wholeFile: external_exports.boolean().optional(),
17144
+ model: external_exports.string().optional(),
17145
+ turnIndex: external_exports.number().int().nonnegative().optional(),
17146
+ // Distributed-tracing correlation. `correlationId` ties a recorded event back
17147
+ // to the request that captured/ingested it (a UUID, generated independently of
17148
+ // the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
17149
+ // originating span when telemetry is enabled. Both optional + backward
17150
+ // compatible — populated by the plugin (see @akasecurity/plugin-sdk).
17151
+ correlationId: external_exports.uuid().optional(),
17152
+ traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
17153
+ // Ids of the detection exceptions that downgraded findings in this capture
17154
+ // to 'allow' — the enforcement audit trail's link back to the grant that
17155
+ // authorized the bypass. Absent on captures where no exception applied.
17156
+ exceptionIds: external_exports.array(external_exports.guid()).optional()
17157
+ }).meta({ id: "EventMetadata" });
17158
+ var Event = external_exports.object({
17159
+ id: external_exports.guid(),
17160
+ sourceTool: SourceTool,
17161
+ kind: EventKind,
17162
+ occurredAt: external_exports.iso.datetime(),
17163
+ contentHash: external_exports.string(),
17164
+ content: external_exports.string(),
17165
+ metadata: EventMetadata.optional()
17166
+ }).meta({ id: "Event" });
17167
+ var IngestEvent = Event.meta({ id: "IngestEvent" });
17168
+ var IngestBatch = external_exports.object({
17169
+ events: external_exports.array(IngestEvent).min(1).max(100),
17170
+ // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
17171
+ // additionally rejects any event whose contentHash the store has already
17172
+ // recorded — for re-runnable bulk ingest (worktree scan, transcript
17173
+ // backfill), where a re-run mints fresh event ids for identical content and
17174
+ // would otherwise accumulate duplicates. Live hook traffic must NOT set it:
17175
+ // two genuinely separate prompts can be byte-identical and both belong on
17176
+ // the timeline.
17177
+ dedupe: external_exports.literal("content-hash").optional()
17178
+ }).meta({ id: "IngestBatch" });
17179
+
17180
+ // ../../packages/schema/src/zod/exception.ts
17181
+ var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
17182
+ var ExceptionConditions = external_exports.object({
17183
+ repo: external_exports.string().optional(),
17184
+ sourceTool: external_exports.string().optional(),
17185
+ provider: external_exports.string().optional()
17186
+ }).strict();
17187
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
17188
+ var DetectionException = external_exports.object({
17189
+ id: external_exports.guid(),
17190
+ ruleId: external_exports.string(),
17191
+ // Denormalized from the rule, for reporting — never matched on.
17192
+ category: DetectionCategory,
17193
+ // HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
17194
+ // fingerprint, never the raw value, and never reversible. Matching recomputes
17195
+ // the fingerprint from a fresh capture; the value itself is never stored.
17196
+ // Shape-constrained so a malformed — or accidentally raw — value is rejected
17197
+ // at the boundary rather than persisted.
17198
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17199
+ // Version of the fingerprint key the grant was written under; a rotated key
17200
+ // invalidates old grants rather than silently mismatching them.
17201
+ keyVersion: external_exports.number().int().positive(),
17202
+ // maskMatch() preview of the approved value — never the raw value.
17203
+ maskedValue: external_exports.string(),
17204
+ capability: ExceptionCapability.default("suppress"),
17205
+ scope: ExceptionScope,
17206
+ expiresAt: external_exports.iso.datetime().nullable(),
17207
+ maxUses: external_exports.number().int().positive().nullable(),
17208
+ useCount: external_exports.number().int().nonnegative(),
17209
+ lastUsedAt: external_exports.iso.datetime().nullable(),
17210
+ // Mandatory: every grant carries the human reason it exists.
17211
+ justification: external_exports.string().min(1),
17212
+ conditions: ExceptionConditions.nullable(),
17213
+ createdBy: external_exports.string(),
17214
+ createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
17215
+ createdAt: external_exports.iso.datetime(),
17216
+ updatedAt: external_exports.iso.datetime(),
17217
+ // Revocation is terminal and retained — consumed/expired/revoked rows are
17218
+ // audit evidence; nothing in the exception lifecycle hard-deletes.
17219
+ revokedAt: external_exports.iso.datetime().nullable(),
17220
+ revokedBy: external_exports.string().nullable(),
17221
+ revokeReason: external_exports.string().nullable()
17222
+ });
17223
+ var ExceptionBundleEntry = DetectionException.pick({
17224
+ id: true,
17225
+ ruleId: true,
17226
+ valueFingerprint: true,
17227
+ keyVersion: true,
17228
+ capability: true,
17229
+ expiresAt: true,
17230
+ maxUses: true,
17231
+ useCount: true,
17232
+ conditions: true
17233
+ });
17234
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
17235
+
17241
17236
  // ../../packages/schema/src/zod/exception-action.ts
17242
17237
  var confirmation = external_exports.string().optional();
17243
17238
  var ApproveBlockedInput = external_exports.object({
@@ -17280,10 +17275,11 @@ function toApiAction(dbVal) {
17280
17275
  }
17281
17276
  function toApiCategory(dbVal) {
17282
17277
  if (dbVal === "code_context") return "source_code";
17283
- return dbVal;
17278
+ const parsed = FindingCategory.safeParse(dbVal);
17279
+ return parsed.success ? parsed.data : "custom";
17284
17280
  }
17285
17281
  function toApiProvider(sourceTool) {
17286
- return TOOL_TO_HARNESS[sourceTool] ?? "api";
17282
+ return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
17287
17283
  }
17288
17284
  var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
17289
17285
  function foldGroupStatus(instanceStatuses) {
@@ -17871,7 +17867,14 @@ function isVaultConsentValid(consent) {
17871
17867
 
17872
17868
  // ../../packages/schema/src/zod/local.ts
17873
17869
  var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17874
- var RunMode = external_exports.enum(["standalone"]);
17870
+ var MODEL_JUDGE_PAYLOAD_VERSION = 1;
17871
+ var RunMode = external_exports.enum(["standalone", "attached"]);
17872
+ var ControlPlaneConnection = external_exports.object({
17873
+ endpoint: external_exports.string().min(1),
17874
+ // Display name for the deployment, shown instead of the raw endpoint.
17875
+ label: external_exports.string().min(1).optional(),
17876
+ attachedAt: external_exports.iso.datetime()
17877
+ }).meta({ id: "ControlPlaneConnection" });
17875
17878
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17876
17879
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
17877
17880
  var ModelJudgeConsent = external_exports.object({
@@ -17880,12 +17883,10 @@ var ModelJudgeConsent = external_exports.object({
17880
17883
  });
17881
17884
  var WorkspaceSettings = external_exports.object({
17882
17885
  specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
17883
- // Settings files written by earlier releases may carry the retired 'attached'
17884
- // value; it parses as 'standalone' so those files keep loading.
17885
- runMode: external_exports.preprocess(
17886
- (v) => v === "attached" ? "standalone" : v,
17887
- RunMode.default("standalone")
17888
- ),
17886
+ runMode: RunMode.default("standalone"),
17887
+ // Present only while attached; a detach clears it. Its presence is what makes
17888
+ // `runMode: 'attached'` mean anything — see isAttached.
17889
+ controlPlane: ControlPlaneConnection.optional(),
17889
17890
  policy: SimpleDetectionPolicy.default("redact"),
17890
17891
  // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
17891
17892
  historicalAccess: HistoricalAccess.default("session-only"),
@@ -18021,6 +18022,211 @@ function toCaptureDefinitionInput(finding) {
18021
18022
  };
18022
18023
  }
18023
18024
 
18025
+ // ../../packages/schema/src/zod/managed.ts
18026
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
18027
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
18028
+ var ManagedSettingKey = external_exports.enum([
18029
+ "runMode",
18030
+ "historicalAccess",
18031
+ "vaultConsent",
18032
+ "vaultKeyCustody",
18033
+ "vaultInlineReveal",
18034
+ "modelJudgeConsent",
18035
+ "dataSharesInPlace"
18036
+ ]).meta({ id: "ManagedSettingKey" });
18037
+ var ManagedSettingsValues = external_exports.object({
18038
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
18039
+ controlPlane: external_exports.object({
18040
+ endpoint: external_exports.string().min(1),
18041
+ label: external_exports.string().min(1).optional()
18042
+ }).optional(),
18043
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
18044
+ vaultConsent: external_exports.boolean().optional(),
18045
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
18046
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
18047
+ modelJudgeConsent: external_exports.boolean().optional(),
18048
+ dataSharesInPlace: external_exports.boolean().optional()
18049
+ }).meta({ id: "ManagedSettingsValues" });
18050
+ var ManagedSettings = external_exports.object({
18051
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
18052
+ // Shown on every locked control, so the user can tell an administrative
18053
+ // decision from a bug. Absent renders as a generic "your organization".
18054
+ organization: external_exports.string().min(1).optional(),
18055
+ // What the administrator pinned.
18056
+ values: ManagedSettingsValues.default({}),
18057
+ // Which of those the user may not change. A key here with no matching value
18058
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
18059
+ // the user may still override. The two are separable on purpose.
18060
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
18061
+ }).meta({ id: "ManagedSettings" });
18062
+
18063
+ // ../../packages/schema/src/zod/policy.ts
18064
+ var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
18065
+ var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
18066
+ var Policy = external_exports.object({
18067
+ id: external_exports.guid(),
18068
+ scope: PolicyScope,
18069
+ target: PolicyTarget,
18070
+ action: ActionTaken,
18071
+ enabled: external_exports.boolean().default(true),
18072
+ customKeywords: external_exports.array(external_exports.string()).optional(),
18073
+ // Display name — optional so older policy rows without name still parse.
18074
+ // Added for the findings API (policy.name column migration).
18075
+ name: external_exports.string().optional()
18076
+ }).meta({ id: "Policy" });
18077
+ var PolicyBundle = external_exports.object({
18078
+ version: external_exports.string(),
18079
+ policies: external_exports.array(Policy),
18080
+ // Rules from the installed marketplace packs (snapshotted by the
18081
+ // control plane). The plugin registers these in addition to its bundled
18082
+ // packs. Optional so older backends — and older on-disk caches — that omit
18083
+ // the field still parse; consumers read `bundle.rules ?? []`.
18084
+ rules: external_exports.array(Rule).optional(),
18085
+ // When true, `rules` IS the complete effective ruleset and the runtime must
18086
+ // NOT merge its compiled-in bundled packs — the standalone gateway sets this
18087
+ // after reading the user's installed snapshot (installed_packs, enabled
18088
+ // packs only), which is how detection updates stay manual: new bundled
18089
+ // rules run only after the user applies the pack update. Absent/false keeps
18090
+ // the historical composition (bundled packs + rules) — older caches.
18091
+ rulesComplete: external_exports.boolean().optional(),
18092
+ // Active detection exceptions, evaluation subset only (see
18093
+ // ExceptionBundleEntry). Optional so older bundle producers — and older
18094
+ // on-disk caches — that omit the field still parse; consumers read
18095
+ // `bundle.exceptions ?? []`.
18096
+ exceptions: external_exports.array(ExceptionBundleEntry).optional(),
18097
+ // Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
18098
+ // A second axis over the same `redact` action, carried beside the policies
18099
+ // rather than on them: nothing writes ruleId-targeted policies to disk, so
18100
+ // widening Policy itself would change a persisted shape to express something
18101
+ // only the in-memory bundle needs. Optional so an older producer — or an
18102
+ // older on-disk cache — still parses; consumers read `?? []` and get the
18103
+ // pre-existing one-way behaviour, which is the safe direction to default.
18104
+ reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
18105
+ // Installed pack version, keyed by ruleId, for rules in `rules` that came
18106
+ // from a versioned installed pack. Optional so older backends — and older
18107
+ // on-disk caches — that omit the field still parse; consumers fall back to
18108
+ // the rule's own spec version. NOT the bundle version above — see
18109
+ // installedRuleset's ruleVersions for the source of truth.
18110
+ ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
18111
+ customKeywords: external_exports.array(external_exports.string()),
18112
+ fetchedAt: external_exports.iso.datetime()
18113
+ }).meta({ id: "PolicyBundle" });
18114
+ var OBSERVE_ONLY_CATEGORIES = ["config"];
18115
+ var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
18116
+ var CATEGORY_PEAK_SEVERITY = {
18117
+ secret: "critical",
18118
+ financial: "critical",
18119
+ // core-financial/credit-card
18120
+ code_flaw: "critical",
18121
+ pii: "high",
18122
+ phi: "high",
18123
+ custom: "high",
18124
+ // user-defined; conservative
18125
+ code_context: "low",
18126
+ config: "low"
18127
+ // observe-only; floors to monitor regardless
18128
+ };
18129
+ function severityFloorPolicy(category) {
18130
+ if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
18131
+ const peak = CATEGORY_PEAK_SEVERITY[category];
18132
+ return peak === "critical" || peak === "high" ? "warn" : "monitor";
18133
+ }
18134
+ var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
18135
+ var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
18136
+ var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
18137
+ var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
18138
+ var BUILTIN_POLICY_SPECS = {
18139
+ monitor: {
18140
+ name: "Monitor",
18141
+ action: "log",
18142
+ reversible: false,
18143
+ description: "Log every match for audit. The request is allowed through untouched."
18144
+ },
18145
+ warn: {
18146
+ name: "Warn",
18147
+ action: "warn",
18148
+ reversible: false,
18149
+ description: "Allow the request, but warn the user inline before it is sent."
18150
+ },
18151
+ redact: {
18152
+ name: "Redact",
18153
+ action: "redact",
18154
+ reversible: false,
18155
+ description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
18156
+ },
18157
+ vault: {
18158
+ name: "Redact & Vault",
18159
+ action: "redact",
18160
+ reversible: true,
18161
+ 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."
18162
+ },
18163
+ block: {
18164
+ name: "Block",
18165
+ action: "block",
18166
+ reversible: false,
18167
+ description: "Refuse the request entirely whenever any rule in this detection matches."
18168
+ }
18169
+ };
18170
+ function builtinPolicyToAction(id) {
18171
+ return BUILTIN_POLICY_SPECS[id].action;
18172
+ }
18173
+ var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18174
+ (id) => !BUILTIN_POLICY_SPECS[id].reversible
18175
+ );
18176
+ var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
18177
+ (id) => BUILTIN_POLICY_SPECS[id].reversible
18178
+ );
18179
+ var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
18180
+ function builtinPolicyIsReversible(id) {
18181
+ return BUILTIN_POLICY_SPECS[id].reversible;
18182
+ }
18183
+ function policyIdIsReversible(policyId) {
18184
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18185
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18186
+ return builtinPolicyIsReversible(id);
18187
+ }
18188
+ var DEFAULT_ACTIONS = Object.fromEntries(
18189
+ DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
18190
+ );
18191
+ var BUILTIN_POLICIES = Object.fromEntries(
18192
+ KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
18193
+ );
18194
+ var DEFAULT_PACK_POLICY_ID = "monitor";
18195
+ function policyIdToAction(policyId) {
18196
+ const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18197
+ const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18198
+ return BUILTIN_POLICIES[id].action;
18199
+ }
18200
+ var UsedByItem = external_exports.object({
18201
+ id: external_exports.string(),
18202
+ name: external_exports.string(),
18203
+ ruleCount: external_exports.number().int().nonnegative(),
18204
+ enabled: external_exports.boolean()
18205
+ }).meta({ id: "UsedByItem" });
18206
+ var PolicyListItem = external_exports.object({
18207
+ id: external_exports.string(),
18208
+ kind: PolicyKind,
18209
+ name: external_exports.string(),
18210
+ enabled: external_exports.boolean(),
18211
+ usedByCount: external_exports.number().int().nonnegative()
18212
+ }).meta({ id: "PolicyListItem" });
18213
+ var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
18214
+ var PolicyDetail = external_exports.object({
18215
+ specVersion: external_exports.literal(1),
18216
+ id: external_exports.string(),
18217
+ kind: PolicyKind,
18218
+ name: external_exports.string(),
18219
+ enabled: external_exports.boolean(),
18220
+ description: external_exports.string(),
18221
+ usedBy: external_exports.array(UsedByItem)
18222
+ }).meta({ id: "PolicyDetail" });
18223
+ var PolicyStatsResponse = external_exports.object({
18224
+ policies: external_exports.number().int().nonnegative(),
18225
+ builtin: external_exports.number().int().nonnegative(),
18226
+ custom: external_exports.number().int().nonnegative(),
18227
+ detectionsGoverned: external_exports.number().int().nonnegative()
18228
+ }).meta({ id: "PolicyStatsResponse" });
18229
+
18024
18230
  // ../../packages/schema/src/zod/project-files.ts
18025
18231
  var ProjectFileInput = external_exports.object({
18026
18232
  path: external_exports.string().min(1),
@@ -18099,44 +18305,6 @@ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
18099
18305
  NoRemediationDecision
18100
18306
  ]);
18101
18307
 
18102
- // ../../packages/schema/src/zod/rule-test.ts
18103
- var TestRulesRequest = external_exports.object({
18104
- rules: external_exports.array(Rule).min(1).max(100),
18105
- text: external_exports.string().max(5e4).optional(),
18106
- fixtures: external_exports.array(RuleFixture).max(200).optional()
18107
- }).refine((v) => v.text !== void 0 || (v.fixtures?.length ?? 0) > 0, {
18108
- message: "Provide `text`, `fixtures`, or both \u2014 there must be something to test"
18109
- }).meta({ id: "TestRulesRequest" });
18110
- var RuleTestMatch = external_exports.object({
18111
- ruleId: external_exports.string(),
18112
- category: DetectionCategory,
18113
- severity: Severity,
18114
- span: Span,
18115
- confidence: external_exports.number().min(0).max(1),
18116
- match: external_exports.string()
18117
- }).meta({ id: "RuleTestMatch" });
18118
- var FixtureResult = external_exports.object({
18119
- label: external_exports.string(),
18120
- shouldMatch: external_exports.boolean(),
18121
- didMatch: external_exports.boolean(),
18122
- passed: external_exports.boolean(),
18123
- matches: external_exports.array(RuleTestMatch)
18124
- }).meta({ id: "FixtureResult" });
18125
- var TestRulesResponse = external_exports.object({
18126
- // Present only when the request supplied `text`.
18127
- adhoc: external_exports.object({ matches: external_exports.array(RuleTestMatch) }).optional(),
18128
- fixtures: external_exports.array(FixtureResult),
18129
- summary: external_exports.object({
18130
- total: external_exports.number().int().nonnegative(),
18131
- passed: external_exports.number().int().nonnegative(),
18132
- failed: external_exports.number().int().nonnegative()
18133
- }),
18134
- // Ids of rules whose matcher type the engine cannot evaluate today (e.g.
18135
- // `validator`), so they silently never match. Surfaced so an author is not
18136
- // misled by a green run that actually skipped a rule.
18137
- unsupportedRuleIds: external_exports.array(external_exports.string())
18138
- }).meta({ id: "TestRulesResponse" });
18139
-
18140
18308
  // ../../packages/schema/src/zod/security.ts
18141
18309
  var SeveritySummaryItem = external_exports.object({
18142
18310
  severity: Severity,
@@ -18234,10 +18402,22 @@ var TopSourcesQuery = external_exports.object({
18234
18402
  // Omit for both kinds.
18235
18403
  kind: external_exports.enum(SOURCE_KINDS).optional()
18236
18404
  });
18237
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18405
+ var Provider = Harness.extract([
18406
+ "ClaudeCode",
18407
+ "Cursor",
18408
+ "Codex",
18409
+ "Antigravity",
18410
+ "ClaudeAi",
18411
+ "ChatGpt",
18412
+ "Copilot",
18413
+ "Api"
18414
+ ]).meta({ id: "Provider" });
18238
18415
  var ScanCoverageProvider = external_exports.object({
18239
18416
  provider: Provider,
18240
- // Percent of that provider's traffic scanned in the window. 0 when unsupported.
18417
+ // Percent of that provider's traffic the shipped capture surface reaches.
18418
+ // A curated business fact, constant across every `range` — not a measured
18419
+ // per-window metric. 0 exactly when `supported` is false. See the comment
18420
+ // above the block for where these numbers are decided.
18241
18421
  coverage: external_exports.number().int().min(0).max(100),
18242
18422
  supported: external_exports.boolean()
18243
18423
  }).meta({ id: "ScanCoverageProvider" });
@@ -18291,6 +18471,18 @@ var ApplyRecommendedActionResponse = external_exports.object({
18291
18471
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
18292
18472
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
18293
18473
 
18474
+ // ../../packages/schema/src/zod/settings-action.ts
18475
+ var SaveSettingsInput = external_exports.object({
18476
+ historicalAccess: external_exports.string(),
18477
+ modelJudgeConsent: external_exports.boolean(),
18478
+ vaultConsent: external_exports.string(),
18479
+ vaultInlineReveal: external_exports.string()
18480
+ });
18481
+ var AttachInput = external_exports.object({
18482
+ endpoint: external_exports.string(),
18483
+ label: external_exports.string().optional()
18484
+ });
18485
+
18294
18486
  // ../../packages/schema/src/zod/triage.ts
18295
18487
  var TriageHit = external_exports.object({
18296
18488
  ruleId: external_exports.string(),
@@ -18305,7 +18497,7 @@ var TriageHit = external_exports.object({
18305
18497
  valueFingerprint: external_exports.string().optional(),
18306
18498
  keyVersion: external_exports.number().int().nonnegative().optional()
18307
18499
  });
18308
- var TriagePolicy = BuiltinPolicyId;
18500
+ var TriagePolicy = CategoryPolicyId;
18309
18501
  var TriageCategoryRec = external_exports.object({
18310
18502
  category: DetectionCategory,
18311
18503
  action: TriagePolicy,
@@ -18445,42 +18637,6 @@ function reviewSeverityRank(reasons) {
18445
18637
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18446
18638
  }
18447
18639
 
18448
- // ../../packages/persistence/src/ids.ts
18449
- import { createHash } from "crypto";
18450
- function sha256Hex(input) {
18451
- return createHash("sha256").update(input).digest("hex");
18452
- }
18453
- function inventoryId(objectType, identityKey) {
18454
- return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18455
- }
18456
- function sourceProjectId(url2) {
18457
- return sha256Hex(canonicalIdentity(["source_project", url2]));
18458
- }
18459
- function classifiedDataId(cls) {
18460
- return sha256Hex(canonicalIdentity(["classified_data", cls]));
18461
- }
18462
- function inspectionDefinitionId(ruleId, version2) {
18463
- return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18464
- }
18465
- function llmCallId(sessionId, messageId) {
18466
- return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18467
- }
18468
- function toolCallId(sessionId, toolUseId) {
18469
- return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18470
- }
18471
- var NO_SESSION = "no_session";
18472
- var NO_PATH = "no_path";
18473
- function captureId(sessionId, contentHash, filePath = null) {
18474
- return sha256Hex(
18475
- canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18476
- );
18477
- }
18478
-
18479
- // ../../packages/persistence/src/internal/snapshot.ts
18480
- import { randomUUID } from "crypto";
18481
- import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18482
- import { basename, dirname, join } from "path";
18483
-
18484
18640
  // ../../packages/persistence/src/paths.ts
18485
18641
  import {
18486
18642
  chmodSync,
@@ -18512,8 +18668,11 @@ function chmodBestEffort(path, mode) {
18512
18668
  function tightenDir(dir) {
18513
18669
  chmodBestEffort(dir, DATA_DIR_MODE);
18514
18670
  }
18671
+ function mkdirOwnerOnlySync(dir, recursive = false) {
18672
+ mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
18673
+ }
18515
18674
  function ensureDataDirSync(dir) {
18516
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18675
+ mkdirOwnerOnlySync(dir, true);
18517
18676
  tightenDir(dir);
18518
18677
  }
18519
18678
  function dbSidecars(file2) {
@@ -18525,6 +18684,26 @@ function tightenFile(file2) {
18525
18684
  function tightenPerms(file2) {
18526
18685
  for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18527
18686
  }
18687
+ function writeExclusiveOwnerOnlySync(file2, data) {
18688
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18689
+ }
18690
+ function writeOwnerOnlyFileSync(file2, data) {
18691
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18692
+ try {
18693
+ rmSync(tmp, { force: true });
18694
+ } catch {
18695
+ }
18696
+ try {
18697
+ writeExclusiveOwnerOnlySync(tmp, data);
18698
+ renameSync(tmp, file2);
18699
+ } finally {
18700
+ try {
18701
+ rmSync(tmp, { force: true });
18702
+ } catch {
18703
+ }
18704
+ }
18705
+ tightenFile(file2);
18706
+ }
18528
18707
  function classifyOccupant(file2) {
18529
18708
  try {
18530
18709
  if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
@@ -18553,7 +18732,7 @@ function createOwnerOnlyFileSync(file2, data) {
18553
18732
  }
18554
18733
  let created;
18555
18734
  try {
18556
- writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18735
+ writeExclusiveOwnerOnlySync(tmp, data);
18557
18736
  created = publishByLink(tmp, file2, data);
18558
18737
  } finally {
18559
18738
  try {
@@ -18575,7 +18754,7 @@ function publishByLink(tmp, file2, data) {
18575
18754
  if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18576
18755
  }
18577
18756
  try {
18578
- writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18757
+ writeExclusiveOwnerOnlySync(file2, data);
18579
18758
  return true;
18580
18759
  } catch (err) {
18581
18760
  if (err.code === "EEXIST") return false;
@@ -18583,11 +18762,69 @@ function publishByLink(tmp, file2, data) {
18583
18762
  }
18584
18763
  }
18585
18764
 
18765
+ // ../../packages/persistence/src/database.ts
18766
+ import { randomUUID as randomUUID10 } from "crypto";
18767
+ import { join as join3, sep } from "path";
18768
+ import { DatabaseSync } from "node:sqlite";
18769
+
18770
+ // ../../packages/persistence/src/ids.ts
18771
+ import { createHash } from "crypto";
18772
+ function sha256Hex(input) {
18773
+ return createHash("sha256").update(input).digest("hex");
18774
+ }
18775
+ function inventoryId(objectType, identityKey) {
18776
+ return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18777
+ }
18778
+ function sourceProjectId(url2) {
18779
+ return sha256Hex(canonicalIdentity(["source_project", url2]));
18780
+ }
18781
+ function classifiedDataId(cls) {
18782
+ return sha256Hex(canonicalIdentity(["classified_data", cls]));
18783
+ }
18784
+ function inspectionDefinitionId(ruleId, version2) {
18785
+ return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18786
+ }
18787
+ function llmCallId(sessionId, messageId) {
18788
+ return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18789
+ }
18790
+ function toolCallId(sessionId, toolUseId) {
18791
+ return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18792
+ }
18793
+ var NO_SESSION = "no_session";
18794
+ var NO_PATH = "no_path";
18795
+ function captureId(sessionId, contentHash, filePath = null) {
18796
+ return sha256Hex(
18797
+ canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18798
+ );
18799
+ }
18800
+
18586
18801
  // ../../packages/persistence/src/internal/snapshot.ts
18802
+ import { randomUUID } from "crypto";
18803
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
18804
+ import { basename, dirname, join as join2 } from "path";
18587
18805
  function backupPath(file2, tag) {
18588
18806
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18589
18807
  }
18590
18808
  var STALE_PARTIAL_MS = 5 * 6e4;
18809
+ var SNAPSHOT_STAGING_SUFFIX = ".partial";
18810
+ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18811
+ var SNAPSHOT_STAGING_COPY = "copy";
18812
+ function createSnapshotStaging(backup) {
18813
+ const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18814
+ rmSync3(stage, { recursive: true, force: true });
18815
+ mkdirOwnerOnlySync(stage);
18816
+ tightenDir(stage);
18817
+ return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
18818
+ }
18819
+ function idleMs(entry) {
18820
+ for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
18821
+ try {
18822
+ return Date.now() - statSync2(candidate).mtimeMs;
18823
+ } catch {
18824
+ }
18825
+ }
18826
+ return null;
18827
+ }
18591
18828
  function reapStalePartials(file2) {
18592
18829
  const dir = dirname(file2);
18593
18830
  const prefix = `${basename(file2)}.`;
@@ -18598,30 +18835,34 @@ function reapStalePartials(file2) {
18598
18835
  return;
18599
18836
  }
18600
18837
  for (const name of entries) {
18601
- if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18602
- const partial2 = join(dir, name);
18838
+ if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18839
+ const staging = join2(dir, name);
18603
18840
  try {
18604
- if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18605
- rmSync2(partial2, { force: true });
18841
+ const idle = idleMs(staging);
18842
+ if (idle !== null && idle > STALE_PARTIAL_MS) {
18843
+ rmSync3(staging, { recursive: true, force: true });
18606
18844
  }
18607
18845
  } catch {
18608
18846
  }
18609
18847
  }
18610
18848
  }
18611
18849
  function snapshotStore(db, backup) {
18612
- const partial2 = `${backup}.partial`;
18850
+ const { stage, copy } = createSnapshotStaging(backup);
18613
18851
  try {
18614
- rmSync2(partial2, { force: true });
18615
- db.prepare("VACUUM INTO ?").run(partial2);
18616
- tightenFile(partial2);
18617
- renameSync2(partial2, backup);
18852
+ db.prepare("VACUUM INTO ?").run(copy);
18853
+ tightenFile(copy);
18854
+ renameSync2(copy, backup);
18618
18855
  } catch (error51) {
18619
18856
  try {
18620
- rmSync2(partial2, { force: true });
18857
+ rmSync3(stage, { recursive: true, force: true });
18621
18858
  } catch {
18622
18859
  }
18623
18860
  throw error51;
18624
18861
  }
18862
+ try {
18863
+ rmSync3(stage, { recursive: true, force: true });
18864
+ } catch {
18865
+ }
18625
18866
  }
18626
18867
  function moveStoreAside(file2, backup) {
18627
18868
  const undo = [];
@@ -18634,7 +18875,7 @@ function moveStoreAside(file2, backup) {
18634
18875
  renameSync2(sidecar, moved);
18635
18876
  undo.push([moved, sidecar]);
18636
18877
  } catch {
18637
- rmSync2(sidecar, { force: true });
18878
+ rmSync3(sidecar, { force: true });
18638
18879
  }
18639
18880
  }
18640
18881
  } catch (error51) {
@@ -18650,14 +18891,14 @@ function moveStoreAside(file2, backup) {
18650
18891
  }
18651
18892
  function discardStore(file2, backup) {
18652
18893
  try {
18653
- rmSync2(file2, { force: true });
18894
+ rmSync3(file2, { force: true });
18654
18895
  for (const sidecar of dbSidecars(file2)) {
18655
- rmSync2(sidecar, { force: true });
18896
+ rmSync3(sidecar, { force: true });
18656
18897
  }
18657
18898
  } catch (error51) {
18658
18899
  if (existsSync(file2)) {
18659
18900
  try {
18660
- rmSync2(backup, { force: true });
18901
+ rmSync3(backup, { force: true });
18661
18902
  } catch {
18662
18903
  }
18663
18904
  }
@@ -18889,10 +19130,31 @@ function applyMigrations(db, file2) {
18889
19130
  if (drained) applyLegacyDropMigration(db, file2);
18890
19131
  }
18891
19132
  }
19133
+ function readLegacyTables(db) {
19134
+ let holdsRows = false;
19135
+ const marks = [];
19136
+ for (const table of ["events", "findings"]) {
19137
+ try {
19138
+ const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
19139
+ if (row === void 0) {
19140
+ holdsRows = true;
19141
+ marks.push(`${table}:unreadable`);
19142
+ continue;
19143
+ }
19144
+ if (row.n > 0) holdsRows = true;
19145
+ marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
19146
+ } catch {
19147
+ holdsRows = true;
19148
+ marks.push(`${table}:unreadable`);
19149
+ }
19150
+ }
19151
+ return { holdsRows, mark: marks.join("|") };
19152
+ }
18892
19153
  function applyLegacyDropMigration(db, file2) {
18893
19154
  const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
18894
19155
  if (!migration) return;
18895
- if (file2) {
19156
+ const before = file2 === void 0 ? void 0 : readLegacyTables(db);
19157
+ if (file2 !== void 0 && before?.holdsRows === true) {
18896
19158
  try {
18897
19159
  backupBeforeLegacyDrop(db, file2);
18898
19160
  } catch (error51) {
@@ -18906,6 +19168,12 @@ function applyLegacyDropMigration(db, file2) {
18906
19168
  () => {
18907
19169
  const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
18908
19170
  if (alreadyDropped) return;
19171
+ if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
19172
+ akaWarn(
19173
+ "legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
19174
+ );
19175
+ return;
19176
+ }
18909
19177
  for (const statement of splitStatements(migration.sql)) {
18910
19178
  db.exec(statement);
18911
19179
  }
@@ -19349,9 +19617,10 @@ function safeParseStringArray(raw) {
19349
19617
  const parsed = safeJson(raw, null);
19350
19618
  return Array.isArray(parsed) ? parsed : [];
19351
19619
  }
19620
+ var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19352
19621
  function toHarness(raw) {
19353
19622
  const parsed = Harness.safeParse(raw);
19354
- return parsed.success ? parsed.data : "claudecode";
19623
+ return parsed.success ? parsed.data : DEFAULT_HARNESS;
19355
19624
  }
19356
19625
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19357
19626
  if (row.status) {
@@ -19482,7 +19751,7 @@ var SqliteActivityRepository = class {
19482
19751
  const params = [];
19483
19752
  if (query.harness && query.harness.length > 0) {
19484
19753
  conditions.push(
19485
- `coalesce(json_extract(attributes, '$.harness'), 'claudecode') IN (${placeholders(query.harness.length)})`
19754
+ `coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
19486
19755
  );
19487
19756
  params.push(...query.harness);
19488
19757
  }
@@ -19689,13 +19958,13 @@ var SqliteActivityRepository = class {
19689
19958
  * The DISTINCT harnesses that actually have sessions (optionally within a
19690
19959
  * `started_at >= fromMs` window), so the filter can offer only the harnesses
19691
19960
  * present rather than the full enum. Each stored value is normalized through
19692
- * the SAME `toHarness` default the list uses (missing → 'claudecode'), so a
19693
- * store of bare (harness-less) roots surfaces exactly `['claudecode']`.
19961
+ * the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
19962
+ * a store of bare (harness-less) roots surfaces exactly that one harness.
19694
19963
  */
19695
19964
  harnessFacets(fromMs) {
19696
19965
  const where = fromMs === void 0 ? "" : " AND started_at >= ?";
19697
19966
  const stmt = this.db.prepare(
19698
- `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), 'claudecode') AS harness
19967
+ `SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
19699
19968
  FROM audit_events WHERE ${SESSION_ROOT}${where}`
19700
19969
  );
19701
19970
  const rows = allRows(
@@ -19909,8 +20178,8 @@ var SqliteAuditEventsRepository = class {
19909
20178
  }
19910
20179
  // Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
19911
20180
  // (which takes a caller-supplied random id), the id here is MINTED internally
19912
- // from the natural key — `llmCallId(sessionId, messageId)` — tenant-free like the
19913
- // sibling local-store ids (the local store is single-tenant). The deterministic
20181
+ // from the natural key — `llmCallId(sessionId, messageId)` — derived from the
20182
+ // session and message alone, like the sibling local-store ids. The deterministic
19914
20183
  // id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
19915
20184
  // AND converge a streaming partial/final split across two incremental passes:
19916
20185
  // a whole-file re-read no-ops (equal output), a lagging final replaces a
@@ -20875,6 +21144,42 @@ var SqliteFindingsRepository = class {
20875
21144
  this.db = db;
20876
21145
  }
20877
21146
  db;
21147
+ /**
21148
+ * The newest `limit` findings, newest first.
21149
+ *
21150
+ * THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
21151
+ * pin it. The natural spelling — drive from `inspection_findings`, order by the
21152
+ * JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
21153
+ * not on the driving table: SQLite sorts every finding in the store through a
21154
+ * temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
21155
+ * against 0.9 ms for the form below, and the gap is a ratio of the store size
21156
+ * rather than a constant.
21157
+ *
21158
+ * What it takes to make `started_at` order come out of an index instead:
21159
+ *
21160
+ * - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
21161
+ * planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
21162
+ * index cannot serve the ORDER BY: the predicate spans four event types, so
21163
+ * satisfying a global `started_at` order across them needs a range merge
21164
+ * SQLite will not do, and it sorts instead. Freed of it, the planner scans
21165
+ * `idx_audit_started_at` — a bare `started_at` index — in DESC order and
21166
+ * filters the type per row, which lets the LIMIT stop the scan early.
21167
+ * - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
21168
+ * purely to stop the tables being reordered. With plain JOINs the planner
21169
+ * drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
21170
+ * unary plus ALONE recovers almost none of the win. Both are needed.
21171
+ *
21172
+ * Neither is a micro-optimisation that a later reader should tidy away, and
21173
+ * `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
21174
+ * the temp B-tree comes back.
21175
+ *
21176
+ * Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
21177
+ * identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
21178
+ * renaming the index turns this read into an ERROR, where `+` turns it into a
21179
+ * scan-and-sort — slower, still correct. The worst case for the chosen form is
21180
+ * a store whose recent captures carry no findings at all, where the scan walks
21181
+ * the whole index; that is still no worse than the full sort it replaced.
21182
+ */
20878
21183
  recentFindings(opts) {
20879
21184
  const limit = opts?.limit ?? 50;
20880
21185
  const rows = allRows(
@@ -20883,10 +21188,10 @@ var SqliteFindingsRepository = class {
20883
21188
  f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
20884
21189
  json_extract(e.attributes, '$.source_tool') AS source_tool,
20885
21190
  e.event_type AS kind
20886
- FROM inspection_findings f
20887
- JOIN audit_events e ON e.id = f.audit_event_id
20888
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20889
- WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
21191
+ FROM audit_events e
21192
+ CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
21193
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21194
+ WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
20890
21195
  ORDER BY e.started_at DESC, f.rowid DESC
20891
21196
  LIMIT :limit`
20892
21197
  ),
@@ -21530,7 +21835,8 @@ var SqliteInspectionDefinitionsRepository = class {
21530
21835
  }
21531
21836
  db;
21532
21837
  insertStmt;
21533
- // Idempotent upsert; returns the content-addressed definition id.
21838
+ // Insert-if-absent; returns the content-addressed definition id. An id already
21839
+ // present keeps the stored row untouched — see the class doc.
21534
21840
  upsert(input) {
21535
21841
  const id = inspectionDefinitionId(input.ruleId, input.version);
21536
21842
  const row = toInspectionDefinitionRow(input, id);
@@ -21674,11 +21980,23 @@ function isParseableBinaryVersion(version2) {
21674
21980
 
21675
21981
  // ../../packages/persistence/src/repositories/installed-packs.ts
21676
21982
  var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
21983
+ function printableRuleId(entry) {
21984
+ if (typeof entry !== "object" || entry === null) return null;
21985
+ const candidate = entry.id;
21986
+ return Rule.shape.id.safeParse(candidate).success ? candidate : null;
21987
+ }
21988
+ function firstIssueReason(error51) {
21989
+ const issue2 = error51.issues[0];
21990
+ if (!issue2) return "unknown";
21991
+ const path = issue2.path.map((segment) => String(segment)).join(".");
21992
+ return path ? `${path}: ${issue2.code}` : issue2.code;
21993
+ }
21994
+ var REJECTED_RULE_DETAIL_CAP = 10;
21677
21995
  function inventorySignature(packs2) {
21678
21996
  return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
21679
21997
  }
21680
21998
  function hashRules(rulesJson) {
21681
- return createHash2("sha1").update(rulesJson).digest("hex");
21999
+ return createHash2("sha256").update(rulesJson).digest("hex");
21682
22000
  }
21683
22001
  function parseVersion(v) {
21684
22002
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -21890,10 +22208,21 @@ var SqliteInstalledPacksRepository = class {
21890
22208
  * (all detection off) instead of falling back to the bundled packs. Every
21891
22209
  * JSON-level failure therefore counts as invalid.
21892
22210
  */
22211
+ /**
22212
+ * ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
22213
+ * index is (namespace, pack_id) — so two enabled packs may contribute the same
22214
+ * id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
22215
+ * winner is whatever order SQLite happens to return, which makes a collision
22216
+ * resolve differently on two machines holding identical stores. Ordering by
22217
+ * (namespace, pack_id) makes the loser deterministic and therefore testable.
22218
+ */
21893
22219
  installedRuleset() {
21894
22220
  const rows = allRows(
21895
22221
  this.db.prepare(
21896
- `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version FROM installed_packs`
22222
+ `SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
22223
+ namespace, pack_id AS packId
22224
+ FROM installed_packs
22225
+ ORDER BY namespace, pack_id`
21897
22226
  )
21898
22227
  );
21899
22228
  const out = {
@@ -21901,22 +22230,32 @@ var SqliteInstalledPacksRepository = class {
21901
22230
  enabledPacks: 0,
21902
22231
  rules: [],
21903
22232
  invalidRules: 0,
22233
+ rejectedRules: [],
21904
22234
  ruleActions: /* @__PURE__ */ new Map(),
21905
- ruleVersions: /* @__PURE__ */ new Map()
22235
+ ruleVersions: /* @__PURE__ */ new Map(),
22236
+ reversibleRules: /* @__PURE__ */ new Set()
22237
+ };
22238
+ const reject = (pack, ruleId, reason) => {
22239
+ if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
22240
+ out.rejectedRules.push({ pack, ruleId, reason });
21906
22241
  };
21907
22242
  for (const row of rows) {
21908
22243
  if (!intToBool(row.enabled)) continue;
21909
22244
  out.enabledPacks += 1;
21910
22245
  const action = policyIdToAction(row.policyId);
22246
+ const reversible = policyIdIsReversible(row.policyId);
22247
+ const pack = `${row.namespace}/${row.packId}`;
21911
22248
  let raw;
21912
22249
  try {
21913
22250
  raw = JSON.parse(row.rulesJson);
21914
22251
  } catch {
21915
22252
  out.invalidRules += 1;
22253
+ reject(pack, null, "rules_json: malformed JSON");
21916
22254
  continue;
21917
22255
  }
21918
22256
  if (!Array.isArray(raw)) {
21919
22257
  out.invalidRules += 1;
22258
+ reject(pack, null, "rules_json: not an array");
21920
22259
  continue;
21921
22260
  }
21922
22261
  for (const entry of raw) {
@@ -21925,7 +22264,12 @@ var SqliteInstalledPacksRepository = class {
21925
22264
  out.rules.push(parsed.data);
21926
22265
  out.ruleActions.set(parsed.data.id, action);
21927
22266
  out.ruleVersions.set(parsed.data.id, row.version);
21928
- } else out.invalidRules += 1;
22267
+ if (reversible) out.reversibleRules.add(parsed.data.id);
22268
+ else out.reversibleRules.delete(parsed.data.id);
22269
+ } else {
22270
+ out.invalidRules += 1;
22271
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22272
+ }
21929
22273
  }
21930
22274
  }
21931
22275
  return out;
@@ -22119,31 +22463,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
22119
22463
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
22120
22464
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
22121
22465
  var HARNESS_LABELS = {
22122
- claudecode: "Claude Code",
22123
- cursor: "Cursor",
22124
- codex: "Codex",
22125
- antigravity: "Antigravity"
22466
+ [HARNESS.ClaudeCode]: "Claude Code",
22467
+ [HARNESS.Cursor]: "Cursor",
22468
+ [HARNESS.Codex]: "Codex",
22469
+ [HARNESS.Antigravity]: "Antigravity"
22126
22470
  };
22127
22471
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
22128
22472
  var EMPTY_PROJECT_AGG = {
22129
22473
  accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
22130
22474
  findingsCount: 0
22131
22475
  };
22476
+ var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
22477
+ var TITLE_NEEDLES = {
22478
+ ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
22479
+ Cursor: stripSeparators(SOURCE_TOOL.Cursor),
22480
+ Codex: stripSeparators(SOURCE_TOOL.Codex),
22481
+ Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
22482
+ };
22132
22483
  function resolveHarnessId(attrs, row) {
22133
22484
  if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
22134
22485
  return attrs.provider;
22135
22486
  }
22136
- const t = (row.title ?? "").toLowerCase().replace(/[\s-]/g, "");
22137
- if (t.includes("claudecode") || t === "claude") return "claudecode";
22138
- if (t.includes("cursor")) return "cursor";
22139
- if (t.includes("codex")) return "codex";
22140
- if (t.includes("antigravity")) return "antigravity";
22487
+ const t = stripSeparators(row.title ?? "");
22488
+ if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
22489
+ if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
22490
+ if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
22491
+ if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
22141
22492
  return null;
22142
22493
  }
22143
22494
  function isLiveRealClaudeCode(rows) {
22144
22495
  return rows.some((r) => {
22145
22496
  const attrs = safeJson(r.attributes, {});
22146
- return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === "claudecode";
22497
+ return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
22147
22498
  });
22148
22499
  }
22149
22500
  function toAssetSummary(row) {
@@ -22411,7 +22762,7 @@ var SqliteInventoryAssetsRepository = class {
22411
22762
  const isRealHarness = rows.some(
22412
22763
  (r) => safeJson(r.attributes, {}).provenance !== "sample"
22413
22764
  );
22414
- const attachConfig = isRealHarness && harnessId === "claudecode" && configAssets.length > 0;
22765
+ const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
22415
22766
  const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
22416
22767
  if (q && assets.length === 0) continue;
22417
22768
  const firstRow = rows[0];
@@ -23085,9 +23436,10 @@ var SqliteProjectFilesRepository = class {
23085
23436
  // ../../packages/persistence/src/repositories/resolutions.ts
23086
23437
  import { randomUUID as randomUUID7 } from "crypto";
23087
23438
  var SqliteResolutionsRepository = class {
23088
- constructor(db, now = () => Date.now()) {
23439
+ constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
23089
23440
  this.db = db;
23090
23441
  this.now = now;
23442
+ this.newId = newId;
23091
23443
  this.insertStmt = db.prepare(
23092
23444
  `INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
23093
23445
  VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
@@ -23120,12 +23472,14 @@ var SqliteResolutionsRepository = class {
23120
23472
  }
23121
23473
  db;
23122
23474
  now;
23475
+ newId;
23123
23476
  insertStmt;
23124
23477
  latestStmt;
23125
23478
  openAtRestStmt;
23126
23479
  resolvedAtRestStmt;
23127
23480
  /**
23128
- * Insert one disposition row. The repo mints the id and stamps created_at.
23481
+ * Insert one disposition row. The repo mints the id and stamps created_at,
23482
+ * both through the constructor's injectable seams.
23129
23483
  * `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
23130
23484
  * FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
23131
23485
  * from the schema enums. NOTE for future manual-resolution writers: this is
@@ -23137,7 +23491,7 @@ var SqliteResolutionsRepository = class {
23137
23491
  */
23138
23492
  insertResolution(r) {
23139
23493
  this.insertStmt.run({
23140
- id: randomUUID7(),
23494
+ id: this.newId(),
23141
23495
  findingKey: r.findingKey,
23142
23496
  status: FindingStatus.parse(r.status),
23143
23497
  method: ResolutionMethod.parse(r.method),
@@ -23716,16 +24070,16 @@ var ACTION_TO_KIND = {
23716
24070
  warn: "warned"
23717
24071
  };
23718
24072
  var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
23719
- var SCAN_COVERAGE = [
23720
- { provider: "claudecode", coverage: 100, supported: true },
23721
- { provider: "cursor", coverage: 0, supported: false },
23722
- { provider: "codex", coverage: 80, supported: true },
23723
- { provider: "antigravity", coverage: 60, supported: true },
23724
- { provider: "claudeai", coverage: 0, supported: false },
23725
- { provider: "chatgpt", coverage: 0, supported: false },
23726
- { provider: "copilot", coverage: 0, supported: false },
23727
- { provider: "api", coverage: 0, supported: false }
23728
- ];
24073
+ var SCAN_COVERAGE = {
24074
+ [HARNESS.Antigravity]: { coverage: 60, supported: true },
24075
+ [HARNESS.Api]: { coverage: 0, supported: false },
24076
+ [HARNESS.ChatGpt]: { coverage: 40, supported: true },
24077
+ [HARNESS.ClaudeAi]: { coverage: 40, supported: true },
24078
+ [HARNESS.ClaudeCode]: { coverage: 100, supported: true },
24079
+ [HARNESS.Codex]: { coverage: 80, supported: true },
24080
+ [HARNESS.Copilot]: { coverage: 0, supported: false },
24081
+ [HARNESS.Cursor]: { coverage: 0, supported: false }
24082
+ };
23729
24083
  var GRANULARITY = {
23730
24084
  "7d": "day",
23731
24085
  "30d": "day",
@@ -23824,9 +24178,22 @@ var SqliteSecurityRepository = class {
23824
24178
  return Promise.resolve({ total, needsRemediation, bySeverity });
23825
24179
  }
23826
24180
  // Range is echoed but does not change the result today — coverage is a constant
23827
- // business fact (see SCAN_COVERAGE), not a measured per-window metric.
24181
+ // business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
24182
+ // comes from Provider.options (the enum's declaration order), not from
24183
+ // SCAN_COVERAGE's own key order — deliberately, not because object literals
24184
+ // leave key order unspecified (ES2015 guarantees insertion order for these
24185
+ // non-integer string keys, so iterating SCAN_COVERAGE directly would be
24186
+ // reliable too). The reason is the schema comment's promise: the returned
24187
+ // order must mirror the generated OpenAPI enum list, which is Provider's
24188
+ // contract, not this table's.
23828
24189
  scanCoverage(range) {
23829
- return Promise.resolve({ range, providers: SCAN_COVERAGE.map((p) => ({ ...p })) });
24190
+ return Promise.resolve({
24191
+ range,
24192
+ providers: Provider.options.map((provider) => ({
24193
+ provider,
24194
+ ...SCAN_COVERAGE[provider]
24195
+ }))
24196
+ });
23830
24197
  }
23831
24198
  enforcementActions(range) {
23832
24199
  const lenMs = RANGE_DAYS[range] * DAY_MS4;
@@ -23884,10 +24251,10 @@ var SqliteSecurityRepository = class {
23884
24251
  // count; a superseding open/redetected row means the finding is not
23885
24252
  // remediated and is excluded, same invariant as severitySummary. Legacy
23886
24253
  // at-rest findings with finding_key IS NULL can never have a resolution row
23887
- // (the lifecycle is keyed by finding_key), so the SQL guard excludes them
23888
- // outright. One raw-row query (fetch every trackable finding + its latest
23889
- // resolution's status/method/resolved_at) + pure-JS filter/bucket/mean,
23890
- // mirroring this file's other methods.
24254
+ // (the lifecycle is keyed by finding_key), so they cannot reach the driving
24255
+ // set below. One raw-row query (fetch the findings with resolution activity in
24256
+ // the window + each one's latest resolution status/method/resolved_at) +
24257
+ // pure-JS filter/bucket/mean, mirroring this file's other methods.
23891
24258
  mttrTrend(range) {
23892
24259
  const granularity = granularityFor(range);
23893
24260
  const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
@@ -23903,29 +24270,80 @@ var SqliteSecurityRepository = class {
23903
24270
  // started_at the upsert overwrites onto inspection_findings.audit_event_id.
23904
24271
  // COALESCE onto the parent event's started_at defends against any
23905
24272
  // legacy/edge row the backfill left null.
23906
- `SELECT COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
24273
+ `SELECT DISTINCT f.finding_key AS finding_key,
24274
+ COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
23907
24275
  latest.status AS latest_status,
23908
24276
  latest.method AS latest_method,
23909
24277
  latest.resolved_at AS latest_resolved_at
23910
- FROM inspection_findings f
23911
- JOIN audit_events e ON e.id = f.audit_event_id
23912
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24278
+ FROM finding_resolution fr
24279
+ CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
24280
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24281
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
23913
24282
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
23914
24283
  ON latest.finding_key = f.finding_key
23915
- WHERE f.finding_key IS NOT NULL
23916
- AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
23917
- AND EXISTS (
23918
- SELECT 1 FROM finding_resolution fr
23919
- WHERE fr.finding_key = f.finding_key
23920
- AND fr.resolved_at >= :windowStart
23921
- )`
23922
- // The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
23923
- // any resolution activity at/after the window start — a row this method
24284
+ WHERE fr.resolved_at >= :windowStart
24285
+ AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
24286
+ // `fr` is a SUPERSET prefilter, not the answer: a finding this method
23924
24287
  // ultimately counts has its LATEST resolution inside the window, which
23925
- // implies such a row exists, so nothing wanted is dropped. The exact
23926
- // latest-wins + status/method + window gate stays in JS below,
23927
- // dialect-agnostic. Without this, a
23928
- // 7d request evaluated the store's entire trackable-findings history.
24288
+ // implies a resolution row at/after the window start exists, so nothing
24289
+ // wanted is dropped. The exact latest-wins + status/method + window gate
24290
+ // stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
24291
+ // implied rather than dropped — the join key comes from
24292
+ // finding_resolution, whose finding_key is NOT NULL.
24293
+ //
24294
+ // IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
24295
+ // the correction this replaced. Spelled as an `EXISTS` in the WHERE it
24296
+ // READ as a bound and was not one: SQLite drove from `audit_events` on
24297
+ // event_type, joined every capture event to its findings, and evaluated
24298
+ // the EXISTS last — bounding the RESULT and not the scan, so a 7d request
24299
+ // still cost the store's whole trackable history. Measured at 44.6 ms on
24300
+ // 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
24301
+ // both cases returning rows for a window holding a fraction of it.
24302
+ //
24303
+ // Two things carry it, and they answer DIFFERENT halves — which is worth
24304
+ // stating precisely, because the obvious reading (both are needed for the
24305
+ // speed) is wrong and was measured to be wrong:
24306
+ //
24307
+ // - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
24308
+ // keyword is semantically identical to JOIN and exists only to stop the
24309
+ // tables being reordered; with plain JOINs the planner puts `e` back on
24310
+ // the outside, because with no ANALYZE statistics it prices
24311
+ // `event_type IN (...)` as a selective probe. Reverting it alone takes
24312
+ // the 2k->20k flatness ratio from 1.32 to 16.87.
24313
+ // - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
24314
+ // `resolved_at >= :windowStart` a range SEARCH instead of a bare
24315
+ // `SCAN fr` — finding_key was this table's only index before it, so the
24316
+ // range had none. It buys NO flatness in store size: remove it and the
24317
+ // ratio above does not move, because the latest-resolution derived
24318
+ // table already passes over the whole of finding_resolution, so this
24319
+ // read is O(resolutions) either way and resolutions are not the store.
24320
+ // What it buys is the criterion `hot-read-query-plans.test.ts` enforces
24321
+ // — no hot read may pass over a table with no index — and that is the
24322
+ // guard that goes red when it is dropped. Neither test catches the
24323
+ // other's defect.
24324
+ //
24325
+ // SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
24326
+ // tidy-up. finding_resolution is append-only, so a key that was fixed,
24327
+ // redetected and fixed again carries several rows inside one window and
24328
+ // matches once per row — and the value below is a MEAN, so a key matched
24329
+ // three times is a key weighted three times.
24330
+ //
24331
+ // The skew is easy to argue away and the argument is wrong, so it is worth
24332
+ // recording. Duplicate rows for ONE key are identical (every projected
24333
+ // column is per-key: `latest.*` is latest-wins, `first_detected_at` is
24334
+ // preserved), so sums and counts scale together and that key's own mean
24335
+ // does not move. What moves is a bucket holding TWO findings that duplicate
24336
+ // UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
24337
+ // 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
24338
+ // `security.test.ts`'s "weights a finding ONCE however many resolution rows
24339
+ // it has inside the window" — which needed a fixture built for it, since no
24340
+ // single-key case can show it.
24341
+ //
24342
+ // `finding_key` is selected to make the DISTINCT dedup by KEY rather than
24343
+ // by value tuple. On the other columns alone, two genuinely different
24344
+ // findings sharing a severity, a first-detection event and a resolution
24345
+ // instant — one commit fixing two secrets in one file — are one tuple, and
24346
+ // collapsing them would under-count in the other direction.
23929
24347
  ),
23930
24348
  { windowStart }
23931
24349
  );
@@ -23983,16 +24401,47 @@ var SqliteSecurityRepository = class {
23983
24401
  }
23984
24402
  // Recently-resolved activity feed: findings whose finding_key's LATEST
23985
24403
  // finding_resolution row is status:'resolved'/method:'fixed-at-source' —
23986
- // same latest-resolution-wins correlated subquery as severitySummary /
23987
- // mttrTrend (NOT a plain JOIN, which would surface every historical
23988
- // resolution row for a key rather than just its current disposition). A key
23989
- // whose latest row is a superseding 'open'/'redetected' row (the same
23990
- // secret came back) is excluded — it is not currently resolved. Legacy
23991
- // at-rest findings with finding_key IS NULL are excluded outright (the
23992
- // resolution lifecycle can never attach to them). Path comes from the
23993
- // finding's parent event (event_type 'code_change', attributes.file_path) —
23994
- // mirrors resolutions.ts's openAtRestStmt accessor. Ordered by resolved_at
23995
- // DESC, capped at `limit`.
24404
+ // same latest-resolution-wins derived table as severitySummary / mttrTrend
24405
+ // (NOT a plain JOIN, which would surface every historical resolution row for
24406
+ // a key rather than just its current disposition). A key whose latest row is
24407
+ // a superseding 'open'/'redetected' row (the same secret came back) is
24408
+ // excluded — it is not currently resolved. Legacy at-rest findings with
24409
+ // finding_key IS NULL are excluded outright (the resolution lifecycle can
24410
+ // never attach to them). Path comes from the finding's parent event
24411
+ // (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
24412
+ // openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
24413
+ //
24414
+ // THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
24415
+ // the plan rather than a preference. Written the other way round — driving
24416
+ // from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
24417
+ // cannot use the join key: `f` is reached FROM `latest` by finding_key, so
24418
+ // `latest` gets probed on (rn, status, method) instead and the plan enumerates
24419
+ // every (code_change event x resolved key) pair before `f` can reject it. That
24420
+ // is a cross product, and it is quadratic in the store: measured at 10,966 ms
24421
+ // on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
24422
+ // returned. It was invisible for as long as it was, and reported at 8 ms,
24423
+ // because an empty finding_resolution table makes the inner side empty and the
24424
+ // cross product collapses to nothing — so the shape is only observable on a
24425
+ // corpus that seeds resolutions.
24426
+ //
24427
+ // Driving from `latest` instead makes every step below it a unique-index or
24428
+ // primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
24429
+ // so the cost is the derived table's own — linear in resolutions, which is
24430
+ // what this feed is legitimately about.
24431
+ //
24432
+ // CROSS JOIN is what actually pins that, and it is load-bearing rather than
24433
+ // decorative: in SQLite the keyword is semantically identical to JOIN and
24434
+ // exists only to stop the optimizer reordering the tables. Written as plain
24435
+ // JOINs in this order the planner puts `e` back on the outside — it has no
24436
+ // ANALYZE statistics to price the alternatives with, so it takes
24437
+ // `event_type = 'code_change'` for a selective index probe and rebuilds the
24438
+ // cross product. The FROM order alone was measured to change the plan not at
24439
+ // all.
24440
+ //
24441
+ // The LEFT JOIN it replaced was already an inner join in effect: three
24442
+ // `latest.*` predicates sit in the WHERE, and each of them is false for a
24443
+ // null-extended row. Spelling it JOIN changes no row and stops the plan
24444
+ // reading as though the findings side could drive.
23996
24445
  recentlyResolved(limit = 20) {
23997
24446
  const rows = allRows(
23998
24447
  this.db.prepare(
@@ -24002,17 +24451,15 @@ var SqliteSecurityRepository = class {
24002
24451
  json_extract(e.attributes, '$.file_path') AS path,
24003
24452
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
24004
24453
  latest.resolved_at AS latest_resolved_at
24005
- FROM inspection_findings f
24006
- JOIN audit_events e ON e.id = f.audit_event_id
24007
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24008
- LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24009
- ON latest.finding_key = f.finding_key
24010
- WHERE e.event_type = 'code_change'
24011
- AND f.finding_key IS NOT NULL
24012
- AND latest.status = 'resolved'
24454
+ FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
24455
+ CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
24456
+ CROSS JOIN audit_events e ON e.id = f.audit_event_id
24457
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
24458
+ WHERE latest.status = 'resolved'
24013
24459
  AND latest.method = 'fixed-at-source'
24014
24460
  AND latest.resolved_at IS NOT NULL
24015
- ORDER BY latest_resolved_at DESC
24461
+ AND e.event_type = 'code_change'
24462
+ ORDER BY latest.resolved_at DESC
24016
24463
  LIMIT :limit`
24017
24464
  ),
24018
24465
  { limit }
@@ -24948,7 +25395,8 @@ function openAndInitialize(file2) {
24948
25395
  }
24949
25396
  function openLocalDatabase(dir) {
24950
25397
  ensureDataDirSync(dir);
24951
- const file2 = join2(dir, DB_FILENAME);
25398
+ const file2 = join3(dir, DB_FILENAME);
25399
+ reapStalePartials(file2);
24952
25400
  const {
24953
25401
  db,
24954
25402
  events,
@@ -25203,9 +25651,9 @@ import {
25203
25651
  closeSync,
25204
25652
  existsSync as existsSync2,
25205
25653
  openSync,
25206
- readFileSync,
25207
- rmSync as rmSync3,
25208
- statSync as statSync2,
25654
+ readFileSync as readFileSync2,
25655
+ rmSync as rmSync4,
25656
+ statSync as statSync3,
25209
25657
  writeFileSync as writeFileSync2
25210
25658
  } from "fs";
25211
25659
  import { hostname as hostname3 } from "os";
@@ -25216,13 +25664,13 @@ import { createHash as createHash3 } from "crypto";
25216
25664
 
25217
25665
  // ../../packages/persistence/src/fingerprint.ts
25218
25666
  import { createHmac, randomBytes } from "crypto";
25219
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25220
- import { join as join3 } from "path";
25667
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
25668
+ import { join as join4 } from "path";
25221
25669
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
25222
25670
  var EXCEPTION_KEY_FILENAME = "exception.key";
25223
25671
  var KEY_MATERIAL_BYTES = 32;
25224
25672
  function keyFilePath(dataDir2) {
25225
- return join3(dataDir2, EXCEPTION_KEY_FILENAME);
25673
+ return join4(dataDir2, EXCEPTION_KEY_FILENAME);
25226
25674
  }
25227
25675
  function parseKeyFile(raw) {
25228
25676
  const parsed = JSON.parse(raw);
@@ -25260,7 +25708,7 @@ var FloorUnreadableError = class extends Error {
25260
25708
  }
25261
25709
  };
25262
25710
  function storedKeyVersionFloor(dataDir2) {
25263
- const file2 = join3(dataDir2, DB_FILENAME);
25711
+ const file2 = join4(dataDir2, DB_FILENAME);
25264
25712
  if (!existsSync3(file2)) return 0;
25265
25713
  let db;
25266
25714
  try {
@@ -25315,7 +25763,7 @@ function occupantMessage(file2, kind) {
25315
25763
  function readFingerprintKey(dataDir2) {
25316
25764
  let raw;
25317
25765
  try {
25318
- raw = readFileSync2(keyFilePath(dataDir2), "utf8");
25766
+ raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25319
25767
  } catch (err) {
25320
25768
  if (err.code === "ENOENT") return null;
25321
25769
  throw err instanceof Error ? err : new Error(String(err));
@@ -25341,21 +25789,21 @@ function fingerprintValue(key, raw) {
25341
25789
  import { renameSync as renameSync3 } from "fs";
25342
25790
  import { mkdir } from "fs/promises";
25343
25791
  import { homedir } from "os";
25344
- import { join as join4 } from "path";
25792
+ import { join as join5 } from "path";
25345
25793
  function defaultDataDir() {
25346
- return join4(homedir(), ".aka");
25794
+ return join5(homedir(), ".aka");
25347
25795
  }
25348
25796
  function settingsDir(base = defaultDataDir()) {
25349
- return join4(base, "settings");
25797
+ return join5(base, "settings");
25350
25798
  }
25351
25799
  function dataDir(base = defaultDataDir()) {
25352
- return join4(base, "data");
25800
+ return join5(base, "data");
25353
25801
  }
25354
25802
  function dbPath(base = defaultDataDir()) {
25355
- return join4(dataDir(base), "aka.db");
25803
+ return join5(dataDir(base), "aka.db");
25356
25804
  }
25357
25805
  function keysDir(base = defaultDataDir()) {
25358
- return join4(base, "keys");
25806
+ return join5(base, "keys");
25359
25807
  }
25360
25808
  function ensureLayoutDirSync(dir = defaultDataDir()) {
25361
25809
  ensureDataDirSync(dir);
@@ -25368,20 +25816,88 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25368
25816
  for (const { name, dest } of moves) {
25369
25817
  try {
25370
25818
  ensureDataDirSync(dest);
25371
- const moved = join4(dest, name);
25372
- renameSync3(join4(base, name), moved);
25819
+ const moved = join5(dest, name);
25820
+ renameSync3(join5(base, name), moved);
25373
25821
  tightenFile(moved);
25374
25822
  } catch {
25375
25823
  }
25376
25824
  }
25377
25825
  }
25378
25826
 
25827
+ // ../../packages/persistence/src/managed-settings.ts
25828
+ import { readFileSync as readFileSync4 } from "fs";
25829
+ import { posix, win32 } from "path";
25830
+ function managedSettingsPaths(platform2 = process.platform) {
25831
+ if (platform2 === "darwin") {
25832
+ return [
25833
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
25834
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
25835
+ ];
25836
+ }
25837
+ if (platform2 === "win32") {
25838
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
25839
+ }
25840
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
25841
+ }
25842
+ function readManagedSettings(paths = managedSettingsPaths()) {
25843
+ for (const path of paths) {
25844
+ let text;
25845
+ try {
25846
+ text = readFileSync4(path, "utf8");
25847
+ } catch {
25848
+ continue;
25849
+ }
25850
+ const record2 = parseJsonObject(text);
25851
+ if (!record2) continue;
25852
+ const parsed = ManagedSettings.safeParse(record2);
25853
+ if (parsed.success) return parsed.data;
25854
+ }
25855
+ return null;
25856
+ }
25857
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
25858
+ if (!managed) return settings;
25859
+ const { values } = managed;
25860
+ const merged = { ...settings };
25861
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
25862
+ if (values.controlPlane !== void 0) {
25863
+ merged.controlPlane = {
25864
+ ...values.controlPlane,
25865
+ // The administrator pinned WHICH deployment, not WHEN this machine
25866
+ // joined it. Keep the user's own attach time when the endpoint is
25867
+ // unchanged, so a managed machine does not appear to re-attach on every
25868
+ // read; stamp a fresh one when the administrator moved it.
25869
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
25870
+ };
25871
+ }
25872
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
25873
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
25874
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
25875
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
25876
+ if (values.vaultConsent !== void 0) {
25877
+ merged.vaultConsent = values.vaultConsent ? (
25878
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
25879
+ // at the current version otherwise.
25880
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
25881
+ ) : void 0;
25882
+ }
25883
+ if (values.modelJudgeConsent !== void 0) {
25884
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
25885
+ acknowledgedAt: now().toISOString(),
25886
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
25887
+ } : void 0;
25888
+ }
25889
+ return merged;
25890
+ }
25891
+
25379
25892
  // ../../packages/persistence/src/settings.ts
25380
- import { readFileSync as readFileSync3 } from "fs";
25381
- import { join as join5 } from "path";
25893
+ import { readFileSync as readFileSync5 } from "fs";
25894
+ import { join as join6 } from "path";
25382
25895
  var SETTINGS_FILENAME = "settings.json";
25383
25896
  function readWorkspaceSettings(base = defaultDataDir()) {
25384
- const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25897
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25898
+ }
25899
+ function readUserSettings(base) {
25900
+ const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
25385
25901
  if (!record2) return defaultWorkspaceSettings();
25386
25902
  try {
25387
25903
  return WorkspaceSettings.parse(record2);
@@ -25392,13 +25908,17 @@ function readWorkspaceSettings(base = defaultDataDir()) {
25392
25908
  function readJson(file2) {
25393
25909
  let text;
25394
25910
  try {
25395
- text = readFileSync3(file2, "utf8");
25911
+ text = readFileSync5(file2, "utf8");
25396
25912
  } catch {
25397
25913
  return null;
25398
25914
  }
25399
25915
  return parseJsonObject(text) ?? null;
25400
25916
  }
25401
25917
 
25918
+ // ../../packages/persistence/src/store-symlinks.ts
25919
+ import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
25920
+ import { dirname as dirname2, join as join7, resolve } from "path";
25921
+
25402
25922
  // ../../packages/persistence/src/vault/crypto.ts
25403
25923
  import {
25404
25924
  createCipheriv,
@@ -25510,16 +26030,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
25510
26030
  // ../../packages/persistence/src/vault/key-provider.ts
25511
26031
  import { execFileSync } from "child_process";
25512
26032
  import { randomBytes as randomBytes2 } from "crypto";
25513
- import {
25514
- chmodSync as chmodSync2,
25515
- mkdirSync as mkdirSync2,
25516
- readFileSync as readFileSync4,
25517
- renameSync as renameSync4,
25518
- rmSync as rmSync4,
25519
- statSync as statSync3,
25520
- writeFileSync as writeFileSync3
25521
- } from "fs";
25522
- import { join as join6 } from "path";
26033
+ import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
26034
+ import { join as join8 } from "path";
25523
26035
  var VAULT_OCCUPANT_REASON = {
25524
26036
  symlink: "the path is a symlink; remove it so a keyring can be created",
25525
26037
  gone: "the path was occupied but holds no keyring (removed while it was being created)",
@@ -25611,34 +26123,35 @@ var LOCK_OWNER_FILE = "owner";
25611
26123
  var ROTATION_IN_PROGRESS = "vault: a key rotation is already in progress";
25612
26124
  function claimRotationLock(lock, owner) {
25613
26125
  try {
25614
- mkdirSync2(lock);
26126
+ mkdirOwnerOnlySync(lock);
26127
+ tightenDir(lock);
25615
26128
  } catch (err) {
25616
26129
  if (err.code === "EEXIST") return false;
25617
26130
  throw asError(err);
25618
26131
  }
25619
26132
  try {
25620
- writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
26133
+ writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
25621
26134
  `, { mode: DATA_FILE_MODE });
25622
26135
  return true;
25623
26136
  } catch (err) {
25624
- rmSync4(lock, { recursive: true, force: true });
26137
+ rmSync5(lock, { recursive: true, force: true });
25625
26138
  throw asError(err);
25626
26139
  }
25627
26140
  }
25628
26141
  function acquireRotationLock(keysDir2) {
25629
- const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
26142
+ const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
25630
26143
  const owner = randomBytes2(16).toString("hex");
25631
26144
  if (claimRotationLock(lock, owner)) return { lock, owner };
25632
26145
  let held;
25633
26146
  try {
25634
- held = statSync3(lock);
26147
+ held = statSync5(lock);
25635
26148
  } catch {
25636
26149
  throw new Error(ROTATION_IN_PROGRESS);
25637
26150
  }
25638
26151
  if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
25639
26152
  const aside = `${lock}.stale.${owner}`;
25640
26153
  try {
25641
- const now = statSync3(lock);
26154
+ const now = statSync5(lock);
25642
26155
  if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
25643
26156
  throw new Error(ROTATION_IN_PROGRESS);
25644
26157
  }
@@ -25647,17 +26160,17 @@ function acquireRotationLock(keysDir2) {
25647
26160
  if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
25648
26161
  throw new Error(ROTATION_IN_PROGRESS, { cause: err });
25649
26162
  }
25650
- rmSync4(aside, { recursive: true, force: true });
26163
+ rmSync5(aside, { recursive: true, force: true });
25651
26164
  if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
25652
26165
  return { lock, owner };
25653
26166
  }
25654
26167
  function releaseRotationLock(lease) {
25655
26168
  try {
25656
- if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26169
+ if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
25657
26170
  } catch {
25658
26171
  return;
25659
26172
  }
25660
- rmSync4(lease.lock, { recursive: true, force: true });
26173
+ rmSync5(lease.lock, { recursive: true, force: true });
25661
26174
  }
25662
26175
  function withRotationLock(keysDir2, work) {
25663
26176
  ensureDataDirSync(keysDir2);
@@ -25674,7 +26187,7 @@ var FileKeyProvider = class {
25674
26187
  this.#keysDir = keysDir2;
25675
26188
  }
25676
26189
  get filePath() {
25677
- return join6(this.#keysDir, VAULT_KEY_FILENAME);
26190
+ return join8(this.#keysDir, VAULT_KEY_FILENAME);
25678
26191
  }
25679
26192
  loadOrCreate() {
25680
26193
  return asAsync(() => {
@@ -25704,7 +26217,7 @@ var FileKeyProvider = class {
25704
26217
  #read() {
25705
26218
  let raw;
25706
26219
  try {
25707
- raw = readFileSync4(this.filePath, "utf8");
26220
+ raw = readFileSync6(this.filePath, "utf8");
25708
26221
  } catch (err) {
25709
26222
  if (err.code === "ENOENT") return null;
25710
26223
  throw err instanceof Error ? err : new Error(String(err));
@@ -25743,33 +26256,95 @@ var FileKeyProvider = class {
25743
26256
  * Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
25744
26257
  * Used only for rotation, under the rotation lock — first creation goes
25745
26258
  * through the creation-exclusive path instead.
26259
+ *
26260
+ * Delegated to the shared owner-only write rather than spelled here, so the
26261
+ * create mode this file is published at is the one paths.ts owns and tests
26262
+ * directly. A local copy of the pair was a second place the mode could be
26263
+ * dropped with the trailing tighten still repairing the end state, which is
26264
+ * the shape no assertion on a published file can see. It also picks up that
26265
+ * primitive's per-process tmp name, its stale-tmp sweep, and an exclusive
26266
+ * create that refuses to follow a symlink planted at the tmp path.
25746
26267
  */
25747
26268
  #write(keyring) {
25748
26269
  ensureDataDirSync(this.#keysDir);
25749
- const file2 = this.filePath;
25750
- const tmp = `${file2}.tmp`;
25751
- writeFileSync3(tmp, `${serializeKeyring(keyring)}
25752
- `, { mode: DATA_FILE_MODE });
25753
- renameSync4(tmp, file2);
25754
- tightenFileMode(file2);
26270
+ writeOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
26271
+ `);
25755
26272
  return keyring;
25756
26273
  }
25757
26274
  };
25758
26275
  function tightenFileMode(file2) {
25759
26276
  try {
25760
- chmodSync2(file2, DATA_FILE_MODE);
26277
+ chmodSync3(file2, DATA_FILE_MODE);
25761
26278
  } catch {
25762
26279
  }
25763
26280
  }
25764
- var runSecurity = (args) => execFileSync("/usr/bin/security", args, {
26281
+ var SECURITY_TIMEOUT_MS = 5e3;
26282
+ var runSecurity = (args, stdin) => execFileSync("/usr/bin/security", args, {
25765
26283
  encoding: "utf8",
25766
- stdio: ["ignore", "pipe", "ignore"]
26284
+ input: stdin,
26285
+ timeout: SECURITY_TIMEOUT_MS,
26286
+ // stderr is discarded rather than captured, and that is deliberate: a
26287
+ // captured stream rides out on an execFileSync error's `.stderr`, and the
26288
+ // write paths here carry the keyring. Exit status is the only thing any
26289
+ // branch below reads.
26290
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
25767
26291
  });
25768
26292
  var SECURITY_ITEM_NOT_FOUND = 44;
26293
+ function corruptReason(err) {
26294
+ if (err instanceof SyntaxError) return "malformed JSON";
26295
+ return err instanceof Error ? err.message : "unknown";
26296
+ }
26297
+ function securityFailureMeta(err) {
26298
+ const e = err;
26299
+ const parts = [];
26300
+ if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
26301
+ if (typeof e.signal === "string" && e.signal) parts.push(`signal ${e.signal}`);
26302
+ if (typeof e.code === "string" && e.code) parts.push(e.code);
26303
+ return parts.length > 0 ? parts.join(", ") : "unknown error";
26304
+ }
26305
+ function writeCommand(keyring, update, keychain) {
26306
+ const hex3 = Buffer.from(serializeKeyring(keyring), "utf8").toString("hex");
26307
+ const parts = [
26308
+ "add-generic-password",
26309
+ ...update ? ["-U"] : [],
26310
+ "-s",
26311
+ KEYCHAIN_SERVICE,
26312
+ "-a",
26313
+ KEYCHAIN_ACCOUNT,
26314
+ "-X",
26315
+ hex3
26316
+ ];
26317
+ if (keychain !== void 0) {
26318
+ if (/['\\\n\r\0]/.test(keychain)) {
26319
+ throw new Error(
26320
+ "vault: keychain path contains a quote, backslash, line break or NUL, which security -i cannot carry intact"
26321
+ );
26322
+ }
26323
+ parts.push(`'${keychain}'`);
26324
+ }
26325
+ return `${parts.join(" ")}
26326
+ `;
26327
+ }
25769
26328
  var KeychainKeyProvider = class {
25770
26329
  #keysDir;
25771
26330
  #exec;
25772
- constructor(keysDir2, exec = runSecurity) {
26331
+ #keychain;
26332
+ /**
26333
+ * The trailing keychain argument, or nothing. Every subcommand used here
26334
+ * takes it last (`add-generic-password [keychain]`,
26335
+ * `find-generic-password [keychain...]`), and omitting it means the default
26336
+ * search list. Fixed at construction, so it is built once rather than per
26337
+ * call on the capture path.
26338
+ */
26339
+ #target;
26340
+ /**
26341
+ * `keychain` names the keychain to operate on, as `security`'s trailing
26342
+ * argument. Production passes nothing and gets the user's default keychain,
26343
+ * which is the whole point of the backend. A test driving the REAL binary
26344
+ * passes a throwaway one, because the alternative is writing vault key
26345
+ * material into the developer's own login keychain and leaving it there.
26346
+ */
26347
+ constructor(keysDir2, exec = runSecurity, keychain) {
25773
26348
  if (exec === runSecurity && process.platform !== "darwin") {
25774
26349
  throw new Error(
25775
26350
  `keychain custody is not available on this platform (${process.platform}); use file custody`
@@ -25777,6 +26352,8 @@ var KeychainKeyProvider = class {
25777
26352
  }
25778
26353
  this.#keysDir = keysDir2;
25779
26354
  this.#exec = exec;
26355
+ this.#keychain = keychain;
26356
+ this.#target = keychain === void 0 ? [] : [keychain];
25780
26357
  }
25781
26358
  /** Where a fallback file provider for the same vault would keep its keyring. */
25782
26359
  get keysDir() {
@@ -25815,18 +26392,22 @@ var KeychainKeyProvider = class {
25815
26392
  KEYCHAIN_SERVICE,
25816
26393
  "-a",
25817
26394
  KEYCHAIN_ACCOUNT,
25818
- "-w"
26395
+ "-w",
26396
+ ...this.#target
25819
26397
  ]);
25820
26398
  } catch (err) {
25821
26399
  if (err.status === SECURITY_ITEM_NOT_FOUND) return null;
25822
26400
  throw new Error(
25823
- `vault: keychain read failed (${err instanceof Error ? err.message : String(err)}); refusing to treat the failure as an absent keyring`,
25824
- { cause: err }
26401
+ `vault: keychain read failed (${securityFailureMeta(err)}); refusing to treat the failure as an absent keyring`
25825
26402
  );
25826
26403
  }
25827
26404
  const body = raw.trim();
25828
26405
  if (body.length === 0) return null;
25829
- return parseKeyring(body);
26406
+ try {
26407
+ return parseKeyring(body);
26408
+ } catch (err) {
26409
+ throw new Error(`vault: keychain item is not a usable keyring (${corruptReason(err)})`);
26410
+ }
25830
26411
  }
25831
26412
  /**
25832
26413
  * First mint: a plain `add-generic-password` (no `-U`) fails when an item
@@ -25834,37 +26415,25 @@ var KeychainKeyProvider = class {
25834
26415
  * keyring — the loser re-reads and adopts it instead.
25835
26416
  */
25836
26417
  #create(keyring) {
25837
- const args = [
25838
- "add-generic-password",
25839
- "-s",
25840
- KEYCHAIN_SERVICE,
25841
- "-a",
25842
- KEYCHAIN_ACCOUNT,
25843
- "-w",
25844
- serializeKeyring(keyring)
25845
- ];
26418
+ const line = writeCommand(keyring, false, this.#keychain);
25846
26419
  try {
25847
- this.#exec(args);
26420
+ this.#exec(["-i"], line);
25848
26421
  } catch (err) {
25849
26422
  const winner = this.#read();
25850
26423
  if (winner) return winner;
25851
- throw asError(err);
26424
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
25852
26425
  }
25853
26426
  return keyring;
25854
26427
  }
25855
26428
  // `-U` updates the item in place, deliberately replacing the stored map with
25856
26429
  // one that contains it — used only for rotation, under the rotation lock.
25857
26430
  #replace(keyring) {
25858
- this.#exec([
25859
- "add-generic-password",
25860
- "-U",
25861
- "-s",
25862
- KEYCHAIN_SERVICE,
25863
- "-a",
25864
- KEYCHAIN_ACCOUNT,
25865
- "-w",
25866
- serializeKeyring(keyring)
25867
- ]);
26431
+ const line = writeCommand(keyring, true, this.#keychain);
26432
+ try {
26433
+ this.#exec(["-i"], line);
26434
+ } catch (err) {
26435
+ throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
26436
+ }
25868
26437
  return keyring;
25869
26438
  }
25870
26439
  };
@@ -26275,8 +26844,8 @@ var SecretVault = class {
26275
26844
  };
26276
26845
 
26277
26846
  // ../../packages/persistence/src/warn-era-cap.ts
26278
- import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
26279
- import { join as join7 } from "path";
26847
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
26848
+ import { join as join9 } from "path";
26280
26849
 
26281
26850
  // ../../packages/plugin-sdk/src/provider-env.ts
26282
26851
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -26330,8 +26899,8 @@ function resolveProvider() {
26330
26899
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
26331
26900
  try {
26332
26901
  ensureLayoutDirSync(base);
26333
- const settingsFile = join8(settingsDir(base), "settings.json");
26334
- if (existsSync5(settingsFile)) tightenFile(settingsFile);
26902
+ const settingsFile = join10(settingsDir(base), "settings.json");
26903
+ if (existsSync6(settingsFile)) tightenFile(settingsFile);
26335
26904
  } catch {
26336
26905
  }
26337
26906
  migrateLegacyLayout(base);
@@ -26354,9 +26923,9 @@ function resolveProviderSafe(resolveProviderFn) {
26354
26923
  }
26355
26924
 
26356
26925
  // ../../packages/plugin-sdk/src/config-inventory.ts
26357
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
26926
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
26358
26927
  import { homedir as homedir2 } from "os";
26359
- import { basename as basename3, join as join10 } from "path";
26928
+ import { basename as basename3, join as join12 } from "path";
26360
26929
 
26361
26930
  // ../../packages/detections/src/egress/registry.ts
26362
26931
  var EXTRACTOR_VERSION = "1";
@@ -27062,6 +27631,10 @@ function luhnCheck(digits) {
27062
27631
  // ../../packages/detections/src/engine.ts
27063
27632
  var keywordMatcher = new KeywordMatcher2();
27064
27633
  var regexMatcher = new RegexMatcher2();
27634
+ var MATCHERS = {
27635
+ keyword: (text, rule) => keywordMatcher.match(text, rule),
27636
+ regex: (text, rule) => regexMatcher.match(text, rule)
27637
+ };
27065
27638
  var packs = /* @__PURE__ */ new Map();
27066
27639
  var POST_VALIDATORS = {
27067
27640
  entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
@@ -27077,8 +27650,7 @@ function passesPostValidators(rule, value) {
27077
27650
  for (const ref of validators) {
27078
27651
  const name = typeof ref === "string" ? ref : ref.name;
27079
27652
  const config2 = typeof ref === "string" ? void 0 : ref.config;
27080
- const validate = POST_VALIDATORS[name];
27081
- if (validate && !validate(value, config2)) return false;
27653
+ if (!POST_VALIDATORS[name](value, config2)) return false;
27082
27654
  }
27083
27655
  return true;
27084
27656
  }
@@ -27139,14 +27711,7 @@ function scan(text, rules, context) {
27139
27711
  const candidates = [];
27140
27712
  for (const rule of ruleset) {
27141
27713
  if (!ruleApplies(rule, extension)) continue;
27142
- let spans;
27143
- if (rule.matcher.type === "keyword") {
27144
- spans = keywordMatcher.match(text, rule);
27145
- } else if (rule.matcher.type === "regex") {
27146
- spans = regexMatcher.match(text, rule);
27147
- } else {
27148
- continue;
27149
- }
27714
+ const spans = MATCHERS[rule.matcher.type](text, rule);
27150
27715
  for (const span of spans) {
27151
27716
  const rawMatch = text.slice(span.start, span.end);
27152
27717
  if (!passesPostValidators(rule, rawMatch)) continue;
@@ -27270,6 +27835,7 @@ var CONFIG_POSTURE_RULES = [
27270
27835
  ];
27271
27836
 
27272
27837
  // ../../packages/detections/src/security/redos-probe.ts
27838
+ var BUDGET_MS = 100;
27273
27839
  var EXPONENTIAL_UNITS = [
27274
27840
  "a",
27275
27841
  "0",
@@ -27293,6 +27859,8 @@ var EXPONENTIAL_PROBES = EXPONENTIAL_UNITS.flatMap(
27293
27859
  var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].map(
27294
27860
  (unit) => unit.repeat(1e4).slice(0, 4e4) + "!"
27295
27861
  );
27862
+ var CPU_CORROBORATION_SHARE = 0.2;
27863
+ var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
27296
27864
 
27297
27865
  // ../../rules/code-flaws/auth-jwt-no-verify.json
27298
27866
  var auth_jwt_no_verify_default = {
@@ -28015,10 +28583,10 @@ var localhost_ref_default = {
28015
28583
  severity: "low",
28016
28584
  matcher: {
28017
28585
  type: "regex",
28018
- pattern: "\\b(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|::1)\\b",
28586
+ pattern: "(?<![A-Za-z0-9_])(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|(?<![A-Za-z0-9_]\\[)::1(?!:|\\.[0-9]))(?![A-Za-z0-9_])",
28019
28587
  flags: "g"
28020
28588
  },
28021
- examples: ["localhost", "127.0.0.1"]
28589
+ examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
28022
28590
  };
28023
28591
 
28024
28592
  // ../../rules/core-code-context/stack-trace.json
@@ -29315,32 +29883,36 @@ function registerBundledPacks() {
29315
29883
  }
29316
29884
 
29317
29885
  // ../../packages/plugin-sdk/src/repo.ts
29318
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
29319
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
29886
+ import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
29887
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join11, sep as sep2 } from "path";
29320
29888
 
29321
29889
  // ../../packages/plugin-sdk/src/events.ts
29322
29890
  import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
29323
29891
 
29324
29892
  // ../../packages/plugin-sdk/src/isolated-scan.ts
29325
- import { existsSync as existsSync7 } from "fs";
29893
+ import { existsSync as existsSync8 } from "fs";
29326
29894
  import { fileURLToPath } from "url";
29327
29895
  import { Worker } from "worker_threads";
29328
29896
 
29897
+ // ../../packages/plugin-sdk/src/ignore-layers.ts
29898
+ var import_ignore = __toESM(require_ignore(), 1);
29899
+ import { readFileSync as readFileSync9 } from "fs";
29900
+ import { join as join13 } from "path";
29901
+
29329
29902
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
29330
29903
  import { arch, hostname as hostname4, platform, release } from "os";
29331
29904
 
29332
29905
  // ../../packages/plugin-sdk/src/nudge.ts
29333
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
29334
- import { join as join11 } from "path";
29906
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
29907
+ import { join as join14 } from "path";
29335
29908
 
29336
29909
  // ../../packages/plugin-sdk/src/paths.ts
29337
- import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
29338
- import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
29910
+ import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
29911
+ import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
29339
29912
 
29340
29913
  // ../../packages/plugin-sdk/src/project-files.ts
29341
- var import_ignore = __toESM(require_ignore(), 1);
29342
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
29343
- import { basename as basename5, join as join12 } from "path";
29914
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
29915
+ import { basename as basename5, join as join15 } from "path";
29344
29916
 
29345
29917
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
29346
29918
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -29375,8 +29947,8 @@ import { randomUUID as randomUUID14 } from "crypto";
29375
29947
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
29376
29948
 
29377
29949
  // ../../packages/plugin-sdk/src/throttle.ts
29378
- import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
29379
- import { join as join13 } from "path";
29950
+ import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
29951
+ import { join as join16 } from "path";
29380
29952
 
29381
29953
  // ../../packages/plugin-sdk/src/tokenize.ts
29382
29954
  function redactedPlaceholder(category) {
@@ -29439,6 +30011,8 @@ var SecretVaultGlue = class {
29439
30011
  async tokenizeText(text, opts) {
29440
30012
  try {
29441
30013
  const findings = opts?.findings ?? this.#selfScan(text);
30014
+ const reversible = opts?.reversible;
30015
+ const keeps = (finding) => reversible === void 0 || reversible.has(finding);
29442
30016
  if (findings === null) return { text: "[REDACTED]", pointers: [], degraded: [] };
29443
30017
  if (findings.length === 0) return { text, pointers: [], degraded: [] };
29444
30018
  const groups = groupSpans(text, findings);
@@ -29455,6 +30029,8 @@ var SecretVaultGlue = class {
29455
30029
  } else if (original !== finding.rawMatch) {
29456
30030
  replacement = redactedPlaceholder(group.category);
29457
30031
  degraded.unshift({ category: group.category });
30032
+ } else if (!keeps(finding)) {
30033
+ replacement = redactedPlaceholder(finding.category);
29458
30034
  } else {
29459
30035
  replacement = await this.tokenizeValue(finding.rawMatch, {
29460
30036
  ruleId: finding.ruleId,
@@ -29687,15 +30263,15 @@ function describePointerSafe(token) {
29687
30263
 
29688
30264
  // src/hooks/message-display-transform.ts
29689
30265
  import {
29690
- mkdirSync as mkdirSync5,
30266
+ mkdirSync as mkdirSync4,
29691
30267
  readdirSync as readdirSync5,
29692
- readFileSync as readFileSync9,
30268
+ readFileSync as readFileSync11,
29693
30269
  renameSync as renameSync5,
29694
- rmSync as rmSync5,
29695
- statSync as statSync7,
30270
+ rmSync as rmSync6,
30271
+ statSync as statSync9,
29696
30272
  writeFileSync as writeFileSync7
29697
30273
  } from "fs";
29698
- import { dirname as dirname4, join as join14 } from "path";
30274
+ import { dirname as dirname5, join as join17 } from "path";
29699
30275
  var EMPTY_CARRY = Object.freeze({
29700
30276
  tail: "",
29701
30277
  fence: null,
@@ -29892,7 +30468,7 @@ var CARRY_FILE_PREFIX = "display-carry";
29892
30468
  var STALE_CARRY_MS = 15 * 60 * 1e3;
29893
30469
  function carryFilePath(dataDir2, sessionId) {
29894
30470
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 80);
29895
- return join14(dataDir2, `${CARRY_FILE_PREFIX}-${safe === "" ? "session" : safe}.json`);
30471
+ return join17(dataDir2, `${CARRY_FILE_PREFIX}-${safe === "" ? "session" : safe}.json`);
29896
30472
  }
29897
30473
  function parseFence(value) {
29898
30474
  if (typeof value !== "object" || value === null) return null;
@@ -29903,7 +30479,7 @@ function parseFence(value) {
29903
30479
  }
29904
30480
  function loadCarry(file2, keys) {
29905
30481
  try {
29906
- const parsed = JSON.parse(readFileSync9(file2, "utf8"));
30482
+ const parsed = JSON.parse(readFileSync11(file2, "utf8"));
29907
30483
  if (typeof parsed !== "object" || parsed === null) return EMPTY_CARRY;
29908
30484
  const record2 = parsed;
29909
30485
  const revealedCount = record2.messageKey === keys.messageKey && typeof record2.revealedCount === "number" && Number.isFinite(record2.revealedCount) ? record2.revealedCount : 0;
@@ -29956,10 +30532,10 @@ function removeStaleCarryFiles(dir, keep) {
29956
30532
  const cutoff = Date.now() - STALE_CARRY_MS;
29957
30533
  for (const name of readdirSync5(dir)) {
29958
30534
  if (!name.startsWith(CARRY_FILE_PREFIX) || !name.endsWith(".json")) continue;
29959
- const path = join14(dir, name);
30535
+ const path = join17(dir, name);
29960
30536
  if (path === keep) continue;
29961
30537
  try {
29962
- if (statSync7(path).mtimeMs < cutoff) rmSync5(path, { force: true });
30538
+ if (statSync9(path).mtimeMs < cutoff) rmSync6(path, { force: true });
29963
30539
  } catch {
29964
30540
  }
29965
30541
  }
@@ -29968,8 +30544,8 @@ function removeStaleCarryFiles(dir, keep) {
29968
30544
  }
29969
30545
  function saveCarry(file2, keys, carry) {
29970
30546
  try {
29971
- const dir = dirname4(file2);
29972
- mkdirSync5(dir, { recursive: true });
30547
+ const dir = dirname5(file2);
30548
+ mkdirSync4(dir, { recursive: true });
29973
30549
  removeStaleCarryFiles(dir, file2);
29974
30550
  writeCarryRecord(file2, keys.blockKey, keys.messageKey, carry);
29975
30551
  } catch {
@@ -29978,7 +30554,7 @@ function saveCarry(file2, keys, carry) {
29978
30554
  function finalizeCarry(file2, keys, carry) {
29979
30555
  try {
29980
30556
  if (carry.revealedCount > 0) {
29981
- mkdirSync5(dirname4(file2), { recursive: true });
30557
+ mkdirSync4(dirname5(file2), { recursive: true });
29982
30558
  writeCarryRecord(file2, null, keys.messageKey, {
29983
30559
  ...EMPTY_CARRY,
29984
30560
  revealedCount: carry.revealedCount
@@ -29987,21 +30563,21 @@ function finalizeCarry(file2, keys, carry) {
29987
30563
  }
29988
30564
  let owned = true;
29989
30565
  try {
29990
- const parsed = JSON.parse(readFileSync9(file2, "utf8"));
30566
+ const parsed = JSON.parse(readFileSync11(file2, "utf8"));
29991
30567
  if (typeof parsed === "object" && parsed !== null) {
29992
30568
  const record2 = parsed;
29993
30569
  owned = record2.blockKey === keys.blockKey || record2.messageKey === keys.messageKey;
29994
30570
  }
29995
30571
  } catch {
29996
30572
  }
29997
- if (owned) rmSync5(file2, { force: true });
30573
+ if (owned) rmSync6(file2, { force: true });
29998
30574
  } catch {
29999
30575
  }
30000
30576
  }
30001
30577
 
30002
30578
  // src/hooks/shared.ts
30003
30579
  async function readStdin() {
30004
- return new Promise((resolve) => {
30580
+ return new Promise((resolve2) => {
30005
30581
  let data = "";
30006
30582
  let settled = false;
30007
30583
  const finish = () => {
@@ -30010,7 +30586,7 @@ async function readStdin() {
30010
30586
  clearTimeout(timer);
30011
30587
  process.stdin.removeListener("data", onData);
30012
30588
  process.stdin.removeListener("end", finish);
30013
- resolve(data);
30589
+ resolve2(data);
30014
30590
  };
30015
30591
  const onData = (chunk) => {
30016
30592
  data += chunk;
@@ -30035,12 +30611,12 @@ function getString(record2, key) {
30035
30611
  return typeof value === "string" ? value : void 0;
30036
30612
  }
30037
30613
  function emit(output) {
30038
- return new Promise((resolve) => {
30614
+ return new Promise((resolve2) => {
30039
30615
  let settled = false;
30040
30616
  const finish = () => {
30041
30617
  if (settled) return;
30042
30618
  settled = true;
30043
- resolve();
30619
+ resolve2();
30044
30620
  };
30045
30621
  process.stdout.on("error", finish);
30046
30622
  process.stdout.write(JSON.stringify(output), finish);