@akasecurity/cli 0.9.9 → 0.9.10
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.
- package/dist/cli.js +2923 -1482
- package/extension/manifest.json +1 -1
- package/native-host/host.js +2092 -1286
- package/native-host/scan-worker.js +278 -176
- package/native-host/sync.js +1182 -918
- package/package.json +4 -4
- package/web-ui/package.json +1 -1
- package/web-ui/packages/dashboard-ui/package.json +1 -0
- package/web-ui/web-ui/.next/BUILD_ID +1 -1
- package/web-ui/web-ui/.next/app-build-manifest.json +56 -56
- package/web-ui/web-ui/.next/app-path-routes-manifest.json +6 -6
- package/web-ui/web-ui/.next/build-manifest.json +2 -2
- package/web-ui/web-ui/.next/prerender-manifest.json +10 -10
- package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/activity/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/detections/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/findings/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/policies/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/policies/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +3 -3
- package/web-ui/web-ui/.next/server/app/(app)/scan/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/security/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/security/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/updates/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/vault/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/vault/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/vault/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found.rsc +1 -1
- package/web-ui/web-ui/.next/server/app/index.html +1 -1
- package/web-ui/web-ui/.next/server/app/index.rsc +1 -1
- package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app-paths-manifest.json +6 -6
- package/web-ui/web-ui/.next/server/chunks/389.js +903 -0
- package/web-ui/web-ui/.next/server/chunks/475.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/667.js +894 -0
- package/web-ui/web-ui/.next/server/chunks/851.js +1 -0
- package/web-ui/web-ui/.next/server/chunks/{318.js → 950.js} +3 -3
- package/web-ui/web-ui/.next/server/functions-config-manifest.json +2 -2
- package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
- package/web-ui/web-ui/.next/server/pages/404.html +1 -1
- package/web-ui/web-ui/.next/server/pages/500.html +1 -1
- package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
- package/web-ui/web-ui/.next/static/chunks/907-207847c57011ba44.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-e4f94a75c631ce86.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-d70fcc5612f380b7.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-e1823ce15bb6277d.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-0fcffe4b9dff201f.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-c95079a2acd284fd.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-4b9f034bd078f5d3.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-b97fc083884ef341.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-e96e7e947ed5f279.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-b90748d139a546c9.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-7a46abe149271870.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-f4a1ee202d6e60a4.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-2ec6d863f1a31792.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-b388003fb0d3efd1.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-6bda74d234a1376a.js +1 -0
- package/web-ui/web-ui/dist/scan-worker.js +278 -176
- package/web-ui/web-ui/package.json +1 -0
- package/web-ui/web-ui/.next/server/chunks/237.js +0 -884
- package/web-ui/web-ui/.next/server/chunks/793.js +0 -893
- package/web-ui/web-ui/.next/server/chunks/83.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/907-b6ee7720ac5bf516.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-0f1263f7b62606c9.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-39fd8cad4b6b709f.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-b8386e543b783552.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-62c9e95207311f3d.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-9792760d5acc2de1.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-807c39bb900256bd.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-e3a531d9590a623b.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-11d47cdf3619126f.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-15e6d747c4e794c7.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-b390a81c67dd6d32.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-07913f82a7e29f67.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-0898d0d22c14bb45.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-ad9483e25680d8b4.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-04450aa587f0d5ef.js +0 -1
- /package/web-ui/web-ui/.next/static/{MbmJUh_qusg2UF-ZJLjuR → k7tk0MLkPITbi_x6JL01X}/_buildManifest.js +0 -0
- /package/web-ui/web-ui/.next/static/{MbmJUh_qusg2UF-ZJLjuR → k7tk0MLkPITbi_x6JL01X}/_ssgManifest.js +0 -0
package/native-host/host.js
CHANGED
|
@@ -500,6 +500,7 @@ import { createHash as createHash4 } from "crypto";
|
|
|
500
500
|
// ../../packages/persistence/src/attached-derived.ts
|
|
501
501
|
import { rmSync } from "fs";
|
|
502
502
|
import { join } from "path";
|
|
503
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
503
504
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
504
505
|
var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
505
506
|
|
|
@@ -600,6 +601,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
600
601
|
{
|
|
601
602
|
tag: "0022_audit_inspection_ms",
|
|
602
603
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
tag: "0023_secret_vault_user_authorized",
|
|
607
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
611
|
+
sql: "DROP INDEX IF EXISTS `idx_finding_resolution_key`;--> statement-breakpoint\nCREATE INDEX `idx_finding_resolution_key_created` ON `finding_resolution` (`finding_key`,`created_at`);"
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
615
|
+
sql: "ALTER TABLE `audit_events` ADD `source_tool` text GENERATED ALWAYS AS (json_extract(attributes, '$.source_tool')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `repo` text GENERATED ALWAYS AS (json_extract(attributes, '$.repo')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `file_path` text GENERATED ALWAYS AS (json_extract(attributes, '$.file_path')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `tool_name` text GENERATED ALWAYS AS (json_extract(attributes, '$.tool_name')) VIRTUAL;"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
619
|
+
sql: "ALTER TABLE `audit_events` ADD `service_tier` text GENERATED ALWAYS AS (json_extract(attributes, '$.service_tier')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_1h_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_1h_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_5m_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_5m_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `web_search_requests` integer GENERATED ALWAYS AS (json_extract(attributes, '$.web_search_requests')) VIRTUAL;"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
tag: "0027_audit_llm_usage_index",
|
|
623
|
+
sql: "CREATE INDEX `idx_audit_llm_usage` ON `audit_events` (`started_at`,`root_session_id`,`provider`,`model`,`service_tier`,`input_tokens`,`output_tokens`,`cache_creation_input_tokens`,`cache_read_input_tokens`,`ephemeral_1h_input_tokens`,`ephemeral_5m_input_tokens`,`web_search_requests`) WHERE event_type = 'llm_call' AND attributes IS NOT NULL;"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
tag: "0028_activity_session_probe_indexes",
|
|
627
|
+
sql: "CREATE INDEX `idx_audit_session_prompt` ON `audit_events` (`root_session_id`) WHERE event_type = 'prompt';--> statement-breakpoint\nCREATE INDEX `idx_audit_session_share` ON `audit_events` (`root_session_id`) WHERE event_type = 'share';--> statement-breakpoint\nCREATE INDEX `idx_audit_ended_at` ON `audit_events` (`ended_at`,`root_session_id`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\nCREATE INDEX `idx_audit_session_ended` ON `audit_events` (`root_session_id`,`ended_at`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\n-- Expression index for the activity list's turns rollup: a live-captured\n-- session's turns are the DISTINCT `run_key` across its `llm_call` leaves, and\n-- carrying the extracted key in the index answers that count from the index\n-- alone instead of parsing every leaf's attribute bag. Written by hand, as\n-- 0013's `idx_audit_code_change_path` was: drizzle-kit cannot emit an\n-- expression containing a comma, so this index is not declared in sqlite.ts.\nCREATE INDEX `idx_audit_session_run_key` ON `audit_events` (`root_session_id`, json_extract(`attributes`, '$.run_key')) WHERE `event_type` = 'llm_call';\n"
|
|
603
628
|
}
|
|
604
629
|
];
|
|
605
630
|
|
|
@@ -22256,6 +22281,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22256
22281
|
AttachTokenExpired,
|
|
22257
22282
|
external_exports.object({ status: printable(64) })
|
|
22258
22283
|
]);
|
|
22284
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22285
|
+
var DeviceCommand = external_exports.object({
|
|
22286
|
+
id: printable(128).min(1),
|
|
22287
|
+
kind: DeviceCommandKind,
|
|
22288
|
+
issuedAt: printable(64).min(1),
|
|
22289
|
+
expiresAt: printable(64).min(1)
|
|
22290
|
+
}).strict();
|
|
22291
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22292
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22293
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22294
|
+
external_exports.object({
|
|
22295
|
+
outcome: external_exports.literal("reported"),
|
|
22296
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22297
|
+
}).strict(),
|
|
22298
|
+
external_exports.object({
|
|
22299
|
+
outcome: external_exports.literal("failed"),
|
|
22300
|
+
reason: DeviceCommandFailureReason,
|
|
22301
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22302
|
+
}).strict()
|
|
22303
|
+
]);
|
|
22259
22304
|
|
|
22260
22305
|
// ../../packages/schema/src/zod/registry.ts
|
|
22261
22306
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22422,7 +22467,7 @@ var PackManifest = external_exports.object({
|
|
|
22422
22467
|
}).meta({ id: "PackManifest" });
|
|
22423
22468
|
|
|
22424
22469
|
// ../../packages/schema/src/zod/detection.ts
|
|
22425
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22470
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22426
22471
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22427
22472
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22428
22473
|
var DetectionCounts = external_exports.object({
|
|
@@ -22559,14 +22604,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22559
22604
|
function isStringArray(value) {
|
|
22560
22605
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22561
22606
|
}
|
|
22607
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22608
|
+
function resolveOrigin(origin) {
|
|
22609
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22610
|
+
}
|
|
22562
22611
|
function summaryToDetectionListItem(s) {
|
|
22563
22612
|
return {
|
|
22564
22613
|
id: `${s.namespace}/${s.packId}`,
|
|
22565
22614
|
name: s.name,
|
|
22566
22615
|
version: s.version,
|
|
22567
22616
|
enabled: s.enabled,
|
|
22568
|
-
origin:
|
|
22569
|
-
// v1: every installed pack is library origin
|
|
22617
|
+
origin: resolveOrigin(s.origin),
|
|
22570
22618
|
namespace: s.namespace,
|
|
22571
22619
|
packId: s.packId,
|
|
22572
22620
|
ruleCount: s.ruleCount,
|
|
@@ -22618,7 +22666,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22618
22666
|
name: row.name,
|
|
22619
22667
|
version: row.version,
|
|
22620
22668
|
enabled: row.enabled,
|
|
22621
|
-
origin:
|
|
22669
|
+
origin: resolveOrigin(row.origin),
|
|
22622
22670
|
namespace: row.namespace,
|
|
22623
22671
|
packId: row.packId,
|
|
22624
22672
|
ruleCount: row.rules.length,
|
|
@@ -22638,16 +22686,20 @@ function splitDetectionId(id) {
|
|
|
22638
22686
|
}
|
|
22639
22687
|
function buildDetectionsList(summaries, query) {
|
|
22640
22688
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22689
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22641
22690
|
const counts = {
|
|
22642
22691
|
all: summaries.length,
|
|
22643
|
-
library: summaries.length,
|
|
22644
|
-
|
|
22645
|
-
|
|
22692
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22693
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22694
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22695
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22696
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22697
|
+
// OriginEnum.
|
|
22646
22698
|
customized: 0,
|
|
22647
22699
|
updates: withUpdate.length
|
|
22648
22700
|
};
|
|
22649
22701
|
const filter = query.filter;
|
|
22650
|
-
let filtered = filter === "custom"
|
|
22702
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22651
22703
|
if (query.q) {
|
|
22652
22704
|
const q = query.q.toLowerCase();
|
|
22653
22705
|
filtered = filtered.filter(
|
|
@@ -22727,8 +22779,9 @@ var Event = external_exports.object({
|
|
|
22727
22779
|
metadata: EventMetadata.optional()
|
|
22728
22780
|
}).meta({ id: "Event" });
|
|
22729
22781
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22782
|
+
var INGEST_BATCH_MAX = 100;
|
|
22730
22783
|
var IngestBatch = external_exports.object({
|
|
22731
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22784
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22732
22785
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22733
22786
|
// additionally rejects any event whose contentHash the store has already
|
|
22734
22787
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23284,380 +23337,11 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23284
23337
|
message: "At least one field must be provided"
|
|
23285
23338
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23286
23339
|
|
|
23287
|
-
// ../../packages/schema/src/zod/vault.ts
|
|
23288
|
-
var POINTER_FORMAT_VERSION = 2;
|
|
23289
|
-
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23290
|
-
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23291
|
-
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23292
|
-
);
|
|
23293
|
-
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23294
|
-
function pointerTokenScanner() {
|
|
23295
|
-
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23296
|
-
}
|
|
23297
|
-
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23298
|
-
var ParsedPointer = external_exports.object({
|
|
23299
|
-
category: DetectionCategory,
|
|
23300
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23301
|
-
pointerId: external_exports.string(),
|
|
23302
|
-
tag: external_exports.string()
|
|
23303
|
-
});
|
|
23304
|
-
var VaultEntry = external_exports.object({
|
|
23305
|
-
pointerId: external_exports.string(),
|
|
23306
|
-
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23307
|
-
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23308
|
-
// independently of the vault encryption key below.
|
|
23309
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23310
|
-
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23311
|
-
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23312
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23313
|
-
// Fixed at first mint and never updated: the same value detected later under a
|
|
23314
|
-
// different rule's category keeps the category it was minted with, so one
|
|
23315
|
-
// value always produces exactly one wire token.
|
|
23316
|
-
category: DetectionCategory,
|
|
23317
|
-
ruleId: external_exports.string(),
|
|
23318
|
-
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23319
|
-
maskedMatch: external_exports.string(),
|
|
23320
|
-
provider: external_exports.string().optional(),
|
|
23321
|
-
ciphertext: external_exports.string(),
|
|
23322
|
-
nonce: external_exports.string(),
|
|
23323
|
-
authTag: external_exports.string(),
|
|
23324
|
-
// How many times this value has been detected on this machine — the reuse
|
|
23325
|
-
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23326
|
-
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23327
|
-
firstSeen: external_exports.string(),
|
|
23328
|
-
lastSeen: external_exports.string()
|
|
23329
|
-
});
|
|
23330
|
-
var PointerDescriptor = external_exports.object({
|
|
23331
|
-
category: DetectionCategory,
|
|
23332
|
-
provider: external_exports.string().optional(),
|
|
23333
|
-
maskedMatch: external_exports.string(),
|
|
23334
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23335
|
-
firstSeen: external_exports.string(),
|
|
23336
|
-
lastSeen: external_exports.string()
|
|
23337
|
-
});
|
|
23338
|
-
var PointerIdentity = external_exports.object({
|
|
23339
|
-
ruleId: external_exports.string(),
|
|
23340
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23341
|
-
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23342
|
-
});
|
|
23343
|
-
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23344
|
-
var VaultDerefReason = external_exports.enum([
|
|
23345
|
-
"display",
|
|
23346
|
-
"explicit-reveal",
|
|
23347
|
-
"view-render",
|
|
23348
|
-
"model-input",
|
|
23349
|
-
"remediation",
|
|
23350
|
-
"purge"
|
|
23351
|
-
]);
|
|
23352
|
-
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23353
|
-
var VaultDeref = external_exports.object({
|
|
23354
|
-
id: external_exports.guid(),
|
|
23355
|
-
pointerId: external_exports.string(),
|
|
23356
|
-
at: external_exports.string(),
|
|
23357
|
-
target: DetokenizeTarget,
|
|
23358
|
-
reason: VaultDerefReason,
|
|
23359
|
-
outcome: VaultDerefOutcome,
|
|
23360
|
-
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23361
|
-
grantId: external_exports.string().optional(),
|
|
23362
|
-
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23363
|
-
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23364
|
-
pointerCount: external_exports.number().int().positive().default(1)
|
|
23365
|
-
});
|
|
23366
|
-
var VaultSightingKind = external_exports.enum([
|
|
23367
|
-
"prompt",
|
|
23368
|
-
"tool-input",
|
|
23369
|
-
"tool-output",
|
|
23370
|
-
"file",
|
|
23371
|
-
"transcript"
|
|
23372
|
-
]);
|
|
23373
|
-
var VaultSighting = external_exports.object({
|
|
23374
|
-
location: external_exports.string(),
|
|
23375
|
-
kind: VaultSightingKind,
|
|
23376
|
-
firstSeen: external_exports.string(),
|
|
23377
|
-
lastSeen: external_exports.string()
|
|
23378
|
-
});
|
|
23379
|
-
var VaultInventoryEntry = external_exports.object({
|
|
23380
|
-
pointerId: external_exports.string(),
|
|
23381
|
-
category: DetectionCategory,
|
|
23382
|
-
provider: external_exports.string().optional(),
|
|
23383
|
-
maskedMatch: external_exports.string(),
|
|
23384
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23385
|
-
firstSeen: external_exports.string(),
|
|
23386
|
-
lastSeen: external_exports.string(),
|
|
23387
|
-
// The active reveal-to-model grant covering this value, when one exists —
|
|
23388
|
-
// the inventory badges it, the row links to revocation.
|
|
23389
|
-
revealGrantId: external_exports.string().nullable(),
|
|
23390
|
-
sightings: external_exports.array(VaultSighting)
|
|
23391
|
-
});
|
|
23392
|
-
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23393
|
-
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23394
|
-
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23395
|
-
var ListVaultInventoryQuery = external_exports.object({
|
|
23396
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23397
|
-
// Opaque; names the last row of the page just served.
|
|
23398
|
-
cursor: external_exports.string().optional()
|
|
23399
|
-
});
|
|
23400
|
-
var ListVaultInventoryResponse = external_exports.object({
|
|
23401
|
-
// Vaulted values across the whole store, not just this page — cursor-
|
|
23402
|
-
// independent, so paging never changes what the count claims.
|
|
23403
|
-
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23404
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23405
|
-
// `null` once the last page is reached.
|
|
23406
|
-
nextCursor: external_exports.string().nullable()
|
|
23407
|
-
});
|
|
23408
|
-
var ListVaultReuseQuery = external_exports.object({
|
|
23409
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23410
|
-
cursor: external_exports.string().optional()
|
|
23411
|
-
});
|
|
23412
|
-
var ListVaultReuseResponse = external_exports.object({
|
|
23413
|
-
// Reused values across the whole store — the number the section's claim
|
|
23414
|
-
// ("values detected in more than one place") is about.
|
|
23415
|
-
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23416
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23417
|
-
nextCursor: external_exports.string().nullable()
|
|
23418
|
-
});
|
|
23419
|
-
var ListVaultDerefsQuery = external_exports.object({
|
|
23420
|
-
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23421
|
-
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23422
|
-
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23423
|
-
// over a Server Action, which preserves the type, never as a URL param.
|
|
23424
|
-
includeBatched: external_exports.boolean().optional(),
|
|
23425
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23426
|
-
cursor: external_exports.string().optional()
|
|
23427
|
-
});
|
|
23428
|
-
var ListVaultDerefsResponse = external_exports.object({
|
|
23429
|
-
items: external_exports.array(VaultDeref),
|
|
23430
|
-
nextCursor: external_exports.string().nullable(),
|
|
23431
|
-
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23432
|
-
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23433
|
-
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23434
|
-
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23435
|
-
});
|
|
23436
|
-
var VaultKeyCustody = external_exports.string();
|
|
23437
|
-
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23438
|
-
var VAULT_CONSENT_VERSION = 1;
|
|
23439
|
-
var VaultConsent = external_exports.object({
|
|
23440
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23441
|
-
version: external_exports.number().int().positive()
|
|
23442
|
-
});
|
|
23443
|
-
|
|
23444
|
-
// ../../packages/schema/src/zod/local.ts
|
|
23445
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
|
|
23446
|
-
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23447
|
-
var HISTORY_SYNC_PAYLOAD_VERSION = 1;
|
|
23448
|
-
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23449
|
-
var ControlPlaneConnection = external_exports.object({
|
|
23450
|
-
endpoint: external_exports.string().min(1),
|
|
23451
|
-
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23452
|
-
label: external_exports.string().min(1).optional(),
|
|
23453
|
-
attachedAt: external_exports.iso.datetime()
|
|
23454
|
-
}).meta({ id: "ControlPlaneConnection" });
|
|
23455
|
-
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23456
|
-
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23457
|
-
var ModelJudgeConsent = external_exports.object({
|
|
23458
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23459
|
-
payloadVersion: external_exports.number().int().positive()
|
|
23460
|
-
});
|
|
23461
|
-
var HistorySyncConsent = external_exports.object({
|
|
23462
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23463
|
-
payloadVersion: external_exports.number().int().positive(),
|
|
23464
|
-
endpoint: external_exports.string()
|
|
23465
|
-
});
|
|
23466
|
-
function isHistorySyncConsentValid(consent, endpoint) {
|
|
23467
|
-
if (consent === void 0 || endpoint === void 0) return false;
|
|
23468
|
-
return consent.payloadVersion === HISTORY_SYNC_PAYLOAD_VERSION && consent.endpoint === endpoint;
|
|
23469
|
-
}
|
|
23470
|
-
var WorkspaceSettings = external_exports.object({
|
|
23471
|
-
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23472
|
-
runMode: RunMode.default("standalone"),
|
|
23473
|
-
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23474
|
-
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23475
|
-
controlPlane: ControlPlaneConnection.optional(),
|
|
23476
|
-
policy: SimpleDetectionPolicy.default("redact"),
|
|
23477
|
-
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23478
|
-
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23479
|
-
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23480
|
-
// Shares writes.
|
|
23481
|
-
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23482
|
-
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23483
|
-
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23484
|
-
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23485
|
-
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23486
|
-
// purging the vault is the eraser.
|
|
23487
|
-
vaultConsent: VaultConsent.optional(),
|
|
23488
|
-
// Where the vault master key lives.
|
|
23489
|
-
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23490
|
-
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23491
|
-
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23492
|
-
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23493
|
-
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23494
|
-
// Records that the user consented to sending findings to the model API for
|
|
23495
|
-
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23496
|
-
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23497
|
-
// covers the current payload and must be re-granted.
|
|
23498
|
-
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23499
|
-
// Records that the user consented to sending the activity already recorded on
|
|
23500
|
-
// this machine to the deployment it is attached to, along with the payload
|
|
23501
|
-
// shape and the endpoint they agreed to. Absent until granted, and a grant for
|
|
23502
|
-
// a different endpoint or an older payload no longer counts.
|
|
23503
|
-
historySyncConsent: HistorySyncConsent.optional()
|
|
23504
|
-
});
|
|
23505
|
-
function defaultWorkspaceSettings() {
|
|
23506
|
-
return WorkspaceSettings.parse({});
|
|
23507
|
-
}
|
|
23508
|
-
function isAttached(settings) {
|
|
23509
|
-
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23510
|
-
}
|
|
23511
|
-
function toInventoryRow(input2, id, now) {
|
|
23512
|
-
return {
|
|
23513
|
-
id,
|
|
23514
|
-
objectType: input2.objectType,
|
|
23515
|
-
location: input2.location ?? null,
|
|
23516
|
-
title: input2.title ?? null,
|
|
23517
|
-
hostId: input2.hostId ?? null,
|
|
23518
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23519
|
-
firstSeen: now,
|
|
23520
|
-
lastSeen: now
|
|
23521
|
-
};
|
|
23522
|
-
}
|
|
23523
|
-
function toSourceProjectRow(input2, id, now) {
|
|
23524
|
-
return {
|
|
23525
|
-
id,
|
|
23526
|
-
url: input2.url,
|
|
23527
|
-
name: input2.name ?? null,
|
|
23528
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23529
|
-
firstSeen: now,
|
|
23530
|
-
lastSeen: now
|
|
23531
|
-
};
|
|
23532
|
-
}
|
|
23533
|
-
function toAuditEventRow(input2) {
|
|
23534
|
-
return {
|
|
23535
|
-
id: input2.id,
|
|
23536
|
-
parentId: input2.parentId ?? null,
|
|
23537
|
-
rootSessionId: input2.rootSessionId ?? null,
|
|
23538
|
-
eventType: input2.eventType,
|
|
23539
|
-
hostId: input2.hostId ?? null,
|
|
23540
|
-
harnessId: input2.harnessId ?? null,
|
|
23541
|
-
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23542
|
-
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23543
|
-
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23544
|
-
severity: input2.severity ?? null,
|
|
23545
|
-
priority: input2.priority ?? null,
|
|
23546
|
-
content: input2.content ?? null,
|
|
23547
|
-
contentHash: input2.contentHash ?? null,
|
|
23548
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23549
|
-
};
|
|
23550
|
-
}
|
|
23551
|
-
function toClassifiedDataRow(input2, id) {
|
|
23552
|
-
return {
|
|
23553
|
-
id,
|
|
23554
|
-
class: input2.class,
|
|
23555
|
-
label: input2.label ?? null,
|
|
23556
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23557
|
-
};
|
|
23558
|
-
}
|
|
23559
|
-
function toInspectionDefinitionRow(input2, id) {
|
|
23560
|
-
return {
|
|
23561
|
-
id,
|
|
23562
|
-
ruleId: input2.ruleId,
|
|
23563
|
-
name: input2.name,
|
|
23564
|
-
category: input2.category,
|
|
23565
|
-
severity: input2.severity,
|
|
23566
|
-
definition: input2.definition,
|
|
23567
|
-
version: input2.version
|
|
23568
|
-
};
|
|
23569
|
-
}
|
|
23570
|
-
function toInspectionFindingRow(input2) {
|
|
23571
|
-
return {
|
|
23572
|
-
id: input2.id,
|
|
23573
|
-
auditEventId: input2.auditEventId,
|
|
23574
|
-
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23575
|
-
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23576
|
-
spanStart: input2.span.start,
|
|
23577
|
-
spanEnd: input2.span.end,
|
|
23578
|
-
maskedMatch: input2.maskedMatch,
|
|
23579
|
-
actionTaken: input2.actionTaken,
|
|
23580
|
-
confidence: input2.confidence,
|
|
23581
|
-
findingKey: input2.findingKey ?? null,
|
|
23582
|
-
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23583
|
-
};
|
|
23584
|
-
}
|
|
23585
|
-
function toCaptureAttributes(event) {
|
|
23586
|
-
const metadata = event.metadata;
|
|
23587
|
-
return {
|
|
23588
|
-
source_tool: event.sourceTool,
|
|
23589
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23590
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23591
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23592
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23593
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23594
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23595
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23596
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23597
|
-
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23598
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23599
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
23600
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23601
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
23602
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23603
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23604
|
-
};
|
|
23605
|
-
}
|
|
23606
|
-
function captureDefinitionVersion(finding2) {
|
|
23607
|
-
return `capture/${finding2.category}/${finding2.severity}`;
|
|
23608
|
-
}
|
|
23609
|
-
function toCaptureDefinitionInput(finding2) {
|
|
23610
|
-
return {
|
|
23611
|
-
ruleId: finding2.ruleId,
|
|
23612
|
-
version: captureDefinitionVersion(finding2),
|
|
23613
|
-
name: finding2.ruleId,
|
|
23614
|
-
category: finding2.category,
|
|
23615
|
-
severity: finding2.severity,
|
|
23616
|
-
definition: JSON.stringify({ ruleId: finding2.ruleId })
|
|
23617
|
-
};
|
|
23618
|
-
}
|
|
23619
|
-
|
|
23620
|
-
// ../../packages/schema/src/zod/managed.ts
|
|
23621
|
-
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23622
|
-
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23623
|
-
var ManagedSettingKey = external_exports.enum([
|
|
23624
|
-
"runMode",
|
|
23625
|
-
"historicalAccess",
|
|
23626
|
-
"vaultConsent",
|
|
23627
|
-
"vaultKeyCustody",
|
|
23628
|
-
"vaultInlineReveal",
|
|
23629
|
-
"modelJudgeConsent",
|
|
23630
|
-
"dataSharesInPlace"
|
|
23631
|
-
]).meta({ id: "ManagedSettingKey" });
|
|
23632
|
-
var ManagedSettingsValues = external_exports.object({
|
|
23633
|
-
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23634
|
-
controlPlane: external_exports.object({
|
|
23635
|
-
endpoint: external_exports.string().min(1),
|
|
23636
|
-
label: external_exports.string().min(1).optional()
|
|
23637
|
-
}).optional(),
|
|
23638
|
-
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23639
|
-
vaultConsent: external_exports.boolean().optional(),
|
|
23640
|
-
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23641
|
-
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23642
|
-
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23643
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23644
|
-
}).meta({ id: "ManagedSettingsValues" });
|
|
23645
|
-
var ManagedSettings = external_exports.object({
|
|
23646
|
-
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23647
|
-
// Shown on every locked control, so the user can tell an administrative
|
|
23648
|
-
// decision from a bug. Absent renders as a generic "your organization".
|
|
23649
|
-
organization: external_exports.string().min(1).optional(),
|
|
23650
|
-
// What the administrator pinned.
|
|
23651
|
-
values: ManagedSettingsValues.default({}),
|
|
23652
|
-
// Which of those the user may not change. A key here with no matching value
|
|
23653
|
-
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23654
|
-
// the user may still override. The two are separable on purpose.
|
|
23655
|
-
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23656
|
-
}).meta({ id: "ManagedSettings" });
|
|
23657
|
-
|
|
23658
23340
|
// ../../packages/schema/src/zod/policy.ts
|
|
23659
23341
|
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23660
23342
|
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23343
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23344
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23661
23345
|
var Policy = external_exports.object({
|
|
23662
23346
|
id: external_exports.guid(),
|
|
23663
23347
|
scope: PolicyScope,
|
|
@@ -23667,7 +23351,27 @@ var Policy = external_exports.object({
|
|
|
23667
23351
|
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23668
23352
|
// Display name — optional so older policy rows without name still parse.
|
|
23669
23353
|
// Added for the findings API (policy.name column migration).
|
|
23670
|
-
name: external_exports.string().optional()
|
|
23354
|
+
name: external_exports.string().optional(),
|
|
23355
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23356
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23357
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23358
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23359
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23360
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23361
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23362
|
+
//
|
|
23363
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23364
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23365
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23366
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23367
|
+
// — the CRUD routes neither accept nor set it.
|
|
23368
|
+
//
|
|
23369
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23370
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23371
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23372
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23373
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23374
|
+
provenance: PolicyProvenance.optional()
|
|
23671
23375
|
}).meta({ id: "Policy" });
|
|
23672
23376
|
var PolicyBundle = external_exports.object({
|
|
23673
23377
|
version: external_exports.string(),
|
|
@@ -23719,6 +23423,12 @@ var PolicyBundle = external_exports.object({
|
|
|
23719
23423
|
customKeywords: external_exports.array(external_exports.string()),
|
|
23720
23424
|
fetchedAt: external_exports.iso.datetime()
|
|
23721
23425
|
}).meta({ id: "PolicyBundle" });
|
|
23426
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23427
|
+
...Object.keys(PolicyBundle.shape),
|
|
23428
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23429
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23430
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23431
|
+
].sort().join(",");
|
|
23722
23432
|
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23723
23433
|
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23724
23434
|
var CATEGORY_PEAK_SEVERITY = {
|
|
@@ -23739,9 +23449,11 @@ function severityFloorPolicy(category) {
|
|
|
23739
23449
|
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23740
23450
|
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23741
23451
|
}
|
|
23742
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23743
23452
|
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23744
23453
|
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23454
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23455
|
+
id: "RedactFallback"
|
|
23456
|
+
});
|
|
23745
23457
|
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23746
23458
|
var BUILTIN_POLICY_SPECS = {
|
|
23747
23459
|
monitor: {
|
|
@@ -23778,6 +23490,42 @@ var BUILTIN_POLICY_SPECS = {
|
|
|
23778
23490
|
function builtinPolicyToAction(id) {
|
|
23779
23491
|
return BUILTIN_POLICY_SPECS[id].action;
|
|
23780
23492
|
}
|
|
23493
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23494
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23495
|
+
];
|
|
23496
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23497
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23498
|
+
);
|
|
23499
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23500
|
+
...BELOW_PALETTE,
|
|
23501
|
+
...PALETTE_WEAKEST_FIRST
|
|
23502
|
+
];
|
|
23503
|
+
function actionRank(action) {
|
|
23504
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23505
|
+
}
|
|
23506
|
+
function isActionAtLeast(action, floor) {
|
|
23507
|
+
return actionRank(action) >= actionRank(floor);
|
|
23508
|
+
}
|
|
23509
|
+
function strongerAction(a, b) {
|
|
23510
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23511
|
+
}
|
|
23512
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23513
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23514
|
+
}
|
|
23515
|
+
var PackPolicyFloor = external_exports.object({
|
|
23516
|
+
/**
|
|
23517
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23518
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23519
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23520
|
+
*/
|
|
23521
|
+
floor: BuiltinPolicyId,
|
|
23522
|
+
/**
|
|
23523
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23524
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23525
|
+
* re-assignable locally in either direction.
|
|
23526
|
+
*/
|
|
23527
|
+
locked: external_exports.boolean()
|
|
23528
|
+
}).describe("PackPolicyFloor");
|
|
23781
23529
|
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23782
23530
|
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23783
23531
|
);
|
|
@@ -23835,6 +23583,402 @@ var PolicyStatsResponse = external_exports.object({
|
|
|
23835
23583
|
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23836
23584
|
}).meta({ id: "PolicyStatsResponse" });
|
|
23837
23585
|
|
|
23586
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
23587
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
23588
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23589
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23590
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23591
|
+
);
|
|
23592
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23593
|
+
function pointerTokenScanner() {
|
|
23594
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23595
|
+
}
|
|
23596
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23597
|
+
var ParsedPointer = external_exports.object({
|
|
23598
|
+
category: DetectionCategory,
|
|
23599
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23600
|
+
pointerId: external_exports.string(),
|
|
23601
|
+
tag: external_exports.string()
|
|
23602
|
+
});
|
|
23603
|
+
var VaultEntry = external_exports.object({
|
|
23604
|
+
pointerId: external_exports.string(),
|
|
23605
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23606
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23607
|
+
// independently of the vault encryption key below.
|
|
23608
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23609
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23610
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23611
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23612
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
23613
|
+
// different rule's category keeps the category it was minted with, so one
|
|
23614
|
+
// value always produces exactly one wire token.
|
|
23615
|
+
category: DetectionCategory,
|
|
23616
|
+
ruleId: external_exports.string(),
|
|
23617
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23618
|
+
maskedMatch: external_exports.string(),
|
|
23619
|
+
provider: external_exports.string().optional(),
|
|
23620
|
+
ciphertext: external_exports.string(),
|
|
23621
|
+
nonce: external_exports.string(),
|
|
23622
|
+
authTag: external_exports.string(),
|
|
23623
|
+
// How many times this value has been detected on this machine — the reuse
|
|
23624
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23625
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23626
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23627
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23628
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23629
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23630
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23631
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23632
|
+
// the value does not expire.
|
|
23633
|
+
userAuthorized: external_exports.boolean(),
|
|
23634
|
+
firstSeen: external_exports.string(),
|
|
23635
|
+
lastSeen: external_exports.string()
|
|
23636
|
+
});
|
|
23637
|
+
var PointerDescriptor = external_exports.object({
|
|
23638
|
+
category: DetectionCategory,
|
|
23639
|
+
provider: external_exports.string().optional(),
|
|
23640
|
+
maskedMatch: external_exports.string(),
|
|
23641
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23642
|
+
firstSeen: external_exports.string(),
|
|
23643
|
+
lastSeen: external_exports.string()
|
|
23644
|
+
});
|
|
23645
|
+
var PointerIdentity = external_exports.object({
|
|
23646
|
+
ruleId: external_exports.string(),
|
|
23647
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23648
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23649
|
+
});
|
|
23650
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23651
|
+
var VaultDerefReason = external_exports.enum([
|
|
23652
|
+
"display",
|
|
23653
|
+
"explicit-reveal",
|
|
23654
|
+
"view-render",
|
|
23655
|
+
"model-input",
|
|
23656
|
+
"remediation",
|
|
23657
|
+
"purge"
|
|
23658
|
+
]);
|
|
23659
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23660
|
+
var VaultDeref = external_exports.object({
|
|
23661
|
+
id: external_exports.guid(),
|
|
23662
|
+
pointerId: external_exports.string(),
|
|
23663
|
+
at: external_exports.string(),
|
|
23664
|
+
target: DetokenizeTarget,
|
|
23665
|
+
reason: VaultDerefReason,
|
|
23666
|
+
outcome: VaultDerefOutcome,
|
|
23667
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23668
|
+
grantId: external_exports.string().optional(),
|
|
23669
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23670
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23671
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
23672
|
+
});
|
|
23673
|
+
var VaultSightingKind = external_exports.enum([
|
|
23674
|
+
"prompt",
|
|
23675
|
+
"tool-input",
|
|
23676
|
+
"tool-output",
|
|
23677
|
+
"file",
|
|
23678
|
+
"transcript"
|
|
23679
|
+
]);
|
|
23680
|
+
var VaultSighting = external_exports.object({
|
|
23681
|
+
location: external_exports.string(),
|
|
23682
|
+
kind: VaultSightingKind,
|
|
23683
|
+
firstSeen: external_exports.string(),
|
|
23684
|
+
lastSeen: external_exports.string()
|
|
23685
|
+
});
|
|
23686
|
+
var VaultInventoryEntry = external_exports.object({
|
|
23687
|
+
pointerId: external_exports.string(),
|
|
23688
|
+
category: DetectionCategory,
|
|
23689
|
+
provider: external_exports.string().optional(),
|
|
23690
|
+
maskedMatch: external_exports.string(),
|
|
23691
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23692
|
+
firstSeen: external_exports.string(),
|
|
23693
|
+
lastSeen: external_exports.string(),
|
|
23694
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
23695
|
+
// the inventory badges it, the row links to revocation.
|
|
23696
|
+
revealGrantId: external_exports.string().nullable(),
|
|
23697
|
+
sightings: external_exports.array(VaultSighting)
|
|
23698
|
+
});
|
|
23699
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23700
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23701
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23702
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
23703
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23704
|
+
// Opaque; names the last row of the page just served.
|
|
23705
|
+
cursor: external_exports.string().optional()
|
|
23706
|
+
});
|
|
23707
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
23708
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
23709
|
+
// independent, so paging never changes what the count claims.
|
|
23710
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23711
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23712
|
+
// `null` once the last page is reached.
|
|
23713
|
+
nextCursor: external_exports.string().nullable()
|
|
23714
|
+
});
|
|
23715
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
23716
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23717
|
+
cursor: external_exports.string().optional()
|
|
23718
|
+
});
|
|
23719
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
23720
|
+
// Reused values across the whole store — the number the section's claim
|
|
23721
|
+
// ("values detected in more than one place") is about.
|
|
23722
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23723
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23724
|
+
nextCursor: external_exports.string().nullable()
|
|
23725
|
+
});
|
|
23726
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
23727
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23728
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23729
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23730
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
23731
|
+
includeBatched: external_exports.boolean().optional(),
|
|
23732
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23733
|
+
cursor: external_exports.string().optional()
|
|
23734
|
+
});
|
|
23735
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
23736
|
+
items: external_exports.array(VaultDeref),
|
|
23737
|
+
nextCursor: external_exports.string().nullable(),
|
|
23738
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23739
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23740
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23741
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23742
|
+
});
|
|
23743
|
+
var VaultKeyCustody = external_exports.string();
|
|
23744
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23745
|
+
var VAULT_CONSENT_VERSION = 1;
|
|
23746
|
+
var VaultConsent = external_exports.object({
|
|
23747
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23748
|
+
version: external_exports.number().int().positive()
|
|
23749
|
+
});
|
|
23750
|
+
|
|
23751
|
+
// ../../packages/schema/src/zod/local.ts
|
|
23752
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23753
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23754
|
+
var HISTORY_SYNC_PAYLOAD_VERSION = 2;
|
|
23755
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23756
|
+
var ControlPlaneConnection = external_exports.object({
|
|
23757
|
+
endpoint: external_exports.string().min(1),
|
|
23758
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23759
|
+
label: external_exports.string().min(1).optional(),
|
|
23760
|
+
attachedAt: external_exports.iso.datetime()
|
|
23761
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
23762
|
+
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23763
|
+
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23764
|
+
var ModelJudgeConsent = external_exports.object({
|
|
23765
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23766
|
+
payloadVersion: external_exports.number().int().positive()
|
|
23767
|
+
});
|
|
23768
|
+
var HistorySyncConsent = external_exports.object({
|
|
23769
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23770
|
+
payloadVersion: external_exports.number().int().positive(),
|
|
23771
|
+
endpoint: external_exports.string()
|
|
23772
|
+
});
|
|
23773
|
+
function isHistorySyncConsentValid(consent, endpoint) {
|
|
23774
|
+
if (consent === void 0 || endpoint === void 0) return false;
|
|
23775
|
+
return consent.payloadVersion === HISTORY_SYNC_PAYLOAD_VERSION && consent.endpoint === endpoint;
|
|
23776
|
+
}
|
|
23777
|
+
var WorkspaceSettings = external_exports.object({
|
|
23778
|
+
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23779
|
+
runMode: RunMode.default("standalone"),
|
|
23780
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23781
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23782
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
23783
|
+
policy: SimpleDetectionPolicy.default("redact"),
|
|
23784
|
+
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23785
|
+
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23786
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23787
|
+
// Shares writes.
|
|
23788
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23789
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23790
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23791
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23792
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23793
|
+
// purging the vault is the eraser.
|
|
23794
|
+
vaultConsent: VaultConsent.optional(),
|
|
23795
|
+
// Where the vault master key lives.
|
|
23796
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23797
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23798
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23799
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23800
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23801
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23802
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23803
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23804
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23805
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23806
|
+
//
|
|
23807
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23808
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23809
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23810
|
+
// word and stays out of the stored value.
|
|
23811
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23812
|
+
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23813
|
+
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23814
|
+
// Records that the user consented to sending findings to the model API for
|
|
23815
|
+
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23816
|
+
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23817
|
+
// covers the current payload and must be re-granted.
|
|
23818
|
+
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23819
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23820
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23821
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23822
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23823
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23824
|
+
// payload no longer counts.
|
|
23825
|
+
historySyncConsent: HistorySyncConsent.optional()
|
|
23826
|
+
});
|
|
23827
|
+
function defaultWorkspaceSettings() {
|
|
23828
|
+
return WorkspaceSettings.parse({});
|
|
23829
|
+
}
|
|
23830
|
+
function isAttached(settings) {
|
|
23831
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23832
|
+
}
|
|
23833
|
+
function toInventoryRow(input2, id, now) {
|
|
23834
|
+
return {
|
|
23835
|
+
id,
|
|
23836
|
+
objectType: input2.objectType,
|
|
23837
|
+
location: input2.location ?? null,
|
|
23838
|
+
title: input2.title ?? null,
|
|
23839
|
+
hostId: input2.hostId ?? null,
|
|
23840
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23841
|
+
firstSeen: now,
|
|
23842
|
+
lastSeen: now
|
|
23843
|
+
};
|
|
23844
|
+
}
|
|
23845
|
+
function toSourceProjectRow(input2, id, now) {
|
|
23846
|
+
return {
|
|
23847
|
+
id,
|
|
23848
|
+
url: input2.url,
|
|
23849
|
+
name: input2.name ?? null,
|
|
23850
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23851
|
+
firstSeen: now,
|
|
23852
|
+
lastSeen: now
|
|
23853
|
+
};
|
|
23854
|
+
}
|
|
23855
|
+
function toAuditEventRow(input2) {
|
|
23856
|
+
return {
|
|
23857
|
+
id: input2.id,
|
|
23858
|
+
parentId: input2.parentId ?? null,
|
|
23859
|
+
rootSessionId: input2.rootSessionId ?? null,
|
|
23860
|
+
eventType: input2.eventType,
|
|
23861
|
+
hostId: input2.hostId ?? null,
|
|
23862
|
+
harnessId: input2.harnessId ?? null,
|
|
23863
|
+
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23864
|
+
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23865
|
+
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23866
|
+
severity: input2.severity ?? null,
|
|
23867
|
+
priority: input2.priority ?? null,
|
|
23868
|
+
content: input2.content ?? null,
|
|
23869
|
+
contentHash: input2.contentHash ?? null,
|
|
23870
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23871
|
+
};
|
|
23872
|
+
}
|
|
23873
|
+
function toClassifiedDataRow(input2, id) {
|
|
23874
|
+
return {
|
|
23875
|
+
id,
|
|
23876
|
+
class: input2.class,
|
|
23877
|
+
label: input2.label ?? null,
|
|
23878
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23879
|
+
};
|
|
23880
|
+
}
|
|
23881
|
+
function toInspectionDefinitionRow(input2, id) {
|
|
23882
|
+
return {
|
|
23883
|
+
id,
|
|
23884
|
+
ruleId: input2.ruleId,
|
|
23885
|
+
name: input2.name,
|
|
23886
|
+
category: input2.category,
|
|
23887
|
+
severity: input2.severity,
|
|
23888
|
+
definition: input2.definition,
|
|
23889
|
+
version: input2.version
|
|
23890
|
+
};
|
|
23891
|
+
}
|
|
23892
|
+
function toInspectionFindingRow(input2) {
|
|
23893
|
+
return {
|
|
23894
|
+
id: input2.id,
|
|
23895
|
+
auditEventId: input2.auditEventId,
|
|
23896
|
+
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23897
|
+
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23898
|
+
spanStart: input2.span.start,
|
|
23899
|
+
spanEnd: input2.span.end,
|
|
23900
|
+
maskedMatch: input2.maskedMatch,
|
|
23901
|
+
actionTaken: input2.actionTaken,
|
|
23902
|
+
confidence: input2.confidence,
|
|
23903
|
+
findingKey: input2.findingKey ?? null,
|
|
23904
|
+
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23905
|
+
};
|
|
23906
|
+
}
|
|
23907
|
+
function toCaptureAttributes(event) {
|
|
23908
|
+
const metadata = event.metadata;
|
|
23909
|
+
return {
|
|
23910
|
+
source_tool: event.sourceTool,
|
|
23911
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23912
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23913
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23914
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23915
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23916
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23917
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23918
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23919
|
+
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23920
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23921
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
23922
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23923
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
23924
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23925
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23926
|
+
};
|
|
23927
|
+
}
|
|
23928
|
+
function captureDefinitionVersion(finding2) {
|
|
23929
|
+
return `capture/${finding2.category}/${finding2.severity}`;
|
|
23930
|
+
}
|
|
23931
|
+
function toCaptureDefinitionInput(finding2) {
|
|
23932
|
+
return {
|
|
23933
|
+
ruleId: finding2.ruleId,
|
|
23934
|
+
version: captureDefinitionVersion(finding2),
|
|
23935
|
+
name: finding2.ruleId,
|
|
23936
|
+
category: finding2.category,
|
|
23937
|
+
severity: finding2.severity,
|
|
23938
|
+
definition: JSON.stringify({ ruleId: finding2.ruleId })
|
|
23939
|
+
};
|
|
23940
|
+
}
|
|
23941
|
+
|
|
23942
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
23943
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23944
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23945
|
+
var ManagedSettingKey = external_exports.enum([
|
|
23946
|
+
"runMode",
|
|
23947
|
+
"historicalAccess",
|
|
23948
|
+
"vaultConsent",
|
|
23949
|
+
"vaultKeyCustody",
|
|
23950
|
+
"vaultInlineReveal",
|
|
23951
|
+
"modelJudgeConsent",
|
|
23952
|
+
"dataSharesInPlace",
|
|
23953
|
+
"redactFallback"
|
|
23954
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
23955
|
+
var ManagedSettingsValues = external_exports.object({
|
|
23956
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23957
|
+
controlPlane: external_exports.object({
|
|
23958
|
+
endpoint: external_exports.string().min(1),
|
|
23959
|
+
label: external_exports.string().min(1).optional()
|
|
23960
|
+
}).optional(),
|
|
23961
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23962
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
23963
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23964
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23965
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23966
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23967
|
+
redactFallback: RedactFallback.optional()
|
|
23968
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
23969
|
+
var ManagedSettings = external_exports.object({
|
|
23970
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23971
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
23972
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
23973
|
+
organization: external_exports.string().min(1).optional(),
|
|
23974
|
+
// What the administrator pinned.
|
|
23975
|
+
values: ManagedSettingsValues.default({}),
|
|
23976
|
+
// Which of those the user may not change. A key here with no matching value
|
|
23977
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23978
|
+
// the user may still override. The two are separable on purpose.
|
|
23979
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23980
|
+
}).meta({ id: "ManagedSettings" });
|
|
23981
|
+
|
|
23838
23982
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23839
23983
|
var ProjectFileInput = external_exports.object({
|
|
23840
23984
|
path: external_exports.string().min(1),
|
|
@@ -24080,10 +24224,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
24080
24224
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
24081
24225
|
|
|
24082
24226
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24227
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24228
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
24083
24229
|
var SaveSettingsInput = external_exports.object({
|
|
24084
24230
|
historicalAccess: external_exports.string(),
|
|
24085
|
-
modelJudgeConsent:
|
|
24086
|
-
historySyncConsent:
|
|
24231
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24232
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
24087
24233
|
vaultConsent: external_exports.string(),
|
|
24088
24234
|
vaultInlineReveal: external_exports.string()
|
|
24089
24235
|
});
|
|
@@ -24233,9 +24379,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24233
24379
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24234
24380
|
return reasons;
|
|
24235
24381
|
}
|
|
24236
|
-
function buildReviewInfo(trust, transports) {
|
|
24382
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24237
24383
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24238
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24384
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24239
24385
|
}
|
|
24240
24386
|
function distinctTransports(transports) {
|
|
24241
24387
|
return Array.from(new Set(transports));
|
|
@@ -24457,8 +24603,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
|
|
|
24457
24603
|
}
|
|
24458
24604
|
|
|
24459
24605
|
// ../../packages/persistence/src/database.ts
|
|
24460
|
-
import { randomUUID as
|
|
24461
|
-
import { join as
|
|
24606
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24607
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24462
24608
|
import { DatabaseSync } from "node:sqlite";
|
|
24463
24609
|
|
|
24464
24610
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24723,6 +24869,10 @@ function allRows(stmt, params) {
|
|
|
24723
24869
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24724
24870
|
return stmt.all(params);
|
|
24725
24871
|
}
|
|
24872
|
+
function* iterateRows(stmt, params) {
|
|
24873
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24874
|
+
for (const row of rows) yield row;
|
|
24875
|
+
}
|
|
24726
24876
|
function getRow(stmt, params) {
|
|
24727
24877
|
if (params === void 0) return stmt.get();
|
|
24728
24878
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -25191,10 +25341,17 @@ function ensureSyncedAtColumn(db, table) {
|
|
|
25191
25341
|
if (!columns.includes("sync_claimed_at")) {
|
|
25192
25342
|
db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
|
|
25193
25343
|
}
|
|
25344
|
+
if (!columns.includes("outbox_owed")) {
|
|
25345
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
|
|
25346
|
+
}
|
|
25194
25347
|
db.exec(
|
|
25195
25348
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
25196
25349
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
25197
25350
|
);
|
|
25351
|
+
db.exec(
|
|
25352
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25353
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25354
|
+
);
|
|
25198
25355
|
db.exec(
|
|
25199
25356
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
25200
25357
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25299,7 +25456,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25299
25456
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25300
25457
|
var DAY_MS = 864e5;
|
|
25301
25458
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25302
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25303
25459
|
function defaultTimeZone() {
|
|
25304
25460
|
try {
|
|
25305
25461
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25354,6 +25510,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25354
25510
|
error: "error",
|
|
25355
25511
|
active: "active"
|
|
25356
25512
|
};
|
|
25513
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25357
25514
|
function safeParseStringArray(raw) {
|
|
25358
25515
|
if (!raw) return [];
|
|
25359
25516
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25427,6 +25584,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25427
25584
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25428
25585
|
json_extract(attributes, '$.internal') AS internal,
|
|
25429
25586
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25587
|
+
var LLM_USAGE_SELECT = `
|
|
25588
|
+
SELECT root_session_id AS sessionId,
|
|
25589
|
+
provider,
|
|
25590
|
+
model,
|
|
25591
|
+
service_tier AS serviceTier,
|
|
25592
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25593
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25594
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25595
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25596
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25597
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25598
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25599
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25600
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25601
|
+
function usageLeaves(rows) {
|
|
25602
|
+
return rows.map((row) => {
|
|
25603
|
+
const attributes = {
|
|
25604
|
+
input_tokens: row.inputTokens,
|
|
25605
|
+
output_tokens: row.outputTokens,
|
|
25606
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25607
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25608
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25609
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25610
|
+
web_search_requests: row.webSearchRequests
|
|
25611
|
+
};
|
|
25612
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25613
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25614
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25615
|
+
return { sessionId: row.sessionId, attributes };
|
|
25616
|
+
});
|
|
25617
|
+
}
|
|
25430
25618
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25431
25619
|
var HAS_ACTIVITY = `EXISTS (
|
|
25432
25620
|
SELECT 1 FROM audit_events c
|
|
@@ -25452,16 +25640,17 @@ var SqliteActivityRepository = class {
|
|
|
25452
25640
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25453
25641
|
const liveNow = countScalar(
|
|
25454
25642
|
this.db,
|
|
25455
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25643
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25456
25644
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25457
|
-
AND
|
|
25458
|
-
|
|
25459
|
-
|
|
25460
|
-
|
|
25461
|
-
|
|
25462
|
-
|
|
25463
|
-
|
|
25464
|
-
|
|
25645
|
+
AND s.id IN (
|
|
25646
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25647
|
+
UNION
|
|
25648
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25649
|
+
WHERE started_at >= ?
|
|
25650
|
+
UNION
|
|
25651
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25652
|
+
WHERE ended_at >= ?)`,
|
|
25653
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25465
25654
|
);
|
|
25466
25655
|
const toolCallsToday = countScalar(
|
|
25467
25656
|
this.db,
|
|
@@ -25591,7 +25780,7 @@ var SqliteActivityRepository = class {
|
|
|
25591
25780
|
this.db.prepare(
|
|
25592
25781
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25593
25782
|
FROM audit_events
|
|
25594
|
-
WHERE id = ? OR root_session_id = ?
|
|
25783
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25595
25784
|
ORDER BY started_at ASC, id ASC`
|
|
25596
25785
|
),
|
|
25597
25786
|
[sessionId, sessionId]
|
|
@@ -25604,14 +25793,14 @@ var SqliteActivityRepository = class {
|
|
|
25604
25793
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25605
25794
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25606
25795
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25607
|
-
FROM audit_events
|
|
25796
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25608
25797
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25609
25798
|
),
|
|
25610
25799
|
[sessionId]
|
|
25611
25800
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25612
25801
|
const primaryModel = getRow(
|
|
25613
25802
|
this.db.prepare(
|
|
25614
|
-
`SELECT model, provider FROM audit_events
|
|
25803
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25615
25804
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25616
25805
|
ORDER BY started_at ASC, id ASC
|
|
25617
25806
|
LIMIT 1`
|
|
@@ -25622,7 +25811,7 @@ var SqliteActivityRepository = class {
|
|
|
25622
25811
|
this.db.prepare(
|
|
25623
25812
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25624
25813
|
count(*) AS n
|
|
25625
|
-
FROM audit_events
|
|
25814
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25626
25815
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25627
25816
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25628
25817
|
),
|
|
@@ -25630,7 +25819,7 @@ var SqliteActivityRepository = class {
|
|
|
25630
25819
|
);
|
|
25631
25820
|
const modelRows = allRows(
|
|
25632
25821
|
this.db.prepare(
|
|
25633
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25822
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25634
25823
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25635
25824
|
ORDER BY model`
|
|
25636
25825
|
),
|
|
@@ -25639,7 +25828,7 @@ var SqliteActivityRepository = class {
|
|
|
25639
25828
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25640
25829
|
const commits = countScalar(
|
|
25641
25830
|
this.db,
|
|
25642
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25831
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25643
25832
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25644
25833
|
[sessionId]
|
|
25645
25834
|
);
|
|
@@ -25675,25 +25864,57 @@ var SqliteActivityRepository = class {
|
|
|
25675
25864
|
return Promise.resolve(session);
|
|
25676
25865
|
}
|
|
25677
25866
|
/**
|
|
25678
|
-
* Cross-session token report — every `llm_call`
|
|
25679
|
-
* `started_at >= fromMs`
|
|
25680
|
-
* USD cost DERIVED at read time via the shared
|
|
25681
|
-
*
|
|
25682
|
-
*
|
|
25683
|
-
*
|
|
25867
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25868
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25869
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25870
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25871
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25872
|
+
*
|
|
25873
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25874
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25875
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25876
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25877
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25878
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25879
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25880
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25881
|
+
* recompute the columns it could have read. The index is one every open
|
|
25882
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25883
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25884
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25885
|
+
* per call, no bag parsed.
|
|
25684
25886
|
*/
|
|
25685
25887
|
tokenReports(fromMs) {
|
|
25686
|
-
const
|
|
25687
|
-
|
|
25888
|
+
const rows = allRows(
|
|
25889
|
+
this.db.prepare(
|
|
25890
|
+
`${LLM_USAGE_SELECT}
|
|
25891
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25892
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25893
|
+
${LLM_USAGE_GROUP}`
|
|
25894
|
+
),
|
|
25895
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25896
|
+
);
|
|
25897
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25688
25898
|
}
|
|
25689
25899
|
/**
|
|
25690
|
-
* One session's token report — its `llm_call`
|
|
25691
|
-
* model) with derived cost, or `null` when the session made no
|
|
25692
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25693
|
-
* breakdown + estimated cost.
|
|
25900
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25901
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25902
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25903
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25904
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25905
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25694
25906
|
*/
|
|
25695
25907
|
tokenReportForSession(sessionId) {
|
|
25696
|
-
const
|
|
25908
|
+
const rows = allRows(
|
|
25909
|
+
this.db.prepare(
|
|
25910
|
+
`${LLM_USAGE_SELECT}
|
|
25911
|
+
FROM audit_events
|
|
25912
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25913
|
+
${LLM_USAGE_GROUP}`
|
|
25914
|
+
),
|
|
25915
|
+
[sessionId]
|
|
25916
|
+
);
|
|
25917
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25697
25918
|
return Promise.resolve(reports[0] ?? null);
|
|
25698
25919
|
}
|
|
25699
25920
|
/**
|
|
@@ -25717,42 +25938,6 @@ var SqliteActivityRepository = class {
|
|
|
25717
25938
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25718
25939
|
return Promise.resolve([...seen]);
|
|
25719
25940
|
}
|
|
25720
|
-
/**
|
|
25721
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25722
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25723
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25724
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25725
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25726
|
-
*/
|
|
25727
|
-
readLlmCallLeaves(opts = {}) {
|
|
25728
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25729
|
-
const params = [];
|
|
25730
|
-
if (opts.sessionId !== void 0) {
|
|
25731
|
-
conditions.push("root_session_id = ?");
|
|
25732
|
-
params.push(opts.sessionId);
|
|
25733
|
-
}
|
|
25734
|
-
if (opts.fromMs !== void 0) {
|
|
25735
|
-
conditions.push("started_at >= ?");
|
|
25736
|
-
params.push(opts.fromMs);
|
|
25737
|
-
}
|
|
25738
|
-
const rows = allRows(
|
|
25739
|
-
this.db.prepare(
|
|
25740
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25741
|
-
FROM audit_events
|
|
25742
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25743
|
-
),
|
|
25744
|
-
params
|
|
25745
|
-
);
|
|
25746
|
-
return mapRowsTolerant(
|
|
25747
|
-
rows.filter(
|
|
25748
|
-
(row) => row.sessionId !== null
|
|
25749
|
-
),
|
|
25750
|
-
(row) => ({
|
|
25751
|
-
sessionId: row.sessionId,
|
|
25752
|
-
attributes: JSON.parse(row.attributes)
|
|
25753
|
-
})
|
|
25754
|
-
);
|
|
25755
|
-
}
|
|
25756
25941
|
/**
|
|
25757
25942
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25758
25943
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25767,20 +25952,23 @@ var SqliteActivityRepository = class {
|
|
|
25767
25952
|
const inClause = placeholders(sessionIds.length);
|
|
25768
25953
|
const lastActivityRows = allRows(
|
|
25769
25954
|
this.db.prepare(
|
|
25770
|
-
`SELECT
|
|
25771
|
-
|
|
25772
|
-
|
|
25955
|
+
`SELECT ids.value AS id,
|
|
25956
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25957
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25958
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25959
|
+
FROM json_each(?) AS ids`
|
|
25773
25960
|
),
|
|
25774
|
-
sessionIds
|
|
25961
|
+
[JSON.stringify(sessionIds)]
|
|
25775
25962
|
);
|
|
25776
25963
|
for (const row of lastActivityRows) {
|
|
25777
|
-
if (row.id === null) continue;
|
|
25778
25964
|
const entry = result.get(row.id);
|
|
25779
|
-
|
|
25965
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25966
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25780
25967
|
}
|
|
25781
25968
|
const turnsRows = allRows(
|
|
25782
25969
|
this.db.prepare(
|
|
25783
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25970
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25971
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25784
25972
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25785
25973
|
GROUP BY root_session_id`
|
|
25786
25974
|
),
|
|
@@ -25795,7 +25983,7 @@ var SqliteActivityRepository = class {
|
|
|
25795
25983
|
this.db.prepare(
|
|
25796
25984
|
`SELECT root_session_id AS id,
|
|
25797
25985
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25798
|
-
FROM audit_events
|
|
25986
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25799
25987
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25800
25988
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25801
25989
|
GROUP BY root_session_id`
|
|
@@ -25825,7 +26013,7 @@ var SqliteActivityRepository = class {
|
|
|
25825
26013
|
this.db.prepare(
|
|
25826
26014
|
`SELECT root_session_id AS id,
|
|
25827
26015
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25828
|
-
FROM audit_events
|
|
26016
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25829
26017
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25830
26018
|
GROUP BY root_session_id`
|
|
25831
26019
|
),
|
|
@@ -26854,7 +27042,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26854
27042
|
|
|
26855
27043
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26856
27044
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26857
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26858
27045
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26859
27046
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26860
27047
|
function compareLocationOrder(a, b) {
|
|
@@ -26883,6 +27070,25 @@ function deriveInstanceStatus(row) {
|
|
|
26883
27070
|
latestResolutionStatus: row.latest_status
|
|
26884
27071
|
});
|
|
26885
27072
|
}
|
|
27073
|
+
function toFlatFindingRow(r) {
|
|
27074
|
+
return {
|
|
27075
|
+
id: r.id,
|
|
27076
|
+
ruleId: r.rule_id,
|
|
27077
|
+
category: r.category,
|
|
27078
|
+
severity: r.severity,
|
|
27079
|
+
maskedMatch: r.masked_match,
|
|
27080
|
+
actionTaken: r.action_taken,
|
|
27081
|
+
confidence: r.confidence,
|
|
27082
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27083
|
+
sourceTool: r.source_tool,
|
|
27084
|
+
repo: r.repo ?? "",
|
|
27085
|
+
file: r.file ?? "",
|
|
27086
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27087
|
+
eventId: r.event_id,
|
|
27088
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27089
|
+
status: deriveInstanceStatus(r)
|
|
27090
|
+
};
|
|
27091
|
+
}
|
|
26886
27092
|
function encodeGroupCursor(group) {
|
|
26887
27093
|
const payload = {
|
|
26888
27094
|
sev: group.severity,
|
|
@@ -26958,7 +27164,7 @@ var SqliteFindingsRepository = class {
|
|
|
26958
27164
|
this.db.prepare(
|
|
26959
27165
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26960
27166
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26961
|
-
|
|
27167
|
+
e.source_tool AS source_tool,
|
|
26962
27168
|
e.event_type AS kind
|
|
26963
27169
|
FROM audit_events e
|
|
26964
27170
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -27066,56 +27272,11 @@ var SqliteFindingsRepository = class {
|
|
|
27066
27272
|
predicate,
|
|
27067
27273
|
params: sessionParams
|
|
27068
27274
|
});
|
|
27069
|
-
const rows =
|
|
27070
|
-
|
|
27071
|
-
|
|
27072
|
-
|
|
27073
|
-
|
|
27074
|
-
FROM (
|
|
27075
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
27076
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
27077
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27078
|
-
e.started_at AS occurred_at,
|
|
27079
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
27080
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
27081
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
27082
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
27083
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27084
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27085
|
-
latest.status AS latest_status,
|
|
27086
|
-
ROW_NUMBER() OVER (
|
|
27087
|
-
PARTITION BY d.rule_id
|
|
27088
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
27089
|
-
) AS rn
|
|
27090
|
-
FROM inspection_findings f
|
|
27091
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
27092
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27093
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
27094
|
-
ON latest.finding_key = f.finding_key
|
|
27095
|
-
${predicate}
|
|
27096
|
-
)
|
|
27097
|
-
WHERE rn <= :cap
|
|
27098
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
27099
|
-
),
|
|
27100
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
27101
|
-
);
|
|
27102
|
-
const groupable = rows.map((r) => ({
|
|
27103
|
-
id: r.id,
|
|
27104
|
-
ruleId: r.rule_id,
|
|
27105
|
-
category: r.category,
|
|
27106
|
-
severity: r.severity,
|
|
27107
|
-
maskedMatch: r.masked_match,
|
|
27108
|
-
actionTaken: r.action_taken,
|
|
27109
|
-
confidence: r.confidence,
|
|
27110
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27111
|
-
sourceTool: r.source_tool,
|
|
27112
|
-
repo: r.repo ?? "",
|
|
27113
|
-
file: r.file ?? "",
|
|
27114
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27115
|
-
eventId: r.event_id,
|
|
27116
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27117
|
-
status: deriveInstanceStatus(r)
|
|
27118
|
-
}));
|
|
27275
|
+
const rows = this.previewRows(aggregates, {
|
|
27276
|
+
sessionId: query.sessionId,
|
|
27277
|
+
from: query.from
|
|
27278
|
+
});
|
|
27279
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
27119
27280
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27120
27281
|
const filterOpts = {
|
|
27121
27282
|
severity: query.severity,
|
|
@@ -27201,8 +27362,10 @@ var SqliteFindingsRepository = class {
|
|
|
27201
27362
|
*
|
|
27202
27363
|
* The scan runs from the top of the scope on every request, not from the
|
|
27203
27364
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
27204
|
-
* move as the caller pages. Rows
|
|
27205
|
-
*
|
|
27365
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27366
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27367
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27368
|
+
* fetched batches; only the page itself is retained.
|
|
27206
27369
|
*/
|
|
27207
27370
|
listFindingInstances(query) {
|
|
27208
27371
|
const opts = {
|
|
@@ -27218,6 +27381,10 @@ var SqliteFindingsRepository = class {
|
|
|
27218
27381
|
};
|
|
27219
27382
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27220
27383
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27384
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27385
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27386
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27387
|
+
};
|
|
27221
27388
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27222
27389
|
const items = [];
|
|
27223
27390
|
let total = 0;
|
|
@@ -27230,6 +27397,7 @@ var SqliteFindingsRepository = class {
|
|
|
27230
27397
|
accumulator.add(row);
|
|
27231
27398
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27232
27399
|
total += 1;
|
|
27400
|
+
if (!isPastCursor(row)) continue;
|
|
27233
27401
|
if (items.length < limit) {
|
|
27234
27402
|
items.push(toInstanceDetail(row));
|
|
27235
27403
|
last = row;
|
|
@@ -27238,15 +27406,6 @@ var SqliteFindingsRepository = class {
|
|
|
27238
27406
|
}
|
|
27239
27407
|
}
|
|
27240
27408
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27241
|
-
if (cursor !== null) {
|
|
27242
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27243
|
-
return Promise.resolve({
|
|
27244
|
-
totals: { findings: total },
|
|
27245
|
-
facets: accumulator.facets(),
|
|
27246
|
-
items: resumed.items,
|
|
27247
|
-
nextCursor: resumed.nextCursor
|
|
27248
|
-
});
|
|
27249
|
-
}
|
|
27250
27409
|
return Promise.resolve({
|
|
27251
27410
|
totals: { findings: total },
|
|
27252
27411
|
facets: accumulator.facets(),
|
|
@@ -27254,35 +27413,6 @@ var SqliteFindingsRepository = class {
|
|
|
27254
27413
|
nextCursor
|
|
27255
27414
|
});
|
|
27256
27415
|
}
|
|
27257
|
-
/**
|
|
27258
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27259
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27260
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27261
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27262
|
-
*/
|
|
27263
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27264
|
-
const items = [];
|
|
27265
|
-
let last;
|
|
27266
|
-
let hasMore = false;
|
|
27267
|
-
for (const row of this.scanFindingRows({
|
|
27268
|
-
sessionId: query.sessionId,
|
|
27269
|
-
from: query.from,
|
|
27270
|
-
after: cursor
|
|
27271
|
-
})) {
|
|
27272
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27273
|
-
if (items.length < limit) {
|
|
27274
|
-
items.push(toInstanceDetail(row));
|
|
27275
|
-
last = row;
|
|
27276
|
-
} else {
|
|
27277
|
-
hasMore = true;
|
|
27278
|
-
break;
|
|
27279
|
-
}
|
|
27280
|
-
}
|
|
27281
|
-
return {
|
|
27282
|
-
items,
|
|
27283
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27284
|
-
};
|
|
27285
|
-
}
|
|
27286
27416
|
/**
|
|
27287
27417
|
* The same findings folded by location: repository, then file within it.
|
|
27288
27418
|
*
|
|
@@ -27365,25 +27495,111 @@ var SqliteFindingsRepository = class {
|
|
|
27365
27495
|
});
|
|
27366
27496
|
}
|
|
27367
27497
|
/**
|
|
27368
|
-
*
|
|
27498
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27499
|
+
*
|
|
27500
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27501
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27502
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27503
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27504
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27505
|
+
* store while the answer does not.
|
|
27506
|
+
*
|
|
27507
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27508
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27509
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27510
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27511
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27512
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27513
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27514
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27515
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27516
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27517
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27518
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27519
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27520
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27521
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27522
|
+
* sort of the scope, which is still that floor.
|
|
27523
|
+
*
|
|
27524
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27525
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27526
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27527
|
+
* built from.
|
|
27528
|
+
*/
|
|
27529
|
+
previewRows(aggregates, scope) {
|
|
27530
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27531
|
+
let remaining = 0;
|
|
27532
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27533
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27534
|
+
wanted.set(ruleId, n);
|
|
27535
|
+
remaining += n;
|
|
27536
|
+
}
|
|
27537
|
+
const rows = [];
|
|
27538
|
+
if (remaining === 0) return rows;
|
|
27539
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27540
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27541
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27542
|
+
const want = wanted.get(r.rule_id);
|
|
27543
|
+
if (want === void 0) continue;
|
|
27544
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27545
|
+
if (have >= want) continue;
|
|
27546
|
+
taken.set(r.rule_id, have + 1);
|
|
27547
|
+
rows.push(r);
|
|
27548
|
+
remaining -= 1;
|
|
27549
|
+
if (remaining === 0) break;
|
|
27550
|
+
}
|
|
27551
|
+
return rows;
|
|
27552
|
+
}
|
|
27553
|
+
/**
|
|
27554
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27369
27555
|
*
|
|
27370
27556
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27371
27557
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27372
|
-
* store is far more rows than any page.
|
|
27373
|
-
*
|
|
27374
|
-
*
|
|
27375
|
-
*
|
|
27376
|
-
*
|
|
27377
|
-
*
|
|
27378
|
-
* makes it a point lookup per row, and the derived table would re-materialize
|
|
27379
|
-
* a window over the whole resolution table once per batch.
|
|
27558
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27559
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27560
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27561
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27562
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27563
|
+
* batch and cost the square of the scope.
|
|
27380
27564
|
*
|
|
27381
|
-
* `
|
|
27382
|
-
* would be missing from its own facet, which is
|
|
27383
|
-
* dimension
|
|
27565
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27566
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27567
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27568
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27569
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27570
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27571
|
+
* page-2+ request would otherwise re-seek for.
|
|
27384
27572
|
*/
|
|
27385
27573
|
*scanFindingRows(scope) {
|
|
27386
|
-
const
|
|
27574
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27575
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27576
|
+
yield toFlatFindingRow(r);
|
|
27577
|
+
}
|
|
27578
|
+
}
|
|
27579
|
+
/**
|
|
27580
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27581
|
+
* joined to its event and definition, newest first.
|
|
27582
|
+
*
|
|
27583
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27584
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27585
|
+
*
|
|
27586
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27587
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27588
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27589
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27590
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27591
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27592
|
+
* ordered within the session — and the order falls out of the index.
|
|
27593
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27594
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27595
|
+
*
|
|
27596
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27597
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27598
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27599
|
+
* table would be materialized before the first row streamed.
|
|
27600
|
+
*/
|
|
27601
|
+
findingScanSql(scope) {
|
|
27602
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27387
27603
|
const params = [];
|
|
27388
27604
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27389
27605
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27397,58 +27613,24 @@ var SqliteFindingsRepository = class {
|
|
|
27397
27613
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27398
27614
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27399
27615
|
e.started_at AS occurred_at,
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
|
|
27403
|
-
|
|
27616
|
+
e.source_tool AS source_tool,
|
|
27617
|
+
e.repo AS repo,
|
|
27618
|
+
e.file_path AS file,
|
|
27619
|
+
e.tool_name AS tool_name,
|
|
27404
27620
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27405
27621
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27406
27622
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27407
|
-
FROM
|
|
27408
|
-
JOIN
|
|
27409
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27623
|
+
FROM audit_events e
|
|
27624
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27625
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27410
27626
|
WHERE ${conditions.join(" AND ")}
|
|
27411
|
-
|
|
27412
|
-
|
|
27413
|
-
LIMIT ?`;
|
|
27414
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27415
|
-
for (; ; ) {
|
|
27416
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27417
|
-
...params,
|
|
27418
|
-
after.startedAtMs,
|
|
27419
|
-
after.startedAtMs,
|
|
27420
|
-
after.id,
|
|
27421
|
-
SCAN_BATCH_ROWS
|
|
27422
|
-
]);
|
|
27423
|
-
for (const r of rows) {
|
|
27424
|
-
yield {
|
|
27425
|
-
id: r.id,
|
|
27426
|
-
ruleId: r.rule_id,
|
|
27427
|
-
category: r.category,
|
|
27428
|
-
severity: r.severity,
|
|
27429
|
-
maskedMatch: r.masked_match,
|
|
27430
|
-
actionTaken: r.action_taken,
|
|
27431
|
-
confidence: r.confidence,
|
|
27432
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27433
|
-
sourceTool: r.source_tool,
|
|
27434
|
-
repo: r.repo ?? "",
|
|
27435
|
-
file: r.file ?? "",
|
|
27436
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27437
|
-
eventId: r.event_id,
|
|
27438
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27439
|
-
status: deriveInstanceStatus(r)
|
|
27440
|
-
};
|
|
27441
|
-
}
|
|
27442
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27443
|
-
const lastRow = rows[rows.length - 1];
|
|
27444
|
-
if (lastRow === void 0) return;
|
|
27445
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27446
|
-
}
|
|
27627
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27628
|
+
return { sql, params };
|
|
27447
27629
|
}
|
|
27448
27630
|
groupAggregates(withSearchText, scope) {
|
|
27449
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27450
|
-
group_concat(DISTINCT
|
|
27451
|
-
group_concat(DISTINCT 'via ' ||
|
|
27631
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27632
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27633
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27452
27634
|
const rows = this.db.prepare(
|
|
27453
27635
|
`SELECT rule_id,
|
|
27454
27636
|
sum(tuple_count) AS instance_count,
|
|
@@ -27466,7 +27648,7 @@ var SqliteFindingsRepository = class {
|
|
|
27466
27648
|
coalesce(latest.status, '') AS status_tuple,
|
|
27467
27649
|
count(*) AS tuple_count,
|
|
27468
27650
|
max(e.started_at) AS latest_at,
|
|
27469
|
-
group_concat(DISTINCT
|
|
27651
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27470
27652
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27471
27653
|
${innerSearchColumns}
|
|
27472
27654
|
FROM inspection_findings f
|
|
@@ -27597,6 +27779,8 @@ function isoDay(ms) {
|
|
|
27597
27779
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27598
27780
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27599
27781
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27782
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27783
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27600
27784
|
var SKIPPED = -1;
|
|
27601
27785
|
var ROW_COLUMNS = `id,
|
|
27602
27786
|
parent_id AS parentId,
|
|
@@ -27636,6 +27820,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27636
27820
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27637
27821
|
LIMIT :limit`
|
|
27638
27822
|
);
|
|
27823
|
+
this.captureRowsStmt = db.prepare(
|
|
27824
|
+
`SELECT ${ROW_COLUMNS}
|
|
27825
|
+
FROM audit_events
|
|
27826
|
+
WHERE synced_at IS NULL
|
|
27827
|
+
AND sync_claimed_at IS NULL
|
|
27828
|
+
AND outbox_owed = 1
|
|
27829
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27830
|
+
AND started_at < :before
|
|
27831
|
+
ORDER BY started_at
|
|
27832
|
+
LIMIT :limit`
|
|
27833
|
+
);
|
|
27834
|
+
this.markOwedStmt = db.prepare(
|
|
27835
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27836
|
+
);
|
|
27639
27837
|
this.stampStmt = db.prepare(
|
|
27640
27838
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27641
27839
|
);
|
|
@@ -27667,6 +27865,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27667
27865
|
FROM audit_events
|
|
27668
27866
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27669
27867
|
);
|
|
27868
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27869
|
+
`SELECT COUNT(*) AS skipped
|
|
27870
|
+
FROM audit_events
|
|
27871
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27872
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27873
|
+
);
|
|
27670
27874
|
this.fingerprintStmt = db.prepare(
|
|
27671
27875
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27672
27876
|
FROM history_sync WHERE id = 1`
|
|
@@ -27676,6 +27880,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27676
27880
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27677
27881
|
WHERE id = 1`
|
|
27678
27882
|
);
|
|
27883
|
+
this.disownCapturesStmt = db.prepare(
|
|
27884
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27885
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27886
|
+
);
|
|
27679
27887
|
this.rearmStmt = db.prepare(
|
|
27680
27888
|
`UPDATE audit_events SET synced_at = NULL
|
|
27681
27889
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27748,6 +27956,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27748
27956
|
closeWindowStmt;
|
|
27749
27957
|
releaseBoundaryStmt;
|
|
27750
27958
|
freezeBoundaryStmt;
|
|
27959
|
+
captureRowsStmt;
|
|
27960
|
+
markOwedStmt;
|
|
27961
|
+
captureSkipCountStmt;
|
|
27962
|
+
disownCapturesStmt;
|
|
27751
27963
|
partitionStmt;
|
|
27752
27964
|
claimRowStmt;
|
|
27753
27965
|
releaseRowStmt;
|
|
@@ -27781,6 +27993,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27781
27993
|
pendingRows(sessionId, limit, before) {
|
|
27782
27994
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27783
27995
|
}
|
|
27996
|
+
/**
|
|
27997
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27998
|
+
*
|
|
27999
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
28000
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
28001
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
28002
|
+
* the live path.
|
|
28003
|
+
*/
|
|
28004
|
+
pendingCaptureRows(limit, before) {
|
|
28005
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
28006
|
+
}
|
|
28007
|
+
/**
|
|
28008
|
+
* Record that a capture is OWED to the deployment.
|
|
28009
|
+
*
|
|
28010
|
+
* Written by the attached forward path when a live send did not confirm
|
|
28011
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
28012
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
28013
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
28014
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
28015
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
28016
|
+
* offered to anyone.
|
|
28017
|
+
*
|
|
28018
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
28019
|
+
* out of the drain's read.
|
|
28020
|
+
*/
|
|
28021
|
+
markCaptureOwed(id) {
|
|
28022
|
+
this.markOwedStmt.run({ id });
|
|
28023
|
+
}
|
|
27784
28024
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27785
28025
|
markSynced(ids, atMs) {
|
|
27786
28026
|
this.stampAll(ids, atMs);
|
|
@@ -27864,10 +28104,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27864
28104
|
this.countsStmt,
|
|
27865
28105
|
{ before }
|
|
27866
28106
|
);
|
|
28107
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27867
28108
|
return {
|
|
27868
28109
|
pending: row?.pending ?? 0,
|
|
27869
28110
|
sent: row?.sent ?? 0,
|
|
27870
|
-
skipped: row?.skipped ?? 0
|
|
28111
|
+
skipped: row?.skipped ?? 0,
|
|
28112
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27871
28113
|
};
|
|
27872
28114
|
}
|
|
27873
28115
|
/**
|
|
@@ -27908,7 +28150,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27908
28150
|
withTransaction(
|
|
27909
28151
|
this.db,
|
|
27910
28152
|
() => {
|
|
28153
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27911
28154
|
this.rearmStmt.run();
|
|
28155
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28156
|
+
this.disownCapturesStmt.run();
|
|
28157
|
+
}
|
|
27912
28158
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27913
28159
|
},
|
|
27914
28160
|
"IMMEDIATE"
|
|
@@ -28105,7 +28351,256 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
28105
28351
|
};
|
|
28106
28352
|
|
|
28107
28353
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
28108
|
-
import { createHash as createHash2, randomUUID as
|
|
28354
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28355
|
+
|
|
28356
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28357
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28358
|
+
import { join as join6 } from "path";
|
|
28359
|
+
|
|
28360
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28361
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28362
|
+
import { mkdir } from "fs/promises";
|
|
28363
|
+
import { homedir } from "os";
|
|
28364
|
+
import { join as join4 } from "path";
|
|
28365
|
+
function defaultDataDir() {
|
|
28366
|
+
return join4(homedir(), ".aka");
|
|
28367
|
+
}
|
|
28368
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28369
|
+
return join4(base, "settings");
|
|
28370
|
+
}
|
|
28371
|
+
function dataDir(base = defaultDataDir()) {
|
|
28372
|
+
return join4(base, "data");
|
|
28373
|
+
}
|
|
28374
|
+
function dbPath(base = defaultDataDir()) {
|
|
28375
|
+
return join4(dataDir(base), "aka.db");
|
|
28376
|
+
}
|
|
28377
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
28378
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
28379
|
+
tightenDir(dir);
|
|
28380
|
+
}
|
|
28381
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28382
|
+
ensureDataDirSync(dir);
|
|
28383
|
+
}
|
|
28384
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28385
|
+
const moves = [
|
|
28386
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28387
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28388
|
+
];
|
|
28389
|
+
for (const { name: name2, dest } of moves) {
|
|
28390
|
+
try {
|
|
28391
|
+
ensureDataDirSync(dest);
|
|
28392
|
+
const moved = join4(dest, name2);
|
|
28393
|
+
renameSync3(join4(base, name2), moved);
|
|
28394
|
+
tightenFile(moved);
|
|
28395
|
+
} catch {
|
|
28396
|
+
}
|
|
28397
|
+
}
|
|
28398
|
+
}
|
|
28399
|
+
|
|
28400
|
+
// ../../packages/persistence/src/settings.ts
|
|
28401
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28402
|
+
import { join as join5 } from "path";
|
|
28403
|
+
|
|
28404
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28405
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28406
|
+
import {
|
|
28407
|
+
closeSync,
|
|
28408
|
+
existsSync as existsSync2,
|
|
28409
|
+
openSync,
|
|
28410
|
+
readFileSync as readFileSync2,
|
|
28411
|
+
rmSync as rmSync5,
|
|
28412
|
+
statSync as statSync3,
|
|
28413
|
+
writeFileSync as writeFileSync2
|
|
28414
|
+
} from "fs";
|
|
28415
|
+
import { hostname as hostname3 } from "os";
|
|
28416
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28417
|
+
|
|
28418
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28419
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28420
|
+
import { posix, win32 } from "path";
|
|
28421
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28422
|
+
if (platform2 === "darwin") {
|
|
28423
|
+
return [
|
|
28424
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28425
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28426
|
+
];
|
|
28427
|
+
}
|
|
28428
|
+
if (platform2 === "win32") {
|
|
28429
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28430
|
+
}
|
|
28431
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28432
|
+
}
|
|
28433
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28434
|
+
for (const path of paths) {
|
|
28435
|
+
let text;
|
|
28436
|
+
try {
|
|
28437
|
+
text = readFileSync3(path, "utf8");
|
|
28438
|
+
} catch {
|
|
28439
|
+
continue;
|
|
28440
|
+
}
|
|
28441
|
+
const record2 = parseJsonObject(text);
|
|
28442
|
+
if (!record2) continue;
|
|
28443
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28444
|
+
if (parsed2.success) return parsed2.data;
|
|
28445
|
+
}
|
|
28446
|
+
return null;
|
|
28447
|
+
}
|
|
28448
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28449
|
+
if (!managed) return settings;
|
|
28450
|
+
const { values } = managed;
|
|
28451
|
+
const merged = { ...settings };
|
|
28452
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28453
|
+
if (values.controlPlane !== void 0) {
|
|
28454
|
+
merged.controlPlane = {
|
|
28455
|
+
...values.controlPlane,
|
|
28456
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28457
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28458
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28459
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28460
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28461
|
+
};
|
|
28462
|
+
}
|
|
28463
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28464
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28465
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28466
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28467
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28468
|
+
if (values.vaultConsent !== void 0) {
|
|
28469
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28470
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28471
|
+
// at the current version otherwise.
|
|
28472
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28473
|
+
) : void 0;
|
|
28474
|
+
}
|
|
28475
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28476
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28477
|
+
acknowledgedAt: now().toISOString(),
|
|
28478
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28479
|
+
} : void 0;
|
|
28480
|
+
}
|
|
28481
|
+
return merged;
|
|
28482
|
+
}
|
|
28483
|
+
|
|
28484
|
+
// ../../packages/persistence/src/settings.ts
|
|
28485
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28486
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28487
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28488
|
+
}
|
|
28489
|
+
function readUserSettings(base) {
|
|
28490
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28491
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28492
|
+
try {
|
|
28493
|
+
return WorkspaceSettings.parse(record2);
|
|
28494
|
+
} catch {
|
|
28495
|
+
return defaultWorkspaceSettings();
|
|
28496
|
+
}
|
|
28497
|
+
}
|
|
28498
|
+
function readJson(file2) {
|
|
28499
|
+
let text;
|
|
28500
|
+
try {
|
|
28501
|
+
text = readFileSync4(file2, "utf8");
|
|
28502
|
+
} catch {
|
|
28503
|
+
return null;
|
|
28504
|
+
}
|
|
28505
|
+
return parseJsonObject(text) ?? null;
|
|
28506
|
+
}
|
|
28507
|
+
|
|
28508
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28509
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28510
|
+
switch (refusal) {
|
|
28511
|
+
case "lock":
|
|
28512
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28513
|
+
case "disable":
|
|
28514
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28515
|
+
case "floor":
|
|
28516
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28517
|
+
}
|
|
28518
|
+
}
|
|
28519
|
+
var PolicyFloorError = class extends Error {
|
|
28520
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28521
|
+
pack;
|
|
28522
|
+
/**
|
|
28523
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28524
|
+
* clearing the assignment, or switching the detection off.
|
|
28525
|
+
*/
|
|
28526
|
+
attempted;
|
|
28527
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28528
|
+
floor;
|
|
28529
|
+
refusal;
|
|
28530
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28531
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28532
|
+
this.name = "PolicyFloorError";
|
|
28533
|
+
this.pack = pack;
|
|
28534
|
+
this.attempted = attempted;
|
|
28535
|
+
this.floor = floor;
|
|
28536
|
+
this.refusal = refusal;
|
|
28537
|
+
}
|
|
28538
|
+
};
|
|
28539
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28540
|
+
try {
|
|
28541
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28542
|
+
const parsed2 = JSON.parse(raw);
|
|
28543
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28544
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28545
|
+
} catch {
|
|
28546
|
+
return null;
|
|
28547
|
+
}
|
|
28548
|
+
}
|
|
28549
|
+
function indexEnabled(policies) {
|
|
28550
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28551
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28552
|
+
for (const policy of policies) {
|
|
28553
|
+
if (!policy.enabled) continue;
|
|
28554
|
+
if ("ruleId" in policy.target) {
|
|
28555
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28556
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28557
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28558
|
+
}
|
|
28559
|
+
}
|
|
28560
|
+
return { byRuleId, byCategory };
|
|
28561
|
+
}
|
|
28562
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28563
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28564
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28565
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28566
|
+
return policies.some((policy) => {
|
|
28567
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28568
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28569
|
+
});
|
|
28570
|
+
}
|
|
28571
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28572
|
+
const floors = openControlPlaneFloors(base);
|
|
28573
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28574
|
+
}
|
|
28575
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28576
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28577
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28578
|
+
if (bundle === null) return null;
|
|
28579
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28580
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28581
|
+
}
|
|
28582
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28583
|
+
let action = null;
|
|
28584
|
+
for (const rule of rules) {
|
|
28585
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28586
|
+
if (resolved === void 0) continue;
|
|
28587
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28588
|
+
}
|
|
28589
|
+
if (action === null) return null;
|
|
28590
|
+
return {
|
|
28591
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28592
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28593
|
+
};
|
|
28594
|
+
}
|
|
28595
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28596
|
+
if (floor.locked) return "lock";
|
|
28597
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28598
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28599
|
+
}
|
|
28600
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28601
|
+
if (floor === null || enabled) return null;
|
|
28602
|
+
return "disable";
|
|
28603
|
+
}
|
|
28109
28604
|
|
|
28110
28605
|
// ../../packages/persistence/src/semver.ts
|
|
28111
28606
|
function parse3(version3) {
|
|
@@ -28199,8 +28694,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
28199
28694
|
return ids;
|
|
28200
28695
|
}
|
|
28201
28696
|
var SqliteInstalledPacksRepository = class {
|
|
28202
|
-
|
|
28697
|
+
/**
|
|
28698
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28699
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28700
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28701
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28702
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28703
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28704
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28705
|
+
* real `~/.aka`.
|
|
28706
|
+
*/
|
|
28707
|
+
constructor(db, baseDir) {
|
|
28203
28708
|
this.db = db;
|
|
28709
|
+
this.baseDir = baseDir;
|
|
28204
28710
|
this.insertMissingStmt = db.prepare(
|
|
28205
28711
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
28206
28712
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28222,11 +28728,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28222
28728
|
this.signatureStmt = db.prepare(
|
|
28223
28729
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28224
28730
|
);
|
|
28731
|
+
this.packRulesStmt = db.prepare(
|
|
28732
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28733
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28734
|
+
);
|
|
28225
28735
|
}
|
|
28226
28736
|
db;
|
|
28737
|
+
baseDir;
|
|
28227
28738
|
insertMissingStmt;
|
|
28228
28739
|
upsertAvailableStmt;
|
|
28229
28740
|
signatureStmt;
|
|
28741
|
+
packRulesStmt;
|
|
28230
28742
|
/**
|
|
28231
28743
|
* Record the running binary's detection inventory. Refreshes the
|
|
28232
28744
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28268,7 +28780,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28268
28780
|
let behind = false;
|
|
28269
28781
|
for (const row of rows) {
|
|
28270
28782
|
const params = {
|
|
28271
|
-
id:
|
|
28783
|
+
id: randomUUID4(),
|
|
28272
28784
|
namespace: row.namespace,
|
|
28273
28785
|
packId: row.packId,
|
|
28274
28786
|
version: row.version,
|
|
@@ -28280,7 +28792,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28280
28792
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28281
28793
|
this.upsertAvailableStmt.run({
|
|
28282
28794
|
...params,
|
|
28283
|
-
id:
|
|
28795
|
+
id: randomUUID4(),
|
|
28284
28796
|
recordedBy: meta4?.recordedBy ?? null
|
|
28285
28797
|
});
|
|
28286
28798
|
} else {
|
|
@@ -28526,9 +29038,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28526
29038
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28527
29039
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28528
29040
|
// caller can tell an edit from a no-such-detection.
|
|
29041
|
+
/**
|
|
29042
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
29043
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
29044
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
29045
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
29046
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
29047
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
29048
|
+
*/
|
|
29049
|
+
packFloorRules(namespace, packId) {
|
|
29050
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
29051
|
+
if (!row) return [];
|
|
29052
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
29053
|
+
}
|
|
29054
|
+
/**
|
|
29055
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
29056
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
29057
|
+
* repository constructed without a layout base).
|
|
29058
|
+
*
|
|
29059
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
29060
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
29061
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
29062
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
29063
|
+
*/
|
|
29064
|
+
policyFloor(namespace, packId) {
|
|
29065
|
+
if (this.baseDir === void 0) return null;
|
|
29066
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
29067
|
+
}
|
|
29068
|
+
/**
|
|
29069
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
29070
|
+
* entry only for a pack the control plane actually governs.
|
|
29071
|
+
*
|
|
29072
|
+
* A surface listing every detection asks per pack, and asking through
|
|
29073
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
29074
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
29075
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
29076
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
29077
|
+
* exactly as the single-pack read returns null for them.
|
|
29078
|
+
*/
|
|
29079
|
+
policyFloors(packs2) {
|
|
29080
|
+
const floors = /* @__PURE__ */ new Map();
|
|
29081
|
+
if (this.baseDir === void 0) return floors;
|
|
29082
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
29083
|
+
if (source === null) return floors;
|
|
29084
|
+
for (const pack of packs2) {
|
|
29085
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
29086
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
29087
|
+
}
|
|
29088
|
+
return floors;
|
|
29089
|
+
}
|
|
28529
29090
|
/**
|
|
28530
29091
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28531
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
29092
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
29093
|
+
*
|
|
29094
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
29095
|
+
* write below, and a detection the organization has authored a policy for is
|
|
29096
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
29097
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
29098
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
29099
|
+
* surface that offers the choice.
|
|
28532
29100
|
*/
|
|
28533
29101
|
setPolicy(namespace, packId, policyId) {
|
|
28534
29102
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28536,14 +29104,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28536
29104
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28537
29105
|
);
|
|
28538
29106
|
}
|
|
29107
|
+
const requested = policyId;
|
|
29108
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29109
|
+
if (floor !== null) {
|
|
29110
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
29111
|
+
if (refusal !== null) {
|
|
29112
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
29113
|
+
}
|
|
29114
|
+
}
|
|
28539
29115
|
const res = this.db.prepare(
|
|
28540
29116
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28541
29117
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28542
29118
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28543
29119
|
return Number(res.changes) > 0;
|
|
28544
29120
|
}
|
|
28545
|
-
/**
|
|
29121
|
+
/**
|
|
29122
|
+
* Enable or disable one installed pack.
|
|
29123
|
+
*
|
|
29124
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29125
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29126
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29127
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29128
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29129
|
+
* that lived in a page would leave the CLI free.
|
|
29130
|
+
*/
|
|
28546
29131
|
setEnabled(namespace, packId, enabled) {
|
|
29132
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29133
|
+
if (floor !== null) {
|
|
29134
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29135
|
+
if (refusal !== null) {
|
|
29136
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29137
|
+
}
|
|
29138
|
+
}
|
|
28547
29139
|
const res = this.db.prepare(
|
|
28548
29140
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28549
29141
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28629,7 +29221,7 @@ var SqliteInventoryRepository = class {
|
|
|
28629
29221
|
};
|
|
28630
29222
|
|
|
28631
29223
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28632
|
-
import { randomUUID as
|
|
29224
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28633
29225
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28634
29226
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28635
29227
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -29118,7 +29710,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29118
29710
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
29119
29711
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
29120
29712
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
29121
|
-
).run({ id:
|
|
29713
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
29122
29714
|
}
|
|
29123
29715
|
return true;
|
|
29124
29716
|
}
|
|
@@ -29139,7 +29731,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29139
29731
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
29140
29732
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
29141
29733
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
29142
|
-
).run({ id:
|
|
29734
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
29143
29735
|
}
|
|
29144
29736
|
this.configRowsCache = void 0;
|
|
29145
29737
|
return "ok";
|
|
@@ -29436,7 +30028,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29436
30028
|
};
|
|
29437
30029
|
|
|
29438
30030
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29439
|
-
import { randomUUID as
|
|
30031
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29440
30032
|
var SqlitePoliciesRepository = class {
|
|
29441
30033
|
constructor(db) {
|
|
29442
30034
|
this.db = db;
|
|
@@ -29471,7 +30063,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29471
30063
|
failOpenTransaction(this.db, () => {
|
|
29472
30064
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29473
30065
|
stmt.run({
|
|
29474
|
-
id:
|
|
30066
|
+
id: randomUUID6(),
|
|
29475
30067
|
target: JSON.stringify({ category }),
|
|
29476
30068
|
action,
|
|
29477
30069
|
now: Date.now()
|
|
@@ -29491,7 +30083,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29491
30083
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29492
30084
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29493
30085
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29494
|
-
).run({ id:
|
|
30086
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29495
30087
|
}
|
|
29496
30088
|
// Caps every global per-category policy currently set to block/redact down
|
|
29497
30089
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29559,7 +30151,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29559
30151
|
};
|
|
29560
30152
|
|
|
29561
30153
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29562
|
-
import { randomUUID as
|
|
30154
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29563
30155
|
var SqliteProjectFilesRepository = class {
|
|
29564
30156
|
constructor(db) {
|
|
29565
30157
|
this.db = db;
|
|
@@ -29591,7 +30183,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29591
30183
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29592
30184
|
for (const file2 of scan2.files) {
|
|
29593
30185
|
this.upsertStmt.run({
|
|
29594
|
-
id:
|
|
30186
|
+
id: randomUUID7(),
|
|
29595
30187
|
projectId,
|
|
29596
30188
|
path: file2.path,
|
|
29597
30189
|
name: file2.name,
|
|
@@ -29605,9 +30197,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29605
30197
|
};
|
|
29606
30198
|
|
|
29607
30199
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29608
|
-
import { randomUUID as
|
|
30200
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29609
30201
|
var SqliteResolutionsRepository = class {
|
|
29610
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30202
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29611
30203
|
this.db = db;
|
|
29612
30204
|
this.now = now;
|
|
29613
30205
|
this.newId = newId;
|
|
@@ -29820,7 +30412,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29820
30412
|
};
|
|
29821
30413
|
|
|
29822
30414
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29823
|
-
import { randomUUID as
|
|
30415
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29824
30416
|
function pageLimit(requested, fallback) {
|
|
29825
30417
|
if (requested === void 0) return fallback;
|
|
29826
30418
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29866,12 +30458,14 @@ var SELECT_COLUMNS = `
|
|
|
29866
30458
|
ciphertext,
|
|
29867
30459
|
nonce,
|
|
29868
30460
|
auth_tag AS authTag,
|
|
30461
|
+
user_authorized AS userAuthorized,
|
|
29869
30462
|
occurrence_count AS occurrenceCount,
|
|
29870
30463
|
first_seen AS firstSeen,
|
|
29871
30464
|
last_seen AS lastSeen`;
|
|
29872
30465
|
function toRow(raw) {
|
|
29873
|
-
const { provider, ...rest } = raw;
|
|
29874
|
-
|
|
30466
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30467
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30468
|
+
return provider === null ? row : { ...row, provider };
|
|
29875
30469
|
}
|
|
29876
30470
|
var SqliteSecretVaultRepository = class {
|
|
29877
30471
|
constructor(db) {
|
|
@@ -29881,17 +30475,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29881
30475
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29882
30476
|
format_version, category, rule_id, masked_match, provider,
|
|
29883
30477
|
ciphertext, nonce, auth_tag,
|
|
29884
|
-
occurrence_count, first_seen, last_seen
|
|
30478
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29885
30479
|
) VALUES (
|
|
29886
30480
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29887
30481
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29888
30482
|
:ciphertext, :nonce, :authTag,
|
|
29889
|
-
1, :now, :now
|
|
30483
|
+
:userAuthorized, 1, :now, :now
|
|
29890
30484
|
)`
|
|
29891
30485
|
);
|
|
29892
30486
|
this.bumpStmt = db.prepare(
|
|
29893
30487
|
`UPDATE secret_vault
|
|
29894
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30488
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30489
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29895
30490
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29896
30491
|
);
|
|
29897
30492
|
this.byPointerStmt = db.prepare(
|
|
@@ -29911,6 +30506,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29911
30506
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29912
30507
|
WHERE pointer_id = :pointerId`
|
|
29913
30508
|
);
|
|
30509
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29914
30510
|
this.derefStmt = db.prepare(
|
|
29915
30511
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29916
30512
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29924,6 +30520,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29924
30520
|
listStmt;
|
|
29925
30521
|
replaceCiphertextStmt;
|
|
29926
30522
|
refreshFingerprintStmt;
|
|
30523
|
+
deleteByPointerStmt;
|
|
29927
30524
|
derefStmt;
|
|
29928
30525
|
/**
|
|
29929
30526
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29932,6 +30529,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29932
30529
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29933
30530
|
* wire token. `minted` is true only when this call created the row.
|
|
29934
30531
|
*
|
|
30532
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30533
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30534
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30535
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30536
|
+
*
|
|
29935
30537
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29936
30538
|
* writers cannot both decide they are minting.
|
|
29937
30539
|
*/
|
|
@@ -29958,13 +30560,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29958
30560
|
ciphertext: input2.ciphertext,
|
|
29959
30561
|
nonce: input2.nonce,
|
|
29960
30562
|
authTag: input2.authTag,
|
|
30563
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29961
30564
|
now
|
|
29962
30565
|
})
|
|
29963
30566
|
);
|
|
29964
30567
|
minted = true;
|
|
29965
30568
|
return;
|
|
29966
30569
|
}
|
|
29967
|
-
this.bumpStmt.run({
|
|
30570
|
+
this.bumpStmt.run({
|
|
30571
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30572
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30573
|
+
now
|
|
30574
|
+
});
|
|
29968
30575
|
},
|
|
29969
30576
|
"IMMEDIATE"
|
|
29970
30577
|
);
|
|
@@ -30024,6 +30631,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
30024
30631
|
);
|
|
30025
30632
|
return destroyed;
|
|
30026
30633
|
}
|
|
30634
|
+
/**
|
|
30635
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30636
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30637
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30638
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30639
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30640
|
+
* it.
|
|
30641
|
+
*
|
|
30642
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30643
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30644
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30645
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30646
|
+
* entry still sitting in the vault.
|
|
30647
|
+
*
|
|
30648
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30649
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30650
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30651
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30652
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30653
|
+
*/
|
|
30654
|
+
deleteByPointerIds(pointerIds) {
|
|
30655
|
+
if (pointerIds.length === 0) return [];
|
|
30656
|
+
const deleted = [];
|
|
30657
|
+
withTransaction(
|
|
30658
|
+
this.db,
|
|
30659
|
+
() => {
|
|
30660
|
+
for (const pointerId of pointerIds) {
|
|
30661
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30662
|
+
deleted.push(pointerId);
|
|
30663
|
+
}
|
|
30664
|
+
}
|
|
30665
|
+
},
|
|
30666
|
+
"IMMEDIATE"
|
|
30667
|
+
);
|
|
30668
|
+
return deleted;
|
|
30669
|
+
}
|
|
30027
30670
|
/**
|
|
30028
30671
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
30029
30672
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -30036,7 +30679,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
30036
30679
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
30037
30680
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
30038
30681
|
).run({
|
|
30039
|
-
id:
|
|
30682
|
+
id: randomUUID9(),
|
|
30040
30683
|
pointerId: entry.pointerId,
|
|
30041
30684
|
location: entry.location,
|
|
30042
30685
|
kind: entry.kind,
|
|
@@ -30549,15 +31192,15 @@ var SqliteSecurityRepository = class {
|
|
|
30549
31192
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30550
31193
|
const rows = allRows(
|
|
30551
31194
|
this.db.prepare(
|
|
30552
|
-
`SELECT
|
|
31195
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30553
31196
|
FROM inspection_findings f
|
|
30554
31197
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30555
31198
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30556
31199
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30557
|
-
AND
|
|
30558
|
-
AND
|
|
30559
|
-
GROUP BY repo
|
|
30560
|
-
ORDER BY c DESC, repo
|
|
31200
|
+
AND e.repo IS NOT NULL
|
|
31201
|
+
AND e.repo != ''
|
|
31202
|
+
GROUP BY e.repo
|
|
31203
|
+
ORDER BY c DESC, e.repo
|
|
30561
31204
|
LIMIT :limit`
|
|
30562
31205
|
),
|
|
30563
31206
|
{ from, to: now, limit }
|
|
@@ -30619,7 +31262,7 @@ var SqliteSecurityRepository = class {
|
|
|
30619
31262
|
`SELECT f.finding_key AS finding_key,
|
|
30620
31263
|
d.rule_id AS rule_id,
|
|
30621
31264
|
d.severity AS severity,
|
|
30622
|
-
|
|
31265
|
+
e.file_path AS path,
|
|
30623
31266
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30624
31267
|
latest.resolved_at AS latest_resolved_at
|
|
30625
31268
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30672,7 +31315,7 @@ var SqliteSecurityRepository = class {
|
|
|
30672
31315
|
};
|
|
30673
31316
|
|
|
30674
31317
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30675
|
-
import { randomUUID as
|
|
31318
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30676
31319
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30677
31320
|
var IN_CHUNK = 500;
|
|
30678
31321
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30760,7 +31403,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30760
31403
|
callSiteCount,
|
|
30761
31404
|
transports: distinctTransports(transports),
|
|
30762
31405
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30763
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31406
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30764
31407
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30765
31408
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30766
31409
|
};
|
|
@@ -30787,7 +31430,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30787
31430
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30788
31431
|
transports: distinctTransports(transports),
|
|
30789
31432
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30790
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31433
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30791
31434
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30792
31435
|
note: dest.note,
|
|
30793
31436
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30816,7 +31459,11 @@ var SqliteSharesRepository = class {
|
|
|
30816
31459
|
FROM share_destination d
|
|
30817
31460
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30818
31461
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30819
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31462
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31463
|
+
AND NOT EXISTS (
|
|
31464
|
+
SELECT 1 FROM egress_decision_override o
|
|
31465
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31466
|
+
)`
|
|
30820
31467
|
);
|
|
30821
31468
|
const kindCounts = countBy(
|
|
30822
31469
|
this.db,
|
|
@@ -30928,7 +31575,7 @@ var SqliteSharesRepository = class {
|
|
|
30928
31575
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30929
31576
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30930
31577
|
).run({
|
|
30931
|
-
id:
|
|
31578
|
+
id: randomUUID10(),
|
|
30932
31579
|
destinationId,
|
|
30933
31580
|
host: dest.host,
|
|
30934
31581
|
decision,
|
|
@@ -31077,7 +31724,7 @@ var SqliteSharesRepository = class {
|
|
|
31077
31724
|
let destinationId = destIds.get(hit.host);
|
|
31078
31725
|
if (destinationId === void 0) {
|
|
31079
31726
|
destStmt.run({
|
|
31080
|
-
id:
|
|
31727
|
+
id: randomUUID10(),
|
|
31081
31728
|
kind: hit.kind,
|
|
31082
31729
|
name: hit.name,
|
|
31083
31730
|
host: hit.host,
|
|
@@ -31093,7 +31740,7 @@ var SqliteSharesRepository = class {
|
|
|
31093
31740
|
let endpointId = endpointIds.get(endpointKey);
|
|
31094
31741
|
if (endpointId === void 0) {
|
|
31095
31742
|
endpointStmt.run({
|
|
31096
|
-
id:
|
|
31743
|
+
id: randomUUID10(),
|
|
31097
31744
|
destinationId,
|
|
31098
31745
|
method: hit.method,
|
|
31099
31746
|
transport: hit.transport,
|
|
@@ -31106,7 +31753,7 @@ var SqliteSharesRepository = class {
|
|
|
31106
31753
|
endpointIds.set(endpointKey, endpointId);
|
|
31107
31754
|
}
|
|
31108
31755
|
siteStmt.run({
|
|
31109
|
-
id:
|
|
31756
|
+
id: randomUUID10(),
|
|
31110
31757
|
endpointId,
|
|
31111
31758
|
project: input2.project,
|
|
31112
31759
|
projectKey: input2.projectKey,
|
|
@@ -31471,6 +32118,7 @@ function purgeSampleData(db) {
|
|
|
31471
32118
|
}
|
|
31472
32119
|
|
|
31473
32120
|
// ../../packages/persistence/src/database.ts
|
|
32121
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31474
32122
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31475
32123
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31476
32124
|
);
|
|
@@ -31518,7 +32166,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31518
32166
|
discardStore(file2, backup);
|
|
31519
32167
|
return backup;
|
|
31520
32168
|
}
|
|
31521
|
-
function openAndInitialize(file2) {
|
|
32169
|
+
function openAndInitialize(file2, base) {
|
|
31522
32170
|
let db = openWithPragmas(file2);
|
|
31523
32171
|
try {
|
|
31524
32172
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31531,7 +32179,7 @@ function openAndInitialize(file2) {
|
|
|
31531
32179
|
applyMigrations(db, file2);
|
|
31532
32180
|
tightenPerms(file2);
|
|
31533
32181
|
const policies = new SqlitePoliciesRepository(db);
|
|
31534
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32182
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31535
32183
|
const repositories = {
|
|
31536
32184
|
events: new SqliteEventsRepository(db),
|
|
31537
32185
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31567,7 +32215,7 @@ function openAndInitialize(file2) {
|
|
|
31567
32215
|
}
|
|
31568
32216
|
function openLocalDatabase(dir) {
|
|
31569
32217
|
ensureDataDirSync(dir);
|
|
31570
|
-
const file2 =
|
|
32218
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31571
32219
|
reapStalePartials(file2);
|
|
31572
32220
|
const {
|
|
31573
32221
|
db,
|
|
@@ -31595,7 +32243,13 @@ function openLocalDatabase(dir) {
|
|
|
31595
32243
|
inspectionDefinitions,
|
|
31596
32244
|
inspectionFindings,
|
|
31597
32245
|
configInventory
|
|
31598
|
-
} = openAndInitialize(
|
|
32246
|
+
} = openAndInitialize(
|
|
32247
|
+
file2,
|
|
32248
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32249
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32250
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32251
|
+
dirname2(dir)
|
|
32252
|
+
);
|
|
31599
32253
|
function captureRowId(event) {
|
|
31600
32254
|
return captureId(
|
|
31601
32255
|
event.metadata?.sessionId ?? null,
|
|
@@ -31608,6 +32262,21 @@ function openLocalDatabase(dir) {
|
|
|
31608
32262
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31609
32263
|
});
|
|
31610
32264
|
}
|
|
32265
|
+
function markCaptureOwed(event) {
|
|
32266
|
+
failOpenTransaction(db, () => {
|
|
32267
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32268
|
+
});
|
|
32269
|
+
}
|
|
32270
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32271
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32272
|
+
if (stampable.length === 0) return;
|
|
32273
|
+
failOpenTransaction(db, () => {
|
|
32274
|
+
historySync.markSynced(
|
|
32275
|
+
stampable.map((event) => event.id),
|
|
32276
|
+
atMs
|
|
32277
|
+
);
|
|
32278
|
+
});
|
|
32279
|
+
}
|
|
31611
32280
|
function recordCapture(event, detected) {
|
|
31612
32281
|
failOpenTransaction(db, () => {
|
|
31613
32282
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31694,7 +32363,7 @@ function openLocalDatabase(dir) {
|
|
|
31694
32363
|
const definitionId = definitionIds.get(`${finding2.ruleId}@${finding2.version}`);
|
|
31695
32364
|
if (!definitionId) continue;
|
|
31696
32365
|
inspectionFindings.insertFinding({
|
|
31697
|
-
id:
|
|
32366
|
+
id: randomUUID11(),
|
|
31698
32367
|
auditEventId: record2.scanEvent.id,
|
|
31699
32368
|
inspectionDefinitionId: definitionId,
|
|
31700
32369
|
span: finding2.span,
|
|
@@ -31790,6 +32459,8 @@ function openLocalDatabase(dir) {
|
|
|
31790
32459
|
inspectionFindings,
|
|
31791
32460
|
recordCapture,
|
|
31792
32461
|
markCaptureDelivered,
|
|
32462
|
+
markCaptureOwed,
|
|
32463
|
+
markAuditEventsDelivered,
|
|
31793
32464
|
ensureInventory,
|
|
31794
32465
|
recordConfigScan,
|
|
31795
32466
|
recordProjectFiles,
|
|
@@ -31808,20 +32479,6 @@ function openLocalDatabase(dir) {
|
|
|
31808
32479
|
};
|
|
31809
32480
|
}
|
|
31810
32481
|
|
|
31811
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31812
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31813
|
-
import {
|
|
31814
|
-
closeSync,
|
|
31815
|
-
existsSync as existsSync2,
|
|
31816
|
-
openSync,
|
|
31817
|
-
readFileSync as readFileSync2,
|
|
31818
|
-
rmSync as rmSync5,
|
|
31819
|
-
statSync as statSync3,
|
|
31820
|
-
writeFileSync as writeFileSync2
|
|
31821
|
-
} from "fs";
|
|
31822
|
-
import { hostname as hostname3 } from "os";
|
|
31823
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31824
|
-
|
|
31825
32482
|
// ../../packages/persistence/src/finding-key.ts
|
|
31826
32483
|
import { createHash as createHash3 } from "crypto";
|
|
31827
32484
|
function normalizeFilePath(filePath) {
|
|
@@ -31834,13 +32491,13 @@ function computeFindingKey(input2) {
|
|
|
31834
32491
|
|
|
31835
32492
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31836
32493
|
import { createHmac, randomBytes } from "crypto";
|
|
31837
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31838
|
-
import { join as
|
|
32494
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32495
|
+
import { join as join8 } from "path";
|
|
31839
32496
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31840
32497
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31841
32498
|
var KEY_MATERIAL_BYTES = 32;
|
|
31842
32499
|
function keyFilePath(dataDir2) {
|
|
31843
|
-
return
|
|
32500
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31844
32501
|
}
|
|
31845
32502
|
function parseKeyFile(raw) {
|
|
31846
32503
|
const parsed2 = JSON.parse(raw);
|
|
@@ -31878,7 +32535,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
31878
32535
|
}
|
|
31879
32536
|
};
|
|
31880
32537
|
function storedKeyVersionFloor(dataDir2) {
|
|
31881
|
-
const file2 =
|
|
32538
|
+
const file2 = join8(dataDir2, DB_FILENAME);
|
|
31882
32539
|
if (!existsSync3(file2)) return 0;
|
|
31883
32540
|
let db;
|
|
31884
32541
|
try {
|
|
@@ -31933,7 +32590,7 @@ function occupantMessage(file2, kind) {
|
|
|
31933
32590
|
function readFingerprintKey(dataDir2) {
|
|
31934
32591
|
let raw;
|
|
31935
32592
|
try {
|
|
31936
|
-
raw =
|
|
32593
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31937
32594
|
} catch (err) {
|
|
31938
32595
|
if (err.code === "ENOENT") return null;
|
|
31939
32596
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31957,143 +32614,12 @@ function fingerprintValue(key, raw) {
|
|
|
31957
32614
|
|
|
31958
32615
|
// ../../packages/persistence/src/history-preview.ts
|
|
31959
32616
|
import { existsSync as existsSync4 } from "fs";
|
|
31960
|
-
import { join as
|
|
32617
|
+
import { join as join9 } from "path";
|
|
31961
32618
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31962
32619
|
|
|
31963
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31964
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31965
|
-
import { mkdir } from "fs/promises";
|
|
31966
|
-
import { homedir } from "os";
|
|
31967
|
-
import { join as join7 } from "path";
|
|
31968
|
-
function defaultDataDir() {
|
|
31969
|
-
return join7(homedir(), ".aka");
|
|
31970
|
-
}
|
|
31971
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31972
|
-
return join7(base, "settings");
|
|
31973
|
-
}
|
|
31974
|
-
function dataDir(base = defaultDataDir()) {
|
|
31975
|
-
return join7(base, "data");
|
|
31976
|
-
}
|
|
31977
|
-
function dbPath(base = defaultDataDir()) {
|
|
31978
|
-
return join7(dataDir(base), "aka.db");
|
|
31979
|
-
}
|
|
31980
|
-
async function ensureDataDir(dir = defaultDataDir()) {
|
|
31981
|
-
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
31982
|
-
tightenDir(dir);
|
|
31983
|
-
}
|
|
31984
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31985
|
-
ensureDataDirSync(dir);
|
|
31986
|
-
}
|
|
31987
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31988
|
-
const moves = [
|
|
31989
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31990
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31991
|
-
];
|
|
31992
|
-
for (const { name: name2, dest } of moves) {
|
|
31993
|
-
try {
|
|
31994
|
-
ensureDataDirSync(dest);
|
|
31995
|
-
const moved = join7(dest, name2);
|
|
31996
|
-
renameSync3(join7(base, name2), moved);
|
|
31997
|
-
tightenFile(moved);
|
|
31998
|
-
} catch {
|
|
31999
|
-
}
|
|
32000
|
-
}
|
|
32001
|
-
}
|
|
32002
|
-
|
|
32003
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
32004
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
32005
|
-
import { posix, win32 } from "path";
|
|
32006
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
32007
|
-
if (platform2 === "darwin") {
|
|
32008
|
-
return [
|
|
32009
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
32010
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
32011
|
-
];
|
|
32012
|
-
}
|
|
32013
|
-
if (platform2 === "win32") {
|
|
32014
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
32015
|
-
}
|
|
32016
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
32017
|
-
}
|
|
32018
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
32019
|
-
for (const path of paths) {
|
|
32020
|
-
let text;
|
|
32021
|
-
try {
|
|
32022
|
-
text = readFileSync4(path, "utf8");
|
|
32023
|
-
} catch {
|
|
32024
|
-
continue;
|
|
32025
|
-
}
|
|
32026
|
-
const record2 = parseJsonObject(text);
|
|
32027
|
-
if (!record2) continue;
|
|
32028
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
32029
|
-
if (parsed2.success) return parsed2.data;
|
|
32030
|
-
}
|
|
32031
|
-
return null;
|
|
32032
|
-
}
|
|
32033
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
32034
|
-
if (!managed) return settings;
|
|
32035
|
-
const { values } = managed;
|
|
32036
|
-
const merged = { ...settings };
|
|
32037
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
32038
|
-
if (values.controlPlane !== void 0) {
|
|
32039
|
-
merged.controlPlane = {
|
|
32040
|
-
...values.controlPlane,
|
|
32041
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
32042
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
32043
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
32044
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
32045
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
32046
|
-
};
|
|
32047
|
-
}
|
|
32048
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
32049
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
32050
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
32051
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
32052
|
-
if (values.vaultConsent !== void 0) {
|
|
32053
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
32054
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
32055
|
-
// at the current version otherwise.
|
|
32056
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
32057
|
-
) : void 0;
|
|
32058
|
-
}
|
|
32059
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
32060
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
32061
|
-
acknowledgedAt: now().toISOString(),
|
|
32062
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
32063
|
-
} : void 0;
|
|
32064
|
-
}
|
|
32065
|
-
return merged;
|
|
32066
|
-
}
|
|
32067
|
-
|
|
32068
|
-
// ../../packages/persistence/src/settings.ts
|
|
32069
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
32070
|
-
import { join as join8 } from "path";
|
|
32071
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
32072
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
32073
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
32074
|
-
}
|
|
32075
|
-
function readUserSettings(base) {
|
|
32076
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
32077
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
32078
|
-
try {
|
|
32079
|
-
return WorkspaceSettings.parse(record2);
|
|
32080
|
-
} catch {
|
|
32081
|
-
return defaultWorkspaceSettings();
|
|
32082
|
-
}
|
|
32083
|
-
}
|
|
32084
|
-
function readJson(file2) {
|
|
32085
|
-
let text;
|
|
32086
|
-
try {
|
|
32087
|
-
text = readFileSync5(file2, "utf8");
|
|
32088
|
-
} catch {
|
|
32089
|
-
return null;
|
|
32090
|
-
}
|
|
32091
|
-
return parseJsonObject(text) ?? null;
|
|
32092
|
-
}
|
|
32093
|
-
|
|
32094
32620
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
32095
32621
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
32096
|
-
import { dirname as
|
|
32622
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
32097
32623
|
|
|
32098
32624
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
32099
32625
|
import {
|
|
@@ -32107,19 +32633,19 @@ import {
|
|
|
32107
32633
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32108
32634
|
import { execFileSync } from "child_process";
|
|
32109
32635
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32110
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32111
|
-
import { join as
|
|
32636
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32637
|
+
import { join as join11 } from "path";
|
|
32112
32638
|
|
|
32113
32639
|
// ../../packages/persistence/src/vault/vault.ts
|
|
32114
32640
|
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
32115
32641
|
|
|
32116
32642
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32117
32643
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32118
|
-
import { join as
|
|
32644
|
+
import { join as join12 } from "path";
|
|
32119
32645
|
var MARKER = "warn-era-capped";
|
|
32120
32646
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32121
32647
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32122
|
-
const marker =
|
|
32648
|
+
const marker = join12(dataDir2, MARKER);
|
|
32123
32649
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
32124
32650
|
const capped = db.policies.capCategoryActions();
|
|
32125
32651
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -32163,6 +32689,307 @@ function toEgressIngestRequest(input2) {
|
|
|
32163
32689
|
};
|
|
32164
32690
|
}
|
|
32165
32691
|
|
|
32692
|
+
// ../../packages/remote/src/http.ts
|
|
32693
|
+
import { request as httpRequest } from "http";
|
|
32694
|
+
import { request as httpsRequest } from "https";
|
|
32695
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32696
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32697
|
+
var RemoteRequestError = class extends Error {
|
|
32698
|
+
constructor(status) {
|
|
32699
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32700
|
+
this.status = status;
|
|
32701
|
+
this.name = "RemoteRequestError";
|
|
32702
|
+
}
|
|
32703
|
+
status;
|
|
32704
|
+
};
|
|
32705
|
+
var RemoteRouteAbsent = class extends Error {
|
|
32706
|
+
constructor(route) {
|
|
32707
|
+
super(`control plane does not serve ${route}`);
|
|
32708
|
+
this.route = route;
|
|
32709
|
+
this.name = "RemoteRouteAbsent";
|
|
32710
|
+
}
|
|
32711
|
+
route;
|
|
32712
|
+
};
|
|
32713
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32714
|
+
constructor(route, cause) {
|
|
32715
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32716
|
+
this.cause = cause;
|
|
32717
|
+
this.name = "RemoteRequestInvalid";
|
|
32718
|
+
}
|
|
32719
|
+
cause;
|
|
32720
|
+
};
|
|
32721
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32722
|
+
constructor(route, detail) {
|
|
32723
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32724
|
+
this.name = "RemoteResponseInvalid";
|
|
32725
|
+
}
|
|
32726
|
+
};
|
|
32727
|
+
var RemoteTransportError = class extends Error {
|
|
32728
|
+
/**
|
|
32729
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32730
|
+
* refused.
|
|
32731
|
+
*
|
|
32732
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32733
|
+
* all. It exists because two paths reject after a status has already been
|
|
32734
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32735
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32736
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32737
|
+
* credential.
|
|
32738
|
+
*/
|
|
32739
|
+
constructor(reason, status) {
|
|
32740
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32741
|
+
this.status = status;
|
|
32742
|
+
this.name = "RemoteTransportError";
|
|
32743
|
+
}
|
|
32744
|
+
status;
|
|
32745
|
+
};
|
|
32746
|
+
async function send(options) {
|
|
32747
|
+
const url2 = new URL(options.url);
|
|
32748
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32749
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32750
|
+
const requestOptions = {
|
|
32751
|
+
method: options.method,
|
|
32752
|
+
headers: {
|
|
32753
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32754
|
+
// last they win, and two of the values below are ones no caller may
|
|
32755
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32756
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32757
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32758
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32759
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32760
|
+
// conditional GET — is untouched by this order.
|
|
32761
|
+
...options.headers,
|
|
32762
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32763
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32764
|
+
// it can be logged by an intermediary for no gain.
|
|
32765
|
+
//
|
|
32766
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
32767
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
32768
|
+
// key differently from an absent one, and "the header is not there" is
|
|
32769
|
+
// the property the attach flow needs.
|
|
32770
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
32771
|
+
accept: "application/json",
|
|
32772
|
+
...options.body === void 0 ? {} : {
|
|
32773
|
+
"content-type": "application/json",
|
|
32774
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32775
|
+
// character count is truncated by the receiver.
|
|
32776
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32777
|
+
}
|
|
32778
|
+
}
|
|
32779
|
+
};
|
|
32780
|
+
return new Promise((resolve2, reject) => {
|
|
32781
|
+
let settled = false;
|
|
32782
|
+
const fail = (reason, status) => {
|
|
32783
|
+
if (settled) return;
|
|
32784
|
+
settled = true;
|
|
32785
|
+
reject(new RemoteTransportError(reason, status));
|
|
32786
|
+
};
|
|
32787
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32788
|
+
const chunks = [];
|
|
32789
|
+
let size = 0;
|
|
32790
|
+
res.on("data", (chunk) => {
|
|
32791
|
+
size += chunk.length;
|
|
32792
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32793
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32794
|
+
res.destroy();
|
|
32795
|
+
req.destroy();
|
|
32796
|
+
return;
|
|
32797
|
+
}
|
|
32798
|
+
chunks.push(chunk);
|
|
32799
|
+
});
|
|
32800
|
+
res.on("aborted", () => {
|
|
32801
|
+
fail("the response was aborted", res.statusCode);
|
|
32802
|
+
});
|
|
32803
|
+
res.on("end", () => {
|
|
32804
|
+
if (settled) return;
|
|
32805
|
+
settled = true;
|
|
32806
|
+
resolve2({
|
|
32807
|
+
status: res.statusCode ?? 0,
|
|
32808
|
+
headers: res.headers,
|
|
32809
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32810
|
+
});
|
|
32811
|
+
});
|
|
32812
|
+
});
|
|
32813
|
+
const deadline = setTimeout(() => {
|
|
32814
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32815
|
+
req.destroy();
|
|
32816
|
+
}, timeoutMs);
|
|
32817
|
+
deadline.unref();
|
|
32818
|
+
req.on("upgrade", (_res, socket) => {
|
|
32819
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32820
|
+
socket.destroy();
|
|
32821
|
+
});
|
|
32822
|
+
req.on("close", () => {
|
|
32823
|
+
fail("the connection closed before a response was read");
|
|
32824
|
+
clearTimeout(deadline);
|
|
32825
|
+
});
|
|
32826
|
+
req.on("error", (err) => {
|
|
32827
|
+
fail(err.message);
|
|
32828
|
+
});
|
|
32829
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32830
|
+
req.end();
|
|
32831
|
+
});
|
|
32832
|
+
}
|
|
32833
|
+
|
|
32834
|
+
// ../../packages/remote/src/client.ts
|
|
32835
|
+
var ROUTES = {
|
|
32836
|
+
events: "/v1/events",
|
|
32837
|
+
auditEvents: "/v1/audit-events",
|
|
32838
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
32839
|
+
inventory: "/v1/inventory",
|
|
32840
|
+
storePosture: "/v1/store-posture",
|
|
32841
|
+
policyBundle: "/v1/policy-bundle",
|
|
32842
|
+
whoami: "/v1/plugin/whoami",
|
|
32843
|
+
shares: "/v1/shares",
|
|
32844
|
+
commands: "/v1/plugin/commands"
|
|
32845
|
+
};
|
|
32846
|
+
function ackRoute(id) {
|
|
32847
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
32848
|
+
}
|
|
32849
|
+
function headerValue(response, name2) {
|
|
32850
|
+
const raw = response.headers[name2];
|
|
32851
|
+
if (raw === void 0) return void 0;
|
|
32852
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32853
|
+
}
|
|
32854
|
+
function okBody(response) {
|
|
32855
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32856
|
+
throw new RemoteRequestError(response.status);
|
|
32857
|
+
}
|
|
32858
|
+
return response.body;
|
|
32859
|
+
}
|
|
32860
|
+
function parsed(schema, body, route) {
|
|
32861
|
+
let json2;
|
|
32862
|
+
try {
|
|
32863
|
+
json2 = JSON.parse(body);
|
|
32864
|
+
} catch {
|
|
32865
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
32866
|
+
}
|
|
32867
|
+
const result = schema.safeParse(json2);
|
|
32868
|
+
if (!result.success) {
|
|
32869
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32870
|
+
}
|
|
32871
|
+
return result.data;
|
|
32872
|
+
}
|
|
32873
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32874
|
+
let end = endpoint.length;
|
|
32875
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32876
|
+
return endpoint.slice(0, end);
|
|
32877
|
+
}
|
|
32878
|
+
var SLASH = "/".charCodeAt(0);
|
|
32879
|
+
function createRemoteClient(options) {
|
|
32880
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32881
|
+
const url2 = (route) => `${base}${route}`;
|
|
32882
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32883
|
+
const sendOne = async (event) => {
|
|
32884
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32885
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32886
|
+
const response = await send({
|
|
32887
|
+
...common,
|
|
32888
|
+
method: "POST",
|
|
32889
|
+
url: url2(ROUTES.auditEvents),
|
|
32890
|
+
body: JSON.stringify(validated.data)
|
|
32891
|
+
});
|
|
32892
|
+
okBody(response);
|
|
32893
|
+
};
|
|
32894
|
+
return {
|
|
32895
|
+
async ingestEvents(batch) {
|
|
32896
|
+
const response = await send({
|
|
32897
|
+
...common,
|
|
32898
|
+
method: "POST",
|
|
32899
|
+
url: url2(ROUTES.events),
|
|
32900
|
+
body: JSON.stringify(batch)
|
|
32901
|
+
});
|
|
32902
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32903
|
+
},
|
|
32904
|
+
async ingestInventory(context) {
|
|
32905
|
+
const response = await send({
|
|
32906
|
+
...common,
|
|
32907
|
+
method: "POST",
|
|
32908
|
+
url: url2(ROUTES.inventory),
|
|
32909
|
+
body: JSON.stringify(context)
|
|
32910
|
+
});
|
|
32911
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32912
|
+
},
|
|
32913
|
+
async recordAuditEvent(event) {
|
|
32914
|
+
await sendOne(event);
|
|
32915
|
+
},
|
|
32916
|
+
async recordAuditEvents(events, opts) {
|
|
32917
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
32918
|
+
if (!validated.success) {
|
|
32919
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
32920
|
+
}
|
|
32921
|
+
const response = await send({
|
|
32922
|
+
...common,
|
|
32923
|
+
method: "POST",
|
|
32924
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
32925
|
+
body: JSON.stringify(validated.data)
|
|
32926
|
+
});
|
|
32927
|
+
if (response.status === 404) {
|
|
32928
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
32929
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
32930
|
+
}
|
|
32931
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
32932
|
+
return { accepted: validated.data.events.length };
|
|
32933
|
+
}
|
|
32934
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
32935
|
+
},
|
|
32936
|
+
async reportStorePosture(snapshot) {
|
|
32937
|
+
const response = await send({
|
|
32938
|
+
...common,
|
|
32939
|
+
method: "POST",
|
|
32940
|
+
url: url2(ROUTES.storePosture),
|
|
32941
|
+
body: JSON.stringify(snapshot)
|
|
32942
|
+
});
|
|
32943
|
+
okBody(response);
|
|
32944
|
+
},
|
|
32945
|
+
async getPolicyBundle(etag) {
|
|
32946
|
+
const response = await send({
|
|
32947
|
+
...common,
|
|
32948
|
+
method: "GET",
|
|
32949
|
+
url: url2(ROUTES.policyBundle),
|
|
32950
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32951
|
+
});
|
|
32952
|
+
if (response.status === 304) {
|
|
32953
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32954
|
+
}
|
|
32955
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32956
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32957
|
+
},
|
|
32958
|
+
async whoami() {
|
|
32959
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32960
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32961
|
+
},
|
|
32962
|
+
async recordProjectEgress(request) {
|
|
32963
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
32964
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
32965
|
+
const response = await send({
|
|
32966
|
+
...common,
|
|
32967
|
+
method: "POST",
|
|
32968
|
+
url: url2(ROUTES.shares),
|
|
32969
|
+
body: JSON.stringify(validated.data)
|
|
32970
|
+
});
|
|
32971
|
+
okBody(response);
|
|
32972
|
+
},
|
|
32973
|
+
async pollCommand() {
|
|
32974
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
32975
|
+
if (response.status === 404) return null;
|
|
32976
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
32977
|
+
},
|
|
32978
|
+
async ackCommand(id, body) {
|
|
32979
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
32980
|
+
const route = ackRoute(id);
|
|
32981
|
+
if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
|
|
32982
|
+
const response = await send({
|
|
32983
|
+
...common,
|
|
32984
|
+
method: "POST",
|
|
32985
|
+
url: url2(route),
|
|
32986
|
+
body: JSON.stringify(validated.data)
|
|
32987
|
+
});
|
|
32988
|
+
okBody(response);
|
|
32989
|
+
}
|
|
32990
|
+
};
|
|
32991
|
+
}
|
|
32992
|
+
|
|
32166
32993
|
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
32167
32994
|
function statusOf(err) {
|
|
32168
32995
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
@@ -32181,12 +33008,27 @@ function classifyFailure(err) {
|
|
|
32181
33008
|
}
|
|
32182
33009
|
}
|
|
32183
33010
|
|
|
33011
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
33012
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
33013
|
+
function withTimeout(promise2, ms) {
|
|
33014
|
+
let timer;
|
|
33015
|
+
const timeout = new Promise((_, reject) => {
|
|
33016
|
+
timer = setTimeout(() => {
|
|
33017
|
+
reject(new Error("attached gateway request timed out"));
|
|
33018
|
+
}, ms);
|
|
33019
|
+
});
|
|
33020
|
+
promise2.catch(() => void 0);
|
|
33021
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
33022
|
+
clearTimeout(timer);
|
|
33023
|
+
});
|
|
33024
|
+
}
|
|
33025
|
+
|
|
32184
33026
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
32185
|
-
import { readFileSync as
|
|
32186
|
-
import { join as
|
|
33027
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
33028
|
+
import { join as join13 } from "path";
|
|
32187
33029
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
32188
33030
|
function forwardDropsPath(dataDir2) {
|
|
32189
|
-
return
|
|
33031
|
+
return join13(dataDir2, FORWARD_DROPS_FILENAME);
|
|
32190
33032
|
}
|
|
32191
33033
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
32192
33034
|
if (count <= 0) return;
|
|
@@ -32204,7 +33046,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
32204
33046
|
}
|
|
32205
33047
|
function readForwardDrops(dataDir2) {
|
|
32206
33048
|
try {
|
|
32207
|
-
const parsed2 = JSON.parse(
|
|
33049
|
+
const parsed2 = JSON.parse(readFileSync8(forwardDropsPath(dataDir2), "utf8"));
|
|
32208
33050
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32209
33051
|
const record2 = parsed2;
|
|
32210
33052
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -32222,13 +33064,13 @@ function readForwardDrops(dataDir2) {
|
|
|
32222
33064
|
|
|
32223
33065
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
32224
33066
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
32225
|
-
import { readFileSync as
|
|
33067
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
32226
33068
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
32227
|
-
import { join as
|
|
33069
|
+
import { join as join22 } from "path";
|
|
32228
33070
|
|
|
32229
33071
|
// ../../packages/plugin-sdk/src/config.ts
|
|
32230
33072
|
import { existsSync as existsSync7 } from "fs";
|
|
32231
|
-
import { join as
|
|
33073
|
+
import { join as join14 } from "path";
|
|
32232
33074
|
|
|
32233
33075
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32234
33076
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32282,7 +33124,7 @@ function resolveProvider() {
|
|
|
32282
33124
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32283
33125
|
try {
|
|
32284
33126
|
ensureLayoutDirSync(base);
|
|
32285
|
-
const settingsFile =
|
|
33127
|
+
const settingsFile = join14(settingsDir(base), "settings.json");
|
|
32286
33128
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32287
33129
|
} catch {
|
|
32288
33130
|
}
|
|
@@ -32306,9 +33148,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32306
33148
|
}
|
|
32307
33149
|
|
|
32308
33150
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32309
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
33151
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32310
33152
|
import { homedir as homedir2 } from "os";
|
|
32311
|
-
import { basename as basename4, join as
|
|
33153
|
+
import { basename as basename4, join as join16 } from "path";
|
|
32312
33154
|
|
|
32313
33155
|
// ../../packages/detections/src/egress/registry.ts
|
|
32314
33156
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -35524,8 +36366,8 @@ function maskText(text) {
|
|
|
35524
36366
|
}
|
|
35525
36367
|
|
|
35526
36368
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
35527
|
-
import { existsSync as existsSync8, readFileSync as
|
|
35528
|
-
import { basename as basename3, dirname as
|
|
36369
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
36370
|
+
import { basename as basename3, dirname as dirname4, isAbsolute, join as join15, sep as sep2 } from "path";
|
|
35529
36371
|
function resolveRepoIdentity(cwd) {
|
|
35530
36372
|
try {
|
|
35531
36373
|
const root = findGitRoot(cwd);
|
|
@@ -35575,7 +36417,7 @@ function resolveGitBranch(cwd) {
|
|
|
35575
36417
|
try {
|
|
35576
36418
|
const root = findGitRoot(cwd);
|
|
35577
36419
|
if (!root) return void 0;
|
|
35578
|
-
const dotGit =
|
|
36420
|
+
const dotGit = join15(root, ".git");
|
|
35579
36421
|
let gitdir;
|
|
35580
36422
|
try {
|
|
35581
36423
|
gitdir = statSync6(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
|
|
@@ -35583,7 +36425,7 @@ function resolveGitBranch(cwd) {
|
|
|
35583
36425
|
return void 0;
|
|
35584
36426
|
}
|
|
35585
36427
|
if (gitdir === void 0) return void 0;
|
|
35586
|
-
const head = safeRead(
|
|
36428
|
+
const head = safeRead(join15(gitdir, "HEAD"));
|
|
35587
36429
|
if (!head) return void 0;
|
|
35588
36430
|
return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
|
|
35589
36431
|
} catch {
|
|
@@ -35593,41 +36435,41 @@ function resolveGitBranch(cwd) {
|
|
|
35593
36435
|
function resolveWorktreeGitdir(root, dotGitFile) {
|
|
35594
36436
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
|
|
35595
36437
|
if (!target) return void 0;
|
|
35596
|
-
return isAbsolute(target) ? target :
|
|
36438
|
+
return isAbsolute(target) ? target : join15(root, target);
|
|
35597
36439
|
}
|
|
35598
36440
|
function findGitRoot(start) {
|
|
35599
36441
|
let dir = start;
|
|
35600
36442
|
for (; ; ) {
|
|
35601
|
-
if (existsSync8(
|
|
35602
|
-
const parent =
|
|
36443
|
+
if (existsSync8(join15(dir, ".git"))) return dir;
|
|
36444
|
+
const parent = dirname4(dir);
|
|
35603
36445
|
if (parent === dir) return void 0;
|
|
35604
36446
|
dir = parent;
|
|
35605
36447
|
}
|
|
35606
36448
|
}
|
|
35607
36449
|
function resolveGitContext(root) {
|
|
35608
|
-
const dotGit =
|
|
36450
|
+
const dotGit = join15(root, ".git");
|
|
35609
36451
|
try {
|
|
35610
36452
|
if (statSync6(dotGit).isDirectory()) {
|
|
35611
|
-
return { configPath:
|
|
36453
|
+
return { configPath: join15(dotGit, "config"), headRoot: root };
|
|
35612
36454
|
}
|
|
35613
36455
|
} catch {
|
|
35614
36456
|
return void 0;
|
|
35615
36457
|
}
|
|
35616
36458
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
35617
36459
|
if (!target) return void 0;
|
|
35618
|
-
const gitdir = isAbsolute(target) ? target :
|
|
35619
|
-
if (existsSync8(
|
|
35620
|
-
return { configPath:
|
|
36460
|
+
const gitdir = isAbsolute(target) ? target : join15(root, target);
|
|
36461
|
+
if (existsSync8(join15(gitdir, "config"))) {
|
|
36462
|
+
return { configPath: join15(gitdir, "config"), headRoot: root };
|
|
35621
36463
|
}
|
|
35622
|
-
const commonRaw = safeRead(
|
|
36464
|
+
const commonRaw = safeRead(join15(gitdir, "commondir"))?.trim();
|
|
35623
36465
|
if (!commonRaw) return void 0;
|
|
35624
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
35625
|
-
const headRoot = basename3(commonGitDir) === ".git" ?
|
|
35626
|
-
return { configPath:
|
|
36466
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join15(gitdir, commonRaw);
|
|
36467
|
+
const headRoot = basename3(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
|
|
36468
|
+
return { configPath: join15(commonGitDir, "config"), headRoot };
|
|
35627
36469
|
}
|
|
35628
36470
|
function safeRead(path) {
|
|
35629
36471
|
try {
|
|
35630
|
-
return
|
|
36472
|
+
return readFileSync9(path, "utf8");
|
|
35631
36473
|
} catch {
|
|
35632
36474
|
return void 0;
|
|
35633
36475
|
}
|
|
@@ -35689,31 +36531,31 @@ function resolveConfigInventory(input2) {
|
|
|
35689
36531
|
};
|
|
35690
36532
|
try {
|
|
35691
36533
|
const home = input2.homeDir ?? homedir2();
|
|
35692
|
-
const claudeDir =
|
|
36534
|
+
const claudeDir = join16(home, ".claude");
|
|
35693
36535
|
const repo = resolveRepoIdentity(input2.cwd);
|
|
35694
36536
|
const repoIdentity = repo?.url ?? input2.cwd;
|
|
35695
36537
|
const projectSource = `project:${repoIdentity}`;
|
|
35696
|
-
collectSettingsHooks(scan2,
|
|
35697
|
-
collectSettingsHooks(scan2,
|
|
35698
|
-
collectSettingsHooks(scan2,
|
|
36538
|
+
collectSettingsHooks(scan2, join16(claudeDir, "settings.json"), "user");
|
|
36539
|
+
collectSettingsHooks(scan2, join16(input2.cwd, ".claude", "settings.json"), "project");
|
|
36540
|
+
collectSettingsHooks(scan2, join16(input2.cwd, ".claude", "settings.local.json"), "local");
|
|
35699
36541
|
const projectOrigin = { scope: "project", project: repoIdentity };
|
|
35700
|
-
collectMcpFile(scan2,
|
|
35701
|
-
collectUserClaudeJson(scan2,
|
|
35702
|
-
collectMcpFile(scan2,
|
|
35703
|
-
collectMcpFile(scan2,
|
|
35704
|
-
collectMcpFile(scan2,
|
|
36542
|
+
collectMcpFile(scan2, join16(input2.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
|
|
36543
|
+
collectUserClaudeJson(scan2, join16(home, ".claude.json"), input2.cwd, repoIdentity);
|
|
36544
|
+
collectMcpFile(scan2, join16(claudeDir, "settings.json"), { scope: "user" });
|
|
36545
|
+
collectMcpFile(scan2, join16(input2.cwd, ".claude", "settings.json"), projectOrigin);
|
|
36546
|
+
collectMcpFile(scan2, join16(input2.cwd, ".claude", "settings.local.json"), {
|
|
35705
36547
|
scope: "local",
|
|
35706
36548
|
project: repoIdentity
|
|
35707
36549
|
});
|
|
35708
36550
|
collectConfigFiles(scan2, claudeDir, input2.cwd);
|
|
35709
|
-
collectSkillsDir(scan2,
|
|
35710
|
-
collectSkillsDir(scan2,
|
|
36551
|
+
collectSkillsDir(scan2, join16(claudeDir, "skills"), { source: "local", scope: "user" });
|
|
36552
|
+
collectSkillsDir(scan2, join16(input2.cwd, ".claude", "skills"), {
|
|
35711
36553
|
source: projectSource,
|
|
35712
36554
|
scope: "project"
|
|
35713
36555
|
});
|
|
35714
36556
|
collectInstalledPlugins(scan2, claudeDir);
|
|
35715
36557
|
collectMarketplaceSkills(scan2, claudeDir);
|
|
35716
|
-
collectSkillsDir(scan2,
|
|
36558
|
+
collectSkillsDir(scan2, join16(input2.cwd, "skills"), { source: projectSource, scope: "project" });
|
|
35717
36559
|
scan2.skills = dedupeSkills(scan2.skills);
|
|
35718
36560
|
scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
|
|
35719
36561
|
} catch (err) {
|
|
@@ -35842,7 +36684,7 @@ function projectEntryFor(projects, cwd) {
|
|
|
35842
36684
|
return void 0;
|
|
35843
36685
|
}
|
|
35844
36686
|
function collectPluginManifestMcp(scan2, installPath, origin) {
|
|
35845
|
-
const manifestPath =
|
|
36687
|
+
const manifestPath = join16(installPath, ".claude-plugin", "plugin.json");
|
|
35846
36688
|
const raw = readOptional(manifestPath);
|
|
35847
36689
|
if (raw === void 0) return;
|
|
35848
36690
|
try {
|
|
@@ -35850,7 +36692,7 @@ function collectPluginManifestMcp(scan2, installPath, origin) {
|
|
|
35850
36692
|
if (typeof parsed2 !== "object" || parsed2 === null) return;
|
|
35851
36693
|
const declared = parsed2.mcpServers;
|
|
35852
36694
|
if (typeof declared === "string" && declared.length > 0) {
|
|
35853
|
-
collectMcpFile(scan2,
|
|
36695
|
+
collectMcpFile(scan2, join16(installPath, declared), origin, { recordErrors: true });
|
|
35854
36696
|
} else {
|
|
35855
36697
|
collectMcpObject(scan2, declared, manifestPath, origin);
|
|
35856
36698
|
}
|
|
@@ -35867,14 +36709,14 @@ var SETTINGS_KEY_LABELS = [
|
|
|
35867
36709
|
["statusLine", "status line"]
|
|
35868
36710
|
];
|
|
35869
36711
|
function collectConfigFiles(scan2, claudeDir, cwd) {
|
|
35870
|
-
settingsConfigFile(scan2,
|
|
35871
|
-
settingsConfigFile(scan2,
|
|
35872
|
-
settingsConfigFile(scan2,
|
|
35873
|
-
memoryConfigFile(scan2,
|
|
35874
|
-
memoryConfigFile(scan2,
|
|
35875
|
-
mcpJsonConfigFile(scan2,
|
|
35876
|
-
dirConfigFile(scan2,
|
|
35877
|
-
dirConfigFile(scan2,
|
|
36712
|
+
settingsConfigFile(scan2, join16(claudeDir, "settings.json"), "user", "User settings");
|
|
36713
|
+
settingsConfigFile(scan2, join16(cwd, ".claude", "settings.json"), "project", "Project settings");
|
|
36714
|
+
settingsConfigFile(scan2, join16(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
|
|
36715
|
+
memoryConfigFile(scan2, join16(claudeDir, "CLAUDE.md"), "user", "User memory");
|
|
36716
|
+
memoryConfigFile(scan2, join16(cwd, "CLAUDE.md"), "project", "Project memory");
|
|
36717
|
+
mcpJsonConfigFile(scan2, join16(cwd, ".mcp.json"));
|
|
36718
|
+
dirConfigFile(scan2, join16(cwd, ".claude", "commands"), "Slash commands", "command");
|
|
36719
|
+
dirConfigFile(scan2, join16(cwd, ".claude", "agents"), "Subagents", "subagent");
|
|
35878
36720
|
}
|
|
35879
36721
|
function configFileEntry(path, scope, kind) {
|
|
35880
36722
|
try {
|
|
@@ -35949,7 +36791,7 @@ function countMarkdownFiles(dir, depth) {
|
|
|
35949
36791
|
let count = 0;
|
|
35950
36792
|
for (const dirent of readdirSync2(dir, { withFileTypes: true })) {
|
|
35951
36793
|
if (dirent.name.startsWith(".")) continue;
|
|
35952
|
-
if (dirent.isDirectory()) count += countMarkdownFiles(
|
|
36794
|
+
if (dirent.isDirectory()) count += countMarkdownFiles(join16(dir, dirent.name), depth + 1);
|
|
35953
36795
|
else if (dirent.name.endsWith(".md")) count += 1;
|
|
35954
36796
|
}
|
|
35955
36797
|
return count;
|
|
@@ -35962,7 +36804,7 @@ function collectSkillsDir(scan2, dir, origin) {
|
|
|
35962
36804
|
return;
|
|
35963
36805
|
}
|
|
35964
36806
|
for (const name2 of names) {
|
|
35965
|
-
const skillFile =
|
|
36807
|
+
const skillFile = join16(dir, name2, "SKILL.md");
|
|
35966
36808
|
try {
|
|
35967
36809
|
const raw = readOptional(skillFile);
|
|
35968
36810
|
if (raw === void 0) continue;
|
|
@@ -35971,7 +36813,7 @@ function collectSkillsDir(scan2, dir, origin) {
|
|
|
35971
36813
|
name: front.name ?? name2,
|
|
35972
36814
|
source: origin.source,
|
|
35973
36815
|
scope: origin.scope,
|
|
35974
|
-
location:
|
|
36816
|
+
location: join16(dir, name2),
|
|
35975
36817
|
updatedAt: statSync7(skillFile).mtime.toISOString()
|
|
35976
36818
|
};
|
|
35977
36819
|
const version3 = front.version ?? origin.defaultVersion;
|
|
@@ -36002,7 +36844,7 @@ function parseFrontmatter(raw) {
|
|
|
36002
36844
|
return out;
|
|
36003
36845
|
}
|
|
36004
36846
|
function collectInstalledPlugins(scan2, claudeDir) {
|
|
36005
|
-
const manifestPath =
|
|
36847
|
+
const manifestPath = join16(claudeDir, "plugins", "installed_plugins.json");
|
|
36006
36848
|
const raw = readOptional(manifestPath);
|
|
36007
36849
|
if (raw === void 0) return;
|
|
36008
36850
|
let plugins;
|
|
@@ -36027,7 +36869,7 @@ function collectInstalledPlugins(scan2, claudeDir) {
|
|
|
36027
36869
|
if (typeof installPath !== "string" || seen.has(installPath)) continue;
|
|
36028
36870
|
seen.add(installPath);
|
|
36029
36871
|
const version3 = install.version;
|
|
36030
|
-
const hooksPath =
|
|
36872
|
+
const hooksPath = join16(installPath, "hooks", "hooks.json");
|
|
36031
36873
|
const hooksRaw = readOptional(hooksPath);
|
|
36032
36874
|
if (hooksRaw !== void 0) {
|
|
36033
36875
|
try {
|
|
@@ -36047,22 +36889,22 @@ function collectInstalledPlugins(scan2, claudeDir) {
|
|
|
36047
36889
|
}
|
|
36048
36890
|
const origin = { source: marketplace, scope: "plugin", pluginName };
|
|
36049
36891
|
if (typeof version3 === "string") origin.defaultVersion = version3;
|
|
36050
|
-
collectSkillsDir(scan2,
|
|
36892
|
+
collectSkillsDir(scan2, join16(installPath, "skills"), origin);
|
|
36051
36893
|
const mcpOrigin = { scope: "plugin", pluginName, marketplace };
|
|
36052
|
-
collectMcpFile(scan2,
|
|
36894
|
+
collectMcpFile(scan2, join16(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
|
|
36053
36895
|
collectPluginManifestMcp(scan2, installPath, mcpOrigin);
|
|
36054
36896
|
}
|
|
36055
36897
|
}
|
|
36056
36898
|
}
|
|
36057
36899
|
function collectMarketplaceSkills(scan2, claudeDir) {
|
|
36058
|
-
for (const mp of readMarketplaces(
|
|
36900
|
+
for (const mp of readMarketplaces(join16(claudeDir, "plugins", "known_marketplaces.json"))) {
|
|
36059
36901
|
if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
|
|
36060
|
-
collectSkillsDir(scan2,
|
|
36902
|
+
collectSkillsDir(scan2, join16(mp.installLocation, "skills"), {
|
|
36061
36903
|
source: mp.name,
|
|
36062
36904
|
scope: "plugin"
|
|
36063
36905
|
});
|
|
36064
|
-
collectPluginSkillDirs(scan2,
|
|
36065
|
-
collectPluginSkillDirs(scan2,
|
|
36906
|
+
collectPluginSkillDirs(scan2, join16(mp.installLocation, "plugins"), mp.name);
|
|
36907
|
+
collectPluginSkillDirs(scan2, join16(mp.installLocation, "external_plugins"), mp.name);
|
|
36066
36908
|
}
|
|
36067
36909
|
}
|
|
36068
36910
|
function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
|
|
@@ -36073,7 +36915,7 @@ function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
|
|
|
36073
36915
|
return;
|
|
36074
36916
|
}
|
|
36075
36917
|
for (const plugin of plugins) {
|
|
36076
|
-
collectSkillsDir(scan2,
|
|
36918
|
+
collectSkillsDir(scan2, join16(pluginsDir, plugin, "skills"), {
|
|
36077
36919
|
source: marketplace,
|
|
36078
36920
|
scope: "plugin",
|
|
36079
36921
|
pluginName: plugin
|
|
@@ -36127,7 +36969,7 @@ function dedupeMcpServers(servers) {
|
|
|
36127
36969
|
}
|
|
36128
36970
|
function readOptional(path) {
|
|
36129
36971
|
try {
|
|
36130
|
-
return
|
|
36972
|
+
return readFileSync10(path, "utf8");
|
|
36131
36973
|
} catch {
|
|
36132
36974
|
return void 0;
|
|
36133
36975
|
}
|
|
@@ -36659,11 +37501,11 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
36659
37501
|
|
|
36660
37502
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
36661
37503
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
36662
|
-
import { readFileSync as
|
|
36663
|
-
import { join as
|
|
37504
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
37505
|
+
import { join as join17 } from "path";
|
|
36664
37506
|
function readIgnoreLayer(dir, filename, anchorLen) {
|
|
36665
37507
|
try {
|
|
36666
|
-
return { matcher: (0, import_ignore.default)().add(
|
|
37508
|
+
return { matcher: (0, import_ignore.default)().add(readFileSync11(join17(dir, filename), "utf8")), anchorLen };
|
|
36667
37509
|
} catch {
|
|
36668
37510
|
return void 0;
|
|
36669
37511
|
}
|
|
@@ -36725,25 +37567,25 @@ import {
|
|
|
36725
37567
|
fstatSync,
|
|
36726
37568
|
mkdirSync as mkdirSync2,
|
|
36727
37569
|
openSync as openSync2,
|
|
36728
|
-
readFileSync as
|
|
37570
|
+
readFileSync as readFileSync12,
|
|
36729
37571
|
readSync,
|
|
36730
37572
|
writeFileSync as writeFileSync5
|
|
36731
37573
|
} from "fs";
|
|
36732
|
-
import { join as
|
|
37574
|
+
import { join as join18 } from "path";
|
|
36733
37575
|
var TAIL_BYTES = 256 * 1024;
|
|
36734
37576
|
|
|
36735
37577
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
36736
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
36737
|
-
import { join as
|
|
37578
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
37579
|
+
import { join as join19 } from "path";
|
|
36738
37580
|
var SESSION_START_MARKER = "session-start-last";
|
|
36739
37581
|
function claimSessionStart(dataDir2, sessionId) {
|
|
36740
37582
|
return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
|
|
36741
37583
|
}
|
|
36742
37584
|
function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
36743
37585
|
if (!sessionId) return true;
|
|
36744
|
-
const path =
|
|
37586
|
+
const path = join19(dataDir2, marker);
|
|
36745
37587
|
try {
|
|
36746
|
-
if (
|
|
37588
|
+
if (readFileSync13(path, "utf8") === sessionId) return false;
|
|
36747
37589
|
} catch {
|
|
36748
37590
|
}
|
|
36749
37591
|
try {
|
|
@@ -36756,11 +37598,46 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
|
36756
37598
|
|
|
36757
37599
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
36758
37600
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
36759
|
-
import { basename as basename5, dirname as
|
|
37601
|
+
import { basename as basename5, dirname as dirname5, sep as sep3 } from "path";
|
|
37602
|
+
|
|
37603
|
+
// ../../packages/plugin-sdk/src/policy-resolver.ts
|
|
37604
|
+
function createPolicyResolver(bundle) {
|
|
37605
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
37606
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
37607
|
+
let reversible = /* @__PURE__ */ new Set();
|
|
37608
|
+
try {
|
|
37609
|
+
for (const policy of bundle.policies) {
|
|
37610
|
+
if (!policy.enabled) continue;
|
|
37611
|
+
if ("ruleId" in policy.target) {
|
|
37612
|
+
if (!byRule.has(policy.target.ruleId)) byRule.set(policy.target.ruleId, policy.action);
|
|
37613
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
37614
|
+
byCategory.set(policy.target.category, policy.action);
|
|
37615
|
+
}
|
|
37616
|
+
}
|
|
37617
|
+
reversible = new Set(bundle.reversibleRuleIds ?? []);
|
|
37618
|
+
} catch {
|
|
37619
|
+
byRule.clear();
|
|
37620
|
+
byCategory.clear();
|
|
37621
|
+
reversible = /* @__PURE__ */ new Set();
|
|
37622
|
+
}
|
|
37623
|
+
return {
|
|
37624
|
+
actionFor(ruleId, category) {
|
|
37625
|
+
const byRuleAction = byRule.get(ruleId);
|
|
37626
|
+
if (byRuleAction !== void 0) return byRuleAction;
|
|
37627
|
+
const byCategoryAction = byCategory.get(category);
|
|
37628
|
+
if (byCategoryAction !== void 0) return byCategoryAction;
|
|
37629
|
+
const fallback = DEFAULT_ACTIONS[category];
|
|
37630
|
+
return fallback ?? "log";
|
|
37631
|
+
},
|
|
37632
|
+
isReversible(ruleId) {
|
|
37633
|
+
return reversible.has(ruleId);
|
|
37634
|
+
}
|
|
37635
|
+
};
|
|
37636
|
+
}
|
|
36760
37637
|
|
|
36761
37638
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
36762
37639
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
36763
|
-
import { basename as basename6, join as
|
|
37640
|
+
import { basename as basename6, join as join20 } from "path";
|
|
36764
37641
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
36765
37642
|
".git",
|
|
36766
37643
|
"node_modules",
|
|
@@ -36857,8 +37734,8 @@ function resolveProjectFiles(cwd, opts = {}) {
|
|
|
36857
37734
|
}
|
|
36858
37735
|
if (entry.isDirectory()) {
|
|
36859
37736
|
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
|
|
36860
|
-
const fullPath =
|
|
36861
|
-
if (existsSync10(
|
|
37737
|
+
const fullPath = join20(dir, entry.name);
|
|
37738
|
+
if (existsSync10(join20(fullPath, ".git"))) continue;
|
|
36862
37739
|
if (depth >= bounds.maxDepth) {
|
|
36863
37740
|
walk.omitted = true;
|
|
36864
37741
|
continue;
|
|
@@ -36926,7 +37803,6 @@ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
|
36926
37803
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
36927
37804
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
36928
37805
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
36929
|
-
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
36930
37806
|
function startTiming() {
|
|
36931
37807
|
try {
|
|
36932
37808
|
return performance.now();
|
|
@@ -36963,28 +37839,22 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
36963
37839
|
bundlesPacked = true;
|
|
36964
37840
|
}
|
|
36965
37841
|
const policyMode = settings.policy;
|
|
37842
|
+
const redactFallback = settings.redactFallback;
|
|
36966
37843
|
const dataDir2 = opts?.dataDir;
|
|
36967
|
-
let policies = [];
|
|
36968
37844
|
let rules = [];
|
|
36969
37845
|
let scanner;
|
|
36970
37846
|
let bundleExceptions = [];
|
|
36971
37847
|
let initialized = false;
|
|
36972
|
-
|
|
36973
|
-
|
|
36974
|
-
|
|
37848
|
+
let resolver = createPolicyResolver({
|
|
37849
|
+
version: "",
|
|
37850
|
+
policies: [],
|
|
37851
|
+
customKeywords: [],
|
|
37852
|
+
fetchedAt: ""
|
|
37853
|
+
});
|
|
36975
37854
|
async function ensureInitialized() {
|
|
36976
37855
|
if (initialized) return;
|
|
36977
37856
|
const bundle = await gateway.getPolicyBundle();
|
|
36978
|
-
|
|
36979
|
-
for (const p of policies) {
|
|
36980
|
-
if (!p.enabled) continue;
|
|
36981
|
-
if ("ruleId" in p.target) {
|
|
36982
|
-
if (!ruleActionIndex.has(p.target.ruleId)) ruleActionIndex.set(p.target.ruleId, p.action);
|
|
36983
|
-
} else if (!categoryActionIndex.has(p.target.category)) {
|
|
36984
|
-
categoryActionIndex.set(p.target.category, p.action);
|
|
36985
|
-
}
|
|
36986
|
-
}
|
|
36987
|
-
reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
|
|
37857
|
+
resolver = createPolicyResolver(bundle);
|
|
36988
37858
|
const bundledProbeKeys = new Set(
|
|
36989
37859
|
getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
|
|
36990
37860
|
);
|
|
@@ -37037,33 +37907,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37037
37907
|
return cachedKey;
|
|
37038
37908
|
}
|
|
37039
37909
|
function resolveAction(ruleId, category) {
|
|
37040
|
-
|
|
37041
|
-
|
|
37042
|
-
|
|
37043
|
-
if (byCategory !== void 0) return byCategory;
|
|
37044
|
-
const fallback = DEFAULT_ACTIONS[category];
|
|
37045
|
-
return fallback ?? "log";
|
|
37046
|
-
}
|
|
37047
|
-
function actionForFinding(finding2, excepted) {
|
|
37910
|
+
return resolver.actionFor(ruleId, category);
|
|
37911
|
+
}
|
|
37912
|
+
function actionForFinding(finding2, excepted, rewritable = true) {
|
|
37048
37913
|
if (excepted?.has(finding2)) return "allow";
|
|
37049
37914
|
const action = resolveAction(finding2.ruleId, finding2.category);
|
|
37915
|
+
if (!rewritable && action === "redact") return builtinPolicyToAction(redactFallback);
|
|
37050
37916
|
if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
|
|
37051
37917
|
return "warn";
|
|
37052
37918
|
}
|
|
37053
37919
|
return action;
|
|
37054
37920
|
}
|
|
37055
|
-
function decide(findings, text, excepted) {
|
|
37921
|
+
function decide(findings, text, excepted, rewritable = true) {
|
|
37056
37922
|
if (findings.length === 0) return { action: "log", text, findings: [] };
|
|
37057
|
-
const actionFor = (finding2) => actionForFinding(finding2, excepted);
|
|
37923
|
+
const actionFor = (finding2) => actionForFinding(finding2, excepted, rewritable);
|
|
37058
37924
|
let worst = "log";
|
|
37059
37925
|
for (const finding2 of findings) {
|
|
37060
|
-
|
|
37061
|
-
if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
|
|
37926
|
+
worst = strongerAction(worst, actionFor(finding2));
|
|
37062
37927
|
}
|
|
37063
37928
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
37064
37929
|
if (worst === "redact") {
|
|
37065
37930
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
37066
|
-
const reversibleFindings = redactFindings.filter((f) =>
|
|
37931
|
+
const reversibleFindings = redactFindings.filter((f) => resolver.isReversible(f.ruleId));
|
|
37067
37932
|
return {
|
|
37068
37933
|
action: "redact",
|
|
37069
37934
|
text: redact(text, redactFindings),
|
|
@@ -37139,7 +38004,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37139
38004
|
return { excepted: /* @__PURE__ */ new Set(), exceptionIds: [] };
|
|
37140
38005
|
}
|
|
37141
38006
|
}
|
|
37142
|
-
async function recordBlockedDetections(decision, excepted, ctx, fpCache) {
|
|
38007
|
+
async function recordBlockedDetections(decision, excepted, ctx, fpCache, rewritable = true) {
|
|
37143
38008
|
const references = [];
|
|
37144
38009
|
try {
|
|
37145
38010
|
if (decision.action !== "block" && decision.action !== "redact") return references;
|
|
@@ -37147,7 +38012,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37147
38012
|
if (!key) return references;
|
|
37148
38013
|
const seen = /* @__PURE__ */ new Set();
|
|
37149
38014
|
for (const finding2 of decision.findings) {
|
|
37150
|
-
const action = actionForFinding(finding2, excepted);
|
|
38015
|
+
const action = actionForFinding(finding2, excepted, rewritable);
|
|
37151
38016
|
if (action !== "block" && action !== "redact") continue;
|
|
37152
38017
|
const fp = fingerprintOf(key, finding2, fpCache);
|
|
37153
38018
|
const pair = `${finding2.ruleId}:${fp}`;
|
|
@@ -37174,7 +38039,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37174
38039
|
}
|
|
37175
38040
|
return references;
|
|
37176
38041
|
}
|
|
37177
|
-
async function evaluate(text, context, ctx) {
|
|
38042
|
+
async function evaluate(text, context, ctx, rewritable = true) {
|
|
37178
38043
|
try {
|
|
37179
38044
|
await ensureInitialized();
|
|
37180
38045
|
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
@@ -37183,8 +38048,14 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37183
38048
|
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
37184
38049
|
const fpCache = /* @__PURE__ */ new Map();
|
|
37185
38050
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
37186
|
-
const decision = decide(findings, text, excepted);
|
|
37187
|
-
const blockedReferences = await recordBlockedDetections(
|
|
38051
|
+
const decision = decide(findings, text, excepted, rewritable);
|
|
38052
|
+
const blockedReferences = await recordBlockedDetections(
|
|
38053
|
+
decision,
|
|
38054
|
+
excepted,
|
|
38055
|
+
ctx,
|
|
38056
|
+
fpCache,
|
|
38057
|
+
rewritable
|
|
38058
|
+
);
|
|
37188
38059
|
if (blockedReferences.length > 0) decision.blockedReferences = blockedReferences;
|
|
37189
38060
|
return { decision, excepted, exceptionIds };
|
|
37190
38061
|
} catch {
|
|
@@ -37208,12 +38079,16 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37208
38079
|
sourceTool: input2.sourceTool,
|
|
37209
38080
|
metadata: input2.metadata,
|
|
37210
38081
|
preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
|
|
37211
|
-
}
|
|
38082
|
+
},
|
|
38083
|
+
opts2.rewritable
|
|
37212
38084
|
);
|
|
37213
38085
|
if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
|
|
37214
38086
|
try {
|
|
37215
38087
|
const contentHash = contentHashOf(input2.text);
|
|
37216
|
-
const
|
|
38088
|
+
const maskedFindings = decision.findings.filter(
|
|
38089
|
+
(match) => isActionAtLeast(actionForFinding(match, excepted, opts2.rewritable), "redact")
|
|
38090
|
+
);
|
|
38091
|
+
const storedContent = maskedFindings.length > 0 ? redact(input2.text, maskedFindings) : input2.text;
|
|
37217
38092
|
const inspectionMs = elapsedMs(timingStartedAt);
|
|
37218
38093
|
const metadata = exceptionIds.length > 0 || inspectionMs !== void 0 ? {
|
|
37219
38094
|
...input2.metadata,
|
|
@@ -37250,7 +38125,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37250
38125
|
severity: match.severity,
|
|
37251
38126
|
span: match.span,
|
|
37252
38127
|
maskedMatch,
|
|
37253
|
-
actionTaken: actionForFinding(match, excepted),
|
|
38128
|
+
actionTaken: actionForFinding(match, excepted, opts2.rewritable),
|
|
37254
38129
|
confidence: match.confidence,
|
|
37255
38130
|
...findingKey ? { findingKey } : {}
|
|
37256
38131
|
};
|
|
@@ -37290,9 +38165,9 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
37290
38165
|
|
|
37291
38166
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
37292
38167
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
37293
|
-
import { join as
|
|
38168
|
+
import { join as join21 } from "path";
|
|
37294
38169
|
function throttled(dataDir2, markerName, windowMs) {
|
|
37295
|
-
const marker =
|
|
38170
|
+
const marker = join21(dataDir2, markerName);
|
|
37296
38171
|
try {
|
|
37297
38172
|
if (Date.now() - statSync8(marker).mtimeMs < windowMs) return true;
|
|
37298
38173
|
} catch {
|
|
@@ -37305,25 +38180,17 @@ function throttled(dataDir2, markerName, windowMs) {
|
|
|
37305
38180
|
return false;
|
|
37306
38181
|
}
|
|
37307
38182
|
|
|
37308
|
-
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
37309
|
-
var REQUEST_TIMEOUT_MS = 2e3;
|
|
37310
|
-
function withTimeout(promise2, ms) {
|
|
37311
|
-
let timer;
|
|
37312
|
-
const timeout = new Promise((_, reject) => {
|
|
37313
|
-
timer = setTimeout(() => {
|
|
37314
|
-
reject(new Error("attached gateway request timed out"));
|
|
37315
|
-
}, ms);
|
|
37316
|
-
});
|
|
37317
|
-
promise2.catch(() => void 0);
|
|
37318
|
-
return Promise.race([promise2, timeout]).finally(() => {
|
|
37319
|
-
clearTimeout(timer);
|
|
37320
|
-
});
|
|
37321
|
-
}
|
|
37322
|
-
|
|
37323
38183
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
37324
38184
|
function isInvalidRequest(err) {
|
|
37325
38185
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
37326
38186
|
}
|
|
38187
|
+
function isRouteAbsent(err) {
|
|
38188
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
|
|
38189
|
+
}
|
|
38190
|
+
function isServerRejection(err) {
|
|
38191
|
+
const status = statusOf(err);
|
|
38192
|
+
return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
|
|
38193
|
+
}
|
|
37327
38194
|
var FORWARD_BUDGET_MS = 1500;
|
|
37328
38195
|
var DECISION_PATH_BUDGET_MS = 800;
|
|
37329
38196
|
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
@@ -37351,7 +38218,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
37351
38218
|
}
|
|
37352
38219
|
function createForwardPolicy(deps) {
|
|
37353
38220
|
const now = deps.now ?? (() => Date.now());
|
|
37354
|
-
const file2 =
|
|
38221
|
+
const file2 = join22(deps.dir, STATE_FILENAME);
|
|
37355
38222
|
let state = null;
|
|
37356
38223
|
let loading = null;
|
|
37357
38224
|
async function readState() {
|
|
@@ -37391,6 +38258,20 @@ function createForwardPolicy(deps) {
|
|
|
37391
38258
|
} catch {
|
|
37392
38259
|
current = { ...CLOSED };
|
|
37393
38260
|
}
|
|
38261
|
+
const restoreOpenedAtMs = (openedAtMs) => persist({
|
|
38262
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
38263
|
+
openedAtMs,
|
|
38264
|
+
lastFailure: current.lastFailure
|
|
38265
|
+
});
|
|
38266
|
+
const recordFailure = (cause) => {
|
|
38267
|
+
const failures = current.consecutiveFailures + 1;
|
|
38268
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
38269
|
+
return persist({
|
|
38270
|
+
consecutiveFailures: failures,
|
|
38271
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
38272
|
+
lastFailure: cause
|
|
38273
|
+
});
|
|
38274
|
+
};
|
|
37394
38275
|
const at = now();
|
|
37395
38276
|
if (current.openedAtMs !== null) {
|
|
37396
38277
|
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
@@ -37409,15 +38290,20 @@ function createForwardPolicy(deps) {
|
|
|
37409
38290
|
}
|
|
37410
38291
|
return { ok: true, value };
|
|
37411
38292
|
} catch (err) {
|
|
37412
|
-
if (isInvalidRequest(err))
|
|
38293
|
+
if (isInvalidRequest(err)) {
|
|
38294
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
|
|
38295
|
+
return { ok: false, reason: "invalid-request" };
|
|
38296
|
+
}
|
|
38297
|
+
if (isRouteAbsent(err)) {
|
|
38298
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
|
|
38299
|
+
return { ok: false, reason: "route-absent" };
|
|
38300
|
+
}
|
|
38301
|
+
if (isServerRejection(err)) {
|
|
38302
|
+
await recordFailure("unreachable");
|
|
38303
|
+
return { ok: false, reason: "rejected" };
|
|
38304
|
+
}
|
|
37413
38305
|
const reason = classifyFailure(err);
|
|
37414
|
-
|
|
37415
|
-
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
37416
|
-
await persist({
|
|
37417
|
-
consecutiveFailures: failures,
|
|
37418
|
-
openedAtMs: shouldOpen ? now() : null,
|
|
37419
|
-
lastFailure: reason
|
|
37420
|
-
});
|
|
38306
|
+
await recordFailure(reason);
|
|
37421
38307
|
return { ok: false, reason };
|
|
37422
38308
|
}
|
|
37423
38309
|
}
|
|
@@ -37425,13 +38311,11 @@ function createForwardPolicy(deps) {
|
|
|
37425
38311
|
}
|
|
37426
38312
|
|
|
37427
38313
|
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
37428
|
-
|
|
37429
|
-
|
|
37430
|
-
|
|
37431
|
-
|
|
37432
|
-
|
|
37433
|
-
block: 4
|
|
37434
|
-
};
|
|
38314
|
+
function strongerOf(a, b) {
|
|
38315
|
+
if (a === null) return b;
|
|
38316
|
+
if (b === null) return a;
|
|
38317
|
+
return strongerAction(a, b);
|
|
38318
|
+
}
|
|
37435
38319
|
function ruleCategoryMap(wireRules, localRules) {
|
|
37436
38320
|
const map2 = /* @__PURE__ */ new Map();
|
|
37437
38321
|
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
@@ -37441,11 +38325,6 @@ function ruleCategoryMap(wireRules, localRules) {
|
|
|
37441
38325
|
}
|
|
37442
38326
|
return map2;
|
|
37443
38327
|
}
|
|
37444
|
-
function strongerOf(a, b) {
|
|
37445
|
-
if (a === null) return b;
|
|
37446
|
-
if (b === null) return a;
|
|
37447
|
-
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
37448
|
-
}
|
|
37449
38328
|
function policyKey(policy) {
|
|
37450
38329
|
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
37451
38330
|
}
|
|
@@ -37464,7 +38343,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
37464
38343
|
const floor = floorFor(policy, categoryByRuleId);
|
|
37465
38344
|
remoteCategoryAction.set(
|
|
37466
38345
|
policy.target.category,
|
|
37467
|
-
floor !== null &&
|
|
38346
|
+
floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
|
|
37468
38347
|
);
|
|
37469
38348
|
}
|
|
37470
38349
|
for (const policy of localPolicies) {
|
|
@@ -37481,7 +38360,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
37481
38360
|
}
|
|
37482
38361
|
merged.set(
|
|
37483
38362
|
key,
|
|
37484
|
-
remoteFloor !== null &&
|
|
38363
|
+
remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
|
|
37485
38364
|
);
|
|
37486
38365
|
}
|
|
37487
38366
|
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
@@ -37501,13 +38380,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
37501
38380
|
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
37502
38381
|
}
|
|
37503
38382
|
const effectiveFloor = strongerOf(floor, localFloor);
|
|
37504
|
-
const clamped = effectiveFloor !== null &&
|
|
38383
|
+
const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
|
|
37505
38384
|
const existing = merged.get(key);
|
|
37506
38385
|
if (existing === void 0) {
|
|
37507
38386
|
merged.set(key, clamped);
|
|
37508
38387
|
continue;
|
|
37509
38388
|
}
|
|
37510
|
-
if (
|
|
38389
|
+
if (actionRank(clamped.action) > actionRank(existing.action)) {
|
|
37511
38390
|
merged.set(key, clamped);
|
|
37512
38391
|
}
|
|
37513
38392
|
}
|
|
@@ -37540,6 +38419,8 @@ var AttachedDataGateway = class {
|
|
|
37540
38419
|
);
|
|
37541
38420
|
if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
|
|
37542
38421
|
this.deps.local.markCaptureDelivered(record2.event, Date.now());
|
|
38422
|
+
} else {
|
|
38423
|
+
this.deps.local.markCaptureOwed(record2.event);
|
|
37543
38424
|
}
|
|
37544
38425
|
}
|
|
37545
38426
|
async ensureInventory(ctx) {
|
|
@@ -37576,9 +38457,10 @@ var AttachedDataGateway = class {
|
|
|
37576
38457
|
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
37577
38458
|
async recordAuditEvent(event) {
|
|
37578
38459
|
await this.deps.local.recordAuditEvent(event);
|
|
37579
|
-
await this.deps.forward.run(
|
|
38460
|
+
const forwarded = await this.deps.forward.run(
|
|
37580
38461
|
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
37581
38462
|
);
|
|
38463
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
37582
38464
|
}
|
|
37583
38465
|
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
37584
38466
|
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
@@ -37587,44 +38469,170 @@ var AttachedDataGateway = class {
|
|
|
37587
38469
|
// which would write the event to the local store a second time.
|
|
37588
38470
|
async recordLlmCall(input2) {
|
|
37589
38471
|
await this.deps.local.recordLlmCall(input2);
|
|
37590
|
-
|
|
37591
|
-
|
|
37592
|
-
|
|
37593
|
-
)
|
|
38472
|
+
const event = llmAuditEvent(input2);
|
|
38473
|
+
const forwarded = await this.deps.forward.run(
|
|
38474
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
37594
38475
|
);
|
|
38476
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
37595
38477
|
}
|
|
37596
38478
|
/**
|
|
37597
|
-
* Forward one batch
|
|
38479
|
+
* Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
|
|
38480
|
+
*
|
|
38481
|
+
* This used to send one HTTP request per event, which is what made the batch
|
|
38482
|
+
* budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
|
|
38483
|
+
* away everything after them. The same rows now cross 50 at a time over
|
|
38484
|
+
* `POST /v1/audit-events/batch` — the route the attach-time drain has always
|
|
38485
|
+
* used — so the same budget admits ~750. The wire cap is the server's own
|
|
38486
|
+
* constant, sized against server cost, and the client REFUSES a longer array
|
|
38487
|
+
* client-side, so the chunking here is not a convention.
|
|
38488
|
+
*
|
|
38489
|
+
* Still serial, and still for the original reason: firing N requests at once
|
|
38490
|
+
* would trade a latency problem for a burst the plane's per-key rate limiting
|
|
38491
|
+
* answers with the refusals the breaker then counts. Fewer, fuller requests is
|
|
38492
|
+
* the fix; more concurrent ones is not.
|
|
38493
|
+
*
|
|
38494
|
+
* When the deadline passes the remainder is dropped rather than sent: the
|
|
38495
|
+
* local write has already succeeded, so every caller has a correct result to
|
|
38496
|
+
* return. What is dropped is COUNTED, everywhere it can happen — this path
|
|
38497
|
+
* returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
|
|
38498
|
+
* `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
|
|
38499
|
+
* the breaker closed, renders a healthy block, and discards the tail of every
|
|
38500
|
+
* batch indefinitely. The SAME tally also covers a single that fails inside
|
|
38501
|
+
* the per-item retry below — the breaker opening mid-retry is a failure the
|
|
38502
|
+
* breaker's own state DOES capture, but the events still in this chunk once
|
|
38503
|
+
* that happens are neither delivered nor otherwise counted anywhere, which is
|
|
38504
|
+
* the same invisibility with a different cause.
|
|
38505
|
+
*
|
|
38506
|
+
* `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
|
|
38507
|
+
* single-event ack and at fifty times the blast radius here:
|
|
38508
|
+
* `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
|
|
38509
|
+
* not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
|
|
38510
|
+
* fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
|
|
38511
|
+
* delivered and never re-offer the twenty the plane did not take. So success
|
|
38512
|
+
* is checked against `chunk.length`; anything short of it falls into the same
|
|
38513
|
+
* per-item pass as a refused chunk, which is the only way to recover the
|
|
38514
|
+
* rows that did not land, since the ack carries no per-row verdict to
|
|
38515
|
+
* resend by.
|
|
38516
|
+
*
|
|
38517
|
+
* That fallback ASSUMES a re-send of an already-landed row is a harmless
|
|
38518
|
+
* no-op rather than a second cost — an assumption this file cannot verify.
|
|
38519
|
+
* `AuditEventBatchAck` carries only `accepted`, unlike its sibling
|
|
38520
|
+
* `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
|
|
38521
|
+
* the batch size as the invariant `recordCapture` reads), so whether a
|
|
38522
|
+
* duplicate counts toward THIS route's `accepted` is not expressed
|
|
38523
|
+
* anywhere in this repo. If it follows its sibling's convention and does
|
|
38524
|
+
* NOT, a chunk containing even one already-delivered row — the ordinary
|
|
38525
|
+
* consequence of a lost stamp, which this file already treats as cheap —
|
|
38526
|
+
* answers short forever and enters the per-item pass on every pass it is
|
|
38527
|
+
* offered again. The cost of that is bounded rather than silent: the
|
|
38528
|
+
* pass converges (every row lands and stamps), so it is one wasted round
|
|
38529
|
+
* of singles rather than a stall, and it errs toward an extra resend
|
|
38530
|
+
* rather than toward the lost row the alternative risks.
|
|
37598
38531
|
*
|
|
37599
|
-
*
|
|
37600
|
-
*
|
|
37601
|
-
*
|
|
37602
|
-
*
|
|
37603
|
-
*
|
|
38532
|
+
* BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
|
|
38533
|
+
* one transaction, so a full 2xx settles every event in it and a non-2xx
|
|
38534
|
+
* settles none — which is why the whole chunk is stamped together on a FULL
|
|
38535
|
+
* accept and none of it otherwise. THREE reasons do not deserve whole-chunk
|
|
38536
|
+
* treatment, alongside a short accept, and all are re-sent one event at a
|
|
38537
|
+
* time:
|
|
37604
38538
|
*
|
|
37605
|
-
*
|
|
37606
|
-
*
|
|
37607
|
-
*
|
|
38539
|
+
* `invalid-request` a chunk the client refused to send at all. One malformed
|
|
38540
|
+
* event would otherwise cost the 49 good ones beside it —
|
|
38541
|
+
* a new way to lose data introduced by the very change
|
|
38542
|
+
* meant to stop losing it.
|
|
38543
|
+
* `route-absent` a deployment that predates the batch route. The
|
|
38544
|
+
* single-event route is the one it serves, and re-sending
|
|
38545
|
+
* here rather than inside the client is what gives each
|
|
38546
|
+
* request its own budget instead of 50 inside one.
|
|
38547
|
+
* `rejected` the deployment's SERVER-side twin of `invalid-request` —
|
|
38548
|
+
* a 4xx body refusal from schema drift on the other side
|
|
38549
|
+
* of the wire. Settlement is batch-atomic on this reason
|
|
38550
|
+
* exactly as on the others, so leaving it out would cost
|
|
38551
|
+
* the whole chunk for one event the DEPLOYMENT considers
|
|
38552
|
+
* malformed, where the per-item form cost only that one.
|
|
37608
38553
|
*
|
|
37609
|
-
*
|
|
37610
|
-
*
|
|
37611
|
-
*
|
|
37612
|
-
*
|
|
37613
|
-
* plane produces no failures, keeps the breaker closed, renders a healthy
|
|
37614
|
-
* block, and discards the tail of every batch indefinitely.
|
|
38554
|
+
* Every other reason (breaker-open, a refusal, a timeout) applies to the whole
|
|
38555
|
+
* chunk, and re-sending it item by item would just spend the budget failing 50
|
|
38556
|
+
* more times — for those, the blast radius stays exactly what it was before
|
|
38557
|
+
* batching.
|
|
37615
38558
|
*/
|
|
37616
38559
|
async forwardBatch(inputs, toEvent) {
|
|
37617
38560
|
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
37618
|
-
|
|
37619
|
-
|
|
37620
|
-
|
|
37621
|
-
|
|
37622
|
-
|
|
38561
|
+
const delivered = [];
|
|
38562
|
+
try {
|
|
38563
|
+
for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
|
|
38564
|
+
const now = Date.now();
|
|
38565
|
+
if (now >= deadline) {
|
|
38566
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
38567
|
+
return;
|
|
38568
|
+
}
|
|
38569
|
+
const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
|
|
38570
|
+
const forwarded = await this.deps.forward.run(
|
|
38571
|
+
() => this.deps.client.recordAuditEvents(
|
|
38572
|
+
chunk.map((event) => reKeyForForward(event, this.remoteInventory))
|
|
38573
|
+
)
|
|
38574
|
+
);
|
|
38575
|
+
if (forwarded.ok) {
|
|
38576
|
+
if (forwarded.value.accepted === chunk.length) {
|
|
38577
|
+
delivered.push(...chunk);
|
|
38578
|
+
continue;
|
|
38579
|
+
}
|
|
38580
|
+
} else if (
|
|
38581
|
+
// THREE reasons are worth a second pass, one at a time, and they are
|
|
38582
|
+
// the three settled BEFORE the control plane refused anything, or
|
|
38583
|
+
// (for `rejected`) refused the BODY rather than the connection.
|
|
38584
|
+
//
|
|
38585
|
+
// `invalid-request` — the CLIENT refused the body before any request
|
|
38586
|
+
// went out: a defect in one event, not an outage. Re-sending singly
|
|
38587
|
+
// isolates the bad one instead of charging its 49 neighbours for it.
|
|
38588
|
+
//
|
|
38589
|
+
// `route-absent` — the deployment predates the batch route and serves
|
|
38590
|
+
// only the single-event one. The retry IS the compatibility path, and
|
|
38591
|
+
// it has to live HERE rather than inside the client: each single gets
|
|
38592
|
+
// its own FORWARD_BUDGET_MS through `run`, whereas the client's own
|
|
38593
|
+
// fallback would spend 50 sequential round trips inside the ONE
|
|
38594
|
+
// budget wrapping this call — turning a working older deployment into
|
|
38595
|
+
// a timeout, three of those into an open breaker, and every row into
|
|
38596
|
+
// a silent drop while the status surface called an answering
|
|
38597
|
+
// deployment down.
|
|
38598
|
+
//
|
|
38599
|
+
// `rejected` — the deployment's own 4xx refusal of the body, the
|
|
38600
|
+
// server-side twin of `invalid-request`: isolating it the same way
|
|
38601
|
+
// costs one event instead of the whole chunk for a defect the
|
|
38602
|
+
// deployment considers local to one row.
|
|
38603
|
+
//
|
|
38604
|
+
// Every other reason (breaker-open, a refusal, a timeout) applies to
|
|
38605
|
+
// the whole chunk; re-sending it item by item would just spend the
|
|
38606
|
+
// budget failing 50 more times.
|
|
38607
|
+
forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
|
|
38608
|
+
) {
|
|
38609
|
+
continue;
|
|
38610
|
+
}
|
|
38611
|
+
for (const [j, event] of chunk.entries()) {
|
|
38612
|
+
const at = Date.now();
|
|
38613
|
+
if (at >= deadline) {
|
|
38614
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
38615
|
+
return;
|
|
38616
|
+
}
|
|
38617
|
+
const single = await this.deps.forward.run(
|
|
38618
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
38619
|
+
);
|
|
38620
|
+
if (single.ok) {
|
|
38621
|
+
delivered.push(event);
|
|
38622
|
+
continue;
|
|
38623
|
+
}
|
|
38624
|
+
if (single.reason === "breaker-open") {
|
|
38625
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
38626
|
+
return;
|
|
38627
|
+
}
|
|
38628
|
+
recordForwardDrops(this.deps.dataDir, 1, at);
|
|
38629
|
+
}
|
|
38630
|
+
}
|
|
38631
|
+
} finally {
|
|
38632
|
+
try {
|
|
38633
|
+
this.deps.local.markAuditEventsDelivered(delivered, Date.now());
|
|
38634
|
+
} catch {
|
|
37623
38635
|
}
|
|
37624
|
-
const input2 = inputs[i];
|
|
37625
|
-
await this.deps.forward.run(
|
|
37626
|
-
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
|
|
37627
|
-
);
|
|
37628
38636
|
}
|
|
37629
38637
|
}
|
|
37630
38638
|
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
@@ -37667,9 +38675,10 @@ var AttachedDataGateway = class {
|
|
|
37667
38675
|
// local store.
|
|
37668
38676
|
async recordConfigScan(record2) {
|
|
37669
38677
|
await this.deps.local.recordConfigScan(record2);
|
|
37670
|
-
await this.deps.forward.run(
|
|
38678
|
+
const forwarded = await this.deps.forward.run(
|
|
37671
38679
|
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
37672
38680
|
);
|
|
38681
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
|
|
37673
38682
|
}
|
|
37674
38683
|
async recordBlockedDetection(entry) {
|
|
37675
38684
|
return this.deps.local.recordBlockedDetection(entry);
|
|
@@ -37803,6 +38812,18 @@ var AttachedDataGateway = class {
|
|
|
37803
38812
|
// exactly what it did, leaving the whole control inert on every device
|
|
37804
38813
|
// while every test around it stayed green.
|
|
37805
38814
|
prohibitedModels: cached2.prohibitedModels
|
|
38815
|
+
// ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
|
|
38816
|
+
// merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
|
|
38817
|
+
// it emits, so an 'authored' policy arriving from the control plane
|
|
38818
|
+
// keeps that marker even where the clamp rebuilds it with a stronger
|
|
38819
|
+
// action. The device reads it in exactly one direction — the rules such a
|
|
38820
|
+
// policy targets are not locally re-assignable — so it sits on the
|
|
38821
|
+
// `prohibitedModels` side of the line for the same reason that field
|
|
38822
|
+
// does: it can only ever ADD a refusal, never relax one, and an unsigned
|
|
38823
|
+
// cache therefore has no relaxation to grant by carrying it. Dropping it
|
|
38824
|
+
// would be the silent failure rather than the safe one — the action would
|
|
38825
|
+
// still be enforced while the local override the organization authored
|
|
38826
|
+
// away quietly came back.
|
|
37806
38827
|
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
37807
38828
|
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
37808
38829
|
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
@@ -37842,10 +38863,10 @@ var AttachedDataGateway = class {
|
|
|
37842
38863
|
//
|
|
37843
38864
|
// Implementing these is what actually closes the skipped-local-maintenance
|
|
37844
38865
|
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
37845
|
-
// any object carrying all
|
|
38866
|
+
// any object carrying them all, so the composite qualifies and SessionStart
|
|
37846
38867
|
// runs maintenance on the device's real store.
|
|
37847
38868
|
//
|
|
37848
|
-
// ⚠
|
|
38869
|
+
// ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
37849
38870
|
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
37850
38871
|
// return value directly; declaring them `async` here would hand those call
|
|
37851
38872
|
// sites a Promise and silently break both.
|
|
@@ -37868,9 +38889,15 @@ var AttachedDataGateway = class {
|
|
|
37868
38889
|
// Delegated like the rest, and SYNCHRONOUS for the reason the note above
|
|
37869
38890
|
// gives: `recordCapture` calls it after the forward has already settled, on a
|
|
37870
38891
|
// path that has nothing left to await.
|
|
38892
|
+
markCaptureOwed(event) {
|
|
38893
|
+
this.deps.local.markCaptureOwed(event);
|
|
38894
|
+
}
|
|
37871
38895
|
markCaptureDelivered(event, atMs) {
|
|
37872
38896
|
this.deps.local.markCaptureDelivered(event, atMs);
|
|
37873
38897
|
}
|
|
38898
|
+
markAuditEventsDelivered(events, atMs) {
|
|
38899
|
+
this.deps.local.markAuditEventsDelivered(events, atMs);
|
|
38900
|
+
}
|
|
37874
38901
|
};
|
|
37875
38902
|
function reKeyForForward(event, remote) {
|
|
37876
38903
|
if (remote === null) {
|
|
@@ -37913,281 +38940,17 @@ function toolAuditEvent(input2) {
|
|
|
37913
38940
|
}
|
|
37914
38941
|
|
|
37915
38942
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
37916
|
-
import { readFileSync as
|
|
37917
|
-
import { join as
|
|
38943
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
38944
|
+
import { join as join23 } from "path";
|
|
37918
38945
|
|
|
37919
38946
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
37920
38947
|
import { createHash as createHash6 } from "crypto";
|
|
37921
38948
|
import { hostname as hostname5 } from "os";
|
|
37922
38949
|
|
|
37923
|
-
// ../../packages/
|
|
37924
|
-
|
|
37925
|
-
|
|
37926
|
-
var
|
|
37927
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
37928
|
-
var RemoteRequestError = class extends Error {
|
|
37929
|
-
constructor(status) {
|
|
37930
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
37931
|
-
this.status = status;
|
|
37932
|
-
this.name = "RemoteRequestError";
|
|
37933
|
-
}
|
|
37934
|
-
status;
|
|
37935
|
-
};
|
|
37936
|
-
var RemoteRequestInvalid = class extends Error {
|
|
37937
|
-
constructor(route, cause) {
|
|
37938
|
-
super(`refusing to send a malformed body to ${route}`);
|
|
37939
|
-
this.cause = cause;
|
|
37940
|
-
this.name = "RemoteRequestInvalid";
|
|
37941
|
-
}
|
|
37942
|
-
cause;
|
|
37943
|
-
};
|
|
37944
|
-
var RemoteResponseInvalid = class extends Error {
|
|
37945
|
-
constructor(route, detail) {
|
|
37946
|
-
super(`control plane answered ${route} with ${detail}`);
|
|
37947
|
-
this.name = "RemoteResponseInvalid";
|
|
37948
|
-
}
|
|
37949
|
-
};
|
|
37950
|
-
var RemoteTransportError = class extends Error {
|
|
37951
|
-
/**
|
|
37952
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
37953
|
-
* refused.
|
|
37954
|
-
*
|
|
37955
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
37956
|
-
* all. It exists because two paths reject after a status has already been
|
|
37957
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
37958
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
37959
|
-
* outage, which sends the reader to look at their network instead of their
|
|
37960
|
-
* credential.
|
|
37961
|
-
*/
|
|
37962
|
-
constructor(reason, status) {
|
|
37963
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
37964
|
-
this.status = status;
|
|
37965
|
-
this.name = "RemoteTransportError";
|
|
37966
|
-
}
|
|
37967
|
-
status;
|
|
37968
|
-
};
|
|
37969
|
-
async function send(options) {
|
|
37970
|
-
const url2 = new URL(options.url);
|
|
37971
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
37972
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
37973
|
-
const requestOptions = {
|
|
37974
|
-
method: options.method,
|
|
37975
|
-
headers: {
|
|
37976
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
37977
|
-
// last they win, and two of the values below are ones no caller may
|
|
37978
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
37979
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
37980
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
37981
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
37982
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
37983
|
-
// conditional GET — is untouched by this order.
|
|
37984
|
-
...options.headers,
|
|
37985
|
-
// The credential. One header, matching what the deployment authenticates
|
|
37986
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
37987
|
-
// it can be logged by an intermediary for no gain.
|
|
37988
|
-
//
|
|
37989
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
37990
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
37991
|
-
// key differently from an absent one, and "the header is not there" is
|
|
37992
|
-
// the property the attach flow needs.
|
|
37993
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
37994
|
-
accept: "application/json",
|
|
37995
|
-
...options.body === void 0 ? {} : {
|
|
37996
|
-
"content-type": "application/json",
|
|
37997
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
37998
|
-
// character count is truncated by the receiver.
|
|
37999
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
38000
|
-
}
|
|
38001
|
-
}
|
|
38002
|
-
};
|
|
38003
|
-
return new Promise((resolve2, reject) => {
|
|
38004
|
-
let settled = false;
|
|
38005
|
-
const fail = (reason, status) => {
|
|
38006
|
-
if (settled) return;
|
|
38007
|
-
settled = true;
|
|
38008
|
-
reject(new RemoteTransportError(reason, status));
|
|
38009
|
-
};
|
|
38010
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
38011
|
-
const chunks = [];
|
|
38012
|
-
let size = 0;
|
|
38013
|
-
res.on("data", (chunk) => {
|
|
38014
|
-
size += chunk.length;
|
|
38015
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
38016
|
-
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
38017
|
-
res.destroy();
|
|
38018
|
-
req.destroy();
|
|
38019
|
-
return;
|
|
38020
|
-
}
|
|
38021
|
-
chunks.push(chunk);
|
|
38022
|
-
});
|
|
38023
|
-
res.on("aborted", () => {
|
|
38024
|
-
fail("the response was aborted", res.statusCode);
|
|
38025
|
-
});
|
|
38026
|
-
res.on("end", () => {
|
|
38027
|
-
if (settled) return;
|
|
38028
|
-
settled = true;
|
|
38029
|
-
resolve2({
|
|
38030
|
-
status: res.statusCode ?? 0,
|
|
38031
|
-
headers: res.headers,
|
|
38032
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
38033
|
-
});
|
|
38034
|
-
});
|
|
38035
|
-
});
|
|
38036
|
-
const deadline = setTimeout(() => {
|
|
38037
|
-
fail(`no response within ${String(timeoutMs)}ms`);
|
|
38038
|
-
req.destroy();
|
|
38039
|
-
}, timeoutMs);
|
|
38040
|
-
deadline.unref();
|
|
38041
|
-
req.on("upgrade", (_res, socket) => {
|
|
38042
|
-
fail("the deployment answered with a protocol upgrade");
|
|
38043
|
-
socket.destroy();
|
|
38044
|
-
});
|
|
38045
|
-
req.on("close", () => {
|
|
38046
|
-
fail("the connection closed before a response was read");
|
|
38047
|
-
clearTimeout(deadline);
|
|
38048
|
-
});
|
|
38049
|
-
req.on("error", (err) => {
|
|
38050
|
-
fail(err.message);
|
|
38051
|
-
});
|
|
38052
|
-
if (options.body !== void 0) req.write(options.body);
|
|
38053
|
-
req.end();
|
|
38054
|
-
});
|
|
38055
|
-
}
|
|
38056
|
-
|
|
38057
|
-
// ../../packages/remote/src/client.ts
|
|
38058
|
-
var ROUTES = {
|
|
38059
|
-
events: "/v1/events",
|
|
38060
|
-
auditEvents: "/v1/audit-events",
|
|
38061
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
38062
|
-
inventory: "/v1/inventory",
|
|
38063
|
-
storePosture: "/v1/store-posture",
|
|
38064
|
-
policyBundle: "/v1/policy-bundle",
|
|
38065
|
-
whoami: "/v1/plugin/whoami",
|
|
38066
|
-
shares: "/v1/shares"
|
|
38067
|
-
};
|
|
38068
|
-
function headerValue(response, name2) {
|
|
38069
|
-
const raw = response.headers[name2];
|
|
38070
|
-
if (raw === void 0) return void 0;
|
|
38071
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
38072
|
-
}
|
|
38073
|
-
function okBody(response) {
|
|
38074
|
-
if (response.status < 200 || response.status >= 300) {
|
|
38075
|
-
throw new RemoteRequestError(response.status);
|
|
38076
|
-
}
|
|
38077
|
-
return response.body;
|
|
38078
|
-
}
|
|
38079
|
-
function parsed(schema, body, route) {
|
|
38080
|
-
let json2;
|
|
38081
|
-
try {
|
|
38082
|
-
json2 = JSON.parse(body);
|
|
38083
|
-
} catch {
|
|
38084
|
-
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
38085
|
-
}
|
|
38086
|
-
const result = schema.safeParse(json2);
|
|
38087
|
-
if (!result.success) {
|
|
38088
|
-
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
38089
|
-
}
|
|
38090
|
-
return result.data;
|
|
38091
|
-
}
|
|
38092
|
-
function withoutTrailingSlashes(endpoint) {
|
|
38093
|
-
let end = endpoint.length;
|
|
38094
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
38095
|
-
return endpoint.slice(0, end);
|
|
38096
|
-
}
|
|
38097
|
-
var SLASH = "/".charCodeAt(0);
|
|
38098
|
-
function createRemoteClient(options) {
|
|
38099
|
-
const base = withoutTrailingSlashes(options.endpoint);
|
|
38100
|
-
const url2 = (route) => `${base}${route}`;
|
|
38101
|
-
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
38102
|
-
const sendOne = async (event) => {
|
|
38103
|
-
const validated = RecordAuditEventRequest.safeParse(event);
|
|
38104
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
38105
|
-
const response = await send({
|
|
38106
|
-
...common,
|
|
38107
|
-
method: "POST",
|
|
38108
|
-
url: url2(ROUTES.auditEvents),
|
|
38109
|
-
body: JSON.stringify(validated.data)
|
|
38110
|
-
});
|
|
38111
|
-
okBody(response);
|
|
38112
|
-
};
|
|
38113
|
-
return {
|
|
38114
|
-
async ingestEvents(batch) {
|
|
38115
|
-
const response = await send({
|
|
38116
|
-
...common,
|
|
38117
|
-
method: "POST",
|
|
38118
|
-
url: url2(ROUTES.events),
|
|
38119
|
-
body: JSON.stringify(batch)
|
|
38120
|
-
});
|
|
38121
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
38122
|
-
},
|
|
38123
|
-
async ingestInventory(context) {
|
|
38124
|
-
const response = await send({
|
|
38125
|
-
...common,
|
|
38126
|
-
method: "POST",
|
|
38127
|
-
url: url2(ROUTES.inventory),
|
|
38128
|
-
body: JSON.stringify(context)
|
|
38129
|
-
});
|
|
38130
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
38131
|
-
},
|
|
38132
|
-
async recordAuditEvent(event) {
|
|
38133
|
-
await sendOne(event);
|
|
38134
|
-
},
|
|
38135
|
-
async recordAuditEvents(events) {
|
|
38136
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
38137
|
-
if (!validated.success) {
|
|
38138
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
38139
|
-
}
|
|
38140
|
-
const response = await send({
|
|
38141
|
-
...common,
|
|
38142
|
-
method: "POST",
|
|
38143
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
38144
|
-
body: JSON.stringify(validated.data)
|
|
38145
|
-
});
|
|
38146
|
-
if (response.status === 404) {
|
|
38147
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
38148
|
-
return { accepted: validated.data.events.length };
|
|
38149
|
-
}
|
|
38150
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
38151
|
-
},
|
|
38152
|
-
async reportStorePosture(snapshot) {
|
|
38153
|
-
const response = await send({
|
|
38154
|
-
...common,
|
|
38155
|
-
method: "POST",
|
|
38156
|
-
url: url2(ROUTES.storePosture),
|
|
38157
|
-
body: JSON.stringify(snapshot)
|
|
38158
|
-
});
|
|
38159
|
-
okBody(response);
|
|
38160
|
-
},
|
|
38161
|
-
async getPolicyBundle(etag) {
|
|
38162
|
-
const response = await send({
|
|
38163
|
-
...common,
|
|
38164
|
-
method: "GET",
|
|
38165
|
-
url: url2(ROUTES.policyBundle),
|
|
38166
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
38167
|
-
});
|
|
38168
|
-
if (response.status === 304) {
|
|
38169
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
38170
|
-
}
|
|
38171
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
38172
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
38173
|
-
},
|
|
38174
|
-
async whoami() {
|
|
38175
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
38176
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
38177
|
-
},
|
|
38178
|
-
async recordProjectEgress(request) {
|
|
38179
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
38180
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
38181
|
-
const response = await send({
|
|
38182
|
-
...common,
|
|
38183
|
-
method: "POST",
|
|
38184
|
-
url: url2(ROUTES.shares),
|
|
38185
|
-
body: JSON.stringify(validated.data)
|
|
38186
|
-
});
|
|
38187
|
-
okBody(response);
|
|
38188
|
-
}
|
|
38189
|
-
};
|
|
38190
|
-
}
|
|
38950
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
38951
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
38952
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
38953
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
38191
38954
|
|
|
38192
38955
|
// ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
|
|
38193
38956
|
import { spawn } from "child_process";
|
|
@@ -38219,7 +38982,7 @@ function spawnDetached(scriptPath) {
|
|
|
38219
38982
|
}
|
|
38220
38983
|
|
|
38221
38984
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
38222
|
-
import { readFileSync as
|
|
38985
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
38223
38986
|
function createPluginBlock(build, policyStore) {
|
|
38224
38987
|
return async () => {
|
|
38225
38988
|
const cached2 = await policyStore.read();
|
|
@@ -38238,7 +39001,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
38238
39001
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
38239
39002
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
38240
39003
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
38241
|
-
import { join as
|
|
39004
|
+
import { join as join24 } from "path";
|
|
38242
39005
|
|
|
38243
39006
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
38244
39007
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -38262,7 +39025,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
38262
39025
|
|
|
38263
39026
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
38264
39027
|
function createPolicyStore(dir = dataDir()) {
|
|
38265
|
-
const file2 =
|
|
39028
|
+
const file2 = join24(dir, "policy-cache.json");
|
|
38266
39029
|
async function read() {
|
|
38267
39030
|
try {
|
|
38268
39031
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -38271,22 +39034,32 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
38271
39034
|
const record2 = parsed2;
|
|
38272
39035
|
const bundle = PolicyBundle.parse(record2.bundle);
|
|
38273
39036
|
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
38274
|
-
const
|
|
39037
|
+
const stored = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
39038
|
+
const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
|
|
39039
|
+
const etag = replayable ? stored : void 0;
|
|
38275
39040
|
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
38276
39041
|
} catch {
|
|
38277
39042
|
return null;
|
|
38278
39043
|
}
|
|
38279
39044
|
}
|
|
38280
|
-
|
|
38281
|
-
|
|
38282
|
-
const
|
|
38283
|
-
|
|
38284
|
-
|
|
38285
|
-
|
|
38286
|
-
|
|
39045
|
+
function knowsMoreThanThisBuild(shapeId) {
|
|
39046
|
+
if (typeof shapeId !== "string" || shapeId === "") return false;
|
|
39047
|
+
const theirs = new Set(shapeId.split(","));
|
|
39048
|
+
const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
|
|
39049
|
+
return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
|
|
39050
|
+
}
|
|
39051
|
+
async function priorRecord() {
|
|
39052
|
+
try {
|
|
39053
|
+
const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
|
|
39054
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
39055
|
+
} catch {
|
|
39056
|
+
return null;
|
|
39057
|
+
}
|
|
39058
|
+
}
|
|
39059
|
+
async function publishRecord(record2) {
|
|
38287
39060
|
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
38288
39061
|
try {
|
|
38289
|
-
await writeFile2(tmp, JSON.stringify(
|
|
39062
|
+
await writeFile2(tmp, JSON.stringify(record2), {
|
|
38290
39063
|
encoding: "utf8",
|
|
38291
39064
|
mode: DATA_FILE_MODE,
|
|
38292
39065
|
flag: "wx"
|
|
@@ -38297,6 +39070,27 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
38297
39070
|
throw err;
|
|
38298
39071
|
}
|
|
38299
39072
|
}
|
|
39073
|
+
async function write(bundle, etag) {
|
|
39074
|
+
await ensureDataDir(dir);
|
|
39075
|
+
const prior = await priorRecord();
|
|
39076
|
+
const priorVersion = prior?.bundle?.version;
|
|
39077
|
+
if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
|
|
39078
|
+
await publishRecord({
|
|
39079
|
+
...prior,
|
|
39080
|
+
fetchedAtMs: Date.now()
|
|
39081
|
+
});
|
|
39082
|
+
return;
|
|
39083
|
+
}
|
|
39084
|
+
await publishRecord({
|
|
39085
|
+
bundle,
|
|
39086
|
+
fetchedAtMs: Date.now(),
|
|
39087
|
+
// Stamped on EVERY write, the 304 arm's included: that arm hands back the
|
|
39088
|
+
// bundle it already holds, and the point of the stamp is to describe the
|
|
39089
|
+
// build that last narrowed those bytes, which is this one.
|
|
39090
|
+
shapeId: POLICY_BUNDLE_SHAPE_ID,
|
|
39091
|
+
...etag === void 0 ? {} : { etag }
|
|
39092
|
+
});
|
|
39093
|
+
}
|
|
38300
39094
|
return { read, write, file: file2 };
|
|
38301
39095
|
}
|
|
38302
39096
|
|
|
@@ -38462,11 +39256,11 @@ function readStorePosture(dbPath2) {
|
|
|
38462
39256
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
38463
39257
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
38464
39258
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
38465
|
-
import { join as
|
|
39259
|
+
import { join as join25 } from "path";
|
|
38466
39260
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
38467
39261
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
38468
|
-
const file2 =
|
|
38469
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
39262
|
+
const file2 = join25(dir, "posture-state.json");
|
|
39263
|
+
const legacyFile = legacyDir === void 0 ? null : join25(legacyDir, "posture-state.json");
|
|
38470
39264
|
async function persist(state) {
|
|
38471
39265
|
await ensureDataDir(dir);
|
|
38472
39266
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -38534,8 +39328,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
38534
39328
|
}
|
|
38535
39329
|
|
|
38536
39330
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
38537
|
-
import { readFileSync as
|
|
38538
|
-
import { join as
|
|
39331
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
39332
|
+
import { join as join26 } from "path";
|
|
38539
39333
|
|
|
38540
39334
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
38541
39335
|
var REFUSAL_LINES = {
|
|
@@ -38877,9 +39671,21 @@ var StandaloneDataGateway = class {
|
|
|
38877
39671
|
// for the whole of it, so a member that threw would make that answer a lie
|
|
38878
39672
|
// the moment a composite delegated to it. A store-level no-op is the honest
|
|
38879
39673
|
// shape — a standalone machine has nothing delivered to record.
|
|
39674
|
+
markCaptureOwed(event) {
|
|
39675
|
+
this.db.markCaptureOwed(event);
|
|
39676
|
+
}
|
|
38880
39677
|
markCaptureDelivered(event, atMs) {
|
|
38881
39678
|
this.db.markCaptureDelivered(event, atMs);
|
|
38882
39679
|
}
|
|
39680
|
+
// Implemented, not stubbed, for the same reason its sibling above is: the
|
|
39681
|
+
// attached gateway is a DECORATOR over an instance of this class
|
|
39682
|
+
// (`attached/factory.ts` builds one and passes it as `deps.local`), so every
|
|
39683
|
+
// stamp the live forward makes lands here with a non-empty array. This is the
|
|
39684
|
+
// production write path for that feature, not a shape-satisfying no-op — a
|
|
39685
|
+
// machine that is merely standalone simply never calls it.
|
|
39686
|
+
markAuditEventsDelivered(events, atMs) {
|
|
39687
|
+
this.db.markAuditEventsDelivered(events, atMs);
|
|
39688
|
+
}
|
|
38883
39689
|
staleBinaryNotice(currentVersion) {
|
|
38884
39690
|
try {
|
|
38885
39691
|
const newest = this.db.installedPacks.newestRecordedBinary();
|