@akasecurity/ai-tc-claude-code 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/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/hooks/hooks.json +1 -1
- package/package.json +5 -5
- package/scripts/apply-suppressions.js +1200 -671
- package/scripts/backfill.js +2299 -1434
- package/scripts/filescan.js +2053 -1249
- package/scripts/firstrun.js +1961 -1188
- package/scripts/history-sync.js +1882 -1029
- package/scripts/intro.js +363 -238
- package/scripts/message-display.js +1212 -662
- package/scripts/onboard.js +1598 -1066
- package/scripts/post-model-switch.js +447 -311
- package/scripts/post-tool-use.js +2101 -1274
- package/scripts/pre-model-switch.js +1981 -1204
- package/scripts/pre-tool-use.js +2113 -1102
- package/scripts/query.js +1961 -1188
- package/scripts/reconcile.js +2082 -1242
- package/scripts/remediate.js +2152 -1279
- package/scripts/scan-worker.js +278 -176
- package/scripts/session-start.js +2050 -1277
- package/scripts/start-light.js +366 -241
- package/scripts/statusline.js +1961 -1188
- package/scripts/stop.js +448 -315
- package/scripts/sync.js +17422 -2018
- package/scripts/user-prompt-submit.js +2110 -1279
|
@@ -493,11 +493,12 @@ var require_ignore = __commonJS({
|
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
495
|
import { existsSync as existsSync7 } from "fs";
|
|
496
|
-
import { join as
|
|
496
|
+
import { join as join13 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/attached-derived.ts
|
|
499
499
|
import { rmSync } from "fs";
|
|
500
500
|
import { join } from "path";
|
|
501
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
501
502
|
|
|
502
503
|
// ../../packages/persistence/src/control-plane-credential.ts
|
|
503
504
|
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync3, statSync } from "fs";
|
|
@@ -596,6 +597,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
596
597
|
{
|
|
597
598
|
tag: "0022_audit_inspection_ms",
|
|
598
599
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
tag: "0023_secret_vault_user_authorized",
|
|
603
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
607
|
+
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`);"
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
611
|
+
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;"
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
615
|
+
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;"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
tag: "0027_audit_llm_usage_index",
|
|
619
|
+
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;"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
tag: "0028_activity_session_probe_indexes",
|
|
623
|
+
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"
|
|
599
624
|
}
|
|
600
625
|
];
|
|
601
626
|
|
|
@@ -22134,6 +22159,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22134
22159
|
AttachTokenExpired,
|
|
22135
22160
|
external_exports.object({ status: printable(64) })
|
|
22136
22161
|
]);
|
|
22162
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22163
|
+
var DeviceCommand = external_exports.object({
|
|
22164
|
+
id: printable(128).min(1),
|
|
22165
|
+
kind: DeviceCommandKind,
|
|
22166
|
+
issuedAt: printable(64).min(1),
|
|
22167
|
+
expiresAt: printable(64).min(1)
|
|
22168
|
+
}).strict();
|
|
22169
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22170
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22171
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22172
|
+
external_exports.object({
|
|
22173
|
+
outcome: external_exports.literal("reported"),
|
|
22174
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22175
|
+
}).strict(),
|
|
22176
|
+
external_exports.object({
|
|
22177
|
+
outcome: external_exports.literal("failed"),
|
|
22178
|
+
reason: DeviceCommandFailureReason,
|
|
22179
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22180
|
+
}).strict()
|
|
22181
|
+
]);
|
|
22137
22182
|
|
|
22138
22183
|
// ../../packages/schema/src/zod/registry.ts
|
|
22139
22184
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22300,7 +22345,7 @@ var PackManifest = external_exports.object({
|
|
|
22300
22345
|
}).meta({ id: "PackManifest" });
|
|
22301
22346
|
|
|
22302
22347
|
// ../../packages/schema/src/zod/detection.ts
|
|
22303
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22348
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22304
22349
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22305
22350
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22306
22351
|
var DetectionCounts = external_exports.object({
|
|
@@ -22437,14 +22482,17 @@ function optional2(key, parsed, raw) {
|
|
|
22437
22482
|
function isStringArray(value) {
|
|
22438
22483
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22439
22484
|
}
|
|
22485
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22486
|
+
function resolveOrigin(origin) {
|
|
22487
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22488
|
+
}
|
|
22440
22489
|
function summaryToDetectionListItem(s) {
|
|
22441
22490
|
return {
|
|
22442
22491
|
id: `${s.namespace}/${s.packId}`,
|
|
22443
22492
|
name: s.name,
|
|
22444
22493
|
version: s.version,
|
|
22445
22494
|
enabled: s.enabled,
|
|
22446
|
-
origin:
|
|
22447
|
-
// v1: every installed pack is library origin
|
|
22495
|
+
origin: resolveOrigin(s.origin),
|
|
22448
22496
|
namespace: s.namespace,
|
|
22449
22497
|
packId: s.packId,
|
|
22450
22498
|
ruleCount: s.ruleCount,
|
|
@@ -22496,7 +22544,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22496
22544
|
name: row.name,
|
|
22497
22545
|
version: row.version,
|
|
22498
22546
|
enabled: row.enabled,
|
|
22499
|
-
origin:
|
|
22547
|
+
origin: resolveOrigin(row.origin),
|
|
22500
22548
|
namespace: row.namespace,
|
|
22501
22549
|
packId: row.packId,
|
|
22502
22550
|
ruleCount: row.rules.length,
|
|
@@ -22516,16 +22564,20 @@ function splitDetectionId(id) {
|
|
|
22516
22564
|
}
|
|
22517
22565
|
function buildDetectionsList(summaries, query) {
|
|
22518
22566
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22567
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22519
22568
|
const counts = {
|
|
22520
22569
|
all: summaries.length,
|
|
22521
|
-
library: summaries.length,
|
|
22522
|
-
|
|
22523
|
-
|
|
22570
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22571
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22572
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22573
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22574
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22575
|
+
// OriginEnum.
|
|
22524
22576
|
customized: 0,
|
|
22525
22577
|
updates: withUpdate.length
|
|
22526
22578
|
};
|
|
22527
22579
|
const filter = query.filter;
|
|
22528
|
-
let filtered = filter === "custom"
|
|
22580
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22529
22581
|
if (query.q) {
|
|
22530
22582
|
const q = query.q.toLowerCase();
|
|
22531
22583
|
filtered = filtered.filter(
|
|
@@ -22605,8 +22657,9 @@ var Event = external_exports.object({
|
|
|
22605
22657
|
metadata: EventMetadata.optional()
|
|
22606
22658
|
}).meta({ id: "Event" });
|
|
22607
22659
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22660
|
+
var INGEST_BATCH_MAX = 100;
|
|
22608
22661
|
var IngestBatch = external_exports.object({
|
|
22609
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22662
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22610
22663
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22611
22664
|
// additionally rejects any event whose contentHash the store has already
|
|
22612
22665
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23162,6 +23215,252 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23162
23215
|
message: "At least one field must be provided"
|
|
23163
23216
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23164
23217
|
|
|
23218
|
+
// ../../packages/schema/src/zod/policy.ts
|
|
23219
|
+
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23220
|
+
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23221
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23222
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23223
|
+
var Policy = external_exports.object({
|
|
23224
|
+
id: external_exports.guid(),
|
|
23225
|
+
scope: PolicyScope,
|
|
23226
|
+
target: PolicyTarget,
|
|
23227
|
+
action: ActionTaken,
|
|
23228
|
+
enabled: external_exports.boolean().default(true),
|
|
23229
|
+
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23230
|
+
// Display name — optional so older policy rows without name still parse.
|
|
23231
|
+
// Added for the findings API (policy.name column migration).
|
|
23232
|
+
name: external_exports.string().optional(),
|
|
23233
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23234
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23235
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23236
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23237
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23238
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23239
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23240
|
+
//
|
|
23241
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23242
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23243
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23244
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23245
|
+
// — the CRUD routes neither accept nor set it.
|
|
23246
|
+
//
|
|
23247
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23248
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23249
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23250
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23251
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23252
|
+
provenance: PolicyProvenance.optional()
|
|
23253
|
+
}).meta({ id: "Policy" });
|
|
23254
|
+
var PolicyBundle = external_exports.object({
|
|
23255
|
+
version: external_exports.string(),
|
|
23256
|
+
policies: external_exports.array(Policy),
|
|
23257
|
+
// Rules from the installed marketplace packs (snapshotted by the
|
|
23258
|
+
// control plane). The plugin registers these in addition to its bundled
|
|
23259
|
+
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23260
|
+
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23261
|
+
rules: external_exports.array(Rule).optional(),
|
|
23262
|
+
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23263
|
+
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23264
|
+
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23265
|
+
// packs only), which is how detection updates stay manual: new bundled
|
|
23266
|
+
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23267
|
+
// the historical composition (bundled packs + rules) — older caches.
|
|
23268
|
+
rulesComplete: external_exports.boolean().optional(),
|
|
23269
|
+
// Active detection exceptions, evaluation subset only (see
|
|
23270
|
+
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23271
|
+
// on-disk caches — that omit the field still parse; consumers read
|
|
23272
|
+
// `bundle.exceptions ?? []`.
|
|
23273
|
+
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23274
|
+
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23275
|
+
// A second axis over the same `redact` action, carried beside the policies
|
|
23276
|
+
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23277
|
+
// widening Policy itself would change a persisted shape to express something
|
|
23278
|
+
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23279
|
+
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23280
|
+
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23281
|
+
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23282
|
+
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23283
|
+
// from a versioned installed pack. Optional so older backends — and older
|
|
23284
|
+
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23285
|
+
// the rule's own spec version. NOT the bundle version above — see
|
|
23286
|
+
// installedRuleset's ruleVersions for the source of truth.
|
|
23287
|
+
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23288
|
+
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23289
|
+
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23290
|
+
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23291
|
+
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23292
|
+
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23293
|
+
// unenforced behaviour that predates this field and the safe direction to
|
|
23294
|
+
// default.
|
|
23295
|
+
//
|
|
23296
|
+
// Ids, not display names: the governance decision is keyed on the exact
|
|
23297
|
+
// string the harness reports (`model_status_override.versionId` in the
|
|
23298
|
+
// control plane), so no name resolution stands between the decision and the
|
|
23299
|
+
// comparison.
|
|
23300
|
+
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23301
|
+
customKeywords: external_exports.array(external_exports.string()),
|
|
23302
|
+
fetchedAt: external_exports.iso.datetime()
|
|
23303
|
+
}).meta({ id: "PolicyBundle" });
|
|
23304
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23305
|
+
...Object.keys(PolicyBundle.shape),
|
|
23306
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23307
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23308
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23309
|
+
].sort().join(",");
|
|
23310
|
+
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23311
|
+
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23312
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
23313
|
+
secret: "critical",
|
|
23314
|
+
financial: "critical",
|
|
23315
|
+
// core-financial/credit-card
|
|
23316
|
+
code_flaw: "critical",
|
|
23317
|
+
pii: "high",
|
|
23318
|
+
phi: "high",
|
|
23319
|
+
custom: "high",
|
|
23320
|
+
// user-defined; conservative
|
|
23321
|
+
code_context: "low",
|
|
23322
|
+
config: "low"
|
|
23323
|
+
// observe-only; floors to monitor regardless
|
|
23324
|
+
};
|
|
23325
|
+
function severityFloorPolicy(category) {
|
|
23326
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23327
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23328
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23329
|
+
}
|
|
23330
|
+
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23331
|
+
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23332
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23333
|
+
id: "RedactFallback"
|
|
23334
|
+
});
|
|
23335
|
+
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23336
|
+
var BUILTIN_POLICY_SPECS = {
|
|
23337
|
+
monitor: {
|
|
23338
|
+
name: "Monitor",
|
|
23339
|
+
action: "log",
|
|
23340
|
+
reversible: false,
|
|
23341
|
+
description: "Log every match for audit. The request is allowed through untouched."
|
|
23342
|
+
},
|
|
23343
|
+
warn: {
|
|
23344
|
+
name: "Warn",
|
|
23345
|
+
action: "warn",
|
|
23346
|
+
reversible: false,
|
|
23347
|
+
description: "Allow the request, but warn the user inline before it is sent."
|
|
23348
|
+
},
|
|
23349
|
+
redact: {
|
|
23350
|
+
name: "Redact",
|
|
23351
|
+
action: "redact",
|
|
23352
|
+
reversible: false,
|
|
23353
|
+
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23354
|
+
},
|
|
23355
|
+
vault: {
|
|
23356
|
+
name: "Redact & Vault",
|
|
23357
|
+
action: "redact",
|
|
23358
|
+
reversible: true,
|
|
23359
|
+
description: "Strip the matched value from the request and keep an encrypted, recoverable copy in the local vault, leaving a pointer in its place. Needs the vault consent granted under Settings; without it this behaves as Redact."
|
|
23360
|
+
},
|
|
23361
|
+
block: {
|
|
23362
|
+
name: "Block",
|
|
23363
|
+
action: "block",
|
|
23364
|
+
reversible: false,
|
|
23365
|
+
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23366
|
+
}
|
|
23367
|
+
};
|
|
23368
|
+
function builtinPolicyToAction(id) {
|
|
23369
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
23370
|
+
}
|
|
23371
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23372
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23373
|
+
];
|
|
23374
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23375
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23376
|
+
);
|
|
23377
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23378
|
+
...BELOW_PALETTE,
|
|
23379
|
+
...PALETTE_WEAKEST_FIRST
|
|
23380
|
+
];
|
|
23381
|
+
function actionRank(action) {
|
|
23382
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23383
|
+
}
|
|
23384
|
+
function isActionAtLeast(action, floor) {
|
|
23385
|
+
return actionRank(action) >= actionRank(floor);
|
|
23386
|
+
}
|
|
23387
|
+
function strongerAction(a, b) {
|
|
23388
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23389
|
+
}
|
|
23390
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23391
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23392
|
+
}
|
|
23393
|
+
var PackPolicyFloor = external_exports.object({
|
|
23394
|
+
/**
|
|
23395
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23396
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23397
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23398
|
+
*/
|
|
23399
|
+
floor: BuiltinPolicyId,
|
|
23400
|
+
/**
|
|
23401
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23402
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23403
|
+
* re-assignable locally in either direction.
|
|
23404
|
+
*/
|
|
23405
|
+
locked: external_exports.boolean()
|
|
23406
|
+
}).describe("PackPolicyFloor");
|
|
23407
|
+
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23408
|
+
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23409
|
+
);
|
|
23410
|
+
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23411
|
+
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23412
|
+
);
|
|
23413
|
+
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23414
|
+
function builtinPolicyIsReversible(id) {
|
|
23415
|
+
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23416
|
+
}
|
|
23417
|
+
function policyIdIsReversible(policyId) {
|
|
23418
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23419
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23420
|
+
return builtinPolicyIsReversible(id);
|
|
23421
|
+
}
|
|
23422
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23423
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23424
|
+
);
|
|
23425
|
+
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23426
|
+
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23427
|
+
);
|
|
23428
|
+
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23429
|
+
function policyIdToAction(policyId) {
|
|
23430
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23431
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23432
|
+
return BUILTIN_POLICIES[id].action;
|
|
23433
|
+
}
|
|
23434
|
+
var UsedByItem = external_exports.object({
|
|
23435
|
+
id: external_exports.string(),
|
|
23436
|
+
name: external_exports.string(),
|
|
23437
|
+
ruleCount: external_exports.number().int().nonnegative(),
|
|
23438
|
+
enabled: external_exports.boolean()
|
|
23439
|
+
}).meta({ id: "UsedByItem" });
|
|
23440
|
+
var PolicyListItem = external_exports.object({
|
|
23441
|
+
id: external_exports.string(),
|
|
23442
|
+
kind: PolicyKind,
|
|
23443
|
+
name: external_exports.string(),
|
|
23444
|
+
enabled: external_exports.boolean(),
|
|
23445
|
+
usedByCount: external_exports.number().int().nonnegative()
|
|
23446
|
+
}).meta({ id: "PolicyListItem" });
|
|
23447
|
+
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23448
|
+
var PolicyDetail = external_exports.object({
|
|
23449
|
+
specVersion: external_exports.literal(1),
|
|
23450
|
+
id: external_exports.string(),
|
|
23451
|
+
kind: PolicyKind,
|
|
23452
|
+
name: external_exports.string(),
|
|
23453
|
+
enabled: external_exports.boolean(),
|
|
23454
|
+
description: external_exports.string(),
|
|
23455
|
+
usedBy: external_exports.array(UsedByItem)
|
|
23456
|
+
}).meta({ id: "PolicyDetail" });
|
|
23457
|
+
var PolicyStatsResponse = external_exports.object({
|
|
23458
|
+
policies: external_exports.number().int().nonnegative(),
|
|
23459
|
+
builtin: external_exports.number().int().nonnegative(),
|
|
23460
|
+
custom: external_exports.number().int().nonnegative(),
|
|
23461
|
+
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23462
|
+
}).meta({ id: "PolicyStatsResponse" });
|
|
23463
|
+
|
|
23165
23464
|
// ../../packages/schema/src/zod/vault.ts
|
|
23166
23465
|
var POINTER_FORMAT_VERSION = 2;
|
|
23167
23466
|
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
@@ -23202,6 +23501,14 @@ var VaultEntry = external_exports.object({
|
|
|
23202
23501
|
// How many times this value has been detected on this machine — the reuse
|
|
23203
23502
|
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23204
23503
|
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23504
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23505
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23506
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23507
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23508
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23509
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23510
|
+
// the value does not expire.
|
|
23511
|
+
userAuthorized: external_exports.boolean(),
|
|
23205
23512
|
firstSeen: external_exports.string(),
|
|
23206
23513
|
lastSeen: external_exports.string()
|
|
23207
23514
|
});
|
|
@@ -23328,7 +23635,7 @@ function isVaultConsentValid(consent) {
|
|
|
23328
23635
|
}
|
|
23329
23636
|
|
|
23330
23637
|
// ../../packages/schema/src/zod/local.ts
|
|
23331
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
23638
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23332
23639
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23333
23640
|
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23334
23641
|
var ControlPlaneConnection = external_exports.object({
|
|
@@ -23370,6 +23677,19 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23370
23677
|
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23371
23678
|
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23372
23679
|
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23680
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23681
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23682
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23683
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23684
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23685
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23686
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23687
|
+
//
|
|
23688
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23689
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23690
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23691
|
+
// word and stays out of the stored value.
|
|
23692
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23373
23693
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23374
23694
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23375
23695
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -23377,15 +23697,20 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23377
23697
|
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23378
23698
|
// covers the current payload and must be re-granted.
|
|
23379
23699
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23380
|
-
// Records that the user consented to
|
|
23381
|
-
//
|
|
23382
|
-
//
|
|
23383
|
-
//
|
|
23700
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23701
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23702
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23703
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23704
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23705
|
+
// payload no longer counts.
|
|
23384
23706
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23385
23707
|
});
|
|
23386
23708
|
function defaultWorkspaceSettings() {
|
|
23387
23709
|
return WorkspaceSettings.parse({});
|
|
23388
23710
|
}
|
|
23711
|
+
function isAttached(settings) {
|
|
23712
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23713
|
+
}
|
|
23389
23714
|
function toInventoryRow(input2, id, now) {
|
|
23390
23715
|
return {
|
|
23391
23716
|
id,
|
|
@@ -23505,7 +23830,8 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23505
23830
|
"vaultKeyCustody",
|
|
23506
23831
|
"vaultInlineReveal",
|
|
23507
23832
|
"modelJudgeConsent",
|
|
23508
|
-
"dataSharesInPlace"
|
|
23833
|
+
"dataSharesInPlace",
|
|
23834
|
+
"redactFallback"
|
|
23509
23835
|
]).meta({ id: "ManagedSettingKey" });
|
|
23510
23836
|
var ManagedSettingsValues = external_exports.object({
|
|
23511
23837
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
@@ -23518,7 +23844,8 @@ var ManagedSettingsValues = external_exports.object({
|
|
|
23518
23844
|
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23519
23845
|
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23520
23846
|
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23521
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23847
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23848
|
+
redactFallback: RedactFallback.optional()
|
|
23522
23849
|
}).meta({ id: "ManagedSettingsValues" });
|
|
23523
23850
|
var ManagedSettings = external_exports.object({
|
|
23524
23851
|
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
@@ -23533,186 +23860,6 @@ var ManagedSettings = external_exports.object({
|
|
|
23533
23860
|
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23534
23861
|
}).meta({ id: "ManagedSettings" });
|
|
23535
23862
|
|
|
23536
|
-
// ../../packages/schema/src/zod/policy.ts
|
|
23537
|
-
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23538
|
-
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23539
|
-
var Policy = external_exports.object({
|
|
23540
|
-
id: external_exports.guid(),
|
|
23541
|
-
scope: PolicyScope,
|
|
23542
|
-
target: PolicyTarget,
|
|
23543
|
-
action: ActionTaken,
|
|
23544
|
-
enabled: external_exports.boolean().default(true),
|
|
23545
|
-
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23546
|
-
// Display name — optional so older policy rows without name still parse.
|
|
23547
|
-
// Added for the findings API (policy.name column migration).
|
|
23548
|
-
name: external_exports.string().optional()
|
|
23549
|
-
}).meta({ id: "Policy" });
|
|
23550
|
-
var PolicyBundle = external_exports.object({
|
|
23551
|
-
version: external_exports.string(),
|
|
23552
|
-
policies: external_exports.array(Policy),
|
|
23553
|
-
// Rules from the installed marketplace packs (snapshotted by the
|
|
23554
|
-
// control plane). The plugin registers these in addition to its bundled
|
|
23555
|
-
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23556
|
-
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23557
|
-
rules: external_exports.array(Rule).optional(),
|
|
23558
|
-
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23559
|
-
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23560
|
-
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23561
|
-
// packs only), which is how detection updates stay manual: new bundled
|
|
23562
|
-
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23563
|
-
// the historical composition (bundled packs + rules) — older caches.
|
|
23564
|
-
rulesComplete: external_exports.boolean().optional(),
|
|
23565
|
-
// Active detection exceptions, evaluation subset only (see
|
|
23566
|
-
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23567
|
-
// on-disk caches — that omit the field still parse; consumers read
|
|
23568
|
-
// `bundle.exceptions ?? []`.
|
|
23569
|
-
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23570
|
-
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23571
|
-
// A second axis over the same `redact` action, carried beside the policies
|
|
23572
|
-
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23573
|
-
// widening Policy itself would change a persisted shape to express something
|
|
23574
|
-
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23575
|
-
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23576
|
-
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23577
|
-
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23578
|
-
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23579
|
-
// from a versioned installed pack. Optional so older backends — and older
|
|
23580
|
-
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23581
|
-
// the rule's own spec version. NOT the bundle version above — see
|
|
23582
|
-
// installedRuleset's ruleVersions for the source of truth.
|
|
23583
|
-
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23584
|
-
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23585
|
-
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23586
|
-
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23587
|
-
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23588
|
-
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23589
|
-
// unenforced behaviour that predates this field and the safe direction to
|
|
23590
|
-
// default.
|
|
23591
|
-
//
|
|
23592
|
-
// Ids, not display names: the governance decision is keyed on the exact
|
|
23593
|
-
// string the harness reports (`model_status_override.versionId` in the
|
|
23594
|
-
// control plane), so no name resolution stands between the decision and the
|
|
23595
|
-
// comparison.
|
|
23596
|
-
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23597
|
-
customKeywords: external_exports.array(external_exports.string()),
|
|
23598
|
-
fetchedAt: external_exports.iso.datetime()
|
|
23599
|
-
}).meta({ id: "PolicyBundle" });
|
|
23600
|
-
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23601
|
-
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23602
|
-
var CATEGORY_PEAK_SEVERITY = {
|
|
23603
|
-
secret: "critical",
|
|
23604
|
-
financial: "critical",
|
|
23605
|
-
// core-financial/credit-card
|
|
23606
|
-
code_flaw: "critical",
|
|
23607
|
-
pii: "high",
|
|
23608
|
-
phi: "high",
|
|
23609
|
-
custom: "high",
|
|
23610
|
-
// user-defined; conservative
|
|
23611
|
-
code_context: "low",
|
|
23612
|
-
config: "low"
|
|
23613
|
-
// observe-only; floors to monitor regardless
|
|
23614
|
-
};
|
|
23615
|
-
function severityFloorPolicy(category) {
|
|
23616
|
-
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23617
|
-
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23618
|
-
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23619
|
-
}
|
|
23620
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23621
|
-
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23622
|
-
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23623
|
-
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23624
|
-
var BUILTIN_POLICY_SPECS = {
|
|
23625
|
-
monitor: {
|
|
23626
|
-
name: "Monitor",
|
|
23627
|
-
action: "log",
|
|
23628
|
-
reversible: false,
|
|
23629
|
-
description: "Log every match for audit. The request is allowed through untouched."
|
|
23630
|
-
},
|
|
23631
|
-
warn: {
|
|
23632
|
-
name: "Warn",
|
|
23633
|
-
action: "warn",
|
|
23634
|
-
reversible: false,
|
|
23635
|
-
description: "Allow the request, but warn the user inline before it is sent."
|
|
23636
|
-
},
|
|
23637
|
-
redact: {
|
|
23638
|
-
name: "Redact",
|
|
23639
|
-
action: "redact",
|
|
23640
|
-
reversible: false,
|
|
23641
|
-
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23642
|
-
},
|
|
23643
|
-
vault: {
|
|
23644
|
-
name: "Redact & Vault",
|
|
23645
|
-
action: "redact",
|
|
23646
|
-
reversible: true,
|
|
23647
|
-
description: "Strip the matched value from the request and keep an encrypted, recoverable copy in the local vault, leaving a pointer in its place. Needs the vault consent granted under Settings; without it this behaves as Redact."
|
|
23648
|
-
},
|
|
23649
|
-
block: {
|
|
23650
|
-
name: "Block",
|
|
23651
|
-
action: "block",
|
|
23652
|
-
reversible: false,
|
|
23653
|
-
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23654
|
-
}
|
|
23655
|
-
};
|
|
23656
|
-
function builtinPolicyToAction(id) {
|
|
23657
|
-
return BUILTIN_POLICY_SPECS[id].action;
|
|
23658
|
-
}
|
|
23659
|
-
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23660
|
-
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23661
|
-
);
|
|
23662
|
-
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23663
|
-
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23664
|
-
);
|
|
23665
|
-
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23666
|
-
function builtinPolicyIsReversible(id) {
|
|
23667
|
-
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23668
|
-
}
|
|
23669
|
-
function policyIdIsReversible(policyId) {
|
|
23670
|
-
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23671
|
-
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23672
|
-
return builtinPolicyIsReversible(id);
|
|
23673
|
-
}
|
|
23674
|
-
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23675
|
-
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23676
|
-
);
|
|
23677
|
-
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23678
|
-
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23679
|
-
);
|
|
23680
|
-
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23681
|
-
function policyIdToAction(policyId) {
|
|
23682
|
-
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23683
|
-
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23684
|
-
return BUILTIN_POLICIES[id].action;
|
|
23685
|
-
}
|
|
23686
|
-
var UsedByItem = external_exports.object({
|
|
23687
|
-
id: external_exports.string(),
|
|
23688
|
-
name: external_exports.string(),
|
|
23689
|
-
ruleCount: external_exports.number().int().nonnegative(),
|
|
23690
|
-
enabled: external_exports.boolean()
|
|
23691
|
-
}).meta({ id: "UsedByItem" });
|
|
23692
|
-
var PolicyListItem = external_exports.object({
|
|
23693
|
-
id: external_exports.string(),
|
|
23694
|
-
kind: PolicyKind,
|
|
23695
|
-
name: external_exports.string(),
|
|
23696
|
-
enabled: external_exports.boolean(),
|
|
23697
|
-
usedByCount: external_exports.number().int().nonnegative()
|
|
23698
|
-
}).meta({ id: "PolicyListItem" });
|
|
23699
|
-
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23700
|
-
var PolicyDetail = external_exports.object({
|
|
23701
|
-
specVersion: external_exports.literal(1),
|
|
23702
|
-
id: external_exports.string(),
|
|
23703
|
-
kind: PolicyKind,
|
|
23704
|
-
name: external_exports.string(),
|
|
23705
|
-
enabled: external_exports.boolean(),
|
|
23706
|
-
description: external_exports.string(),
|
|
23707
|
-
usedBy: external_exports.array(UsedByItem)
|
|
23708
|
-
}).meta({ id: "PolicyDetail" });
|
|
23709
|
-
var PolicyStatsResponse = external_exports.object({
|
|
23710
|
-
policies: external_exports.number().int().nonnegative(),
|
|
23711
|
-
builtin: external_exports.number().int().nonnegative(),
|
|
23712
|
-
custom: external_exports.number().int().nonnegative(),
|
|
23713
|
-
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23714
|
-
}).meta({ id: "PolicyStatsResponse" });
|
|
23715
|
-
|
|
23716
23863
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23717
23864
|
var ProjectFileInput = external_exports.object({
|
|
23718
23865
|
path: external_exports.string().min(1),
|
|
@@ -23958,10 +24105,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
23958
24105
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
23959
24106
|
|
|
23960
24107
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24108
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24109
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
23961
24110
|
var SaveSettingsInput = external_exports.object({
|
|
23962
24111
|
historicalAccess: external_exports.string(),
|
|
23963
|
-
modelJudgeConsent:
|
|
23964
|
-
historySyncConsent:
|
|
24112
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24113
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
23965
24114
|
vaultConsent: external_exports.string(),
|
|
23966
24115
|
vaultInlineReveal: external_exports.string()
|
|
23967
24116
|
});
|
|
@@ -24111,9 +24260,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24111
24260
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24112
24261
|
return reasons;
|
|
24113
24262
|
}
|
|
24114
|
-
function buildReviewInfo(trust, transports) {
|
|
24263
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24115
24264
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24116
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24265
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24117
24266
|
}
|
|
24118
24267
|
function distinctTransports(transports) {
|
|
24119
24268
|
return Array.from(new Set(transports));
|
|
@@ -24261,8 +24410,8 @@ function publishByLink(tmp, file2, data) {
|
|
|
24261
24410
|
}
|
|
24262
24411
|
|
|
24263
24412
|
// ../../packages/persistence/src/database.ts
|
|
24264
|
-
import { randomUUID as
|
|
24265
|
-
import { join as
|
|
24413
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24414
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24266
24415
|
import { DatabaseSync } from "node:sqlite";
|
|
24267
24416
|
|
|
24268
24417
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24506,6 +24655,10 @@ function allRows(stmt, params) {
|
|
|
24506
24655
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24507
24656
|
return stmt.all(params);
|
|
24508
24657
|
}
|
|
24658
|
+
function* iterateRows(stmt, params) {
|
|
24659
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24660
|
+
for (const row of rows) yield row;
|
|
24661
|
+
}
|
|
24509
24662
|
function getRow(stmt, params) {
|
|
24510
24663
|
if (params === void 0) return stmt.get();
|
|
24511
24664
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -24974,10 +25127,17 @@ function ensureSyncedAtColumn(db, table) {
|
|
|
24974
25127
|
if (!columns.includes("sync_claimed_at")) {
|
|
24975
25128
|
db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
|
|
24976
25129
|
}
|
|
25130
|
+
if (!columns.includes("outbox_owed")) {
|
|
25131
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
|
|
25132
|
+
}
|
|
24977
25133
|
db.exec(
|
|
24978
25134
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
24979
25135
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
24980
25136
|
);
|
|
25137
|
+
db.exec(
|
|
25138
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25139
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25140
|
+
);
|
|
24981
25141
|
db.exec(
|
|
24982
25142
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
24983
25143
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25082,7 +25242,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25082
25242
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25083
25243
|
var DAY_MS = 864e5;
|
|
25084
25244
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25085
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25086
25245
|
function defaultTimeZone() {
|
|
25087
25246
|
try {
|
|
25088
25247
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25137,6 +25296,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25137
25296
|
error: "error",
|
|
25138
25297
|
active: "active"
|
|
25139
25298
|
};
|
|
25299
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25140
25300
|
function safeParseStringArray(raw) {
|
|
25141
25301
|
if (!raw) return [];
|
|
25142
25302
|
const parsed = safeJson(raw, null);
|
|
@@ -25210,6 +25370,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25210
25370
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25211
25371
|
json_extract(attributes, '$.internal') AS internal,
|
|
25212
25372
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25373
|
+
var LLM_USAGE_SELECT = `
|
|
25374
|
+
SELECT root_session_id AS sessionId,
|
|
25375
|
+
provider,
|
|
25376
|
+
model,
|
|
25377
|
+
service_tier AS serviceTier,
|
|
25378
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25379
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25380
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25381
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25382
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25383
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25384
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25385
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25386
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25387
|
+
function usageLeaves(rows) {
|
|
25388
|
+
return rows.map((row) => {
|
|
25389
|
+
const attributes = {
|
|
25390
|
+
input_tokens: row.inputTokens,
|
|
25391
|
+
output_tokens: row.outputTokens,
|
|
25392
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25393
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25394
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25395
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25396
|
+
web_search_requests: row.webSearchRequests
|
|
25397
|
+
};
|
|
25398
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25399
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25400
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25401
|
+
return { sessionId: row.sessionId, attributes };
|
|
25402
|
+
});
|
|
25403
|
+
}
|
|
25213
25404
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25214
25405
|
var HAS_ACTIVITY = `EXISTS (
|
|
25215
25406
|
SELECT 1 FROM audit_events c
|
|
@@ -25235,16 +25426,17 @@ var SqliteActivityRepository = class {
|
|
|
25235
25426
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25236
25427
|
const liveNow = countScalar(
|
|
25237
25428
|
this.db,
|
|
25238
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25429
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25239
25430
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25240
|
-
AND
|
|
25241
|
-
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25247
|
-
|
|
25431
|
+
AND s.id IN (
|
|
25432
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25433
|
+
UNION
|
|
25434
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25435
|
+
WHERE started_at >= ?
|
|
25436
|
+
UNION
|
|
25437
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25438
|
+
WHERE ended_at >= ?)`,
|
|
25439
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25248
25440
|
);
|
|
25249
25441
|
const toolCallsToday = countScalar(
|
|
25250
25442
|
this.db,
|
|
@@ -25374,7 +25566,7 @@ var SqliteActivityRepository = class {
|
|
|
25374
25566
|
this.db.prepare(
|
|
25375
25567
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25376
25568
|
FROM audit_events
|
|
25377
|
-
WHERE id = ? OR root_session_id = ?
|
|
25569
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25378
25570
|
ORDER BY started_at ASC, id ASC`
|
|
25379
25571
|
),
|
|
25380
25572
|
[sessionId, sessionId]
|
|
@@ -25387,14 +25579,14 @@ var SqliteActivityRepository = class {
|
|
|
25387
25579
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25388
25580
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25389
25581
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25390
|
-
FROM audit_events
|
|
25582
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25391
25583
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25392
25584
|
),
|
|
25393
25585
|
[sessionId]
|
|
25394
25586
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25395
25587
|
const primaryModel = getRow(
|
|
25396
25588
|
this.db.prepare(
|
|
25397
|
-
`SELECT model, provider FROM audit_events
|
|
25589
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25398
25590
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25399
25591
|
ORDER BY started_at ASC, id ASC
|
|
25400
25592
|
LIMIT 1`
|
|
@@ -25405,7 +25597,7 @@ var SqliteActivityRepository = class {
|
|
|
25405
25597
|
this.db.prepare(
|
|
25406
25598
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25407
25599
|
count(*) AS n
|
|
25408
|
-
FROM audit_events
|
|
25600
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25409
25601
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25410
25602
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25411
25603
|
),
|
|
@@ -25413,7 +25605,7 @@ var SqliteActivityRepository = class {
|
|
|
25413
25605
|
);
|
|
25414
25606
|
const modelRows = allRows(
|
|
25415
25607
|
this.db.prepare(
|
|
25416
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25608
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25417
25609
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25418
25610
|
ORDER BY model`
|
|
25419
25611
|
),
|
|
@@ -25422,7 +25614,7 @@ var SqliteActivityRepository = class {
|
|
|
25422
25614
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25423
25615
|
const commits = countScalar(
|
|
25424
25616
|
this.db,
|
|
25425
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25617
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25426
25618
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25427
25619
|
[sessionId]
|
|
25428
25620
|
);
|
|
@@ -25458,25 +25650,57 @@ var SqliteActivityRepository = class {
|
|
|
25458
25650
|
return Promise.resolve(session);
|
|
25459
25651
|
}
|
|
25460
25652
|
/**
|
|
25461
|
-
* Cross-session token report — every `llm_call`
|
|
25462
|
-
* `started_at >= fromMs`
|
|
25463
|
-
* USD cost DERIVED at read time via the shared
|
|
25464
|
-
*
|
|
25465
|
-
*
|
|
25466
|
-
*
|
|
25653
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25654
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25655
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25656
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25657
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25658
|
+
*
|
|
25659
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25660
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25661
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25662
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25663
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25664
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25665
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25666
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25667
|
+
* recompute the columns it could have read. The index is one every open
|
|
25668
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25669
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25670
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25671
|
+
* per call, no bag parsed.
|
|
25467
25672
|
*/
|
|
25468
25673
|
tokenReports(fromMs) {
|
|
25469
|
-
const
|
|
25470
|
-
|
|
25674
|
+
const rows = allRows(
|
|
25675
|
+
this.db.prepare(
|
|
25676
|
+
`${LLM_USAGE_SELECT}
|
|
25677
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25678
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25679
|
+
${LLM_USAGE_GROUP}`
|
|
25680
|
+
),
|
|
25681
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25682
|
+
);
|
|
25683
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25471
25684
|
}
|
|
25472
25685
|
/**
|
|
25473
|
-
* One session's token report — its `llm_call`
|
|
25474
|
-
* model) with derived cost, or `null` when the session made no
|
|
25475
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25476
|
-
* breakdown + estimated cost.
|
|
25686
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25687
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25688
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25689
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25690
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25691
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25477
25692
|
*/
|
|
25478
25693
|
tokenReportForSession(sessionId) {
|
|
25479
|
-
const
|
|
25694
|
+
const rows = allRows(
|
|
25695
|
+
this.db.prepare(
|
|
25696
|
+
`${LLM_USAGE_SELECT}
|
|
25697
|
+
FROM audit_events
|
|
25698
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25699
|
+
${LLM_USAGE_GROUP}`
|
|
25700
|
+
),
|
|
25701
|
+
[sessionId]
|
|
25702
|
+
);
|
|
25703
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25480
25704
|
return Promise.resolve(reports[0] ?? null);
|
|
25481
25705
|
}
|
|
25482
25706
|
/**
|
|
@@ -25500,42 +25724,6 @@ var SqliteActivityRepository = class {
|
|
|
25500
25724
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25501
25725
|
return Promise.resolve([...seen]);
|
|
25502
25726
|
}
|
|
25503
|
-
/**
|
|
25504
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25505
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25506
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25507
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25508
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25509
|
-
*/
|
|
25510
|
-
readLlmCallLeaves(opts = {}) {
|
|
25511
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25512
|
-
const params = [];
|
|
25513
|
-
if (opts.sessionId !== void 0) {
|
|
25514
|
-
conditions.push("root_session_id = ?");
|
|
25515
|
-
params.push(opts.sessionId);
|
|
25516
|
-
}
|
|
25517
|
-
if (opts.fromMs !== void 0) {
|
|
25518
|
-
conditions.push("started_at >= ?");
|
|
25519
|
-
params.push(opts.fromMs);
|
|
25520
|
-
}
|
|
25521
|
-
const rows = allRows(
|
|
25522
|
-
this.db.prepare(
|
|
25523
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25524
|
-
FROM audit_events
|
|
25525
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25526
|
-
),
|
|
25527
|
-
params
|
|
25528
|
-
);
|
|
25529
|
-
return mapRowsTolerant(
|
|
25530
|
-
rows.filter(
|
|
25531
|
-
(row) => row.sessionId !== null
|
|
25532
|
-
),
|
|
25533
|
-
(row) => ({
|
|
25534
|
-
sessionId: row.sessionId,
|
|
25535
|
-
attributes: JSON.parse(row.attributes)
|
|
25536
|
-
})
|
|
25537
|
-
);
|
|
25538
|
-
}
|
|
25539
25727
|
/**
|
|
25540
25728
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25541
25729
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25550,20 +25738,23 @@ var SqliteActivityRepository = class {
|
|
|
25550
25738
|
const inClause = placeholders(sessionIds.length);
|
|
25551
25739
|
const lastActivityRows = allRows(
|
|
25552
25740
|
this.db.prepare(
|
|
25553
|
-
`SELECT
|
|
25554
|
-
|
|
25555
|
-
|
|
25741
|
+
`SELECT ids.value AS id,
|
|
25742
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25743
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25744
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25745
|
+
FROM json_each(?) AS ids`
|
|
25556
25746
|
),
|
|
25557
|
-
sessionIds
|
|
25747
|
+
[JSON.stringify(sessionIds)]
|
|
25558
25748
|
);
|
|
25559
25749
|
for (const row of lastActivityRows) {
|
|
25560
|
-
if (row.id === null) continue;
|
|
25561
25750
|
const entry = result.get(row.id);
|
|
25562
|
-
|
|
25751
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25752
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25563
25753
|
}
|
|
25564
25754
|
const turnsRows = allRows(
|
|
25565
25755
|
this.db.prepare(
|
|
25566
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25756
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25757
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25567
25758
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25568
25759
|
GROUP BY root_session_id`
|
|
25569
25760
|
),
|
|
@@ -25578,7 +25769,7 @@ var SqliteActivityRepository = class {
|
|
|
25578
25769
|
this.db.prepare(
|
|
25579
25770
|
`SELECT root_session_id AS id,
|
|
25580
25771
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25581
|
-
FROM audit_events
|
|
25772
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25582
25773
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25583
25774
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25584
25775
|
GROUP BY root_session_id`
|
|
@@ -25608,7 +25799,7 @@ var SqliteActivityRepository = class {
|
|
|
25608
25799
|
this.db.prepare(
|
|
25609
25800
|
`SELECT root_session_id AS id,
|
|
25610
25801
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25611
|
-
FROM audit_events
|
|
25802
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25612
25803
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25613
25804
|
GROUP BY root_session_id`
|
|
25614
25805
|
),
|
|
@@ -26637,7 +26828,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26637
26828
|
|
|
26638
26829
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26639
26830
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26640
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26641
26831
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26642
26832
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26643
26833
|
function compareLocationOrder(a, b) {
|
|
@@ -26666,6 +26856,25 @@ function deriveInstanceStatus(row) {
|
|
|
26666
26856
|
latestResolutionStatus: row.latest_status
|
|
26667
26857
|
});
|
|
26668
26858
|
}
|
|
26859
|
+
function toFlatFindingRow(r) {
|
|
26860
|
+
return {
|
|
26861
|
+
id: r.id,
|
|
26862
|
+
ruleId: r.rule_id,
|
|
26863
|
+
category: r.category,
|
|
26864
|
+
severity: r.severity,
|
|
26865
|
+
maskedMatch: r.masked_match,
|
|
26866
|
+
actionTaken: r.action_taken,
|
|
26867
|
+
confidence: r.confidence,
|
|
26868
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26869
|
+
sourceTool: r.source_tool,
|
|
26870
|
+
repo: r.repo ?? "",
|
|
26871
|
+
file: r.file ?? "",
|
|
26872
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26873
|
+
eventId: r.event_id,
|
|
26874
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26875
|
+
status: deriveInstanceStatus(r)
|
|
26876
|
+
};
|
|
26877
|
+
}
|
|
26669
26878
|
function encodeGroupCursor(group) {
|
|
26670
26879
|
const payload = {
|
|
26671
26880
|
sev: group.severity,
|
|
@@ -26741,7 +26950,7 @@ var SqliteFindingsRepository = class {
|
|
|
26741
26950
|
this.db.prepare(
|
|
26742
26951
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26743
26952
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26744
|
-
|
|
26953
|
+
e.source_tool AS source_tool,
|
|
26745
26954
|
e.event_type AS kind
|
|
26746
26955
|
FROM audit_events e
|
|
26747
26956
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26849,56 +27058,11 @@ var SqliteFindingsRepository = class {
|
|
|
26849
27058
|
predicate,
|
|
26850
27059
|
params: sessionParams
|
|
26851
27060
|
});
|
|
26852
|
-
const rows =
|
|
26853
|
-
|
|
26854
|
-
|
|
26855
|
-
|
|
26856
|
-
|
|
26857
|
-
FROM (
|
|
26858
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26859
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26860
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26861
|
-
e.started_at AS occurred_at,
|
|
26862
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26863
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26864
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26865
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26866
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26867
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26868
|
-
latest.status AS latest_status,
|
|
26869
|
-
ROW_NUMBER() OVER (
|
|
26870
|
-
PARTITION BY d.rule_id
|
|
26871
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26872
|
-
) AS rn
|
|
26873
|
-
FROM inspection_findings f
|
|
26874
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26875
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26876
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26877
|
-
ON latest.finding_key = f.finding_key
|
|
26878
|
-
${predicate}
|
|
26879
|
-
)
|
|
26880
|
-
WHERE rn <= :cap
|
|
26881
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26882
|
-
),
|
|
26883
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
26884
|
-
);
|
|
26885
|
-
const groupable = rows.map((r) => ({
|
|
26886
|
-
id: r.id,
|
|
26887
|
-
ruleId: r.rule_id,
|
|
26888
|
-
category: r.category,
|
|
26889
|
-
severity: r.severity,
|
|
26890
|
-
maskedMatch: r.masked_match,
|
|
26891
|
-
actionTaken: r.action_taken,
|
|
26892
|
-
confidence: r.confidence,
|
|
26893
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26894
|
-
sourceTool: r.source_tool,
|
|
26895
|
-
repo: r.repo ?? "",
|
|
26896
|
-
file: r.file ?? "",
|
|
26897
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26898
|
-
eventId: r.event_id,
|
|
26899
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26900
|
-
status: deriveInstanceStatus(r)
|
|
26901
|
-
}));
|
|
27061
|
+
const rows = this.previewRows(aggregates, {
|
|
27062
|
+
sessionId: query.sessionId,
|
|
27063
|
+
from: query.from
|
|
27064
|
+
});
|
|
27065
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
26902
27066
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
26903
27067
|
const filterOpts = {
|
|
26904
27068
|
severity: query.severity,
|
|
@@ -26984,8 +27148,10 @@ var SqliteFindingsRepository = class {
|
|
|
26984
27148
|
*
|
|
26985
27149
|
* The scan runs from the top of the scope on every request, not from the
|
|
26986
27150
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
26987
|
-
* move as the caller pages. Rows
|
|
26988
|
-
*
|
|
27151
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27152
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27153
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27154
|
+
* fetched batches; only the page itself is retained.
|
|
26989
27155
|
*/
|
|
26990
27156
|
listFindingInstances(query) {
|
|
26991
27157
|
const opts = {
|
|
@@ -27001,6 +27167,10 @@ var SqliteFindingsRepository = class {
|
|
|
27001
27167
|
};
|
|
27002
27168
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27003
27169
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27170
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27171
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27172
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27173
|
+
};
|
|
27004
27174
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27005
27175
|
const items = [];
|
|
27006
27176
|
let total = 0;
|
|
@@ -27013,6 +27183,7 @@ var SqliteFindingsRepository = class {
|
|
|
27013
27183
|
accumulator.add(row);
|
|
27014
27184
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27015
27185
|
total += 1;
|
|
27186
|
+
if (!isPastCursor(row)) continue;
|
|
27016
27187
|
if (items.length < limit) {
|
|
27017
27188
|
items.push(toInstanceDetail(row));
|
|
27018
27189
|
last = row;
|
|
@@ -27021,15 +27192,6 @@ var SqliteFindingsRepository = class {
|
|
|
27021
27192
|
}
|
|
27022
27193
|
}
|
|
27023
27194
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27024
|
-
if (cursor !== null) {
|
|
27025
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27026
|
-
return Promise.resolve({
|
|
27027
|
-
totals: { findings: total },
|
|
27028
|
-
facets: accumulator.facets(),
|
|
27029
|
-
items: resumed.items,
|
|
27030
|
-
nextCursor: resumed.nextCursor
|
|
27031
|
-
});
|
|
27032
|
-
}
|
|
27033
27195
|
return Promise.resolve({
|
|
27034
27196
|
totals: { findings: total },
|
|
27035
27197
|
facets: accumulator.facets(),
|
|
@@ -27037,35 +27199,6 @@ var SqliteFindingsRepository = class {
|
|
|
27037
27199
|
nextCursor
|
|
27038
27200
|
});
|
|
27039
27201
|
}
|
|
27040
|
-
/**
|
|
27041
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27042
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27043
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27044
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27045
|
-
*/
|
|
27046
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27047
|
-
const items = [];
|
|
27048
|
-
let last;
|
|
27049
|
-
let hasMore = false;
|
|
27050
|
-
for (const row of this.scanFindingRows({
|
|
27051
|
-
sessionId: query.sessionId,
|
|
27052
|
-
from: query.from,
|
|
27053
|
-
after: cursor
|
|
27054
|
-
})) {
|
|
27055
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27056
|
-
if (items.length < limit) {
|
|
27057
|
-
items.push(toInstanceDetail(row));
|
|
27058
|
-
last = row;
|
|
27059
|
-
} else {
|
|
27060
|
-
hasMore = true;
|
|
27061
|
-
break;
|
|
27062
|
-
}
|
|
27063
|
-
}
|
|
27064
|
-
return {
|
|
27065
|
-
items,
|
|
27066
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27067
|
-
};
|
|
27068
|
-
}
|
|
27069
27202
|
/**
|
|
27070
27203
|
* The same findings folded by location: repository, then file within it.
|
|
27071
27204
|
*
|
|
@@ -27148,25 +27281,111 @@ var SqliteFindingsRepository = class {
|
|
|
27148
27281
|
});
|
|
27149
27282
|
}
|
|
27150
27283
|
/**
|
|
27151
|
-
*
|
|
27284
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27285
|
+
*
|
|
27286
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27287
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27288
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27289
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27290
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27291
|
+
* store while the answer does not.
|
|
27292
|
+
*
|
|
27293
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27294
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27295
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27296
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27297
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27298
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27299
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27300
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27301
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27302
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27303
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27304
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27305
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27306
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27307
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27308
|
+
* sort of the scope, which is still that floor.
|
|
27309
|
+
*
|
|
27310
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27311
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27312
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27313
|
+
* built from.
|
|
27314
|
+
*/
|
|
27315
|
+
previewRows(aggregates, scope) {
|
|
27316
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27317
|
+
let remaining = 0;
|
|
27318
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27319
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27320
|
+
wanted.set(ruleId, n);
|
|
27321
|
+
remaining += n;
|
|
27322
|
+
}
|
|
27323
|
+
const rows = [];
|
|
27324
|
+
if (remaining === 0) return rows;
|
|
27325
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27326
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27327
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27328
|
+
const want = wanted.get(r.rule_id);
|
|
27329
|
+
if (want === void 0) continue;
|
|
27330
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27331
|
+
if (have >= want) continue;
|
|
27332
|
+
taken.set(r.rule_id, have + 1);
|
|
27333
|
+
rows.push(r);
|
|
27334
|
+
remaining -= 1;
|
|
27335
|
+
if (remaining === 0) break;
|
|
27336
|
+
}
|
|
27337
|
+
return rows;
|
|
27338
|
+
}
|
|
27339
|
+
/**
|
|
27340
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27152
27341
|
*
|
|
27153
27342
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27154
27343
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27155
|
-
* store is far more rows than any page.
|
|
27156
|
-
*
|
|
27157
|
-
*
|
|
27158
|
-
*
|
|
27159
|
-
*
|
|
27160
|
-
*
|
|
27161
|
-
* makes it a point lookup per row, and the derived table would re-materialize
|
|
27162
|
-
* a window over the whole resolution table once per batch.
|
|
27344
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27345
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27346
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27347
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27348
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27349
|
+
* batch and cost the square of the scope.
|
|
27163
27350
|
*
|
|
27164
|
-
* `
|
|
27165
|
-
* would be missing from its own facet, which is
|
|
27166
|
-
* dimension
|
|
27351
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27352
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27353
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27354
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27355
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27356
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27357
|
+
* page-2+ request would otherwise re-seek for.
|
|
27167
27358
|
*/
|
|
27168
27359
|
*scanFindingRows(scope) {
|
|
27169
|
-
const
|
|
27360
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27361
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27362
|
+
yield toFlatFindingRow(r);
|
|
27363
|
+
}
|
|
27364
|
+
}
|
|
27365
|
+
/**
|
|
27366
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27367
|
+
* joined to its event and definition, newest first.
|
|
27368
|
+
*
|
|
27369
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27370
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27371
|
+
*
|
|
27372
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27373
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27374
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27375
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27376
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27377
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27378
|
+
* ordered within the session — and the order falls out of the index.
|
|
27379
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27380
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27381
|
+
*
|
|
27382
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27383
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27384
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27385
|
+
* table would be materialized before the first row streamed.
|
|
27386
|
+
*/
|
|
27387
|
+
findingScanSql(scope) {
|
|
27388
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27170
27389
|
const params = [];
|
|
27171
27390
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27172
27391
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27180,58 +27399,24 @@ var SqliteFindingsRepository = class {
|
|
|
27180
27399
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27181
27400
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27182
27401
|
e.started_at AS occurred_at,
|
|
27183
|
-
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
|
|
27402
|
+
e.source_tool AS source_tool,
|
|
27403
|
+
e.repo AS repo,
|
|
27404
|
+
e.file_path AS file,
|
|
27405
|
+
e.tool_name AS tool_name,
|
|
27187
27406
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27188
27407
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27189
27408
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27190
|
-
FROM
|
|
27191
|
-
JOIN
|
|
27192
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27409
|
+
FROM audit_events e
|
|
27410
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27411
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27193
27412
|
WHERE ${conditions.join(" AND ")}
|
|
27194
|
-
|
|
27195
|
-
|
|
27196
|
-
LIMIT ?`;
|
|
27197
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27198
|
-
for (; ; ) {
|
|
27199
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27200
|
-
...params,
|
|
27201
|
-
after.startedAtMs,
|
|
27202
|
-
after.startedAtMs,
|
|
27203
|
-
after.id,
|
|
27204
|
-
SCAN_BATCH_ROWS
|
|
27205
|
-
]);
|
|
27206
|
-
for (const r of rows) {
|
|
27207
|
-
yield {
|
|
27208
|
-
id: r.id,
|
|
27209
|
-
ruleId: r.rule_id,
|
|
27210
|
-
category: r.category,
|
|
27211
|
-
severity: r.severity,
|
|
27212
|
-
maskedMatch: r.masked_match,
|
|
27213
|
-
actionTaken: r.action_taken,
|
|
27214
|
-
confidence: r.confidence,
|
|
27215
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27216
|
-
sourceTool: r.source_tool,
|
|
27217
|
-
repo: r.repo ?? "",
|
|
27218
|
-
file: r.file ?? "",
|
|
27219
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27220
|
-
eventId: r.event_id,
|
|
27221
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27222
|
-
status: deriveInstanceStatus(r)
|
|
27223
|
-
};
|
|
27224
|
-
}
|
|
27225
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27226
|
-
const lastRow = rows[rows.length - 1];
|
|
27227
|
-
if (lastRow === void 0) return;
|
|
27228
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27229
|
-
}
|
|
27413
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27414
|
+
return { sql, params };
|
|
27230
27415
|
}
|
|
27231
27416
|
groupAggregates(withSearchText, scope) {
|
|
27232
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27233
|
-
group_concat(DISTINCT
|
|
27234
|
-
group_concat(DISTINCT 'via ' ||
|
|
27417
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27418
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27419
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27235
27420
|
const rows = this.db.prepare(
|
|
27236
27421
|
`SELECT rule_id,
|
|
27237
27422
|
sum(tuple_count) AS instance_count,
|
|
@@ -27249,7 +27434,7 @@ var SqliteFindingsRepository = class {
|
|
|
27249
27434
|
coalesce(latest.status, '') AS status_tuple,
|
|
27250
27435
|
count(*) AS tuple_count,
|
|
27251
27436
|
max(e.started_at) AS latest_at,
|
|
27252
|
-
group_concat(DISTINCT
|
|
27437
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27253
27438
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27254
27439
|
${innerSearchColumns}
|
|
27255
27440
|
FROM inspection_findings f
|
|
@@ -27380,6 +27565,8 @@ function isoDay(ms) {
|
|
|
27380
27565
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27381
27566
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27382
27567
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27568
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27569
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27383
27570
|
var SKIPPED = -1;
|
|
27384
27571
|
var ROW_COLUMNS = `id,
|
|
27385
27572
|
parent_id AS parentId,
|
|
@@ -27419,6 +27606,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27419
27606
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27420
27607
|
LIMIT :limit`
|
|
27421
27608
|
);
|
|
27609
|
+
this.captureRowsStmt = db.prepare(
|
|
27610
|
+
`SELECT ${ROW_COLUMNS}
|
|
27611
|
+
FROM audit_events
|
|
27612
|
+
WHERE synced_at IS NULL
|
|
27613
|
+
AND sync_claimed_at IS NULL
|
|
27614
|
+
AND outbox_owed = 1
|
|
27615
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27616
|
+
AND started_at < :before
|
|
27617
|
+
ORDER BY started_at
|
|
27618
|
+
LIMIT :limit`
|
|
27619
|
+
);
|
|
27620
|
+
this.markOwedStmt = db.prepare(
|
|
27621
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27622
|
+
);
|
|
27422
27623
|
this.stampStmt = db.prepare(
|
|
27423
27624
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27424
27625
|
);
|
|
@@ -27450,6 +27651,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27450
27651
|
FROM audit_events
|
|
27451
27652
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27452
27653
|
);
|
|
27654
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27655
|
+
`SELECT COUNT(*) AS skipped
|
|
27656
|
+
FROM audit_events
|
|
27657
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27658
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27659
|
+
);
|
|
27453
27660
|
this.fingerprintStmt = db.prepare(
|
|
27454
27661
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27455
27662
|
FROM history_sync WHERE id = 1`
|
|
@@ -27459,6 +27666,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27459
27666
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27460
27667
|
WHERE id = 1`
|
|
27461
27668
|
);
|
|
27669
|
+
this.disownCapturesStmt = db.prepare(
|
|
27670
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27671
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27672
|
+
);
|
|
27462
27673
|
this.rearmStmt = db.prepare(
|
|
27463
27674
|
`UPDATE audit_events SET synced_at = NULL
|
|
27464
27675
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27531,6 +27742,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27531
27742
|
closeWindowStmt;
|
|
27532
27743
|
releaseBoundaryStmt;
|
|
27533
27744
|
freezeBoundaryStmt;
|
|
27745
|
+
captureRowsStmt;
|
|
27746
|
+
markOwedStmt;
|
|
27747
|
+
captureSkipCountStmt;
|
|
27748
|
+
disownCapturesStmt;
|
|
27534
27749
|
partitionStmt;
|
|
27535
27750
|
claimRowStmt;
|
|
27536
27751
|
releaseRowStmt;
|
|
@@ -27564,6 +27779,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27564
27779
|
pendingRows(sessionId, limit, before) {
|
|
27565
27780
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27566
27781
|
}
|
|
27782
|
+
/**
|
|
27783
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27784
|
+
*
|
|
27785
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27786
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27787
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27788
|
+
* the live path.
|
|
27789
|
+
*/
|
|
27790
|
+
pendingCaptureRows(limit, before) {
|
|
27791
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27792
|
+
}
|
|
27793
|
+
/**
|
|
27794
|
+
* Record that a capture is OWED to the deployment.
|
|
27795
|
+
*
|
|
27796
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27797
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27798
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27799
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27800
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27801
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27802
|
+
* offered to anyone.
|
|
27803
|
+
*
|
|
27804
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27805
|
+
* out of the drain's read.
|
|
27806
|
+
*/
|
|
27807
|
+
markCaptureOwed(id) {
|
|
27808
|
+
this.markOwedStmt.run({ id });
|
|
27809
|
+
}
|
|
27567
27810
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27568
27811
|
markSynced(ids, atMs) {
|
|
27569
27812
|
this.stampAll(ids, atMs);
|
|
@@ -27647,10 +27890,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27647
27890
|
this.countsStmt,
|
|
27648
27891
|
{ before }
|
|
27649
27892
|
);
|
|
27893
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27650
27894
|
return {
|
|
27651
27895
|
pending: row?.pending ?? 0,
|
|
27652
27896
|
sent: row?.sent ?? 0,
|
|
27653
|
-
skipped: row?.skipped ?? 0
|
|
27897
|
+
skipped: row?.skipped ?? 0,
|
|
27898
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27654
27899
|
};
|
|
27655
27900
|
}
|
|
27656
27901
|
/**
|
|
@@ -27691,7 +27936,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27691
27936
|
withTransaction(
|
|
27692
27937
|
this.db,
|
|
27693
27938
|
() => {
|
|
27939
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27694
27940
|
this.rearmStmt.run();
|
|
27941
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
27942
|
+
this.disownCapturesStmt.run();
|
|
27943
|
+
}
|
|
27695
27944
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27696
27945
|
},
|
|
27697
27946
|
"IMMEDIATE"
|
|
@@ -27888,7 +28137,255 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
27888
28137
|
};
|
|
27889
28138
|
|
|
27890
28139
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
27891
|
-
import { createHash as createHash2, randomUUID as
|
|
28140
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28141
|
+
|
|
28142
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28143
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28144
|
+
import { join as join6 } from "path";
|
|
28145
|
+
|
|
28146
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28147
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28148
|
+
import { mkdir } from "fs/promises";
|
|
28149
|
+
import { homedir } from "os";
|
|
28150
|
+
import { join as join4 } from "path";
|
|
28151
|
+
function defaultDataDir() {
|
|
28152
|
+
return join4(homedir(), ".aka");
|
|
28153
|
+
}
|
|
28154
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28155
|
+
return join4(base, "settings");
|
|
28156
|
+
}
|
|
28157
|
+
function dataDir(base = defaultDataDir()) {
|
|
28158
|
+
return join4(base, "data");
|
|
28159
|
+
}
|
|
28160
|
+
function dbPath(base = defaultDataDir()) {
|
|
28161
|
+
return join4(dataDir(base), "aka.db");
|
|
28162
|
+
}
|
|
28163
|
+
function keysDir(base = defaultDataDir()) {
|
|
28164
|
+
return join4(base, "keys");
|
|
28165
|
+
}
|
|
28166
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28167
|
+
ensureDataDirSync(dir);
|
|
28168
|
+
}
|
|
28169
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28170
|
+
const moves = [
|
|
28171
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28172
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28173
|
+
];
|
|
28174
|
+
for (const { name, dest } of moves) {
|
|
28175
|
+
try {
|
|
28176
|
+
ensureDataDirSync(dest);
|
|
28177
|
+
const moved = join4(dest, name);
|
|
28178
|
+
renameSync3(join4(base, name), moved);
|
|
28179
|
+
tightenFile(moved);
|
|
28180
|
+
} catch {
|
|
28181
|
+
}
|
|
28182
|
+
}
|
|
28183
|
+
}
|
|
28184
|
+
|
|
28185
|
+
// ../../packages/persistence/src/settings.ts
|
|
28186
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28187
|
+
import { join as join5 } from "path";
|
|
28188
|
+
|
|
28189
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28190
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28191
|
+
import {
|
|
28192
|
+
closeSync,
|
|
28193
|
+
existsSync as existsSync2,
|
|
28194
|
+
openSync,
|
|
28195
|
+
readFileSync as readFileSync2,
|
|
28196
|
+
rmSync as rmSync5,
|
|
28197
|
+
statSync as statSync3,
|
|
28198
|
+
writeFileSync as writeFileSync2
|
|
28199
|
+
} from "fs";
|
|
28200
|
+
import { hostname as hostname3 } from "os";
|
|
28201
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28202
|
+
|
|
28203
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28204
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28205
|
+
import { posix, win32 } from "path";
|
|
28206
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28207
|
+
if (platform2 === "darwin") {
|
|
28208
|
+
return [
|
|
28209
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28210
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28211
|
+
];
|
|
28212
|
+
}
|
|
28213
|
+
if (platform2 === "win32") {
|
|
28214
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28215
|
+
}
|
|
28216
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28217
|
+
}
|
|
28218
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28219
|
+
for (const path of paths) {
|
|
28220
|
+
let text;
|
|
28221
|
+
try {
|
|
28222
|
+
text = readFileSync3(path, "utf8");
|
|
28223
|
+
} catch {
|
|
28224
|
+
continue;
|
|
28225
|
+
}
|
|
28226
|
+
const record2 = parseJsonObject(text);
|
|
28227
|
+
if (!record2) continue;
|
|
28228
|
+
const parsed = ManagedSettings.safeParse(record2);
|
|
28229
|
+
if (parsed.success) return parsed.data;
|
|
28230
|
+
}
|
|
28231
|
+
return null;
|
|
28232
|
+
}
|
|
28233
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28234
|
+
if (!managed) return settings;
|
|
28235
|
+
const { values } = managed;
|
|
28236
|
+
const merged = { ...settings };
|
|
28237
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28238
|
+
if (values.controlPlane !== void 0) {
|
|
28239
|
+
merged.controlPlane = {
|
|
28240
|
+
...values.controlPlane,
|
|
28241
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28242
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28243
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28244
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28245
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28246
|
+
};
|
|
28247
|
+
}
|
|
28248
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28249
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28250
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28251
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28252
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28253
|
+
if (values.vaultConsent !== void 0) {
|
|
28254
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28255
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28256
|
+
// at the current version otherwise.
|
|
28257
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28258
|
+
) : void 0;
|
|
28259
|
+
}
|
|
28260
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28261
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28262
|
+
acknowledgedAt: now().toISOString(),
|
|
28263
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28264
|
+
} : void 0;
|
|
28265
|
+
}
|
|
28266
|
+
return merged;
|
|
28267
|
+
}
|
|
28268
|
+
|
|
28269
|
+
// ../../packages/persistence/src/settings.ts
|
|
28270
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28271
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28272
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28273
|
+
}
|
|
28274
|
+
function readUserSettings(base) {
|
|
28275
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28276
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28277
|
+
try {
|
|
28278
|
+
return WorkspaceSettings.parse(record2);
|
|
28279
|
+
} catch {
|
|
28280
|
+
return defaultWorkspaceSettings();
|
|
28281
|
+
}
|
|
28282
|
+
}
|
|
28283
|
+
function readJson(file2) {
|
|
28284
|
+
let text;
|
|
28285
|
+
try {
|
|
28286
|
+
text = readFileSync4(file2, "utf8");
|
|
28287
|
+
} catch {
|
|
28288
|
+
return null;
|
|
28289
|
+
}
|
|
28290
|
+
return parseJsonObject(text) ?? null;
|
|
28291
|
+
}
|
|
28292
|
+
|
|
28293
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28294
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28295
|
+
switch (refusal) {
|
|
28296
|
+
case "lock":
|
|
28297
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28298
|
+
case "disable":
|
|
28299
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28300
|
+
case "floor":
|
|
28301
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28302
|
+
}
|
|
28303
|
+
}
|
|
28304
|
+
var PolicyFloorError = class extends Error {
|
|
28305
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28306
|
+
pack;
|
|
28307
|
+
/**
|
|
28308
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28309
|
+
* clearing the assignment, or switching the detection off.
|
|
28310
|
+
*/
|
|
28311
|
+
attempted;
|
|
28312
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28313
|
+
floor;
|
|
28314
|
+
refusal;
|
|
28315
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28316
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28317
|
+
this.name = "PolicyFloorError";
|
|
28318
|
+
this.pack = pack;
|
|
28319
|
+
this.attempted = attempted;
|
|
28320
|
+
this.floor = floor;
|
|
28321
|
+
this.refusal = refusal;
|
|
28322
|
+
}
|
|
28323
|
+
};
|
|
28324
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28325
|
+
try {
|
|
28326
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28327
|
+
const parsed = JSON.parse(raw);
|
|
28328
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
28329
|
+
return PolicyBundle.parse(parsed.bundle);
|
|
28330
|
+
} catch {
|
|
28331
|
+
return null;
|
|
28332
|
+
}
|
|
28333
|
+
}
|
|
28334
|
+
function indexEnabled(policies) {
|
|
28335
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28336
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28337
|
+
for (const policy of policies) {
|
|
28338
|
+
if (!policy.enabled) continue;
|
|
28339
|
+
if ("ruleId" in policy.target) {
|
|
28340
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28341
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28342
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28343
|
+
}
|
|
28344
|
+
}
|
|
28345
|
+
return { byRuleId, byCategory };
|
|
28346
|
+
}
|
|
28347
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28348
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28349
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28350
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28351
|
+
return policies.some((policy) => {
|
|
28352
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28353
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28354
|
+
});
|
|
28355
|
+
}
|
|
28356
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28357
|
+
const floors = openControlPlaneFloors(base);
|
|
28358
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28359
|
+
}
|
|
28360
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28361
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28362
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28363
|
+
if (bundle === null) return null;
|
|
28364
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28365
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28366
|
+
}
|
|
28367
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28368
|
+
let action = null;
|
|
28369
|
+
for (const rule of rules) {
|
|
28370
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28371
|
+
if (resolved === void 0) continue;
|
|
28372
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28373
|
+
}
|
|
28374
|
+
if (action === null) return null;
|
|
28375
|
+
return {
|
|
28376
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28377
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28378
|
+
};
|
|
28379
|
+
}
|
|
28380
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28381
|
+
if (floor.locked) return "lock";
|
|
28382
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28383
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28384
|
+
}
|
|
28385
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28386
|
+
if (floor === null || enabled) return null;
|
|
28387
|
+
return "disable";
|
|
28388
|
+
}
|
|
27892
28389
|
|
|
27893
28390
|
// ../../packages/persistence/src/semver.ts
|
|
27894
28391
|
function parse3(version2) {
|
|
@@ -27982,8 +28479,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
27982
28479
|
return ids;
|
|
27983
28480
|
}
|
|
27984
28481
|
var SqliteInstalledPacksRepository = class {
|
|
27985
|
-
|
|
28482
|
+
/**
|
|
28483
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28484
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28485
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28486
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28487
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28488
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28489
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28490
|
+
* real `~/.aka`.
|
|
28491
|
+
*/
|
|
28492
|
+
constructor(db, baseDir) {
|
|
27986
28493
|
this.db = db;
|
|
28494
|
+
this.baseDir = baseDir;
|
|
27987
28495
|
this.insertMissingStmt = db.prepare(
|
|
27988
28496
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
27989
28497
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28005,11 +28513,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28005
28513
|
this.signatureStmt = db.prepare(
|
|
28006
28514
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28007
28515
|
);
|
|
28516
|
+
this.packRulesStmt = db.prepare(
|
|
28517
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28518
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28519
|
+
);
|
|
28008
28520
|
}
|
|
28009
28521
|
db;
|
|
28522
|
+
baseDir;
|
|
28010
28523
|
insertMissingStmt;
|
|
28011
28524
|
upsertAvailableStmt;
|
|
28012
28525
|
signatureStmt;
|
|
28526
|
+
packRulesStmt;
|
|
28013
28527
|
/**
|
|
28014
28528
|
* Record the running binary's detection inventory. Refreshes the
|
|
28015
28529
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28051,7 +28565,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28051
28565
|
let behind = false;
|
|
28052
28566
|
for (const row of rows) {
|
|
28053
28567
|
const params = {
|
|
28054
|
-
id:
|
|
28568
|
+
id: randomUUID4(),
|
|
28055
28569
|
namespace: row.namespace,
|
|
28056
28570
|
packId: row.packId,
|
|
28057
28571
|
version: row.version,
|
|
@@ -28063,7 +28577,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28063
28577
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28064
28578
|
this.upsertAvailableStmt.run({
|
|
28065
28579
|
...params,
|
|
28066
|
-
id:
|
|
28580
|
+
id: randomUUID4(),
|
|
28067
28581
|
recordedBy: meta4?.recordedBy ?? null
|
|
28068
28582
|
});
|
|
28069
28583
|
} else {
|
|
@@ -28309,9 +28823,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28309
28823
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28310
28824
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28311
28825
|
// caller can tell an edit from a no-such-detection.
|
|
28826
|
+
/**
|
|
28827
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
28828
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
28829
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
28830
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
28831
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
28832
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
28833
|
+
*/
|
|
28834
|
+
packFloorRules(namespace, packId) {
|
|
28835
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
28836
|
+
if (!row) return [];
|
|
28837
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
28838
|
+
}
|
|
28839
|
+
/**
|
|
28840
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
28841
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
28842
|
+
* repository constructed without a layout base).
|
|
28843
|
+
*
|
|
28844
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
28845
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
28846
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
28847
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
28848
|
+
*/
|
|
28849
|
+
policyFloor(namespace, packId) {
|
|
28850
|
+
if (this.baseDir === void 0) return null;
|
|
28851
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
28852
|
+
}
|
|
28853
|
+
/**
|
|
28854
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
28855
|
+
* entry only for a pack the control plane actually governs.
|
|
28856
|
+
*
|
|
28857
|
+
* A surface listing every detection asks per pack, and asking through
|
|
28858
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
28859
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
28860
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
28861
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
28862
|
+
* exactly as the single-pack read returns null for them.
|
|
28863
|
+
*/
|
|
28864
|
+
policyFloors(packs2) {
|
|
28865
|
+
const floors = /* @__PURE__ */ new Map();
|
|
28866
|
+
if (this.baseDir === void 0) return floors;
|
|
28867
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
28868
|
+
if (source === null) return floors;
|
|
28869
|
+
for (const pack of packs2) {
|
|
28870
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
28871
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
28872
|
+
}
|
|
28873
|
+
return floors;
|
|
28874
|
+
}
|
|
28312
28875
|
/**
|
|
28313
28876
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28314
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
28877
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
28878
|
+
*
|
|
28879
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
28880
|
+
* write below, and a detection the organization has authored a policy for is
|
|
28881
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
28882
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
28883
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
28884
|
+
* surface that offers the choice.
|
|
28315
28885
|
*/
|
|
28316
28886
|
setPolicy(namespace, packId, policyId) {
|
|
28317
28887
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28319,14 +28889,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28319
28889
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28320
28890
|
);
|
|
28321
28891
|
}
|
|
28892
|
+
const requested = policyId;
|
|
28893
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28894
|
+
if (floor !== null) {
|
|
28895
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
28896
|
+
if (refusal !== null) {
|
|
28897
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
28898
|
+
}
|
|
28899
|
+
}
|
|
28322
28900
|
const res = this.db.prepare(
|
|
28323
28901
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28324
28902
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28325
28903
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28326
28904
|
return Number(res.changes) > 0;
|
|
28327
28905
|
}
|
|
28328
|
-
/**
|
|
28906
|
+
/**
|
|
28907
|
+
* Enable or disable one installed pack.
|
|
28908
|
+
*
|
|
28909
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
28910
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
28911
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
28912
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
28913
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
28914
|
+
* that lived in a page would leave the CLI free.
|
|
28915
|
+
*/
|
|
28329
28916
|
setEnabled(namespace, packId, enabled) {
|
|
28917
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28918
|
+
if (floor !== null) {
|
|
28919
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
28920
|
+
if (refusal !== null) {
|
|
28921
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
28922
|
+
}
|
|
28923
|
+
}
|
|
28330
28924
|
const res = this.db.prepare(
|
|
28331
28925
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28332
28926
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28412,7 +29006,7 @@ var SqliteInventoryRepository = class {
|
|
|
28412
29006
|
};
|
|
28413
29007
|
|
|
28414
29008
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28415
|
-
import { randomUUID as
|
|
29009
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28416
29010
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28417
29011
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28418
29012
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -28901,7 +29495,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28901
29495
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
28902
29496
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
28903
29497
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
28904
|
-
).run({ id:
|
|
29498
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
28905
29499
|
}
|
|
28906
29500
|
return true;
|
|
28907
29501
|
}
|
|
@@ -28922,7 +29516,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28922
29516
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
28923
29517
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
28924
29518
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
28925
|
-
).run({ id:
|
|
29519
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
28926
29520
|
}
|
|
28927
29521
|
this.configRowsCache = void 0;
|
|
28928
29522
|
return "ok";
|
|
@@ -29219,7 +29813,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29219
29813
|
};
|
|
29220
29814
|
|
|
29221
29815
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29222
|
-
import { randomUUID as
|
|
29816
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29223
29817
|
var SqlitePoliciesRepository = class {
|
|
29224
29818
|
constructor(db) {
|
|
29225
29819
|
this.db = db;
|
|
@@ -29254,7 +29848,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29254
29848
|
failOpenTransaction(this.db, () => {
|
|
29255
29849
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29256
29850
|
stmt.run({
|
|
29257
|
-
id:
|
|
29851
|
+
id: randomUUID6(),
|
|
29258
29852
|
target: JSON.stringify({ category }),
|
|
29259
29853
|
action,
|
|
29260
29854
|
now: Date.now()
|
|
@@ -29274,7 +29868,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29274
29868
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29275
29869
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29276
29870
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29277
|
-
).run({ id:
|
|
29871
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29278
29872
|
}
|
|
29279
29873
|
// Caps every global per-category policy currently set to block/redact down
|
|
29280
29874
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29342,7 +29936,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29342
29936
|
};
|
|
29343
29937
|
|
|
29344
29938
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29345
|
-
import { randomUUID as
|
|
29939
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29346
29940
|
var SqliteProjectFilesRepository = class {
|
|
29347
29941
|
constructor(db) {
|
|
29348
29942
|
this.db = db;
|
|
@@ -29374,7 +29968,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29374
29968
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29375
29969
|
for (const file2 of scan2.files) {
|
|
29376
29970
|
this.upsertStmt.run({
|
|
29377
|
-
id:
|
|
29971
|
+
id: randomUUID7(),
|
|
29378
29972
|
projectId,
|
|
29379
29973
|
path: file2.path,
|
|
29380
29974
|
name: file2.name,
|
|
@@ -29388,9 +29982,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29388
29982
|
};
|
|
29389
29983
|
|
|
29390
29984
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29391
|
-
import { randomUUID as
|
|
29985
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29392
29986
|
var SqliteResolutionsRepository = class {
|
|
29393
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
29987
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29394
29988
|
this.db = db;
|
|
29395
29989
|
this.now = now;
|
|
29396
29990
|
this.newId = newId;
|
|
@@ -29603,7 +30197,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29603
30197
|
};
|
|
29604
30198
|
|
|
29605
30199
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29606
|
-
import { randomUUID as
|
|
30200
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29607
30201
|
function pageLimit(requested, fallback) {
|
|
29608
30202
|
if (requested === void 0) return fallback;
|
|
29609
30203
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29649,12 +30243,14 @@ var SELECT_COLUMNS = `
|
|
|
29649
30243
|
ciphertext,
|
|
29650
30244
|
nonce,
|
|
29651
30245
|
auth_tag AS authTag,
|
|
30246
|
+
user_authorized AS userAuthorized,
|
|
29652
30247
|
occurrence_count AS occurrenceCount,
|
|
29653
30248
|
first_seen AS firstSeen,
|
|
29654
30249
|
last_seen AS lastSeen`;
|
|
29655
30250
|
function toRow(raw) {
|
|
29656
|
-
const { provider, ...rest } = raw;
|
|
29657
|
-
|
|
30251
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30252
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30253
|
+
return provider === null ? row : { ...row, provider };
|
|
29658
30254
|
}
|
|
29659
30255
|
var SqliteSecretVaultRepository = class {
|
|
29660
30256
|
constructor(db) {
|
|
@@ -29664,17 +30260,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29664
30260
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29665
30261
|
format_version, category, rule_id, masked_match, provider,
|
|
29666
30262
|
ciphertext, nonce, auth_tag,
|
|
29667
|
-
occurrence_count, first_seen, last_seen
|
|
30263
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29668
30264
|
) VALUES (
|
|
29669
30265
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29670
30266
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29671
30267
|
:ciphertext, :nonce, :authTag,
|
|
29672
|
-
1, :now, :now
|
|
30268
|
+
:userAuthorized, 1, :now, :now
|
|
29673
30269
|
)`
|
|
29674
30270
|
);
|
|
29675
30271
|
this.bumpStmt = db.prepare(
|
|
29676
30272
|
`UPDATE secret_vault
|
|
29677
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30273
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30274
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29678
30275
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29679
30276
|
);
|
|
29680
30277
|
this.byPointerStmt = db.prepare(
|
|
@@ -29694,6 +30291,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29694
30291
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29695
30292
|
WHERE pointer_id = :pointerId`
|
|
29696
30293
|
);
|
|
30294
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29697
30295
|
this.derefStmt = db.prepare(
|
|
29698
30296
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29699
30297
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29707,6 +30305,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29707
30305
|
listStmt;
|
|
29708
30306
|
replaceCiphertextStmt;
|
|
29709
30307
|
refreshFingerprintStmt;
|
|
30308
|
+
deleteByPointerStmt;
|
|
29710
30309
|
derefStmt;
|
|
29711
30310
|
/**
|
|
29712
30311
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29715,6 +30314,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29715
30314
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29716
30315
|
* wire token. `minted` is true only when this call created the row.
|
|
29717
30316
|
*
|
|
30317
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30318
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30319
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30320
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30321
|
+
*
|
|
29718
30322
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29719
30323
|
* writers cannot both decide they are minting.
|
|
29720
30324
|
*/
|
|
@@ -29741,13 +30345,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29741
30345
|
ciphertext: input2.ciphertext,
|
|
29742
30346
|
nonce: input2.nonce,
|
|
29743
30347
|
authTag: input2.authTag,
|
|
30348
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29744
30349
|
now
|
|
29745
30350
|
})
|
|
29746
30351
|
);
|
|
29747
30352
|
minted = true;
|
|
29748
30353
|
return;
|
|
29749
30354
|
}
|
|
29750
|
-
this.bumpStmt.run({
|
|
30355
|
+
this.bumpStmt.run({
|
|
30356
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30357
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30358
|
+
now
|
|
30359
|
+
});
|
|
29751
30360
|
},
|
|
29752
30361
|
"IMMEDIATE"
|
|
29753
30362
|
);
|
|
@@ -29807,6 +30416,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29807
30416
|
);
|
|
29808
30417
|
return destroyed;
|
|
29809
30418
|
}
|
|
30419
|
+
/**
|
|
30420
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30421
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30422
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30423
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30424
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30425
|
+
* it.
|
|
30426
|
+
*
|
|
30427
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30428
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30429
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30430
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30431
|
+
* entry still sitting in the vault.
|
|
30432
|
+
*
|
|
30433
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30434
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30435
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30436
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30437
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30438
|
+
*/
|
|
30439
|
+
deleteByPointerIds(pointerIds) {
|
|
30440
|
+
if (pointerIds.length === 0) return [];
|
|
30441
|
+
const deleted = [];
|
|
30442
|
+
withTransaction(
|
|
30443
|
+
this.db,
|
|
30444
|
+
() => {
|
|
30445
|
+
for (const pointerId of pointerIds) {
|
|
30446
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30447
|
+
deleted.push(pointerId);
|
|
30448
|
+
}
|
|
30449
|
+
}
|
|
30450
|
+
},
|
|
30451
|
+
"IMMEDIATE"
|
|
30452
|
+
);
|
|
30453
|
+
return deleted;
|
|
30454
|
+
}
|
|
29810
30455
|
/**
|
|
29811
30456
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29812
30457
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29819,7 +30464,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29819
30464
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29820
30465
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29821
30466
|
).run({
|
|
29822
|
-
id:
|
|
30467
|
+
id: randomUUID9(),
|
|
29823
30468
|
pointerId: entry.pointerId,
|
|
29824
30469
|
location: entry.location,
|
|
29825
30470
|
kind: entry.kind,
|
|
@@ -30332,15 +30977,15 @@ var SqliteSecurityRepository = class {
|
|
|
30332
30977
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30333
30978
|
const rows = allRows(
|
|
30334
30979
|
this.db.prepare(
|
|
30335
|
-
`SELECT
|
|
30980
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30336
30981
|
FROM inspection_findings f
|
|
30337
30982
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30338
30983
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30339
30984
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30340
|
-
AND
|
|
30341
|
-
AND
|
|
30342
|
-
GROUP BY repo
|
|
30343
|
-
ORDER BY c DESC, repo
|
|
30985
|
+
AND e.repo IS NOT NULL
|
|
30986
|
+
AND e.repo != ''
|
|
30987
|
+
GROUP BY e.repo
|
|
30988
|
+
ORDER BY c DESC, e.repo
|
|
30344
30989
|
LIMIT :limit`
|
|
30345
30990
|
),
|
|
30346
30991
|
{ from, to: now, limit }
|
|
@@ -30402,7 +31047,7 @@ var SqliteSecurityRepository = class {
|
|
|
30402
31047
|
`SELECT f.finding_key AS finding_key,
|
|
30403
31048
|
d.rule_id AS rule_id,
|
|
30404
31049
|
d.severity AS severity,
|
|
30405
|
-
|
|
31050
|
+
e.file_path AS path,
|
|
30406
31051
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30407
31052
|
latest.resolved_at AS latest_resolved_at
|
|
30408
31053
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30455,7 +31100,7 @@ var SqliteSecurityRepository = class {
|
|
|
30455
31100
|
};
|
|
30456
31101
|
|
|
30457
31102
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30458
|
-
import { randomUUID as
|
|
31103
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30459
31104
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30460
31105
|
var IN_CHUNK = 500;
|
|
30461
31106
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30543,7 +31188,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30543
31188
|
callSiteCount,
|
|
30544
31189
|
transports: distinctTransports(transports),
|
|
30545
31190
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30546
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31191
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30547
31192
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30548
31193
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30549
31194
|
};
|
|
@@ -30570,7 +31215,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30570
31215
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30571
31216
|
transports: distinctTransports(transports),
|
|
30572
31217
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30573
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31218
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30574
31219
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30575
31220
|
note: dest.note,
|
|
30576
31221
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30599,7 +31244,11 @@ var SqliteSharesRepository = class {
|
|
|
30599
31244
|
FROM share_destination d
|
|
30600
31245
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30601
31246
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30602
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31247
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31248
|
+
AND NOT EXISTS (
|
|
31249
|
+
SELECT 1 FROM egress_decision_override o
|
|
31250
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31251
|
+
)`
|
|
30603
31252
|
);
|
|
30604
31253
|
const kindCounts = countBy(
|
|
30605
31254
|
this.db,
|
|
@@ -30711,7 +31360,7 @@ var SqliteSharesRepository = class {
|
|
|
30711
31360
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30712
31361
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30713
31362
|
).run({
|
|
30714
|
-
id:
|
|
31363
|
+
id: randomUUID10(),
|
|
30715
31364
|
destinationId,
|
|
30716
31365
|
host: dest.host,
|
|
30717
31366
|
decision,
|
|
@@ -30860,7 +31509,7 @@ var SqliteSharesRepository = class {
|
|
|
30860
31509
|
let destinationId = destIds.get(hit.host);
|
|
30861
31510
|
if (destinationId === void 0) {
|
|
30862
31511
|
destStmt.run({
|
|
30863
|
-
id:
|
|
31512
|
+
id: randomUUID10(),
|
|
30864
31513
|
kind: hit.kind,
|
|
30865
31514
|
name: hit.name,
|
|
30866
31515
|
host: hit.host,
|
|
@@ -30876,7 +31525,7 @@ var SqliteSharesRepository = class {
|
|
|
30876
31525
|
let endpointId = endpointIds.get(endpointKey);
|
|
30877
31526
|
if (endpointId === void 0) {
|
|
30878
31527
|
endpointStmt.run({
|
|
30879
|
-
id:
|
|
31528
|
+
id: randomUUID10(),
|
|
30880
31529
|
destinationId,
|
|
30881
31530
|
method: hit.method,
|
|
30882
31531
|
transport: hit.transport,
|
|
@@ -30889,7 +31538,7 @@ var SqliteSharesRepository = class {
|
|
|
30889
31538
|
endpointIds.set(endpointKey, endpointId);
|
|
30890
31539
|
}
|
|
30891
31540
|
siteStmt.run({
|
|
30892
|
-
id:
|
|
31541
|
+
id: randomUUID10(),
|
|
30893
31542
|
endpointId,
|
|
30894
31543
|
project: input2.project,
|
|
30895
31544
|
projectKey: input2.projectKey,
|
|
@@ -31254,6 +31903,7 @@ function purgeSampleData(db) {
|
|
|
31254
31903
|
}
|
|
31255
31904
|
|
|
31256
31905
|
// ../../packages/persistence/src/database.ts
|
|
31906
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31257
31907
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31258
31908
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31259
31909
|
);
|
|
@@ -31301,7 +31951,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31301
31951
|
discardStore(file2, backup);
|
|
31302
31952
|
return backup;
|
|
31303
31953
|
}
|
|
31304
|
-
function openAndInitialize(file2) {
|
|
31954
|
+
function openAndInitialize(file2, base) {
|
|
31305
31955
|
let db = openWithPragmas(file2);
|
|
31306
31956
|
try {
|
|
31307
31957
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31314,7 +31964,7 @@ function openAndInitialize(file2) {
|
|
|
31314
31964
|
applyMigrations(db, file2);
|
|
31315
31965
|
tightenPerms(file2);
|
|
31316
31966
|
const policies = new SqlitePoliciesRepository(db);
|
|
31317
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
31967
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31318
31968
|
const repositories = {
|
|
31319
31969
|
events: new SqliteEventsRepository(db),
|
|
31320
31970
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31350,7 +32000,7 @@ function openAndInitialize(file2) {
|
|
|
31350
32000
|
}
|
|
31351
32001
|
function openLocalDatabase(dir) {
|
|
31352
32002
|
ensureDataDirSync(dir);
|
|
31353
|
-
const file2 =
|
|
32003
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31354
32004
|
reapStalePartials(file2);
|
|
31355
32005
|
const {
|
|
31356
32006
|
db,
|
|
@@ -31378,7 +32028,13 @@ function openLocalDatabase(dir) {
|
|
|
31378
32028
|
inspectionDefinitions,
|
|
31379
32029
|
inspectionFindings,
|
|
31380
32030
|
configInventory
|
|
31381
|
-
} = openAndInitialize(
|
|
32031
|
+
} = openAndInitialize(
|
|
32032
|
+
file2,
|
|
32033
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32034
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32035
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32036
|
+
dirname2(dir)
|
|
32037
|
+
);
|
|
31382
32038
|
function captureRowId(event) {
|
|
31383
32039
|
return captureId(
|
|
31384
32040
|
event.metadata?.sessionId ?? null,
|
|
@@ -31391,6 +32047,21 @@ function openLocalDatabase(dir) {
|
|
|
31391
32047
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31392
32048
|
});
|
|
31393
32049
|
}
|
|
32050
|
+
function markCaptureOwed(event) {
|
|
32051
|
+
failOpenTransaction(db, () => {
|
|
32052
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32053
|
+
});
|
|
32054
|
+
}
|
|
32055
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32056
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32057
|
+
if (stampable.length === 0) return;
|
|
32058
|
+
failOpenTransaction(db, () => {
|
|
32059
|
+
historySync.markSynced(
|
|
32060
|
+
stampable.map((event) => event.id),
|
|
32061
|
+
atMs
|
|
32062
|
+
);
|
|
32063
|
+
});
|
|
32064
|
+
}
|
|
31394
32065
|
function recordCapture(event, detected) {
|
|
31395
32066
|
failOpenTransaction(db, () => {
|
|
31396
32067
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31477,7 +32148,7 @@ function openLocalDatabase(dir) {
|
|
|
31477
32148
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31478
32149
|
if (!definitionId) continue;
|
|
31479
32150
|
inspectionFindings.insertFinding({
|
|
31480
|
-
id:
|
|
32151
|
+
id: randomUUID11(),
|
|
31481
32152
|
auditEventId: record2.scanEvent.id,
|
|
31482
32153
|
inspectionDefinitionId: definitionId,
|
|
31483
32154
|
span: finding.span,
|
|
@@ -31573,6 +32244,8 @@ function openLocalDatabase(dir) {
|
|
|
31573
32244
|
inspectionFindings,
|
|
31574
32245
|
recordCapture,
|
|
31575
32246
|
markCaptureDelivered,
|
|
32247
|
+
markCaptureOwed,
|
|
32248
|
+
markAuditEventsDelivered,
|
|
31576
32249
|
ensureInventory,
|
|
31577
32250
|
recordConfigScan,
|
|
31578
32251
|
recordProjectFiles,
|
|
@@ -31611,32 +32284,18 @@ var UserGrantPolicyProvider = class {
|
|
|
31611
32284
|
}
|
|
31612
32285
|
};
|
|
31613
32286
|
|
|
31614
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31615
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31616
|
-
import {
|
|
31617
|
-
closeSync,
|
|
31618
|
-
existsSync as existsSync2,
|
|
31619
|
-
openSync,
|
|
31620
|
-
readFileSync as readFileSync2,
|
|
31621
|
-
rmSync as rmSync5,
|
|
31622
|
-
statSync as statSync3,
|
|
31623
|
-
writeFileSync as writeFileSync2
|
|
31624
|
-
} from "fs";
|
|
31625
|
-
import { hostname as hostname3 } from "os";
|
|
31626
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31627
|
-
|
|
31628
32287
|
// ../../packages/persistence/src/finding-key.ts
|
|
31629
32288
|
import { createHash as createHash3 } from "crypto";
|
|
31630
32289
|
|
|
31631
32290
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31632
32291
|
import { createHmac, randomBytes } from "crypto";
|
|
31633
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31634
|
-
import { join as
|
|
32292
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32293
|
+
import { join as join8 } from "path";
|
|
31635
32294
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31636
32295
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31637
32296
|
var KEY_MATERIAL_BYTES = 32;
|
|
31638
32297
|
function keyFilePath(dataDir2) {
|
|
31639
|
-
return
|
|
32298
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31640
32299
|
}
|
|
31641
32300
|
function parseKeyFile(raw) {
|
|
31642
32301
|
const parsed = JSON.parse(raw);
|
|
@@ -31674,7 +32333,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
31674
32333
|
}
|
|
31675
32334
|
};
|
|
31676
32335
|
function storedKeyVersionFloor(dataDir2) {
|
|
31677
|
-
const file2 =
|
|
32336
|
+
const file2 = join8(dataDir2, DB_FILENAME);
|
|
31678
32337
|
if (!existsSync3(file2)) return 0;
|
|
31679
32338
|
let db;
|
|
31680
32339
|
try {
|
|
@@ -31729,7 +32388,7 @@ function occupantMessage(file2, kind) {
|
|
|
31729
32388
|
function readFingerprintKey(dataDir2) {
|
|
31730
32389
|
let raw;
|
|
31731
32390
|
try {
|
|
31732
|
-
raw =
|
|
32391
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31733
32392
|
} catch (err) {
|
|
31734
32393
|
if (err.code === "ENOENT") return null;
|
|
31735
32394
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31753,142 +32412,12 @@ function fingerprintValue(key, raw) {
|
|
|
31753
32412
|
|
|
31754
32413
|
// ../../packages/persistence/src/history-preview.ts
|
|
31755
32414
|
import { existsSync as existsSync4 } from "fs";
|
|
31756
|
-
import { join as
|
|
32415
|
+
import { join as join9 } from "path";
|
|
31757
32416
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31758
32417
|
|
|
31759
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31760
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31761
|
-
import { mkdir } from "fs/promises";
|
|
31762
|
-
import { homedir } from "os";
|
|
31763
|
-
import { join as join7 } from "path";
|
|
31764
|
-
function defaultDataDir() {
|
|
31765
|
-
return join7(homedir(), ".aka");
|
|
31766
|
-
}
|
|
31767
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31768
|
-
return join7(base, "settings");
|
|
31769
|
-
}
|
|
31770
|
-
function dataDir(base = defaultDataDir()) {
|
|
31771
|
-
return join7(base, "data");
|
|
31772
|
-
}
|
|
31773
|
-
function dbPath(base = defaultDataDir()) {
|
|
31774
|
-
return join7(dataDir(base), "aka.db");
|
|
31775
|
-
}
|
|
31776
|
-
function keysDir(base = defaultDataDir()) {
|
|
31777
|
-
return join7(base, "keys");
|
|
31778
|
-
}
|
|
31779
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31780
|
-
ensureDataDirSync(dir);
|
|
31781
|
-
}
|
|
31782
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31783
|
-
const moves = [
|
|
31784
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31785
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31786
|
-
];
|
|
31787
|
-
for (const { name, dest } of moves) {
|
|
31788
|
-
try {
|
|
31789
|
-
ensureDataDirSync(dest);
|
|
31790
|
-
const moved = join7(dest, name);
|
|
31791
|
-
renameSync3(join7(base, name), moved);
|
|
31792
|
-
tightenFile(moved);
|
|
31793
|
-
} catch {
|
|
31794
|
-
}
|
|
31795
|
-
}
|
|
31796
|
-
}
|
|
31797
|
-
|
|
31798
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
31799
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
31800
|
-
import { posix, win32 } from "path";
|
|
31801
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
31802
|
-
if (platform2 === "darwin") {
|
|
31803
|
-
return [
|
|
31804
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31805
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31806
|
-
];
|
|
31807
|
-
}
|
|
31808
|
-
if (platform2 === "win32") {
|
|
31809
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31810
|
-
}
|
|
31811
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31812
|
-
}
|
|
31813
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
31814
|
-
for (const path of paths) {
|
|
31815
|
-
let text;
|
|
31816
|
-
try {
|
|
31817
|
-
text = readFileSync4(path, "utf8");
|
|
31818
|
-
} catch {
|
|
31819
|
-
continue;
|
|
31820
|
-
}
|
|
31821
|
-
const record2 = parseJsonObject(text);
|
|
31822
|
-
if (!record2) continue;
|
|
31823
|
-
const parsed = ManagedSettings.safeParse(record2);
|
|
31824
|
-
if (parsed.success) return parsed.data;
|
|
31825
|
-
}
|
|
31826
|
-
return null;
|
|
31827
|
-
}
|
|
31828
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31829
|
-
if (!managed) return settings;
|
|
31830
|
-
const { values } = managed;
|
|
31831
|
-
const merged = { ...settings };
|
|
31832
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31833
|
-
if (values.controlPlane !== void 0) {
|
|
31834
|
-
merged.controlPlane = {
|
|
31835
|
-
...values.controlPlane,
|
|
31836
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31837
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31838
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31839
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31840
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31841
|
-
};
|
|
31842
|
-
}
|
|
31843
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31844
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31845
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31846
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31847
|
-
if (values.vaultConsent !== void 0) {
|
|
31848
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31849
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31850
|
-
// at the current version otherwise.
|
|
31851
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31852
|
-
) : void 0;
|
|
31853
|
-
}
|
|
31854
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31855
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31856
|
-
acknowledgedAt: now().toISOString(),
|
|
31857
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31858
|
-
} : void 0;
|
|
31859
|
-
}
|
|
31860
|
-
return merged;
|
|
31861
|
-
}
|
|
31862
|
-
|
|
31863
|
-
// ../../packages/persistence/src/settings.ts
|
|
31864
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31865
|
-
import { join as join8 } from "path";
|
|
31866
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31867
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31868
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31869
|
-
}
|
|
31870
|
-
function readUserSettings(base) {
|
|
31871
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31872
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31873
|
-
try {
|
|
31874
|
-
return WorkspaceSettings.parse(record2);
|
|
31875
|
-
} catch {
|
|
31876
|
-
return defaultWorkspaceSettings();
|
|
31877
|
-
}
|
|
31878
|
-
}
|
|
31879
|
-
function readJson(file2) {
|
|
31880
|
-
let text;
|
|
31881
|
-
try {
|
|
31882
|
-
text = readFileSync5(file2, "utf8");
|
|
31883
|
-
} catch {
|
|
31884
|
-
return null;
|
|
31885
|
-
}
|
|
31886
|
-
return parseJsonObject(text) ?? null;
|
|
31887
|
-
}
|
|
31888
|
-
|
|
31889
32418
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31890
32419
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31891
|
-
import { dirname as
|
|
32420
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31892
32421
|
|
|
31893
32422
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
31894
32423
|
import {
|
|
@@ -32001,8 +32530,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
32001
32530
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32002
32531
|
import { execFileSync } from "child_process";
|
|
32003
32532
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32004
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32005
|
-
import { join as
|
|
32533
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32534
|
+
import { join as join11 } from "path";
|
|
32006
32535
|
var VAULT_OCCUPANT_REASON = {
|
|
32007
32536
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
32008
32537
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -32101,7 +32630,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32101
32630
|
throw asError(err);
|
|
32102
32631
|
}
|
|
32103
32632
|
try {
|
|
32104
|
-
writeFileSync3(
|
|
32633
|
+
writeFileSync3(join11(lock, LOCK_OWNER_FILE), `${owner}
|
|
32105
32634
|
`, { mode: DATA_FILE_MODE });
|
|
32106
32635
|
return true;
|
|
32107
32636
|
} catch (err) {
|
|
@@ -32110,7 +32639,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32110
32639
|
}
|
|
32111
32640
|
}
|
|
32112
32641
|
function acquireRotationLock(keysDir2) {
|
|
32113
|
-
const lock =
|
|
32642
|
+
const lock = join11(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
32114
32643
|
const owner = randomBytes2(16).toString("hex");
|
|
32115
32644
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
32116
32645
|
let held;
|
|
@@ -32137,7 +32666,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
32137
32666
|
}
|
|
32138
32667
|
function releaseRotationLock(lease) {
|
|
32139
32668
|
try {
|
|
32140
|
-
if (
|
|
32669
|
+
if (readFileSync7(join11(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
32141
32670
|
} catch {
|
|
32142
32671
|
return;
|
|
32143
32672
|
}
|
|
@@ -32158,7 +32687,7 @@ var FileKeyProvider = class {
|
|
|
32158
32687
|
this.#keysDir = keysDir2;
|
|
32159
32688
|
}
|
|
32160
32689
|
get filePath() {
|
|
32161
|
-
return
|
|
32690
|
+
return join11(this.#keysDir, VAULT_KEY_FILENAME);
|
|
32162
32691
|
}
|
|
32163
32692
|
loadOrCreate() {
|
|
32164
32693
|
return asAsync(() => {
|
|
@@ -32188,7 +32717,7 @@ var FileKeyProvider = class {
|
|
|
32188
32717
|
#read() {
|
|
32189
32718
|
let raw;
|
|
32190
32719
|
try {
|
|
32191
|
-
raw =
|
|
32720
|
+
raw = readFileSync7(this.filePath, "utf8");
|
|
32192
32721
|
} catch (err) {
|
|
32193
32722
|
if (err.code === "ENOENT") return null;
|
|
32194
32723
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -32475,7 +33004,14 @@ var SecretVault = class {
|
|
|
32475
33004
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
32476
33005
|
const now = this.#now();
|
|
32477
33006
|
if (existing) {
|
|
32478
|
-
this.#repo.upsert(
|
|
33007
|
+
this.#repo.upsert(
|
|
33008
|
+
{
|
|
33009
|
+
...existing,
|
|
33010
|
+
provider: existing.provider ?? void 0,
|
|
33011
|
+
userAuthorized: meta4.userAuthorized === true
|
|
33012
|
+
},
|
|
33013
|
+
now
|
|
33014
|
+
);
|
|
32479
33015
|
return await this.#emitToken(existing.keyVersion, existing.pointerId, existing.category);
|
|
32480
33016
|
}
|
|
32481
33017
|
const { material, version: version2 } = await this.#keys.loadOrCreate();
|
|
@@ -32497,6 +33033,7 @@ var SecretVault = class {
|
|
|
32497
33033
|
ruleId: meta4.ruleId,
|
|
32498
33034
|
maskedMatch: meta4.maskedMatch,
|
|
32499
33035
|
provider: meta4.provider,
|
|
33036
|
+
userAuthorized: meta4.userAuthorized === true,
|
|
32500
33037
|
ciphertext: sealed.ciphertext.toString("base64"),
|
|
32501
33038
|
nonce: sealed.nonce.toString("base64"),
|
|
32502
33039
|
authTag: sealed.authTag.toString("base64")
|
|
@@ -32816,7 +33353,7 @@ var SecretVault = class {
|
|
|
32816
33353
|
|
|
32817
33354
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32818
33355
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32819
|
-
import { join as
|
|
33356
|
+
import { join as join12 } from "path";
|
|
32820
33357
|
|
|
32821
33358
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32822
33359
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32870,7 +33407,7 @@ function resolveProvider() {
|
|
|
32870
33407
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32871
33408
|
try {
|
|
32872
33409
|
ensureLayoutDirSync(base);
|
|
32873
|
-
const settingsFile =
|
|
33410
|
+
const settingsFile = join13(settingsDir(base), "settings.json");
|
|
32874
33411
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32875
33412
|
} catch {
|
|
32876
33413
|
}
|
|
@@ -32894,9 +33431,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32894
33431
|
}
|
|
32895
33432
|
|
|
32896
33433
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32897
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
33434
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32898
33435
|
import { homedir as homedir2 } from "os";
|
|
32899
|
-
import { basename as basename3, join as
|
|
33436
|
+
import { basename as basename3, join as join15 } from "path";
|
|
32900
33437
|
|
|
32901
33438
|
// ../../packages/detections/src/egress/registry.ts
|
|
32902
33439
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -35854,8 +36391,8 @@ function registerBundledPacks() {
|
|
|
35854
36391
|
}
|
|
35855
36392
|
|
|
35856
36393
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
35857
|
-
import { existsSync as existsSync8, readFileSync as
|
|
35858
|
-
import { basename as basename2, dirname as
|
|
36394
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
36395
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join14, sep as sep2 } from "path";
|
|
35859
36396
|
|
|
35860
36397
|
// ../../packages/plugin-sdk/src/events.ts
|
|
35861
36398
|
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
@@ -35867,8 +36404,8 @@ import { Worker } from "worker_threads";
|
|
|
35867
36404
|
|
|
35868
36405
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
35869
36406
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
35870
|
-
import { readFileSync as
|
|
35871
|
-
import { join as
|
|
36407
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
36408
|
+
import { join as join16 } from "path";
|
|
35872
36409
|
|
|
35873
36410
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
35874
36411
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -35879,24 +36416,24 @@ import {
|
|
|
35879
36416
|
fstatSync,
|
|
35880
36417
|
mkdirSync as mkdirSync2,
|
|
35881
36418
|
openSync as openSync2,
|
|
35882
|
-
readFileSync as
|
|
36419
|
+
readFileSync as readFileSync11,
|
|
35883
36420
|
readSync,
|
|
35884
36421
|
writeFileSync as writeFileSync5
|
|
35885
36422
|
} from "fs";
|
|
35886
|
-
import { join as
|
|
36423
|
+
import { join as join17 } from "path";
|
|
35887
36424
|
var TAIL_BYTES = 256 * 1024;
|
|
35888
36425
|
|
|
35889
36426
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
35890
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
35891
|
-
import { join as
|
|
36427
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
36428
|
+
import { join as join18 } from "path";
|
|
35892
36429
|
|
|
35893
36430
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
35894
36431
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
35895
|
-
import { basename as basename4, dirname as
|
|
36432
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
35896
36433
|
|
|
35897
36434
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
35898
36435
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
35899
|
-
import { basename as basename5, join as
|
|
36436
|
+
import { basename as basename5, join as join19 } from "path";
|
|
35900
36437
|
|
|
35901
36438
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
35902
36439
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -35932,7 +36469,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
35932
36469
|
|
|
35933
36470
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
35934
36471
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
35935
|
-
import { join as
|
|
36472
|
+
import { join as join20 } from "path";
|
|
35936
36473
|
|
|
35937
36474
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
35938
36475
|
function redactedPlaceholder(category) {
|
|
@@ -35994,14 +36531,26 @@ var SecretVaultGlue = class {
|
|
|
35994
36531
|
}
|
|
35995
36532
|
async tokenizeText(text, opts) {
|
|
35996
36533
|
try {
|
|
35997
|
-
const
|
|
35998
|
-
const
|
|
35999
|
-
const
|
|
36000
|
-
if (
|
|
36001
|
-
|
|
36534
|
+
const supplied = opts?.findings;
|
|
36535
|
+
const resolver = opts?.resolver;
|
|
36536
|
+
const scanned = supplied ?? this.#selfScan(text);
|
|
36537
|
+
if (scanned === null) {
|
|
36538
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
36539
|
+
}
|
|
36540
|
+
const findings = supplied === void 0 && resolver !== void 0 ? scanned.filter(
|
|
36541
|
+
(f) => isActionAtLeast(resolver.actionFor(f.ruleId, f.category), "redact")
|
|
36542
|
+
) : scanned;
|
|
36543
|
+
let reversible = opts?.reversible;
|
|
36544
|
+
if (resolver !== void 0 && reversible === void 0) {
|
|
36545
|
+
reversible = new Set(findings.filter((f) => resolver.isReversible(f.ruleId)));
|
|
36546
|
+
}
|
|
36547
|
+
const reversibleSet = reversible;
|
|
36548
|
+
const keeps = (finding) => reversibleSet === void 0 || reversibleSet.has(finding);
|
|
36549
|
+
if (findings.length === 0) return { text, pointers: [], degraded: [], redacted: [] };
|
|
36002
36550
|
const groups = groupSpans(text, findings);
|
|
36003
36551
|
const pointers = [];
|
|
36004
36552
|
const degraded = [];
|
|
36553
|
+
const redacted = [];
|
|
36005
36554
|
let out = text;
|
|
36006
36555
|
for (const group of [...groups].reverse()) {
|
|
36007
36556
|
const original = text.slice(group.start, group.end);
|
|
@@ -36015,6 +36564,7 @@ var SecretVaultGlue = class {
|
|
|
36015
36564
|
degraded.unshift({ category: group.category });
|
|
36016
36565
|
} else if (!keeps(finding)) {
|
|
36017
36566
|
replacement = redactedPlaceholder(finding.category);
|
|
36567
|
+
redacted.unshift({ category: finding.category });
|
|
36018
36568
|
} else {
|
|
36019
36569
|
replacement = await this.tokenizeValue(finding.rawMatch, {
|
|
36020
36570
|
ruleId: finding.ruleId,
|
|
@@ -36035,9 +36585,9 @@ var SecretVaultGlue = class {
|
|
|
36035
36585
|
}
|
|
36036
36586
|
}
|
|
36037
36587
|
}
|
|
36038
|
-
return { text: out, pointers, degraded };
|
|
36588
|
+
return { text: out, pointers, degraded, redacted };
|
|
36039
36589
|
} catch {
|
|
36040
|
-
return { text: "[REDACTED]", pointers: [], degraded: [] };
|
|
36590
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
36041
36591
|
}
|
|
36042
36592
|
}
|
|
36043
36593
|
async detokenizeText(text, opts) {
|
|
@@ -36249,13 +36799,13 @@ function describePointerSafe(token) {
|
|
|
36249
36799
|
import {
|
|
36250
36800
|
mkdirSync as mkdirSync5,
|
|
36251
36801
|
readdirSync as readdirSync5,
|
|
36252
|
-
readFileSync as
|
|
36802
|
+
readFileSync as readFileSync13,
|
|
36253
36803
|
renameSync as renameSync5,
|
|
36254
36804
|
rmSync as rmSync7,
|
|
36255
36805
|
statSync as statSync9,
|
|
36256
36806
|
writeFileSync as writeFileSync8
|
|
36257
36807
|
} from "fs";
|
|
36258
|
-
import { dirname as
|
|
36808
|
+
import { dirname as dirname6, join as join21 } from "path";
|
|
36259
36809
|
var EMPTY_CARRY = Object.freeze({
|
|
36260
36810
|
tail: "",
|
|
36261
36811
|
fence: null,
|
|
@@ -36452,7 +37002,7 @@ var CARRY_FILE_PREFIX = "display-carry";
|
|
|
36452
37002
|
var STALE_CARRY_MS = 15 * 60 * 1e3;
|
|
36453
37003
|
function carryFilePath(dataDir2, sessionId) {
|
|
36454
37004
|
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 80);
|
|
36455
|
-
return
|
|
37005
|
+
return join21(dataDir2, `${CARRY_FILE_PREFIX}-${safe === "" ? "session" : safe}.json`);
|
|
36456
37006
|
}
|
|
36457
37007
|
function parseFence(value) {
|
|
36458
37008
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -36463,7 +37013,7 @@ function parseFence(value) {
|
|
|
36463
37013
|
}
|
|
36464
37014
|
function loadCarry(file2, keys) {
|
|
36465
37015
|
try {
|
|
36466
|
-
const parsed = JSON.parse(
|
|
37016
|
+
const parsed = JSON.parse(readFileSync13(file2, "utf8"));
|
|
36467
37017
|
if (typeof parsed !== "object" || parsed === null) return EMPTY_CARRY;
|
|
36468
37018
|
const record2 = parsed;
|
|
36469
37019
|
const revealedCount = record2.messageKey === keys.messageKey && typeof record2.revealedCount === "number" && Number.isFinite(record2.revealedCount) ? record2.revealedCount : 0;
|
|
@@ -36516,7 +37066,7 @@ function removeStaleCarryFiles(dir, keep) {
|
|
|
36516
37066
|
const cutoff = Date.now() - STALE_CARRY_MS;
|
|
36517
37067
|
for (const name of readdirSync5(dir)) {
|
|
36518
37068
|
if (!name.startsWith(CARRY_FILE_PREFIX) || !name.endsWith(".json")) continue;
|
|
36519
|
-
const path =
|
|
37069
|
+
const path = join21(dir, name);
|
|
36520
37070
|
if (path === keep) continue;
|
|
36521
37071
|
try {
|
|
36522
37072
|
if (statSync9(path).mtimeMs < cutoff) rmSync7(path, { force: true });
|
|
@@ -36528,7 +37078,7 @@ function removeStaleCarryFiles(dir, keep) {
|
|
|
36528
37078
|
}
|
|
36529
37079
|
function saveCarry(file2, keys, carry) {
|
|
36530
37080
|
try {
|
|
36531
|
-
const dir =
|
|
37081
|
+
const dir = dirname6(file2);
|
|
36532
37082
|
mkdirSync5(dir, { recursive: true });
|
|
36533
37083
|
removeStaleCarryFiles(dir, file2);
|
|
36534
37084
|
writeCarryRecord(file2, keys.blockKey, keys.messageKey, carry);
|
|
@@ -36538,7 +37088,7 @@ function saveCarry(file2, keys, carry) {
|
|
|
36538
37088
|
function finalizeCarry(file2, keys, carry) {
|
|
36539
37089
|
try {
|
|
36540
37090
|
if (carry.revealedCount > 0) {
|
|
36541
|
-
mkdirSync5(
|
|
37091
|
+
mkdirSync5(dirname6(file2), { recursive: true });
|
|
36542
37092
|
writeCarryRecord(file2, null, keys.messageKey, {
|
|
36543
37093
|
...EMPTY_CARRY,
|
|
36544
37094
|
revealedCount: carry.revealedCount
|
|
@@ -36547,7 +37097,7 @@ function finalizeCarry(file2, keys, carry) {
|
|
|
36547
37097
|
}
|
|
36548
37098
|
let owned = true;
|
|
36549
37099
|
try {
|
|
36550
|
-
const parsed = JSON.parse(
|
|
37100
|
+
const parsed = JSON.parse(readFileSync13(file2, "utf8"));
|
|
36551
37101
|
if (typeof parsed === "object" && parsed !== null) {
|
|
36552
37102
|
const record2 = parsed;
|
|
36553
37103
|
owned = record2.blockKey === keys.blockKey || record2.messageKey === keys.messageKey;
|