@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
package/scripts/onboard.js
CHANGED
|
@@ -494,6 +494,7 @@ var require_ignore = __commonJS({
|
|
|
494
494
|
// ../../packages/persistence/src/attached-derived.ts
|
|
495
495
|
import { rmSync } from "fs";
|
|
496
496
|
import { join } from "path";
|
|
497
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
497
498
|
|
|
498
499
|
// ../../packages/persistence/src/control-plane-credential.ts
|
|
499
500
|
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync3, statSync } from "fs";
|
|
@@ -592,6 +593,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
592
593
|
{
|
|
593
594
|
tag: "0022_audit_inspection_ms",
|
|
594
595
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
tag: "0023_secret_vault_user_authorized",
|
|
599
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
603
|
+
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`);"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
607
|
+
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;"
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
611
|
+
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;"
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
tag: "0027_audit_llm_usage_index",
|
|
615
|
+
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;"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
tag: "0028_activity_session_probe_indexes",
|
|
619
|
+
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"
|
|
595
620
|
}
|
|
596
621
|
];
|
|
597
622
|
|
|
@@ -22130,6 +22155,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22130
22155
|
AttachTokenExpired,
|
|
22131
22156
|
external_exports.object({ status: printable(64) })
|
|
22132
22157
|
]);
|
|
22158
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22159
|
+
var DeviceCommand = external_exports.object({
|
|
22160
|
+
id: printable(128).min(1),
|
|
22161
|
+
kind: DeviceCommandKind,
|
|
22162
|
+
issuedAt: printable(64).min(1),
|
|
22163
|
+
expiresAt: printable(64).min(1)
|
|
22164
|
+
}).strict();
|
|
22165
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22166
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22167
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22168
|
+
external_exports.object({
|
|
22169
|
+
outcome: external_exports.literal("reported"),
|
|
22170
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22171
|
+
}).strict(),
|
|
22172
|
+
external_exports.object({
|
|
22173
|
+
outcome: external_exports.literal("failed"),
|
|
22174
|
+
reason: DeviceCommandFailureReason,
|
|
22175
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22176
|
+
}).strict()
|
|
22177
|
+
]);
|
|
22133
22178
|
|
|
22134
22179
|
// ../../packages/schema/src/zod/registry.ts
|
|
22135
22180
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22296,7 +22341,7 @@ var PackManifest = external_exports.object({
|
|
|
22296
22341
|
}).meta({ id: "PackManifest" });
|
|
22297
22342
|
|
|
22298
22343
|
// ../../packages/schema/src/zod/detection.ts
|
|
22299
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22344
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22300
22345
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22301
22346
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22302
22347
|
var DetectionCounts = external_exports.object({
|
|
@@ -22433,14 +22478,17 @@ function optional2(key, parsed, raw) {
|
|
|
22433
22478
|
function isStringArray(value) {
|
|
22434
22479
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22435
22480
|
}
|
|
22481
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22482
|
+
function resolveOrigin(origin) {
|
|
22483
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22484
|
+
}
|
|
22436
22485
|
function summaryToDetectionListItem(s) {
|
|
22437
22486
|
return {
|
|
22438
22487
|
id: `${s.namespace}/${s.packId}`,
|
|
22439
22488
|
name: s.name,
|
|
22440
22489
|
version: s.version,
|
|
22441
22490
|
enabled: s.enabled,
|
|
22442
|
-
origin:
|
|
22443
|
-
// v1: every installed pack is library origin
|
|
22491
|
+
origin: resolveOrigin(s.origin),
|
|
22444
22492
|
namespace: s.namespace,
|
|
22445
22493
|
packId: s.packId,
|
|
22446
22494
|
ruleCount: s.ruleCount,
|
|
@@ -22492,7 +22540,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22492
22540
|
name: row.name,
|
|
22493
22541
|
version: row.version,
|
|
22494
22542
|
enabled: row.enabled,
|
|
22495
|
-
origin:
|
|
22543
|
+
origin: resolveOrigin(row.origin),
|
|
22496
22544
|
namespace: row.namespace,
|
|
22497
22545
|
packId: row.packId,
|
|
22498
22546
|
ruleCount: row.rules.length,
|
|
@@ -22512,16 +22560,20 @@ function splitDetectionId(id) {
|
|
|
22512
22560
|
}
|
|
22513
22561
|
function buildDetectionsList(summaries, query) {
|
|
22514
22562
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22563
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22515
22564
|
const counts = {
|
|
22516
22565
|
all: summaries.length,
|
|
22517
|
-
library: summaries.length,
|
|
22518
|
-
|
|
22519
|
-
|
|
22566
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22567
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22568
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22569
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22570
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22571
|
+
// OriginEnum.
|
|
22520
22572
|
customized: 0,
|
|
22521
22573
|
updates: withUpdate.length
|
|
22522
22574
|
};
|
|
22523
22575
|
const filter = query.filter;
|
|
22524
|
-
let filtered = filter === "custom"
|
|
22576
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22525
22577
|
if (query.q) {
|
|
22526
22578
|
const q = query.q.toLowerCase();
|
|
22527
22579
|
filtered = filtered.filter(
|
|
@@ -22601,8 +22653,9 @@ var Event = external_exports.object({
|
|
|
22601
22653
|
metadata: EventMetadata.optional()
|
|
22602
22654
|
}).meta({ id: "Event" });
|
|
22603
22655
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22656
|
+
var INGEST_BATCH_MAX = 100;
|
|
22604
22657
|
var IngestBatch = external_exports.object({
|
|
22605
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22658
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22606
22659
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22607
22660
|
// additionally rejects any event whose contentHash the store has already
|
|
22608
22661
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23158,6 +23211,257 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23158
23211
|
message: "At least one field must be provided"
|
|
23159
23212
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23160
23213
|
|
|
23214
|
+
// ../../packages/schema/src/zod/policy.ts
|
|
23215
|
+
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23216
|
+
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23217
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23218
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23219
|
+
var Policy = external_exports.object({
|
|
23220
|
+
id: external_exports.guid(),
|
|
23221
|
+
scope: PolicyScope,
|
|
23222
|
+
target: PolicyTarget,
|
|
23223
|
+
action: ActionTaken,
|
|
23224
|
+
enabled: external_exports.boolean().default(true),
|
|
23225
|
+
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23226
|
+
// Display name — optional so older policy rows without name still parse.
|
|
23227
|
+
// Added for the findings API (policy.name column migration).
|
|
23228
|
+
name: external_exports.string().optional(),
|
|
23229
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23230
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23231
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23232
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23233
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23234
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23235
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23236
|
+
//
|
|
23237
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23238
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23239
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23240
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23241
|
+
// — the CRUD routes neither accept nor set it.
|
|
23242
|
+
//
|
|
23243
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23244
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23245
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23246
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23247
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23248
|
+
provenance: PolicyProvenance.optional()
|
|
23249
|
+
}).meta({ id: "Policy" });
|
|
23250
|
+
var PolicyBundle = external_exports.object({
|
|
23251
|
+
version: external_exports.string(),
|
|
23252
|
+
policies: external_exports.array(Policy),
|
|
23253
|
+
// Rules from the installed marketplace packs (snapshotted by the
|
|
23254
|
+
// control plane). The plugin registers these in addition to its bundled
|
|
23255
|
+
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23256
|
+
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23257
|
+
rules: external_exports.array(Rule).optional(),
|
|
23258
|
+
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23259
|
+
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23260
|
+
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23261
|
+
// packs only), which is how detection updates stay manual: new bundled
|
|
23262
|
+
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23263
|
+
// the historical composition (bundled packs + rules) — older caches.
|
|
23264
|
+
rulesComplete: external_exports.boolean().optional(),
|
|
23265
|
+
// Active detection exceptions, evaluation subset only (see
|
|
23266
|
+
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23267
|
+
// on-disk caches — that omit the field still parse; consumers read
|
|
23268
|
+
// `bundle.exceptions ?? []`.
|
|
23269
|
+
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23270
|
+
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23271
|
+
// A second axis over the same `redact` action, carried beside the policies
|
|
23272
|
+
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23273
|
+
// widening Policy itself would change a persisted shape to express something
|
|
23274
|
+
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23275
|
+
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23276
|
+
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23277
|
+
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23278
|
+
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23279
|
+
// from a versioned installed pack. Optional so older backends — and older
|
|
23280
|
+
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23281
|
+
// the rule's own spec version. NOT the bundle version above — see
|
|
23282
|
+
// installedRuleset's ruleVersions for the source of truth.
|
|
23283
|
+
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23284
|
+
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23285
|
+
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23286
|
+
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23287
|
+
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23288
|
+
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23289
|
+
// unenforced behaviour that predates this field and the safe direction to
|
|
23290
|
+
// default.
|
|
23291
|
+
//
|
|
23292
|
+
// Ids, not display names: the governance decision is keyed on the exact
|
|
23293
|
+
// string the harness reports (`model_status_override.versionId` in the
|
|
23294
|
+
// control plane), so no name resolution stands between the decision and the
|
|
23295
|
+
// comparison.
|
|
23296
|
+
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23297
|
+
customKeywords: external_exports.array(external_exports.string()),
|
|
23298
|
+
fetchedAt: external_exports.iso.datetime()
|
|
23299
|
+
}).meta({ id: "PolicyBundle" });
|
|
23300
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23301
|
+
...Object.keys(PolicyBundle.shape),
|
|
23302
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23303
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23304
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23305
|
+
].sort().join(",");
|
|
23306
|
+
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23307
|
+
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23308
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
23309
|
+
secret: "critical",
|
|
23310
|
+
financial: "critical",
|
|
23311
|
+
// core-financial/credit-card
|
|
23312
|
+
code_flaw: "critical",
|
|
23313
|
+
pii: "high",
|
|
23314
|
+
phi: "high",
|
|
23315
|
+
custom: "high",
|
|
23316
|
+
// user-defined; conservative
|
|
23317
|
+
code_context: "low",
|
|
23318
|
+
config: "low"
|
|
23319
|
+
// observe-only; floors to monitor regardless
|
|
23320
|
+
};
|
|
23321
|
+
function severityFloorPolicy(category) {
|
|
23322
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23323
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23324
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23325
|
+
}
|
|
23326
|
+
function severityFloorPosture() {
|
|
23327
|
+
const out = {};
|
|
23328
|
+
for (const c of DetectionCategory.options) out[c] = severityFloorPolicy(c);
|
|
23329
|
+
return out;
|
|
23330
|
+
}
|
|
23331
|
+
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23332
|
+
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23333
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23334
|
+
id: "RedactFallback"
|
|
23335
|
+
});
|
|
23336
|
+
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23337
|
+
var BUILTIN_POLICY_SPECS = {
|
|
23338
|
+
monitor: {
|
|
23339
|
+
name: "Monitor",
|
|
23340
|
+
action: "log",
|
|
23341
|
+
reversible: false,
|
|
23342
|
+
description: "Log every match for audit. The request is allowed through untouched."
|
|
23343
|
+
},
|
|
23344
|
+
warn: {
|
|
23345
|
+
name: "Warn",
|
|
23346
|
+
action: "warn",
|
|
23347
|
+
reversible: false,
|
|
23348
|
+
description: "Allow the request, but warn the user inline before it is sent."
|
|
23349
|
+
},
|
|
23350
|
+
redact: {
|
|
23351
|
+
name: "Redact",
|
|
23352
|
+
action: "redact",
|
|
23353
|
+
reversible: false,
|
|
23354
|
+
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23355
|
+
},
|
|
23356
|
+
vault: {
|
|
23357
|
+
name: "Redact & Vault",
|
|
23358
|
+
action: "redact",
|
|
23359
|
+
reversible: true,
|
|
23360
|
+
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."
|
|
23361
|
+
},
|
|
23362
|
+
block: {
|
|
23363
|
+
name: "Block",
|
|
23364
|
+
action: "block",
|
|
23365
|
+
reversible: false,
|
|
23366
|
+
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23367
|
+
}
|
|
23368
|
+
};
|
|
23369
|
+
function builtinPolicyToAction(id) {
|
|
23370
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
23371
|
+
}
|
|
23372
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23373
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23374
|
+
];
|
|
23375
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23376
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23377
|
+
);
|
|
23378
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23379
|
+
...BELOW_PALETTE,
|
|
23380
|
+
...PALETTE_WEAKEST_FIRST
|
|
23381
|
+
];
|
|
23382
|
+
function actionRank(action) {
|
|
23383
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23384
|
+
}
|
|
23385
|
+
function isActionAtLeast(action, floor) {
|
|
23386
|
+
return actionRank(action) >= actionRank(floor);
|
|
23387
|
+
}
|
|
23388
|
+
function strongerAction(a, b) {
|
|
23389
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23390
|
+
}
|
|
23391
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23392
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23393
|
+
}
|
|
23394
|
+
var PackPolicyFloor = external_exports.object({
|
|
23395
|
+
/**
|
|
23396
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23397
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23398
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23399
|
+
*/
|
|
23400
|
+
floor: BuiltinPolicyId,
|
|
23401
|
+
/**
|
|
23402
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23403
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23404
|
+
* re-assignable locally in either direction.
|
|
23405
|
+
*/
|
|
23406
|
+
locked: external_exports.boolean()
|
|
23407
|
+
}).describe("PackPolicyFloor");
|
|
23408
|
+
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23409
|
+
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23410
|
+
);
|
|
23411
|
+
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23412
|
+
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23413
|
+
);
|
|
23414
|
+
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23415
|
+
function builtinPolicyIsReversible(id) {
|
|
23416
|
+
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23417
|
+
}
|
|
23418
|
+
function policyIdIsReversible(policyId) {
|
|
23419
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23420
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23421
|
+
return builtinPolicyIsReversible(id);
|
|
23422
|
+
}
|
|
23423
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23424
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23425
|
+
);
|
|
23426
|
+
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23427
|
+
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23428
|
+
);
|
|
23429
|
+
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23430
|
+
function policyIdToAction(policyId) {
|
|
23431
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23432
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23433
|
+
return BUILTIN_POLICIES[id].action;
|
|
23434
|
+
}
|
|
23435
|
+
var UsedByItem = external_exports.object({
|
|
23436
|
+
id: external_exports.string(),
|
|
23437
|
+
name: external_exports.string(),
|
|
23438
|
+
ruleCount: external_exports.number().int().nonnegative(),
|
|
23439
|
+
enabled: external_exports.boolean()
|
|
23440
|
+
}).meta({ id: "UsedByItem" });
|
|
23441
|
+
var PolicyListItem = external_exports.object({
|
|
23442
|
+
id: external_exports.string(),
|
|
23443
|
+
kind: PolicyKind,
|
|
23444
|
+
name: external_exports.string(),
|
|
23445
|
+
enabled: external_exports.boolean(),
|
|
23446
|
+
usedByCount: external_exports.number().int().nonnegative()
|
|
23447
|
+
}).meta({ id: "PolicyListItem" });
|
|
23448
|
+
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23449
|
+
var PolicyDetail = external_exports.object({
|
|
23450
|
+
specVersion: external_exports.literal(1),
|
|
23451
|
+
id: external_exports.string(),
|
|
23452
|
+
kind: PolicyKind,
|
|
23453
|
+
name: external_exports.string(),
|
|
23454
|
+
enabled: external_exports.boolean(),
|
|
23455
|
+
description: external_exports.string(),
|
|
23456
|
+
usedBy: external_exports.array(UsedByItem)
|
|
23457
|
+
}).meta({ id: "PolicyDetail" });
|
|
23458
|
+
var PolicyStatsResponse = external_exports.object({
|
|
23459
|
+
policies: external_exports.number().int().nonnegative(),
|
|
23460
|
+
builtin: external_exports.number().int().nonnegative(),
|
|
23461
|
+
custom: external_exports.number().int().nonnegative(),
|
|
23462
|
+
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23463
|
+
}).meta({ id: "PolicyStatsResponse" });
|
|
23464
|
+
|
|
23161
23465
|
// ../../packages/schema/src/zod/vault.ts
|
|
23162
23466
|
var POINTER_FORMAT_VERSION = 2;
|
|
23163
23467
|
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
@@ -23195,6 +23499,14 @@ var VaultEntry = external_exports.object({
|
|
|
23195
23499
|
// How many times this value has been detected on this machine — the reuse
|
|
23196
23500
|
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23197
23501
|
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23502
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23503
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23504
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23505
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23506
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23507
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23508
|
+
// the value does not expire.
|
|
23509
|
+
userAuthorized: external_exports.boolean(),
|
|
23198
23510
|
firstSeen: external_exports.string(),
|
|
23199
23511
|
lastSeen: external_exports.string()
|
|
23200
23512
|
});
|
|
@@ -23316,7 +23628,7 @@ function isVaultConsentValid(consent) {
|
|
|
23316
23628
|
}
|
|
23317
23629
|
|
|
23318
23630
|
// ../../packages/schema/src/zod/local.ts
|
|
23319
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
23631
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23320
23632
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23321
23633
|
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23322
23634
|
var ControlPlaneConnection = external_exports.object({
|
|
@@ -23361,6 +23673,19 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23361
23673
|
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23362
23674
|
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23363
23675
|
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23676
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23677
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23678
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23679
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23680
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23681
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23682
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23683
|
+
//
|
|
23684
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23685
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23686
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23687
|
+
// word and stays out of the stored value.
|
|
23688
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23364
23689
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23365
23690
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23366
23691
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -23368,15 +23693,20 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23368
23693
|
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23369
23694
|
// covers the current payload and must be re-granted.
|
|
23370
23695
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23371
|
-
// Records that the user consented to
|
|
23372
|
-
//
|
|
23373
|
-
//
|
|
23374
|
-
//
|
|
23696
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23697
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23698
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23699
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23700
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23701
|
+
// payload no longer counts.
|
|
23375
23702
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23376
23703
|
});
|
|
23377
23704
|
function defaultWorkspaceSettings() {
|
|
23378
23705
|
return WorkspaceSettings.parse({});
|
|
23379
23706
|
}
|
|
23707
|
+
function isAttached(settings) {
|
|
23708
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23709
|
+
}
|
|
23380
23710
|
function toInventoryRow(input2, id, now) {
|
|
23381
23711
|
return {
|
|
23382
23712
|
id,
|
|
@@ -23496,7 +23826,8 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23496
23826
|
"vaultKeyCustody",
|
|
23497
23827
|
"vaultInlineReveal",
|
|
23498
23828
|
"modelJudgeConsent",
|
|
23499
|
-
"dataSharesInPlace"
|
|
23829
|
+
"dataSharesInPlace",
|
|
23830
|
+
"redactFallback"
|
|
23500
23831
|
]).meta({ id: "ManagedSettingKey" });
|
|
23501
23832
|
var ManagedSettingsValues = external_exports.object({
|
|
23502
23833
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
@@ -23509,7 +23840,8 @@ var ManagedSettingsValues = external_exports.object({
|
|
|
23509
23840
|
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23510
23841
|
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23511
23842
|
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23512
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23843
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23844
|
+
redactFallback: RedactFallback.optional()
|
|
23513
23845
|
}).meta({ id: "ManagedSettingsValues" });
|
|
23514
23846
|
var ManagedSettings = external_exports.object({
|
|
23515
23847
|
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
@@ -23525,191 +23857,6 @@ var ManagedSettings = external_exports.object({
|
|
|
23525
23857
|
}).meta({ id: "ManagedSettings" });
|
|
23526
23858
|
var NO_MANAGED_CONTEXT = { present: false, lockedFields: [] };
|
|
23527
23859
|
|
|
23528
|
-
// ../../packages/schema/src/zod/policy.ts
|
|
23529
|
-
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23530
|
-
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23531
|
-
var Policy = external_exports.object({
|
|
23532
|
-
id: external_exports.guid(),
|
|
23533
|
-
scope: PolicyScope,
|
|
23534
|
-
target: PolicyTarget,
|
|
23535
|
-
action: ActionTaken,
|
|
23536
|
-
enabled: external_exports.boolean().default(true),
|
|
23537
|
-
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23538
|
-
// Display name — optional so older policy rows without name still parse.
|
|
23539
|
-
// Added for the findings API (policy.name column migration).
|
|
23540
|
-
name: external_exports.string().optional()
|
|
23541
|
-
}).meta({ id: "Policy" });
|
|
23542
|
-
var PolicyBundle = external_exports.object({
|
|
23543
|
-
version: external_exports.string(),
|
|
23544
|
-
policies: external_exports.array(Policy),
|
|
23545
|
-
// Rules from the installed marketplace packs (snapshotted by the
|
|
23546
|
-
// control plane). The plugin registers these in addition to its bundled
|
|
23547
|
-
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23548
|
-
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23549
|
-
rules: external_exports.array(Rule).optional(),
|
|
23550
|
-
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23551
|
-
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23552
|
-
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23553
|
-
// packs only), which is how detection updates stay manual: new bundled
|
|
23554
|
-
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23555
|
-
// the historical composition (bundled packs + rules) — older caches.
|
|
23556
|
-
rulesComplete: external_exports.boolean().optional(),
|
|
23557
|
-
// Active detection exceptions, evaluation subset only (see
|
|
23558
|
-
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23559
|
-
// on-disk caches — that omit the field still parse; consumers read
|
|
23560
|
-
// `bundle.exceptions ?? []`.
|
|
23561
|
-
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23562
|
-
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23563
|
-
// A second axis over the same `redact` action, carried beside the policies
|
|
23564
|
-
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23565
|
-
// widening Policy itself would change a persisted shape to express something
|
|
23566
|
-
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23567
|
-
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23568
|
-
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23569
|
-
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23570
|
-
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23571
|
-
// from a versioned installed pack. Optional so older backends — and older
|
|
23572
|
-
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23573
|
-
// the rule's own spec version. NOT the bundle version above — see
|
|
23574
|
-
// installedRuleset's ruleVersions for the source of truth.
|
|
23575
|
-
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23576
|
-
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23577
|
-
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23578
|
-
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23579
|
-
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23580
|
-
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23581
|
-
// unenforced behaviour that predates this field and the safe direction to
|
|
23582
|
-
// default.
|
|
23583
|
-
//
|
|
23584
|
-
// Ids, not display names: the governance decision is keyed on the exact
|
|
23585
|
-
// string the harness reports (`model_status_override.versionId` in the
|
|
23586
|
-
// control plane), so no name resolution stands between the decision and the
|
|
23587
|
-
// comparison.
|
|
23588
|
-
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23589
|
-
customKeywords: external_exports.array(external_exports.string()),
|
|
23590
|
-
fetchedAt: external_exports.iso.datetime()
|
|
23591
|
-
}).meta({ id: "PolicyBundle" });
|
|
23592
|
-
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23593
|
-
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23594
|
-
var CATEGORY_PEAK_SEVERITY = {
|
|
23595
|
-
secret: "critical",
|
|
23596
|
-
financial: "critical",
|
|
23597
|
-
// core-financial/credit-card
|
|
23598
|
-
code_flaw: "critical",
|
|
23599
|
-
pii: "high",
|
|
23600
|
-
phi: "high",
|
|
23601
|
-
custom: "high",
|
|
23602
|
-
// user-defined; conservative
|
|
23603
|
-
code_context: "low",
|
|
23604
|
-
config: "low"
|
|
23605
|
-
// observe-only; floors to monitor regardless
|
|
23606
|
-
};
|
|
23607
|
-
function severityFloorPolicy(category) {
|
|
23608
|
-
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23609
|
-
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23610
|
-
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23611
|
-
}
|
|
23612
|
-
function severityFloorPosture() {
|
|
23613
|
-
const out = {};
|
|
23614
|
-
for (const c of DetectionCategory.options) out[c] = severityFloorPolicy(c);
|
|
23615
|
-
return out;
|
|
23616
|
-
}
|
|
23617
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23618
|
-
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23619
|
-
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23620
|
-
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23621
|
-
var BUILTIN_POLICY_SPECS = {
|
|
23622
|
-
monitor: {
|
|
23623
|
-
name: "Monitor",
|
|
23624
|
-
action: "log",
|
|
23625
|
-
reversible: false,
|
|
23626
|
-
description: "Log every match for audit. The request is allowed through untouched."
|
|
23627
|
-
},
|
|
23628
|
-
warn: {
|
|
23629
|
-
name: "Warn",
|
|
23630
|
-
action: "warn",
|
|
23631
|
-
reversible: false,
|
|
23632
|
-
description: "Allow the request, but warn the user inline before it is sent."
|
|
23633
|
-
},
|
|
23634
|
-
redact: {
|
|
23635
|
-
name: "Redact",
|
|
23636
|
-
action: "redact",
|
|
23637
|
-
reversible: false,
|
|
23638
|
-
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23639
|
-
},
|
|
23640
|
-
vault: {
|
|
23641
|
-
name: "Redact & Vault",
|
|
23642
|
-
action: "redact",
|
|
23643
|
-
reversible: true,
|
|
23644
|
-
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."
|
|
23645
|
-
},
|
|
23646
|
-
block: {
|
|
23647
|
-
name: "Block",
|
|
23648
|
-
action: "block",
|
|
23649
|
-
reversible: false,
|
|
23650
|
-
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23651
|
-
}
|
|
23652
|
-
};
|
|
23653
|
-
function builtinPolicyToAction(id) {
|
|
23654
|
-
return BUILTIN_POLICY_SPECS[id].action;
|
|
23655
|
-
}
|
|
23656
|
-
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23657
|
-
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23658
|
-
);
|
|
23659
|
-
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23660
|
-
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23661
|
-
);
|
|
23662
|
-
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23663
|
-
function builtinPolicyIsReversible(id) {
|
|
23664
|
-
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23665
|
-
}
|
|
23666
|
-
function policyIdIsReversible(policyId) {
|
|
23667
|
-
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23668
|
-
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23669
|
-
return builtinPolicyIsReversible(id);
|
|
23670
|
-
}
|
|
23671
|
-
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23672
|
-
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23673
|
-
);
|
|
23674
|
-
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23675
|
-
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23676
|
-
);
|
|
23677
|
-
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23678
|
-
function policyIdToAction(policyId) {
|
|
23679
|
-
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23680
|
-
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
23681
|
-
return BUILTIN_POLICIES[id].action;
|
|
23682
|
-
}
|
|
23683
|
-
var UsedByItem = external_exports.object({
|
|
23684
|
-
id: external_exports.string(),
|
|
23685
|
-
name: external_exports.string(),
|
|
23686
|
-
ruleCount: external_exports.number().int().nonnegative(),
|
|
23687
|
-
enabled: external_exports.boolean()
|
|
23688
|
-
}).meta({ id: "UsedByItem" });
|
|
23689
|
-
var PolicyListItem = external_exports.object({
|
|
23690
|
-
id: external_exports.string(),
|
|
23691
|
-
kind: PolicyKind,
|
|
23692
|
-
name: external_exports.string(),
|
|
23693
|
-
enabled: external_exports.boolean(),
|
|
23694
|
-
usedByCount: external_exports.number().int().nonnegative()
|
|
23695
|
-
}).meta({ id: "PolicyListItem" });
|
|
23696
|
-
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23697
|
-
var PolicyDetail = external_exports.object({
|
|
23698
|
-
specVersion: external_exports.literal(1),
|
|
23699
|
-
id: external_exports.string(),
|
|
23700
|
-
kind: PolicyKind,
|
|
23701
|
-
name: external_exports.string(),
|
|
23702
|
-
enabled: external_exports.boolean(),
|
|
23703
|
-
description: external_exports.string(),
|
|
23704
|
-
usedBy: external_exports.array(UsedByItem)
|
|
23705
|
-
}).meta({ id: "PolicyDetail" });
|
|
23706
|
-
var PolicyStatsResponse = external_exports.object({
|
|
23707
|
-
policies: external_exports.number().int().nonnegative(),
|
|
23708
|
-
builtin: external_exports.number().int().nonnegative(),
|
|
23709
|
-
custom: external_exports.number().int().nonnegative(),
|
|
23710
|
-
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23711
|
-
}).meta({ id: "PolicyStatsResponse" });
|
|
23712
|
-
|
|
23713
23860
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23714
23861
|
var ProjectFileInput = external_exports.object({
|
|
23715
23862
|
path: external_exports.string().min(1),
|
|
@@ -23955,10 +24102,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
23955
24102
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
23956
24103
|
|
|
23957
24104
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24105
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24106
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
23958
24107
|
var SaveSettingsInput = external_exports.object({
|
|
23959
24108
|
historicalAccess: external_exports.string(),
|
|
23960
|
-
modelJudgeConsent:
|
|
23961
|
-
historySyncConsent:
|
|
24109
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24110
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
23962
24111
|
vaultConsent: external_exports.string(),
|
|
23963
24112
|
vaultInlineReveal: external_exports.string()
|
|
23964
24113
|
});
|
|
@@ -24108,9 +24257,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24108
24257
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24109
24258
|
return reasons;
|
|
24110
24259
|
}
|
|
24111
|
-
function buildReviewInfo(trust, transports) {
|
|
24260
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24112
24261
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24113
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24262
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24114
24263
|
}
|
|
24115
24264
|
function distinctTransports(transports) {
|
|
24116
24265
|
return Array.from(new Set(transports));
|
|
@@ -24201,8 +24350,8 @@ function writeOwnerOnlyFileSync(file2, data) {
|
|
|
24201
24350
|
}
|
|
24202
24351
|
|
|
24203
24352
|
// ../../packages/persistence/src/database.ts
|
|
24204
|
-
import { randomUUID as
|
|
24205
|
-
import { join as
|
|
24353
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24354
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24206
24355
|
import { DatabaseSync } from "node:sqlite";
|
|
24207
24356
|
|
|
24208
24357
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24446,6 +24595,10 @@ function allRows(stmt, params) {
|
|
|
24446
24595
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24447
24596
|
return stmt.all(params);
|
|
24448
24597
|
}
|
|
24598
|
+
function* iterateRows(stmt, params) {
|
|
24599
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24600
|
+
for (const row of rows) yield row;
|
|
24601
|
+
}
|
|
24449
24602
|
function getRow(stmt, params) {
|
|
24450
24603
|
if (params === void 0) return stmt.get();
|
|
24451
24604
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -24914,10 +25067,17 @@ function ensureSyncedAtColumn(db, table2) {
|
|
|
24914
25067
|
if (!columns.includes("sync_claimed_at")) {
|
|
24915
25068
|
db.exec(`ALTER TABLE ${table2} ADD COLUMN sync_claimed_at integer`);
|
|
24916
25069
|
}
|
|
25070
|
+
if (!columns.includes("outbox_owed")) {
|
|
25071
|
+
db.exec(`ALTER TABLE ${table2} ADD COLUMN outbox_owed integer`);
|
|
25072
|
+
}
|
|
24917
25073
|
db.exec(
|
|
24918
25074
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
24919
25075
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
24920
25076
|
);
|
|
25077
|
+
db.exec(
|
|
25078
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25079
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25080
|
+
);
|
|
24921
25081
|
db.exec(
|
|
24922
25082
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
24923
25083
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25022,7 +25182,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25022
25182
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25023
25183
|
var DAY_MS = 864e5;
|
|
25024
25184
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25025
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25026
25185
|
function defaultTimeZone() {
|
|
25027
25186
|
try {
|
|
25028
25187
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25077,6 +25236,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25077
25236
|
error: "error",
|
|
25078
25237
|
active: "active"
|
|
25079
25238
|
};
|
|
25239
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25080
25240
|
function safeParseStringArray(raw) {
|
|
25081
25241
|
if (!raw) return [];
|
|
25082
25242
|
const parsed = safeJson(raw, null);
|
|
@@ -25150,6 +25310,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25150
25310
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25151
25311
|
json_extract(attributes, '$.internal') AS internal,
|
|
25152
25312
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25313
|
+
var LLM_USAGE_SELECT = `
|
|
25314
|
+
SELECT root_session_id AS sessionId,
|
|
25315
|
+
provider,
|
|
25316
|
+
model,
|
|
25317
|
+
service_tier AS serviceTier,
|
|
25318
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25319
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25320
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25321
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25322
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25323
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25324
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25325
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25326
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25327
|
+
function usageLeaves(rows) {
|
|
25328
|
+
return rows.map((row) => {
|
|
25329
|
+
const attributes = {
|
|
25330
|
+
input_tokens: row.inputTokens,
|
|
25331
|
+
output_tokens: row.outputTokens,
|
|
25332
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25333
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25334
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25335
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25336
|
+
web_search_requests: row.webSearchRequests
|
|
25337
|
+
};
|
|
25338
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25339
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25340
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25341
|
+
return { sessionId: row.sessionId, attributes };
|
|
25342
|
+
});
|
|
25343
|
+
}
|
|
25153
25344
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25154
25345
|
var HAS_ACTIVITY = `EXISTS (
|
|
25155
25346
|
SELECT 1 FROM audit_events c
|
|
@@ -25175,16 +25366,17 @@ var SqliteActivityRepository = class {
|
|
|
25175
25366
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25176
25367
|
const liveNow = countScalar(
|
|
25177
25368
|
this.db,
|
|
25178
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25369
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25179
25370
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25180
|
-
AND
|
|
25181
|
-
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25186
|
-
|
|
25187
|
-
|
|
25371
|
+
AND s.id IN (
|
|
25372
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25373
|
+
UNION
|
|
25374
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25375
|
+
WHERE started_at >= ?
|
|
25376
|
+
UNION
|
|
25377
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25378
|
+
WHERE ended_at >= ?)`,
|
|
25379
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25188
25380
|
);
|
|
25189
25381
|
const toolCallsToday = countScalar(
|
|
25190
25382
|
this.db,
|
|
@@ -25314,7 +25506,7 @@ var SqliteActivityRepository = class {
|
|
|
25314
25506
|
this.db.prepare(
|
|
25315
25507
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25316
25508
|
FROM audit_events
|
|
25317
|
-
WHERE id = ? OR root_session_id = ?
|
|
25509
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25318
25510
|
ORDER BY started_at ASC, id ASC`
|
|
25319
25511
|
),
|
|
25320
25512
|
[sessionId, sessionId]
|
|
@@ -25327,14 +25519,14 @@ var SqliteActivityRepository = class {
|
|
|
25327
25519
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25328
25520
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25329
25521
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25330
|
-
FROM audit_events
|
|
25522
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25331
25523
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25332
25524
|
),
|
|
25333
25525
|
[sessionId]
|
|
25334
25526
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25335
25527
|
const primaryModel = getRow(
|
|
25336
25528
|
this.db.prepare(
|
|
25337
|
-
`SELECT model, provider FROM audit_events
|
|
25529
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25338
25530
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25339
25531
|
ORDER BY started_at ASC, id ASC
|
|
25340
25532
|
LIMIT 1`
|
|
@@ -25345,7 +25537,7 @@ var SqliteActivityRepository = class {
|
|
|
25345
25537
|
this.db.prepare(
|
|
25346
25538
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25347
25539
|
count(*) AS n
|
|
25348
|
-
FROM audit_events
|
|
25540
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25349
25541
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25350
25542
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25351
25543
|
),
|
|
@@ -25353,7 +25545,7 @@ var SqliteActivityRepository = class {
|
|
|
25353
25545
|
);
|
|
25354
25546
|
const modelRows = allRows(
|
|
25355
25547
|
this.db.prepare(
|
|
25356
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25548
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25357
25549
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25358
25550
|
ORDER BY model`
|
|
25359
25551
|
),
|
|
@@ -25362,7 +25554,7 @@ var SqliteActivityRepository = class {
|
|
|
25362
25554
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25363
25555
|
const commits = countScalar(
|
|
25364
25556
|
this.db,
|
|
25365
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25557
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25366
25558
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25367
25559
|
[sessionId]
|
|
25368
25560
|
);
|
|
@@ -25398,25 +25590,57 @@ var SqliteActivityRepository = class {
|
|
|
25398
25590
|
return Promise.resolve(session);
|
|
25399
25591
|
}
|
|
25400
25592
|
/**
|
|
25401
|
-
* Cross-session token report — every `llm_call`
|
|
25402
|
-
* `started_at >= fromMs`
|
|
25403
|
-
* USD cost DERIVED at read time via the shared
|
|
25404
|
-
*
|
|
25405
|
-
*
|
|
25406
|
-
*
|
|
25593
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25594
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25595
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25596
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25597
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25598
|
+
*
|
|
25599
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25600
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25601
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25602
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25603
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25604
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25605
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25606
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25607
|
+
* recompute the columns it could have read. The index is one every open
|
|
25608
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25609
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25610
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25611
|
+
* per call, no bag parsed.
|
|
25407
25612
|
*/
|
|
25408
25613
|
tokenReports(fromMs) {
|
|
25409
|
-
const
|
|
25410
|
-
|
|
25614
|
+
const rows = allRows(
|
|
25615
|
+
this.db.prepare(
|
|
25616
|
+
`${LLM_USAGE_SELECT}
|
|
25617
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25618
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25619
|
+
${LLM_USAGE_GROUP}`
|
|
25620
|
+
),
|
|
25621
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25622
|
+
);
|
|
25623
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25411
25624
|
}
|
|
25412
25625
|
/**
|
|
25413
|
-
* One session's token report — its `llm_call`
|
|
25414
|
-
* model) with derived cost, or `null` when the session made no
|
|
25415
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25416
|
-
* breakdown + estimated cost.
|
|
25626
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25627
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25628
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25629
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25630
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25631
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25417
25632
|
*/
|
|
25418
25633
|
tokenReportForSession(sessionId) {
|
|
25419
|
-
const
|
|
25634
|
+
const rows = allRows(
|
|
25635
|
+
this.db.prepare(
|
|
25636
|
+
`${LLM_USAGE_SELECT}
|
|
25637
|
+
FROM audit_events
|
|
25638
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25639
|
+
${LLM_USAGE_GROUP}`
|
|
25640
|
+
),
|
|
25641
|
+
[sessionId]
|
|
25642
|
+
);
|
|
25643
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25420
25644
|
return Promise.resolve(reports[0] ?? null);
|
|
25421
25645
|
}
|
|
25422
25646
|
/**
|
|
@@ -25440,42 +25664,6 @@ var SqliteActivityRepository = class {
|
|
|
25440
25664
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25441
25665
|
return Promise.resolve([...seen]);
|
|
25442
25666
|
}
|
|
25443
|
-
/**
|
|
25444
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25445
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25446
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25447
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25448
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25449
|
-
*/
|
|
25450
|
-
readLlmCallLeaves(opts = {}) {
|
|
25451
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25452
|
-
const params = [];
|
|
25453
|
-
if (opts.sessionId !== void 0) {
|
|
25454
|
-
conditions.push("root_session_id = ?");
|
|
25455
|
-
params.push(opts.sessionId);
|
|
25456
|
-
}
|
|
25457
|
-
if (opts.fromMs !== void 0) {
|
|
25458
|
-
conditions.push("started_at >= ?");
|
|
25459
|
-
params.push(opts.fromMs);
|
|
25460
|
-
}
|
|
25461
|
-
const rows = allRows(
|
|
25462
|
-
this.db.prepare(
|
|
25463
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25464
|
-
FROM audit_events
|
|
25465
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25466
|
-
),
|
|
25467
|
-
params
|
|
25468
|
-
);
|
|
25469
|
-
return mapRowsTolerant(
|
|
25470
|
-
rows.filter(
|
|
25471
|
-
(row) => row.sessionId !== null
|
|
25472
|
-
),
|
|
25473
|
-
(row) => ({
|
|
25474
|
-
sessionId: row.sessionId,
|
|
25475
|
-
attributes: JSON.parse(row.attributes)
|
|
25476
|
-
})
|
|
25477
|
-
);
|
|
25478
|
-
}
|
|
25479
25667
|
/**
|
|
25480
25668
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25481
25669
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25490,20 +25678,23 @@ var SqliteActivityRepository = class {
|
|
|
25490
25678
|
const inClause = placeholders(sessionIds.length);
|
|
25491
25679
|
const lastActivityRows = allRows(
|
|
25492
25680
|
this.db.prepare(
|
|
25493
|
-
`SELECT
|
|
25494
|
-
|
|
25495
|
-
|
|
25681
|
+
`SELECT ids.value AS id,
|
|
25682
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25683
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25684
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25685
|
+
FROM json_each(?) AS ids`
|
|
25496
25686
|
),
|
|
25497
|
-
sessionIds
|
|
25687
|
+
[JSON.stringify(sessionIds)]
|
|
25498
25688
|
);
|
|
25499
25689
|
for (const row of lastActivityRows) {
|
|
25500
|
-
if (row.id === null) continue;
|
|
25501
25690
|
const entry = result.get(row.id);
|
|
25502
|
-
|
|
25691
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25692
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25503
25693
|
}
|
|
25504
25694
|
const turnsRows = allRows(
|
|
25505
25695
|
this.db.prepare(
|
|
25506
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25696
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25697
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25507
25698
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25508
25699
|
GROUP BY root_session_id`
|
|
25509
25700
|
),
|
|
@@ -25518,7 +25709,7 @@ var SqliteActivityRepository = class {
|
|
|
25518
25709
|
this.db.prepare(
|
|
25519
25710
|
`SELECT root_session_id AS id,
|
|
25520
25711
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25521
|
-
FROM audit_events
|
|
25712
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25522
25713
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25523
25714
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25524
25715
|
GROUP BY root_session_id`
|
|
@@ -25548,7 +25739,7 @@ var SqliteActivityRepository = class {
|
|
|
25548
25739
|
this.db.prepare(
|
|
25549
25740
|
`SELECT root_session_id AS id,
|
|
25550
25741
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25551
|
-
FROM audit_events
|
|
25742
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25552
25743
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25553
25744
|
GROUP BY root_session_id`
|
|
25554
25745
|
),
|
|
@@ -26577,7 +26768,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26577
26768
|
|
|
26578
26769
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26579
26770
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26580
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26581
26771
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26582
26772
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26583
26773
|
function compareLocationOrder(a, b) {
|
|
@@ -26606,6 +26796,25 @@ function deriveInstanceStatus(row) {
|
|
|
26606
26796
|
latestResolutionStatus: row.latest_status
|
|
26607
26797
|
});
|
|
26608
26798
|
}
|
|
26799
|
+
function toFlatFindingRow(r) {
|
|
26800
|
+
return {
|
|
26801
|
+
id: r.id,
|
|
26802
|
+
ruleId: r.rule_id,
|
|
26803
|
+
category: r.category,
|
|
26804
|
+
severity: r.severity,
|
|
26805
|
+
maskedMatch: r.masked_match,
|
|
26806
|
+
actionTaken: r.action_taken,
|
|
26807
|
+
confidence: r.confidence,
|
|
26808
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26809
|
+
sourceTool: r.source_tool,
|
|
26810
|
+
repo: r.repo ?? "",
|
|
26811
|
+
file: r.file ?? "",
|
|
26812
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26813
|
+
eventId: r.event_id,
|
|
26814
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26815
|
+
status: deriveInstanceStatus(r)
|
|
26816
|
+
};
|
|
26817
|
+
}
|
|
26609
26818
|
function encodeGroupCursor(group) {
|
|
26610
26819
|
const payload = {
|
|
26611
26820
|
sev: group.severity,
|
|
@@ -26681,7 +26890,7 @@ var SqliteFindingsRepository = class {
|
|
|
26681
26890
|
this.db.prepare(
|
|
26682
26891
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26683
26892
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26684
|
-
|
|
26893
|
+
e.source_tool AS source_tool,
|
|
26685
26894
|
e.event_type AS kind
|
|
26686
26895
|
FROM audit_events e
|
|
26687
26896
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26789,56 +26998,11 @@ var SqliteFindingsRepository = class {
|
|
|
26789
26998
|
predicate,
|
|
26790
26999
|
params: sessionParams
|
|
26791
27000
|
});
|
|
26792
|
-
const rows =
|
|
26793
|
-
|
|
26794
|
-
|
|
26795
|
-
|
|
26796
|
-
|
|
26797
|
-
FROM (
|
|
26798
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26799
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26800
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26801
|
-
e.started_at AS occurred_at,
|
|
26802
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26803
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26804
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26805
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26806
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26807
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26808
|
-
latest.status AS latest_status,
|
|
26809
|
-
ROW_NUMBER() OVER (
|
|
26810
|
-
PARTITION BY d.rule_id
|
|
26811
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26812
|
-
) AS rn
|
|
26813
|
-
FROM inspection_findings f
|
|
26814
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26815
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26816
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26817
|
-
ON latest.finding_key = f.finding_key
|
|
26818
|
-
${predicate}
|
|
26819
|
-
)
|
|
26820
|
-
WHERE rn <= :cap
|
|
26821
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26822
|
-
),
|
|
26823
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
26824
|
-
);
|
|
26825
|
-
const groupable = rows.map((r) => ({
|
|
26826
|
-
id: r.id,
|
|
26827
|
-
ruleId: r.rule_id,
|
|
26828
|
-
category: r.category,
|
|
26829
|
-
severity: r.severity,
|
|
26830
|
-
maskedMatch: r.masked_match,
|
|
26831
|
-
actionTaken: r.action_taken,
|
|
26832
|
-
confidence: r.confidence,
|
|
26833
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26834
|
-
sourceTool: r.source_tool,
|
|
26835
|
-
repo: r.repo ?? "",
|
|
26836
|
-
file: r.file ?? "",
|
|
26837
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26838
|
-
eventId: r.event_id,
|
|
26839
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26840
|
-
status: deriveInstanceStatus(r)
|
|
26841
|
-
}));
|
|
27001
|
+
const rows = this.previewRows(aggregates, {
|
|
27002
|
+
sessionId: query.sessionId,
|
|
27003
|
+
from: query.from
|
|
27004
|
+
});
|
|
27005
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
26842
27006
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
26843
27007
|
const filterOpts = {
|
|
26844
27008
|
severity: query.severity,
|
|
@@ -26924,8 +27088,10 @@ var SqliteFindingsRepository = class {
|
|
|
26924
27088
|
*
|
|
26925
27089
|
* The scan runs from the top of the scope on every request, not from the
|
|
26926
27090
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
26927
|
-
* move as the caller pages. Rows
|
|
26928
|
-
*
|
|
27091
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27092
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27093
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27094
|
+
* fetched batches; only the page itself is retained.
|
|
26929
27095
|
*/
|
|
26930
27096
|
listFindingInstances(query) {
|
|
26931
27097
|
const opts = {
|
|
@@ -26941,6 +27107,10 @@ var SqliteFindingsRepository = class {
|
|
|
26941
27107
|
};
|
|
26942
27108
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
26943
27109
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27110
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27111
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27112
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27113
|
+
};
|
|
26944
27114
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
26945
27115
|
const items = [];
|
|
26946
27116
|
let total = 0;
|
|
@@ -26953,6 +27123,7 @@ var SqliteFindingsRepository = class {
|
|
|
26953
27123
|
accumulator.add(row);
|
|
26954
27124
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
26955
27125
|
total += 1;
|
|
27126
|
+
if (!isPastCursor(row)) continue;
|
|
26956
27127
|
if (items.length < limit) {
|
|
26957
27128
|
items.push(toInstanceDetail(row));
|
|
26958
27129
|
last = row;
|
|
@@ -26961,15 +27132,6 @@ var SqliteFindingsRepository = class {
|
|
|
26961
27132
|
}
|
|
26962
27133
|
}
|
|
26963
27134
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
26964
|
-
if (cursor !== null) {
|
|
26965
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
26966
|
-
return Promise.resolve({
|
|
26967
|
-
totals: { findings: total },
|
|
26968
|
-
facets: accumulator.facets(),
|
|
26969
|
-
items: resumed.items,
|
|
26970
|
-
nextCursor: resumed.nextCursor
|
|
26971
|
-
});
|
|
26972
|
-
}
|
|
26973
27135
|
return Promise.resolve({
|
|
26974
27136
|
totals: { findings: total },
|
|
26975
27137
|
facets: accumulator.facets(),
|
|
@@ -26977,35 +27139,6 @@ var SqliteFindingsRepository = class {
|
|
|
26977
27139
|
nextCursor
|
|
26978
27140
|
});
|
|
26979
27141
|
}
|
|
26980
|
-
/**
|
|
26981
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
26982
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
26983
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
26984
|
-
* uses, so a later page costs less than the first rather than more.
|
|
26985
|
-
*/
|
|
26986
|
-
pageAfter(cursor, opts, limit, query) {
|
|
26987
|
-
const items = [];
|
|
26988
|
-
let last;
|
|
26989
|
-
let hasMore = false;
|
|
26990
|
-
for (const row of this.scanFindingRows({
|
|
26991
|
-
sessionId: query.sessionId,
|
|
26992
|
-
from: query.from,
|
|
26993
|
-
after: cursor
|
|
26994
|
-
})) {
|
|
26995
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
26996
|
-
if (items.length < limit) {
|
|
26997
|
-
items.push(toInstanceDetail(row));
|
|
26998
|
-
last = row;
|
|
26999
|
-
} else {
|
|
27000
|
-
hasMore = true;
|
|
27001
|
-
break;
|
|
27002
|
-
}
|
|
27003
|
-
}
|
|
27004
|
-
return {
|
|
27005
|
-
items,
|
|
27006
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27007
|
-
};
|
|
27008
|
-
}
|
|
27009
27142
|
/**
|
|
27010
27143
|
* The same findings folded by location: repository, then file within it.
|
|
27011
27144
|
*
|
|
@@ -27088,25 +27221,111 @@ var SqliteFindingsRepository = class {
|
|
|
27088
27221
|
});
|
|
27089
27222
|
}
|
|
27090
27223
|
/**
|
|
27091
|
-
*
|
|
27224
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27225
|
+
*
|
|
27226
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27227
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27228
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27229
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27230
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27231
|
+
* store while the answer does not.
|
|
27232
|
+
*
|
|
27233
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27234
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27235
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27236
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27237
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27238
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27239
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27240
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27241
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27242
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27243
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27244
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27245
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27246
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27247
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27248
|
+
* sort of the scope, which is still that floor.
|
|
27249
|
+
*
|
|
27250
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27251
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27252
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27253
|
+
* built from.
|
|
27254
|
+
*/
|
|
27255
|
+
previewRows(aggregates, scope) {
|
|
27256
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27257
|
+
let remaining = 0;
|
|
27258
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27259
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27260
|
+
wanted.set(ruleId, n);
|
|
27261
|
+
remaining += n;
|
|
27262
|
+
}
|
|
27263
|
+
const rows = [];
|
|
27264
|
+
if (remaining === 0) return rows;
|
|
27265
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27266
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27267
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27268
|
+
const want = wanted.get(r.rule_id);
|
|
27269
|
+
if (want === void 0) continue;
|
|
27270
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27271
|
+
if (have >= want) continue;
|
|
27272
|
+
taken.set(r.rule_id, have + 1);
|
|
27273
|
+
rows.push(r);
|
|
27274
|
+
remaining -= 1;
|
|
27275
|
+
if (remaining === 0) break;
|
|
27276
|
+
}
|
|
27277
|
+
return rows;
|
|
27278
|
+
}
|
|
27279
|
+
/**
|
|
27280
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27092
27281
|
*
|
|
27093
27282
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27094
27283
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27095
|
-
* store is far more rows than any page.
|
|
27096
|
-
*
|
|
27097
|
-
*
|
|
27098
|
-
*
|
|
27099
|
-
*
|
|
27100
|
-
*
|
|
27101
|
-
* makes it a point lookup per row, and the derived table would re-materialize
|
|
27102
|
-
* a window over the whole resolution table once per batch.
|
|
27284
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27285
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27286
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27287
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27288
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27289
|
+
* batch and cost the square of the scope.
|
|
27103
27290
|
*
|
|
27104
|
-
* `
|
|
27105
|
-
* would be missing from its own facet, which is
|
|
27106
|
-
* dimension
|
|
27291
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27292
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27293
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27294
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27295
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27296
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27297
|
+
* page-2+ request would otherwise re-seek for.
|
|
27107
27298
|
*/
|
|
27108
27299
|
*scanFindingRows(scope) {
|
|
27109
|
-
const
|
|
27300
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27301
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27302
|
+
yield toFlatFindingRow(r);
|
|
27303
|
+
}
|
|
27304
|
+
}
|
|
27305
|
+
/**
|
|
27306
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27307
|
+
* joined to its event and definition, newest first.
|
|
27308
|
+
*
|
|
27309
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27310
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27311
|
+
*
|
|
27312
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27313
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27314
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27315
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27316
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27317
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27318
|
+
* ordered within the session — and the order falls out of the index.
|
|
27319
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27320
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27321
|
+
*
|
|
27322
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27323
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27324
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27325
|
+
* table would be materialized before the first row streamed.
|
|
27326
|
+
*/
|
|
27327
|
+
findingScanSql(scope) {
|
|
27328
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27110
27329
|
const params = [];
|
|
27111
27330
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27112
27331
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27120,58 +27339,24 @@ var SqliteFindingsRepository = class {
|
|
|
27120
27339
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27121
27340
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27122
27341
|
e.started_at AS occurred_at,
|
|
27123
|
-
|
|
27124
|
-
|
|
27125
|
-
|
|
27126
|
-
|
|
27342
|
+
e.source_tool AS source_tool,
|
|
27343
|
+
e.repo AS repo,
|
|
27344
|
+
e.file_path AS file,
|
|
27345
|
+
e.tool_name AS tool_name,
|
|
27127
27346
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27128
27347
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27129
27348
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27130
|
-
FROM
|
|
27131
|
-
JOIN
|
|
27132
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27133
|
-
WHERE ${conditions.join(" AND ")}
|
|
27134
|
-
|
|
27135
|
-
|
|
27136
|
-
LIMIT ?`;
|
|
27137
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27138
|
-
for (; ; ) {
|
|
27139
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27140
|
-
...params,
|
|
27141
|
-
after.startedAtMs,
|
|
27142
|
-
after.startedAtMs,
|
|
27143
|
-
after.id,
|
|
27144
|
-
SCAN_BATCH_ROWS
|
|
27145
|
-
]);
|
|
27146
|
-
for (const r of rows) {
|
|
27147
|
-
yield {
|
|
27148
|
-
id: r.id,
|
|
27149
|
-
ruleId: r.rule_id,
|
|
27150
|
-
category: r.category,
|
|
27151
|
-
severity: r.severity,
|
|
27152
|
-
maskedMatch: r.masked_match,
|
|
27153
|
-
actionTaken: r.action_taken,
|
|
27154
|
-
confidence: r.confidence,
|
|
27155
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27156
|
-
sourceTool: r.source_tool,
|
|
27157
|
-
repo: r.repo ?? "",
|
|
27158
|
-
file: r.file ?? "",
|
|
27159
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27160
|
-
eventId: r.event_id,
|
|
27161
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27162
|
-
status: deriveInstanceStatus(r)
|
|
27163
|
-
};
|
|
27164
|
-
}
|
|
27165
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27166
|
-
const lastRow = rows[rows.length - 1];
|
|
27167
|
-
if (lastRow === void 0) return;
|
|
27168
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27169
|
-
}
|
|
27349
|
+
FROM audit_events e
|
|
27350
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27351
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27352
|
+
WHERE ${conditions.join(" AND ")}
|
|
27353
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27354
|
+
return { sql, params };
|
|
27170
27355
|
}
|
|
27171
27356
|
groupAggregates(withSearchText, scope) {
|
|
27172
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27173
|
-
group_concat(DISTINCT
|
|
27174
|
-
group_concat(DISTINCT 'via ' ||
|
|
27357
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27358
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27359
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27175
27360
|
const rows = this.db.prepare(
|
|
27176
27361
|
`SELECT rule_id,
|
|
27177
27362
|
sum(tuple_count) AS instance_count,
|
|
@@ -27189,7 +27374,7 @@ var SqliteFindingsRepository = class {
|
|
|
27189
27374
|
coalesce(latest.status, '') AS status_tuple,
|
|
27190
27375
|
count(*) AS tuple_count,
|
|
27191
27376
|
max(e.started_at) AS latest_at,
|
|
27192
|
-
group_concat(DISTINCT
|
|
27377
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27193
27378
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27194
27379
|
${innerSearchColumns}
|
|
27195
27380
|
FROM inspection_findings f
|
|
@@ -27320,6 +27505,8 @@ function isoDay(ms) {
|
|
|
27320
27505
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27321
27506
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27322
27507
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27508
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27509
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27323
27510
|
var SKIPPED = -1;
|
|
27324
27511
|
var ROW_COLUMNS = `id,
|
|
27325
27512
|
parent_id AS parentId,
|
|
@@ -27359,6 +27546,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27359
27546
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27360
27547
|
LIMIT :limit`
|
|
27361
27548
|
);
|
|
27549
|
+
this.captureRowsStmt = db.prepare(
|
|
27550
|
+
`SELECT ${ROW_COLUMNS}
|
|
27551
|
+
FROM audit_events
|
|
27552
|
+
WHERE synced_at IS NULL
|
|
27553
|
+
AND sync_claimed_at IS NULL
|
|
27554
|
+
AND outbox_owed = 1
|
|
27555
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27556
|
+
AND started_at < :before
|
|
27557
|
+
ORDER BY started_at
|
|
27558
|
+
LIMIT :limit`
|
|
27559
|
+
);
|
|
27560
|
+
this.markOwedStmt = db.prepare(
|
|
27561
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27562
|
+
);
|
|
27362
27563
|
this.stampStmt = db.prepare(
|
|
27363
27564
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27364
27565
|
);
|
|
@@ -27390,6 +27591,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27390
27591
|
FROM audit_events
|
|
27391
27592
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27392
27593
|
);
|
|
27594
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27595
|
+
`SELECT COUNT(*) AS skipped
|
|
27596
|
+
FROM audit_events
|
|
27597
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27598
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27599
|
+
);
|
|
27393
27600
|
this.fingerprintStmt = db.prepare(
|
|
27394
27601
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27395
27602
|
FROM history_sync WHERE id = 1`
|
|
@@ -27399,6 +27606,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27399
27606
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27400
27607
|
WHERE id = 1`
|
|
27401
27608
|
);
|
|
27609
|
+
this.disownCapturesStmt = db.prepare(
|
|
27610
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27611
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27612
|
+
);
|
|
27402
27613
|
this.rearmStmt = db.prepare(
|
|
27403
27614
|
`UPDATE audit_events SET synced_at = NULL
|
|
27404
27615
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27471,6 +27682,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27471
27682
|
closeWindowStmt;
|
|
27472
27683
|
releaseBoundaryStmt;
|
|
27473
27684
|
freezeBoundaryStmt;
|
|
27685
|
+
captureRowsStmt;
|
|
27686
|
+
markOwedStmt;
|
|
27687
|
+
captureSkipCountStmt;
|
|
27688
|
+
disownCapturesStmt;
|
|
27474
27689
|
partitionStmt;
|
|
27475
27690
|
claimRowStmt;
|
|
27476
27691
|
releaseRowStmt;
|
|
@@ -27504,6 +27719,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27504
27719
|
pendingRows(sessionId, limit, before) {
|
|
27505
27720
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27506
27721
|
}
|
|
27722
|
+
/**
|
|
27723
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27724
|
+
*
|
|
27725
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27726
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27727
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27728
|
+
* the live path.
|
|
27729
|
+
*/
|
|
27730
|
+
pendingCaptureRows(limit, before) {
|
|
27731
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27732
|
+
}
|
|
27733
|
+
/**
|
|
27734
|
+
* Record that a capture is OWED to the deployment.
|
|
27735
|
+
*
|
|
27736
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27737
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27738
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27739
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27740
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27741
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27742
|
+
* offered to anyone.
|
|
27743
|
+
*
|
|
27744
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27745
|
+
* out of the drain's read.
|
|
27746
|
+
*/
|
|
27747
|
+
markCaptureOwed(id) {
|
|
27748
|
+
this.markOwedStmt.run({ id });
|
|
27749
|
+
}
|
|
27507
27750
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27508
27751
|
markSynced(ids, atMs) {
|
|
27509
27752
|
this.stampAll(ids, atMs);
|
|
@@ -27587,10 +27830,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27587
27830
|
this.countsStmt,
|
|
27588
27831
|
{ before }
|
|
27589
27832
|
);
|
|
27833
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27590
27834
|
return {
|
|
27591
27835
|
pending: row?.pending ?? 0,
|
|
27592
27836
|
sent: row?.sent ?? 0,
|
|
27593
|
-
skipped: row?.skipped ?? 0
|
|
27837
|
+
skipped: row?.skipped ?? 0,
|
|
27838
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27594
27839
|
};
|
|
27595
27840
|
}
|
|
27596
27841
|
/**
|
|
@@ -27631,7 +27876,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27631
27876
|
withTransaction(
|
|
27632
27877
|
this.db,
|
|
27633
27878
|
() => {
|
|
27879
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27634
27880
|
this.rearmStmt.run();
|
|
27881
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
27882
|
+
this.disownCapturesStmt.run();
|
|
27883
|
+
}
|
|
27635
27884
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27636
27885
|
},
|
|
27637
27886
|
"IMMEDIATE"
|
|
@@ -27727,108 +27976,651 @@ var SqliteInspectionDefinitionsRepository = class {
|
|
|
27727
27976
|
(:id, :ruleId, :name, :category, :severity, :definition, :version)`
|
|
27728
27977
|
);
|
|
27729
27978
|
}
|
|
27730
|
-
db;
|
|
27731
|
-
insertStmt;
|
|
27732
|
-
// Insert-if-absent; returns the content-addressed definition id. An id already
|
|
27733
|
-
// present keeps the stored row untouched — see the class doc.
|
|
27734
|
-
upsert(input2) {
|
|
27735
|
-
const id = inspectionDefinitionId(input2.ruleId, input2.version);
|
|
27736
|
-
const row = toInspectionDefinitionRow(input2, id);
|
|
27737
|
-
this.insertStmt.run({
|
|
27738
|
-
id: row.id,
|
|
27739
|
-
ruleId: row.ruleId,
|
|
27740
|
-
name: row.name,
|
|
27741
|
-
category: row.category,
|
|
27742
|
-
severity: row.severity,
|
|
27743
|
-
definition: row.definition,
|
|
27744
|
-
version: row.version
|
|
27745
|
-
});
|
|
27746
|
-
return id;
|
|
27979
|
+
db;
|
|
27980
|
+
insertStmt;
|
|
27981
|
+
// Insert-if-absent; returns the content-addressed definition id. An id already
|
|
27982
|
+
// present keeps the stored row untouched — see the class doc.
|
|
27983
|
+
upsert(input2) {
|
|
27984
|
+
const id = inspectionDefinitionId(input2.ruleId, input2.version);
|
|
27985
|
+
const row = toInspectionDefinitionRow(input2, id);
|
|
27986
|
+
this.insertStmt.run({
|
|
27987
|
+
id: row.id,
|
|
27988
|
+
ruleId: row.ruleId,
|
|
27989
|
+
name: row.name,
|
|
27990
|
+
category: row.category,
|
|
27991
|
+
severity: row.severity,
|
|
27992
|
+
definition: row.definition,
|
|
27993
|
+
version: row.version
|
|
27994
|
+
});
|
|
27995
|
+
return id;
|
|
27996
|
+
}
|
|
27997
|
+
};
|
|
27998
|
+
|
|
27999
|
+
// ../../packages/persistence/src/repositories/inspection-findings.ts
|
|
28000
|
+
var SqliteInspectionFindingsRepository = class {
|
|
28001
|
+
constructor(db) {
|
|
28002
|
+
this.db = db;
|
|
28003
|
+
this.insertStmt = db.prepare(
|
|
28004
|
+
`INSERT INTO inspection_findings
|
|
28005
|
+
(id, audit_event_id, inspection_definition_id, classified_data_id,
|
|
28006
|
+
span_start, span_end, masked_match, action_taken, confidence,
|
|
28007
|
+
finding_key, first_detected_at)
|
|
28008
|
+
VALUES
|
|
28009
|
+
(:id, :auditEventId, :inspectionDefinitionId, :classifiedDataId,
|
|
28010
|
+
:spanStart, :spanEnd, :maskedMatch, :actionTaken, :confidence,
|
|
28011
|
+
:findingKey,
|
|
28012
|
+
COALESCE(:firstDetectedAt, (SELECT started_at FROM audit_events WHERE id = :auditEventId)))
|
|
28013
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
28014
|
+
inspection_definition_id = excluded.inspection_definition_id
|
|
28015
|
+
ON CONFLICT (finding_key) DO UPDATE SET
|
|
28016
|
+
audit_event_id = excluded.audit_event_id,
|
|
28017
|
+
inspection_definition_id = excluded.inspection_definition_id,
|
|
28018
|
+
classified_data_id = excluded.classified_data_id,
|
|
28019
|
+
span_start = excluded.span_start,
|
|
28020
|
+
span_end = excluded.span_end,
|
|
28021
|
+
masked_match = excluded.masked_match,
|
|
28022
|
+
action_taken = excluded.action_taken,
|
|
28023
|
+
confidence = excluded.confidence`
|
|
28024
|
+
);
|
|
28025
|
+
this.sessionDupStmt = db.prepare(
|
|
28026
|
+
`SELECT 1 FROM inspection_findings f
|
|
28027
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
28028
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
28029
|
+
WHERE d.rule_id = :ruleId AND f.masked_match = :maskedMatch
|
|
28030
|
+
AND e.root_session_id = :sessionId
|
|
28031
|
+
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
28032
|
+
LIMIT 1`
|
|
28033
|
+
);
|
|
28034
|
+
this.eventDupStmt = db.prepare(
|
|
28035
|
+
`SELECT 1 FROM inspection_findings f
|
|
28036
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
28037
|
+
WHERE f.audit_event_id = :auditEventId AND d.rule_id = :ruleId
|
|
28038
|
+
AND f.masked_match = :maskedMatch
|
|
28039
|
+
AND f.span_start = :spanStart AND f.span_end = :spanEnd
|
|
28040
|
+
LIMIT 1`
|
|
28041
|
+
);
|
|
28042
|
+
}
|
|
28043
|
+
db;
|
|
28044
|
+
insertStmt;
|
|
28045
|
+
sessionDupStmt;
|
|
28046
|
+
eventDupStmt;
|
|
28047
|
+
// True when an earlier event in the same session already recorded a finding
|
|
28048
|
+
// with the same rule and masked value. The current event's own findings are
|
|
28049
|
+
// inserted one at a time in caller order, so an earlier finding in the SAME
|
|
28050
|
+
// recordCapture call is visible to a later duplicate check within it too.
|
|
28051
|
+
isSessionDuplicate(ruleId, maskedMatch, sessionId) {
|
|
28052
|
+
return this.sessionDupStmt.get({ ruleId, maskedMatch, sessionId }) !== void 0;
|
|
28053
|
+
}
|
|
28054
|
+
// True when this exact detection (rule + masked value + span) is already
|
|
28055
|
+
// recorded against the given audit event.
|
|
28056
|
+
isEventDuplicate(auditEventId, ruleId, maskedMatch, spanStart, spanEnd) {
|
|
28057
|
+
return this.eventDupStmt.get({ auditEventId, ruleId, maskedMatch, spanStart, spanEnd }) !== void 0;
|
|
28058
|
+
}
|
|
28059
|
+
insertFinding(input2) {
|
|
28060
|
+
const row = toInspectionFindingRow(input2);
|
|
28061
|
+
this.insertStmt.run(
|
|
28062
|
+
bindParams({
|
|
28063
|
+
id: row.id,
|
|
28064
|
+
auditEventId: row.auditEventId,
|
|
28065
|
+
inspectionDefinitionId: row.inspectionDefinitionId,
|
|
28066
|
+
classifiedDataId: row.classifiedDataId,
|
|
28067
|
+
spanStart: row.spanStart,
|
|
28068
|
+
spanEnd: row.spanEnd,
|
|
28069
|
+
maskedMatch: row.maskedMatch,
|
|
28070
|
+
actionTaken: row.actionTaken,
|
|
28071
|
+
confidence: row.confidence,
|
|
28072
|
+
findingKey: row.findingKey,
|
|
28073
|
+
firstDetectedAt: row.firstDetectedAt
|
|
28074
|
+
})
|
|
28075
|
+
);
|
|
28076
|
+
}
|
|
28077
|
+
};
|
|
28078
|
+
|
|
28079
|
+
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
28080
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28081
|
+
|
|
28082
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28083
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28084
|
+
import { join as join6 } from "path";
|
|
28085
|
+
|
|
28086
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28087
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28088
|
+
import { mkdir } from "fs/promises";
|
|
28089
|
+
import { homedir } from "os";
|
|
28090
|
+
import { join as join4 } from "path";
|
|
28091
|
+
function defaultDataDir() {
|
|
28092
|
+
return join4(homedir(), ".aka");
|
|
28093
|
+
}
|
|
28094
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28095
|
+
return join4(base, "settings");
|
|
28096
|
+
}
|
|
28097
|
+
function dataDir(base = defaultDataDir()) {
|
|
28098
|
+
return join4(base, "data");
|
|
28099
|
+
}
|
|
28100
|
+
function dbPath(base = defaultDataDir()) {
|
|
28101
|
+
return join4(dataDir(base), "aka.db");
|
|
28102
|
+
}
|
|
28103
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28104
|
+
ensureDataDirSync(dir);
|
|
28105
|
+
}
|
|
28106
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28107
|
+
const moves = [
|
|
28108
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28109
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28110
|
+
];
|
|
28111
|
+
for (const { name, dest } of moves) {
|
|
28112
|
+
try {
|
|
28113
|
+
ensureDataDirSync(dest);
|
|
28114
|
+
const moved = join4(dest, name);
|
|
28115
|
+
renameSync3(join4(base, name), moved);
|
|
28116
|
+
tightenFile(moved);
|
|
28117
|
+
} catch {
|
|
28118
|
+
}
|
|
28119
|
+
}
|
|
28120
|
+
}
|
|
28121
|
+
|
|
28122
|
+
// ../../packages/persistence/src/settings.ts
|
|
28123
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28124
|
+
import { join as join5 } from "path";
|
|
28125
|
+
|
|
28126
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28127
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28128
|
+
import {
|
|
28129
|
+
closeSync,
|
|
28130
|
+
existsSync as existsSync2,
|
|
28131
|
+
openSync,
|
|
28132
|
+
readFileSync as readFileSync2,
|
|
28133
|
+
rmSync as rmSync5,
|
|
28134
|
+
statSync as statSync3,
|
|
28135
|
+
writeFileSync as writeFileSync2
|
|
28136
|
+
} from "fs";
|
|
28137
|
+
import { hostname as hostname3 } from "os";
|
|
28138
|
+
var LOCK_SUFFIX = ".lock";
|
|
28139
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
28140
|
+
var DEFAULT_STALE_MS = 2e3;
|
|
28141
|
+
var RETRY_INTERVAL_MS = 5;
|
|
28142
|
+
var RETRYABLE_CREATE_ERRNOS = /* @__PURE__ */ new Set(["EEXIST", "EACCES", "EPERM", "EBUSY"]);
|
|
28143
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28144
|
+
function sleepSync(ms) {
|
|
28145
|
+
Atomics.wait(PARK, 0, 0, ms);
|
|
28146
|
+
}
|
|
28147
|
+
var FileLockError = class extends Error {
|
|
28148
|
+
reason;
|
|
28149
|
+
file;
|
|
28150
|
+
holderPid;
|
|
28151
|
+
constructor(reason, file2, detail, holderPid, options) {
|
|
28152
|
+
super(
|
|
28153
|
+
`cannot lock ${file2} for writing: ${detail}` + (holderPid === void 0 ? "" : ` (held by pid ${String(holderPid)})`),
|
|
28154
|
+
options
|
|
28155
|
+
);
|
|
28156
|
+
this.name = "FileLockError";
|
|
28157
|
+
this.reason = reason;
|
|
28158
|
+
this.file = file2;
|
|
28159
|
+
this.holderPid = holderPid;
|
|
28160
|
+
}
|
|
28161
|
+
};
|
|
28162
|
+
function lockPathFor(file2) {
|
|
28163
|
+
return `${file2}${LOCK_SUFFIX}`;
|
|
28164
|
+
}
|
|
28165
|
+
function readLockBody(lock) {
|
|
28166
|
+
let raw;
|
|
28167
|
+
try {
|
|
28168
|
+
raw = readFileSync2(lock, "utf8");
|
|
28169
|
+
} catch {
|
|
28170
|
+
return null;
|
|
28171
|
+
}
|
|
28172
|
+
try {
|
|
28173
|
+
const parsed = JSON.parse(raw);
|
|
28174
|
+
const { pid, token, at, host } = parsed;
|
|
28175
|
+
if (typeof pid !== "number" || typeof token !== "string" || typeof at !== "number") return null;
|
|
28176
|
+
return { pid, token, at, host: typeof host === "string" ? host : "" };
|
|
28177
|
+
} catch {
|
|
28178
|
+
return null;
|
|
28179
|
+
}
|
|
28180
|
+
}
|
|
28181
|
+
function holderIsAlive(pid) {
|
|
28182
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
28183
|
+
try {
|
|
28184
|
+
process.kill(pid, 0);
|
|
28185
|
+
return true;
|
|
28186
|
+
} catch (err) {
|
|
28187
|
+
return err.code !== "ESRCH";
|
|
28188
|
+
}
|
|
28189
|
+
}
|
|
28190
|
+
function directoryAcceptsCreates(lock) {
|
|
28191
|
+
const probe = `${lock}.probe-${randomUUID3()}`;
|
|
28192
|
+
try {
|
|
28193
|
+
closeSync(openSync(probe, "wx", DATA_FILE_MODE));
|
|
28194
|
+
return true;
|
|
28195
|
+
} catch {
|
|
28196
|
+
return false;
|
|
28197
|
+
} finally {
|
|
28198
|
+
try {
|
|
28199
|
+
rmSync5(probe, { force: true });
|
|
28200
|
+
} catch {
|
|
28201
|
+
}
|
|
28202
|
+
}
|
|
28203
|
+
}
|
|
28204
|
+
function tryAcquire(lock, file2) {
|
|
28205
|
+
const token = randomUUID3();
|
|
28206
|
+
let fd;
|
|
28207
|
+
try {
|
|
28208
|
+
fd = openSync(lock, "wx", DATA_FILE_MODE);
|
|
28209
|
+
} catch (err) {
|
|
28210
|
+
const code = err.code ?? "";
|
|
28211
|
+
if (code === "EEXIST") return null;
|
|
28212
|
+
if (RETRYABLE_CREATE_ERRNOS.has(code) && (existsSync2(lock) || directoryAcceptsCreates(lock))) {
|
|
28213
|
+
return null;
|
|
28214
|
+
}
|
|
28215
|
+
throw new FileLockError(
|
|
28216
|
+
"unavailable",
|
|
28217
|
+
file2,
|
|
28218
|
+
err instanceof Error ? err.message : String(err),
|
|
28219
|
+
void 0,
|
|
28220
|
+
{ cause: err }
|
|
28221
|
+
);
|
|
28222
|
+
}
|
|
28223
|
+
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
28224
|
+
try {
|
|
28225
|
+
writeFileSync2(fd, `${JSON.stringify(body)}
|
|
28226
|
+
`);
|
|
28227
|
+
} catch {
|
|
28228
|
+
try {
|
|
28229
|
+
closeSync(fd);
|
|
28230
|
+
} catch {
|
|
28231
|
+
}
|
|
28232
|
+
rmSync5(lock, { force: true });
|
|
28233
|
+
return null;
|
|
28234
|
+
}
|
|
28235
|
+
try {
|
|
28236
|
+
closeSync(fd);
|
|
28237
|
+
} catch {
|
|
28238
|
+
}
|
|
28239
|
+
return token;
|
|
28240
|
+
}
|
|
28241
|
+
function isAbandoned(body, lock, staleMs) {
|
|
28242
|
+
if (!body) {
|
|
28243
|
+
try {
|
|
28244
|
+
return Date.now() - statSync3(lock).mtimeMs >= staleMs;
|
|
28245
|
+
} catch {
|
|
28246
|
+
return false;
|
|
28247
|
+
}
|
|
28248
|
+
}
|
|
28249
|
+
if (Date.now() - body.at < staleMs) return false;
|
|
28250
|
+
if (body.host !== hostname3() || !holderIsAlive(body.pid)) return true;
|
|
28251
|
+
return Date.now() - body.at >= abandonWindow(staleMs);
|
|
28252
|
+
}
|
|
28253
|
+
function breakIfStale(lock, staleMs) {
|
|
28254
|
+
const breaker2 = `${lock}.break`;
|
|
28255
|
+
let fd;
|
|
28256
|
+
try {
|
|
28257
|
+
fd = openSync(breaker2, "wx", DATA_FILE_MODE);
|
|
28258
|
+
} catch {
|
|
28259
|
+
reapAbandonedBreaker(breaker2);
|
|
28260
|
+
return false;
|
|
28261
|
+
}
|
|
28262
|
+
try {
|
|
28263
|
+
closeSync(fd);
|
|
28264
|
+
} catch {
|
|
28265
|
+
}
|
|
28266
|
+
try {
|
|
28267
|
+
if (!existsSync2(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
28268
|
+
rmSync5(lock, { force: true });
|
|
28269
|
+
return true;
|
|
28270
|
+
} catch {
|
|
28271
|
+
return false;
|
|
28272
|
+
} finally {
|
|
28273
|
+
try {
|
|
28274
|
+
rmSync5(breaker2, { force: true });
|
|
28275
|
+
} catch {
|
|
28276
|
+
}
|
|
28277
|
+
}
|
|
28278
|
+
}
|
|
28279
|
+
var BREAKER_ABANDONED_MS = 1e4;
|
|
28280
|
+
function reapAbandonedBreaker(breaker2) {
|
|
28281
|
+
try {
|
|
28282
|
+
if (Date.now() - statSync3(breaker2).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
28283
|
+
rmSync5(breaker2, { force: true });
|
|
28284
|
+
}
|
|
28285
|
+
} catch {
|
|
28286
|
+
}
|
|
28287
|
+
}
|
|
28288
|
+
function abandonWindow(staleMs) {
|
|
28289
|
+
return Math.max(staleMs * 30, 6e4);
|
|
28290
|
+
}
|
|
28291
|
+
function release(lock, token) {
|
|
28292
|
+
try {
|
|
28293
|
+
if (readLockBody(lock)?.token !== token) return;
|
|
28294
|
+
rmSync5(lock, { force: true });
|
|
28295
|
+
} catch {
|
|
28296
|
+
}
|
|
28297
|
+
}
|
|
28298
|
+
function withFileLock(file2, fn, options = {}) {
|
|
28299
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28300
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
28301
|
+
const lock = lockPathFor(file2);
|
|
28302
|
+
const deadline = Date.now() + timeoutMs;
|
|
28303
|
+
let token = tryAcquire(lock, file2);
|
|
28304
|
+
while (token === null) {
|
|
28305
|
+
if (breakIfStale(lock, staleMs)) {
|
|
28306
|
+
token = tryAcquire(lock, file2);
|
|
28307
|
+
continue;
|
|
28308
|
+
}
|
|
28309
|
+
if (Date.now() >= deadline) {
|
|
28310
|
+
throw new FileLockError(
|
|
28311
|
+
"timeout",
|
|
28312
|
+
file2,
|
|
28313
|
+
`still held after ${String(timeoutMs)}ms`,
|
|
28314
|
+
readLockBody(lock)?.pid
|
|
28315
|
+
);
|
|
28316
|
+
}
|
|
28317
|
+
sleepSync(RETRY_INTERVAL_MS);
|
|
28318
|
+
token = tryAcquire(lock, file2);
|
|
28319
|
+
}
|
|
28320
|
+
try {
|
|
28321
|
+
const result = fn();
|
|
28322
|
+
if (isThenable(result)) {
|
|
28323
|
+
void result.then(
|
|
28324
|
+
() => void 0,
|
|
28325
|
+
() => void 0
|
|
28326
|
+
);
|
|
28327
|
+
throw new TypeError(
|
|
28328
|
+
`withFileLock(${file2}) was given an async body; the lock is released as soon as it returns, so the awaited work would run unguarded. Pass a synchronous function.`
|
|
28329
|
+
);
|
|
28330
|
+
}
|
|
28331
|
+
return result;
|
|
28332
|
+
} finally {
|
|
28333
|
+
release(lock, token);
|
|
28334
|
+
}
|
|
28335
|
+
}
|
|
28336
|
+
function isThenable(value) {
|
|
28337
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
28338
|
+
}
|
|
28339
|
+
|
|
28340
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28341
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28342
|
+
import { posix, win32 } from "path";
|
|
28343
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28344
|
+
if (platform2 === "darwin") {
|
|
28345
|
+
return [
|
|
28346
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28347
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28348
|
+
];
|
|
28349
|
+
}
|
|
28350
|
+
if (platform2 === "win32") {
|
|
28351
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28352
|
+
}
|
|
28353
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28354
|
+
}
|
|
28355
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28356
|
+
for (const path of paths) {
|
|
28357
|
+
let text;
|
|
28358
|
+
try {
|
|
28359
|
+
text = readFileSync3(path, "utf8");
|
|
28360
|
+
} catch {
|
|
28361
|
+
continue;
|
|
28362
|
+
}
|
|
28363
|
+
const record2 = parseJsonObject(text);
|
|
28364
|
+
if (!record2) continue;
|
|
28365
|
+
const parsed = ManagedSettings.safeParse(record2);
|
|
28366
|
+
if (parsed.success) return parsed.data;
|
|
28367
|
+
}
|
|
28368
|
+
return null;
|
|
28369
|
+
}
|
|
28370
|
+
function managedContextOf(managed) {
|
|
28371
|
+
if (!managed) return NO_MANAGED_CONTEXT;
|
|
28372
|
+
return {
|
|
28373
|
+
present: true,
|
|
28374
|
+
...managed.organization === void 0 ? {} : { organization: managed.organization },
|
|
28375
|
+
lockedFields: managed.lockedFields
|
|
28376
|
+
};
|
|
28377
|
+
}
|
|
28378
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28379
|
+
if (!managed) return settings;
|
|
28380
|
+
const { values } = managed;
|
|
28381
|
+
const merged = { ...settings };
|
|
28382
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28383
|
+
if (values.controlPlane !== void 0) {
|
|
28384
|
+
merged.controlPlane = {
|
|
28385
|
+
...values.controlPlane,
|
|
28386
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28387
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28388
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28389
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28390
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28391
|
+
};
|
|
28392
|
+
}
|
|
28393
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28394
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28395
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28396
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28397
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28398
|
+
if (values.vaultConsent !== void 0) {
|
|
28399
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28400
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28401
|
+
// at the current version otherwise.
|
|
28402
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28403
|
+
) : void 0;
|
|
28404
|
+
}
|
|
28405
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28406
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28407
|
+
acknowledgedAt: now().toISOString(),
|
|
28408
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28409
|
+
} : void 0;
|
|
28410
|
+
}
|
|
28411
|
+
return merged;
|
|
28412
|
+
}
|
|
28413
|
+
function lockedAmong(context, requested) {
|
|
28414
|
+
if (!context.present) return [];
|
|
28415
|
+
return requested.filter((key) => context.lockedFields.includes(key));
|
|
28416
|
+
}
|
|
28417
|
+
|
|
28418
|
+
// ../../packages/persistence/src/settings.ts
|
|
28419
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28420
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28421
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28422
|
+
}
|
|
28423
|
+
function readUserSettings(base) {
|
|
28424
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28425
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28426
|
+
try {
|
|
28427
|
+
return WorkspaceSettings.parse(record2);
|
|
28428
|
+
} catch {
|
|
28429
|
+
return defaultWorkspaceSettings();
|
|
28430
|
+
}
|
|
28431
|
+
}
|
|
28432
|
+
var ManagedFieldError = class extends Error {
|
|
28433
|
+
fields;
|
|
28434
|
+
constructor(fields) {
|
|
28435
|
+
super(`refusing to write administratively locked settings: ${fields.join(", ")}`);
|
|
28436
|
+
this.name = "ManagedFieldError";
|
|
28437
|
+
this.fields = fields;
|
|
27747
28438
|
}
|
|
27748
28439
|
};
|
|
27749
|
-
|
|
27750
|
-
|
|
27751
|
-
|
|
27752
|
-
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
27756
|
-
|
|
27757
|
-
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27761
|
-
:spanStart, :spanEnd, :maskedMatch, :actionTaken, :confidence,
|
|
27762
|
-
:findingKey,
|
|
27763
|
-
COALESCE(:firstDetectedAt, (SELECT started_at FROM audit_events WHERE id = :auditEventId)))
|
|
27764
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
27765
|
-
inspection_definition_id = excluded.inspection_definition_id
|
|
27766
|
-
ON CONFLICT (finding_key) DO UPDATE SET
|
|
27767
|
-
audit_event_id = excluded.audit_event_id,
|
|
27768
|
-
inspection_definition_id = excluded.inspection_definition_id,
|
|
27769
|
-
classified_data_id = excluded.classified_data_id,
|
|
27770
|
-
span_start = excluded.span_start,
|
|
27771
|
-
span_end = excluded.span_end,
|
|
27772
|
-
masked_match = excluded.masked_match,
|
|
27773
|
-
action_taken = excluded.action_taken,
|
|
27774
|
-
confidence = excluded.confidence`
|
|
27775
|
-
);
|
|
27776
|
-
this.sessionDupStmt = db.prepare(
|
|
27777
|
-
`SELECT 1 FROM inspection_findings f
|
|
27778
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
27779
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27780
|
-
WHERE d.rule_id = :ruleId AND f.masked_match = :maskedMatch
|
|
27781
|
-
AND e.root_session_id = :sessionId
|
|
27782
|
-
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
27783
|
-
LIMIT 1`
|
|
27784
|
-
);
|
|
27785
|
-
this.eventDupStmt = db.prepare(
|
|
27786
|
-
`SELECT 1 FROM inspection_findings f
|
|
27787
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27788
|
-
WHERE f.audit_event_id = :auditEventId AND d.rule_id = :ruleId
|
|
27789
|
-
AND f.masked_match = :maskedMatch
|
|
27790
|
-
AND f.span_start = :spanStart AND f.span_end = :spanEnd
|
|
27791
|
-
LIMIT 1`
|
|
27792
|
-
);
|
|
28440
|
+
function lockableKeysTouched(current, applied) {
|
|
28441
|
+
const keys = [];
|
|
28442
|
+
const changed = (key) => key in applied && applied[key] !== current[key];
|
|
28443
|
+
const descriptorChanged = "controlPlane" in applied && (applied.controlPlane?.endpoint !== current.controlPlane?.endpoint || applied.controlPlane?.label !== current.controlPlane?.label);
|
|
28444
|
+
if (changed("runMode") || descriptorChanged) keys.push("runMode");
|
|
28445
|
+
if (changed("historicalAccess")) keys.push("historicalAccess");
|
|
28446
|
+
if (changed("vaultKeyCustody")) keys.push("vaultKeyCustody");
|
|
28447
|
+
if (changed("vaultInlineReveal")) keys.push("vaultInlineReveal");
|
|
28448
|
+
if (changed("dataSharesInPlace")) keys.push("dataSharesInPlace");
|
|
28449
|
+
if (changed("redactFallback")) keys.push("redactFallback");
|
|
28450
|
+
if ("vaultConsent" in applied && isVaultConsentValid(applied.vaultConsent) !== isVaultConsentValid(current.vaultConsent)) {
|
|
28451
|
+
keys.push("vaultConsent");
|
|
27793
28452
|
}
|
|
27794
|
-
|
|
27795
|
-
|
|
27796
|
-
sessionDupStmt;
|
|
27797
|
-
eventDupStmt;
|
|
27798
|
-
// True when an earlier event in the same session already recorded a finding
|
|
27799
|
-
// with the same rule and masked value. The current event's own findings are
|
|
27800
|
-
// inserted one at a time in caller order, so an earlier finding in the SAME
|
|
27801
|
-
// recordCapture call is visible to a later duplicate check within it too.
|
|
27802
|
-
isSessionDuplicate(ruleId, maskedMatch, sessionId) {
|
|
27803
|
-
return this.sessionDupStmt.get({ ruleId, maskedMatch, sessionId }) !== void 0;
|
|
28453
|
+
if ("modelJudgeConsent" in applied && isModelJudgeConsentValid(applied.modelJudgeConsent) !== isModelJudgeConsentValid(current.modelJudgeConsent)) {
|
|
28454
|
+
keys.push("modelJudgeConsent");
|
|
27804
28455
|
}
|
|
27805
|
-
|
|
27806
|
-
|
|
27807
|
-
|
|
27808
|
-
|
|
28456
|
+
return keys;
|
|
28457
|
+
}
|
|
28458
|
+
function pinnedKeys(managed) {
|
|
28459
|
+
if (!managed) return [];
|
|
28460
|
+
const { values } = managed;
|
|
28461
|
+
const keys = [];
|
|
28462
|
+
if (values.runMode !== void 0 || values.controlPlane !== void 0) keys.push("runMode");
|
|
28463
|
+
if (values.historicalAccess !== void 0) keys.push("historicalAccess");
|
|
28464
|
+
if (values.vaultConsent !== void 0) keys.push("vaultConsent");
|
|
28465
|
+
if (values.vaultKeyCustody !== void 0) keys.push("vaultKeyCustody");
|
|
28466
|
+
if (values.vaultInlineReveal !== void 0) keys.push("vaultInlineReveal");
|
|
28467
|
+
if (values.modelJudgeConsent !== void 0) keys.push("modelJudgeConsent");
|
|
28468
|
+
if (values.dataSharesInPlace !== void 0) keys.push("dataSharesInPlace");
|
|
28469
|
+
if (values.redactFallback !== void 0) keys.push("redactFallback");
|
|
28470
|
+
return keys;
|
|
28471
|
+
}
|
|
28472
|
+
function withoutManagedKeys(applied, managed, pinned, touched) {
|
|
28473
|
+
if (!managed.present) return applied;
|
|
28474
|
+
const strip = (key) => (managed.lockedFields.includes(key) || pinned.includes(key)) && !touched.includes(key);
|
|
28475
|
+
const out = { ...applied };
|
|
28476
|
+
if (strip("runMode")) {
|
|
28477
|
+
delete out.runMode;
|
|
28478
|
+
delete out.controlPlane;
|
|
27809
28479
|
}
|
|
27810
|
-
|
|
27811
|
-
|
|
27812
|
-
|
|
27813
|
-
|
|
27814
|
-
|
|
27815
|
-
|
|
27816
|
-
|
|
27817
|
-
|
|
27818
|
-
|
|
27819
|
-
|
|
27820
|
-
|
|
27821
|
-
|
|
27822
|
-
|
|
27823
|
-
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
);
|
|
28480
|
+
if (strip("historicalAccess")) delete out.historicalAccess;
|
|
28481
|
+
if (strip("vaultConsent")) delete out.vaultConsent;
|
|
28482
|
+
if (strip("vaultKeyCustody")) delete out.vaultKeyCustody;
|
|
28483
|
+
if (strip("vaultInlineReveal")) delete out.vaultInlineReveal;
|
|
28484
|
+
if (strip("modelJudgeConsent")) delete out.modelJudgeConsent;
|
|
28485
|
+
if (strip("dataSharesInPlace")) delete out.dataSharesInPlace;
|
|
28486
|
+
if (strip("redactFallback")) delete out.redactFallback;
|
|
28487
|
+
return out;
|
|
28488
|
+
}
|
|
28489
|
+
function applyOnboarding(answers2, base = defaultDataDir(), managedOverride) {
|
|
28490
|
+
const dir = settingsDir(base);
|
|
28491
|
+
ensureDataDirSync(dir);
|
|
28492
|
+
const file2 = join5(dir, SETTINGS_FILENAME);
|
|
28493
|
+
const managedSettings = managedOverride === void 0 ? readManagedSettings() : managedOverride;
|
|
28494
|
+
const managed = managedContextOf(managedSettings);
|
|
28495
|
+
return withFileLock(file2, () => {
|
|
28496
|
+
const current = readUserSettings(base);
|
|
28497
|
+
const applied = typeof answers2 === "function" ? answers2(current) : answers2;
|
|
28498
|
+
const effective = overlayManagedSettings(current, managedSettings);
|
|
28499
|
+
const touched = lockableKeysTouched(effective, applied);
|
|
28500
|
+
const refused = lockedAmong(managed, touched);
|
|
28501
|
+
if (refused.length > 0) throw new ManagedFieldError(refused);
|
|
28502
|
+
const merged = WorkspaceSettings.parse({
|
|
28503
|
+
...current,
|
|
28504
|
+
// Locked keys are stripped rather than merged. Everything still here is,
|
|
28505
|
+
// by the refusal above, an unchanged ECHO of the administrator's value —
|
|
28506
|
+
// so dropping it discards no answer of the user's, and writing it would
|
|
28507
|
+
// persist the pin into their file, where it would outlive the managed
|
|
28508
|
+
// file and read as their own choice once the lock was gone.
|
|
28509
|
+
...withoutManagedKeys(applied, managed, pinnedKeys(managedSettings), touched),
|
|
28510
|
+
// First setup stamps the time; later edits keep the original completion mark.
|
|
28511
|
+
onboardedAt: applied.onboardedAt ?? current.onboardedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
28512
|
+
});
|
|
28513
|
+
writeOwnerOnlyFileSync(file2, `${JSON.stringify(merged, null, 2)}
|
|
28514
|
+
`);
|
|
28515
|
+
return merged;
|
|
28516
|
+
});
|
|
28517
|
+
}
|
|
28518
|
+
function readJson(file2) {
|
|
28519
|
+
let text;
|
|
28520
|
+
try {
|
|
28521
|
+
text = readFileSync4(file2, "utf8");
|
|
28522
|
+
} catch {
|
|
28523
|
+
return null;
|
|
27827
28524
|
}
|
|
27828
|
-
|
|
28525
|
+
return parseJsonObject(text) ?? null;
|
|
28526
|
+
}
|
|
27829
28527
|
|
|
27830
|
-
// ../../packages/persistence/src/
|
|
27831
|
-
|
|
28528
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28529
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28530
|
+
switch (refusal) {
|
|
28531
|
+
case "lock":
|
|
28532
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28533
|
+
case "disable":
|
|
28534
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28535
|
+
case "floor":
|
|
28536
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28537
|
+
}
|
|
28538
|
+
}
|
|
28539
|
+
var PolicyFloorError = class extends Error {
|
|
28540
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28541
|
+
pack;
|
|
28542
|
+
/**
|
|
28543
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28544
|
+
* clearing the assignment, or switching the detection off.
|
|
28545
|
+
*/
|
|
28546
|
+
attempted;
|
|
28547
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28548
|
+
floor;
|
|
28549
|
+
refusal;
|
|
28550
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28551
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28552
|
+
this.name = "PolicyFloorError";
|
|
28553
|
+
this.pack = pack;
|
|
28554
|
+
this.attempted = attempted;
|
|
28555
|
+
this.floor = floor;
|
|
28556
|
+
this.refusal = refusal;
|
|
28557
|
+
}
|
|
28558
|
+
};
|
|
28559
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28560
|
+
try {
|
|
28561
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28562
|
+
const parsed = JSON.parse(raw);
|
|
28563
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
28564
|
+
return PolicyBundle.parse(parsed.bundle);
|
|
28565
|
+
} catch {
|
|
28566
|
+
return null;
|
|
28567
|
+
}
|
|
28568
|
+
}
|
|
28569
|
+
function indexEnabled(policies) {
|
|
28570
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28571
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28572
|
+
for (const policy of policies) {
|
|
28573
|
+
if (!policy.enabled) continue;
|
|
28574
|
+
if ("ruleId" in policy.target) {
|
|
28575
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28576
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28577
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28578
|
+
}
|
|
28579
|
+
}
|
|
28580
|
+
return { byRuleId, byCategory };
|
|
28581
|
+
}
|
|
28582
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28583
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28584
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28585
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28586
|
+
return policies.some((policy) => {
|
|
28587
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28588
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28589
|
+
});
|
|
28590
|
+
}
|
|
28591
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28592
|
+
const floors = openControlPlaneFloors(base);
|
|
28593
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28594
|
+
}
|
|
28595
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28596
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28597
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28598
|
+
if (bundle === null) return null;
|
|
28599
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28600
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28601
|
+
}
|
|
28602
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28603
|
+
let action = null;
|
|
28604
|
+
for (const rule of rules) {
|
|
28605
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28606
|
+
if (resolved === void 0) continue;
|
|
28607
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28608
|
+
}
|
|
28609
|
+
if (action === null) return null;
|
|
28610
|
+
return {
|
|
28611
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28612
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28613
|
+
};
|
|
28614
|
+
}
|
|
28615
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28616
|
+
if (floor.locked) return "lock";
|
|
28617
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28618
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28619
|
+
}
|
|
28620
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28621
|
+
if (floor === null || enabled) return null;
|
|
28622
|
+
return "disable";
|
|
28623
|
+
}
|
|
27832
28624
|
|
|
27833
28625
|
// ../../packages/persistence/src/semver.ts
|
|
27834
28626
|
function parse3(version2) {
|
|
@@ -27922,8 +28714,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
27922
28714
|
return ids;
|
|
27923
28715
|
}
|
|
27924
28716
|
var SqliteInstalledPacksRepository = class {
|
|
27925
|
-
|
|
28717
|
+
/**
|
|
28718
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28719
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28720
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28721
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28722
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28723
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28724
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28725
|
+
* real `~/.aka`.
|
|
28726
|
+
*/
|
|
28727
|
+
constructor(db, baseDir) {
|
|
27926
28728
|
this.db = db;
|
|
28729
|
+
this.baseDir = baseDir;
|
|
27927
28730
|
this.insertMissingStmt = db.prepare(
|
|
27928
28731
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
27929
28732
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -27945,11 +28748,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
27945
28748
|
this.signatureStmt = db.prepare(
|
|
27946
28749
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
27947
28750
|
);
|
|
28751
|
+
this.packRulesStmt = db.prepare(
|
|
28752
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28753
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28754
|
+
);
|
|
27948
28755
|
}
|
|
27949
28756
|
db;
|
|
28757
|
+
baseDir;
|
|
27950
28758
|
insertMissingStmt;
|
|
27951
28759
|
upsertAvailableStmt;
|
|
27952
28760
|
signatureStmt;
|
|
28761
|
+
packRulesStmt;
|
|
27953
28762
|
/**
|
|
27954
28763
|
* Record the running binary's detection inventory. Refreshes the
|
|
27955
28764
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -27991,7 +28800,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
27991
28800
|
let behind = false;
|
|
27992
28801
|
for (const row of rows) {
|
|
27993
28802
|
const params = {
|
|
27994
|
-
id:
|
|
28803
|
+
id: randomUUID4(),
|
|
27995
28804
|
namespace: row.namespace,
|
|
27996
28805
|
packId: row.packId,
|
|
27997
28806
|
version: row.version,
|
|
@@ -28003,7 +28812,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28003
28812
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28004
28813
|
this.upsertAvailableStmt.run({
|
|
28005
28814
|
...params,
|
|
28006
|
-
id:
|
|
28815
|
+
id: randomUUID4(),
|
|
28007
28816
|
recordedBy: meta4?.recordedBy ?? null
|
|
28008
28817
|
});
|
|
28009
28818
|
} else {
|
|
@@ -28249,9 +29058,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28249
29058
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28250
29059
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28251
29060
|
// caller can tell an edit from a no-such-detection.
|
|
29061
|
+
/**
|
|
29062
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
29063
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
29064
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
29065
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
29066
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
29067
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
29068
|
+
*/
|
|
29069
|
+
packFloorRules(namespace, packId) {
|
|
29070
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
29071
|
+
if (!row) return [];
|
|
29072
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
29073
|
+
}
|
|
29074
|
+
/**
|
|
29075
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
29076
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
29077
|
+
* repository constructed without a layout base).
|
|
29078
|
+
*
|
|
29079
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
29080
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
29081
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
29082
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
29083
|
+
*/
|
|
29084
|
+
policyFloor(namespace, packId) {
|
|
29085
|
+
if (this.baseDir === void 0) return null;
|
|
29086
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
29087
|
+
}
|
|
29088
|
+
/**
|
|
29089
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
29090
|
+
* entry only for a pack the control plane actually governs.
|
|
29091
|
+
*
|
|
29092
|
+
* A surface listing every detection asks per pack, and asking through
|
|
29093
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
29094
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
29095
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
29096
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
29097
|
+
* exactly as the single-pack read returns null for them.
|
|
29098
|
+
*/
|
|
29099
|
+
policyFloors(packs) {
|
|
29100
|
+
const floors = /* @__PURE__ */ new Map();
|
|
29101
|
+
if (this.baseDir === void 0) return floors;
|
|
29102
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
29103
|
+
if (source === null) return floors;
|
|
29104
|
+
for (const pack of packs) {
|
|
29105
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
29106
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
29107
|
+
}
|
|
29108
|
+
return floors;
|
|
29109
|
+
}
|
|
28252
29110
|
/**
|
|
28253
29111
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28254
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
29112
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
29113
|
+
*
|
|
29114
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
29115
|
+
* write below, and a detection the organization has authored a policy for is
|
|
29116
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
29117
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
29118
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
29119
|
+
* surface that offers the choice.
|
|
28255
29120
|
*/
|
|
28256
29121
|
setPolicy(namespace, packId, policyId) {
|
|
28257
29122
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28259,14 +29124,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28259
29124
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28260
29125
|
);
|
|
28261
29126
|
}
|
|
29127
|
+
const requested = policyId;
|
|
29128
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29129
|
+
if (floor !== null) {
|
|
29130
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
29131
|
+
if (refusal !== null) {
|
|
29132
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
29133
|
+
}
|
|
29134
|
+
}
|
|
28262
29135
|
const res = this.db.prepare(
|
|
28263
29136
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28264
29137
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28265
29138
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28266
29139
|
return Number(res.changes) > 0;
|
|
28267
29140
|
}
|
|
28268
|
-
/**
|
|
29141
|
+
/**
|
|
29142
|
+
* Enable or disable one installed pack.
|
|
29143
|
+
*
|
|
29144
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29145
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29146
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29147
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29148
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29149
|
+
* that lived in a page would leave the CLI free.
|
|
29150
|
+
*/
|
|
28269
29151
|
setEnabled(namespace, packId, enabled) {
|
|
29152
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29153
|
+
if (floor !== null) {
|
|
29154
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29155
|
+
if (refusal !== null) {
|
|
29156
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29157
|
+
}
|
|
29158
|
+
}
|
|
28270
29159
|
const res = this.db.prepare(
|
|
28271
29160
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28272
29161
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28352,7 +29241,7 @@ var SqliteInventoryRepository = class {
|
|
|
28352
29241
|
};
|
|
28353
29242
|
|
|
28354
29243
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28355
|
-
import { randomUUID as
|
|
29244
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28356
29245
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28357
29246
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28358
29247
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -28841,7 +29730,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28841
29730
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
28842
29731
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
28843
29732
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
28844
|
-
).run({ id:
|
|
29733
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
28845
29734
|
}
|
|
28846
29735
|
return true;
|
|
28847
29736
|
}
|
|
@@ -28862,7 +29751,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28862
29751
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
28863
29752
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
28864
29753
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
28865
|
-
).run({ id:
|
|
29754
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
28866
29755
|
}
|
|
28867
29756
|
this.configRowsCache = void 0;
|
|
28868
29757
|
return "ok";
|
|
@@ -29159,7 +30048,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29159
30048
|
};
|
|
29160
30049
|
|
|
29161
30050
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29162
|
-
import { randomUUID as
|
|
30051
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29163
30052
|
var SqlitePoliciesRepository = class {
|
|
29164
30053
|
constructor(db) {
|
|
29165
30054
|
this.db = db;
|
|
@@ -29194,7 +30083,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29194
30083
|
failOpenTransaction(this.db, () => {
|
|
29195
30084
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29196
30085
|
stmt.run({
|
|
29197
|
-
id:
|
|
30086
|
+
id: randomUUID6(),
|
|
29198
30087
|
target: JSON.stringify({ category }),
|
|
29199
30088
|
action,
|
|
29200
30089
|
now: Date.now()
|
|
@@ -29214,7 +30103,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29214
30103
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29215
30104
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29216
30105
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29217
|
-
).run({ id:
|
|
30106
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29218
30107
|
}
|
|
29219
30108
|
// Caps every global per-category policy currently set to block/redact down
|
|
29220
30109
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29282,7 +30171,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29282
30171
|
};
|
|
29283
30172
|
|
|
29284
30173
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29285
|
-
import { randomUUID as
|
|
30174
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29286
30175
|
var SqliteProjectFilesRepository = class {
|
|
29287
30176
|
constructor(db) {
|
|
29288
30177
|
this.db = db;
|
|
@@ -29314,7 +30203,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29314
30203
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29315
30204
|
for (const file2 of scan2.files) {
|
|
29316
30205
|
this.upsertStmt.run({
|
|
29317
|
-
id:
|
|
30206
|
+
id: randomUUID7(),
|
|
29318
30207
|
projectId,
|
|
29319
30208
|
path: file2.path,
|
|
29320
30209
|
name: file2.name,
|
|
@@ -29328,9 +30217,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29328
30217
|
};
|
|
29329
30218
|
|
|
29330
30219
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29331
|
-
import { randomUUID as
|
|
30220
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29332
30221
|
var SqliteResolutionsRepository = class {
|
|
29333
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30222
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29334
30223
|
this.db = db;
|
|
29335
30224
|
this.now = now;
|
|
29336
30225
|
this.newId = newId;
|
|
@@ -29543,7 +30432,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29543
30432
|
};
|
|
29544
30433
|
|
|
29545
30434
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29546
|
-
import { randomUUID as
|
|
30435
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29547
30436
|
function pageLimit(requested, fallback) {
|
|
29548
30437
|
if (requested === void 0) return fallback;
|
|
29549
30438
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29589,12 +30478,14 @@ var SELECT_COLUMNS = `
|
|
|
29589
30478
|
ciphertext,
|
|
29590
30479
|
nonce,
|
|
29591
30480
|
auth_tag AS authTag,
|
|
30481
|
+
user_authorized AS userAuthorized,
|
|
29592
30482
|
occurrence_count AS occurrenceCount,
|
|
29593
30483
|
first_seen AS firstSeen,
|
|
29594
30484
|
last_seen AS lastSeen`;
|
|
29595
30485
|
function toRow(raw) {
|
|
29596
|
-
const { provider, ...rest } = raw;
|
|
29597
|
-
|
|
30486
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30487
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30488
|
+
return provider === null ? row : { ...row, provider };
|
|
29598
30489
|
}
|
|
29599
30490
|
var SqliteSecretVaultRepository = class {
|
|
29600
30491
|
constructor(db) {
|
|
@@ -29604,17 +30495,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29604
30495
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29605
30496
|
format_version, category, rule_id, masked_match, provider,
|
|
29606
30497
|
ciphertext, nonce, auth_tag,
|
|
29607
|
-
occurrence_count, first_seen, last_seen
|
|
30498
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29608
30499
|
) VALUES (
|
|
29609
30500
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29610
30501
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29611
30502
|
:ciphertext, :nonce, :authTag,
|
|
29612
|
-
1, :now, :now
|
|
30503
|
+
:userAuthorized, 1, :now, :now
|
|
29613
30504
|
)`
|
|
29614
30505
|
);
|
|
29615
30506
|
this.bumpStmt = db.prepare(
|
|
29616
30507
|
`UPDATE secret_vault
|
|
29617
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30508
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30509
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29618
30510
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29619
30511
|
);
|
|
29620
30512
|
this.byPointerStmt = db.prepare(
|
|
@@ -29634,6 +30526,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29634
30526
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29635
30527
|
WHERE pointer_id = :pointerId`
|
|
29636
30528
|
);
|
|
30529
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29637
30530
|
this.derefStmt = db.prepare(
|
|
29638
30531
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29639
30532
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29647,6 +30540,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29647
30540
|
listStmt;
|
|
29648
30541
|
replaceCiphertextStmt;
|
|
29649
30542
|
refreshFingerprintStmt;
|
|
30543
|
+
deleteByPointerStmt;
|
|
29650
30544
|
derefStmt;
|
|
29651
30545
|
/**
|
|
29652
30546
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29655,6 +30549,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29655
30549
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29656
30550
|
* wire token. `minted` is true only when this call created the row.
|
|
29657
30551
|
*
|
|
30552
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30553
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30554
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30555
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30556
|
+
*
|
|
29658
30557
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29659
30558
|
* writers cannot both decide they are minting.
|
|
29660
30559
|
*/
|
|
@@ -29681,13 +30580,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29681
30580
|
ciphertext: input2.ciphertext,
|
|
29682
30581
|
nonce: input2.nonce,
|
|
29683
30582
|
authTag: input2.authTag,
|
|
30583
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29684
30584
|
now
|
|
29685
30585
|
})
|
|
29686
30586
|
);
|
|
29687
30587
|
minted = true;
|
|
29688
30588
|
return;
|
|
29689
30589
|
}
|
|
29690
|
-
this.bumpStmt.run({
|
|
30590
|
+
this.bumpStmt.run({
|
|
30591
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30592
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30593
|
+
now
|
|
30594
|
+
});
|
|
29691
30595
|
},
|
|
29692
30596
|
"IMMEDIATE"
|
|
29693
30597
|
);
|
|
@@ -29747,6 +30651,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29747
30651
|
);
|
|
29748
30652
|
return destroyed;
|
|
29749
30653
|
}
|
|
30654
|
+
/**
|
|
30655
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30656
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30657
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30658
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30659
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30660
|
+
* it.
|
|
30661
|
+
*
|
|
30662
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30663
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30664
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30665
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30666
|
+
* entry still sitting in the vault.
|
|
30667
|
+
*
|
|
30668
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30669
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30670
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30671
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30672
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30673
|
+
*/
|
|
30674
|
+
deleteByPointerIds(pointerIds) {
|
|
30675
|
+
if (pointerIds.length === 0) return [];
|
|
30676
|
+
const deleted = [];
|
|
30677
|
+
withTransaction(
|
|
30678
|
+
this.db,
|
|
30679
|
+
() => {
|
|
30680
|
+
for (const pointerId of pointerIds) {
|
|
30681
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30682
|
+
deleted.push(pointerId);
|
|
30683
|
+
}
|
|
30684
|
+
}
|
|
30685
|
+
},
|
|
30686
|
+
"IMMEDIATE"
|
|
30687
|
+
);
|
|
30688
|
+
return deleted;
|
|
30689
|
+
}
|
|
29750
30690
|
/**
|
|
29751
30691
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29752
30692
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29759,7 +30699,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29759
30699
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29760
30700
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29761
30701
|
).run({
|
|
29762
|
-
id:
|
|
30702
|
+
id: randomUUID9(),
|
|
29763
30703
|
pointerId: entry.pointerId,
|
|
29764
30704
|
location: entry.location,
|
|
29765
30705
|
kind: entry.kind,
|
|
@@ -30272,15 +31212,15 @@ var SqliteSecurityRepository = class {
|
|
|
30272
31212
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30273
31213
|
const rows = allRows(
|
|
30274
31214
|
this.db.prepare(
|
|
30275
|
-
`SELECT
|
|
31215
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30276
31216
|
FROM inspection_findings f
|
|
30277
31217
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30278
31218
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30279
31219
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30280
|
-
AND
|
|
30281
|
-
AND
|
|
30282
|
-
GROUP BY repo
|
|
30283
|
-
ORDER BY c DESC, repo
|
|
31220
|
+
AND e.repo IS NOT NULL
|
|
31221
|
+
AND e.repo != ''
|
|
31222
|
+
GROUP BY e.repo
|
|
31223
|
+
ORDER BY c DESC, e.repo
|
|
30284
31224
|
LIMIT :limit`
|
|
30285
31225
|
),
|
|
30286
31226
|
{ from, to: now, limit }
|
|
@@ -30342,7 +31282,7 @@ var SqliteSecurityRepository = class {
|
|
|
30342
31282
|
`SELECT f.finding_key AS finding_key,
|
|
30343
31283
|
d.rule_id AS rule_id,
|
|
30344
31284
|
d.severity AS severity,
|
|
30345
|
-
|
|
31285
|
+
e.file_path AS path,
|
|
30346
31286
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30347
31287
|
latest.resolved_at AS latest_resolved_at
|
|
30348
31288
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30395,7 +31335,7 @@ var SqliteSecurityRepository = class {
|
|
|
30395
31335
|
};
|
|
30396
31336
|
|
|
30397
31337
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30398
|
-
import { randomUUID as
|
|
31338
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30399
31339
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30400
31340
|
var IN_CHUNK = 500;
|
|
30401
31341
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30483,7 +31423,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30483
31423
|
callSiteCount,
|
|
30484
31424
|
transports: distinctTransports(transports),
|
|
30485
31425
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30486
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31426
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30487
31427
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30488
31428
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30489
31429
|
};
|
|
@@ -30510,7 +31450,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30510
31450
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30511
31451
|
transports: distinctTransports(transports),
|
|
30512
31452
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30513
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31453
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30514
31454
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30515
31455
|
note: dest.note,
|
|
30516
31456
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30539,7 +31479,11 @@ var SqliteSharesRepository = class {
|
|
|
30539
31479
|
FROM share_destination d
|
|
30540
31480
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30541
31481
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30542
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31482
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31483
|
+
AND NOT EXISTS (
|
|
31484
|
+
SELECT 1 FROM egress_decision_override o
|
|
31485
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31486
|
+
)`
|
|
30543
31487
|
);
|
|
30544
31488
|
const kindCounts = countBy(
|
|
30545
31489
|
this.db,
|
|
@@ -30651,7 +31595,7 @@ var SqliteSharesRepository = class {
|
|
|
30651
31595
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30652
31596
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30653
31597
|
).run({
|
|
30654
|
-
id:
|
|
31598
|
+
id: randomUUID10(),
|
|
30655
31599
|
destinationId,
|
|
30656
31600
|
host: dest.host,
|
|
30657
31601
|
decision,
|
|
@@ -30800,7 +31744,7 @@ var SqliteSharesRepository = class {
|
|
|
30800
31744
|
let destinationId = destIds.get(hit.host);
|
|
30801
31745
|
if (destinationId === void 0) {
|
|
30802
31746
|
destStmt.run({
|
|
30803
|
-
id:
|
|
31747
|
+
id: randomUUID10(),
|
|
30804
31748
|
kind: hit.kind,
|
|
30805
31749
|
name: hit.name,
|
|
30806
31750
|
host: hit.host,
|
|
@@ -30816,7 +31760,7 @@ var SqliteSharesRepository = class {
|
|
|
30816
31760
|
let endpointId = endpointIds.get(endpointKey);
|
|
30817
31761
|
if (endpointId === void 0) {
|
|
30818
31762
|
endpointStmt.run({
|
|
30819
|
-
id:
|
|
31763
|
+
id: randomUUID10(),
|
|
30820
31764
|
destinationId,
|
|
30821
31765
|
method: hit.method,
|
|
30822
31766
|
transport: hit.transport,
|
|
@@ -30829,7 +31773,7 @@ var SqliteSharesRepository = class {
|
|
|
30829
31773
|
endpointIds.set(endpointKey, endpointId);
|
|
30830
31774
|
}
|
|
30831
31775
|
siteStmt.run({
|
|
30832
|
-
id:
|
|
31776
|
+
id: randomUUID10(),
|
|
30833
31777
|
endpointId,
|
|
30834
31778
|
project: input2.project,
|
|
30835
31779
|
projectKey: input2.projectKey,
|
|
@@ -31194,6 +32138,7 @@ function purgeSampleData(db) {
|
|
|
31194
32138
|
}
|
|
31195
32139
|
|
|
31196
32140
|
// ../../packages/persistence/src/database.ts
|
|
32141
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31197
32142
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31198
32143
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31199
32144
|
);
|
|
@@ -31241,7 +32186,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31241
32186
|
discardStore(file2, backup);
|
|
31242
32187
|
return backup;
|
|
31243
32188
|
}
|
|
31244
|
-
function openAndInitialize(file2) {
|
|
32189
|
+
function openAndInitialize(file2, base) {
|
|
31245
32190
|
let db = openWithPragmas(file2);
|
|
31246
32191
|
try {
|
|
31247
32192
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31254,7 +32199,7 @@ function openAndInitialize(file2) {
|
|
|
31254
32199
|
applyMigrations(db, file2);
|
|
31255
32200
|
tightenPerms(file2);
|
|
31256
32201
|
const policies = new SqlitePoliciesRepository(db);
|
|
31257
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32202
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31258
32203
|
const repositories = {
|
|
31259
32204
|
events: new SqliteEventsRepository(db),
|
|
31260
32205
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31290,7 +32235,7 @@ function openAndInitialize(file2) {
|
|
|
31290
32235
|
}
|
|
31291
32236
|
function openLocalDatabase(dir) {
|
|
31292
32237
|
ensureDataDirSync(dir);
|
|
31293
|
-
const file2 =
|
|
32238
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31294
32239
|
reapStalePartials(file2);
|
|
31295
32240
|
const {
|
|
31296
32241
|
db,
|
|
@@ -31318,7 +32263,13 @@ function openLocalDatabase(dir) {
|
|
|
31318
32263
|
inspectionDefinitions,
|
|
31319
32264
|
inspectionFindings,
|
|
31320
32265
|
configInventory
|
|
31321
|
-
} = openAndInitialize(
|
|
32266
|
+
} = openAndInitialize(
|
|
32267
|
+
file2,
|
|
32268
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32269
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32270
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32271
|
+
dirname2(dir)
|
|
32272
|
+
);
|
|
31322
32273
|
function captureRowId(event) {
|
|
31323
32274
|
return captureId(
|
|
31324
32275
|
event.metadata?.sessionId ?? null,
|
|
@@ -31331,6 +32282,21 @@ function openLocalDatabase(dir) {
|
|
|
31331
32282
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31332
32283
|
});
|
|
31333
32284
|
}
|
|
32285
|
+
function markCaptureOwed(event) {
|
|
32286
|
+
failOpenTransaction(db, () => {
|
|
32287
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32288
|
+
});
|
|
32289
|
+
}
|
|
32290
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32291
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32292
|
+
if (stampable.length === 0) return;
|
|
32293
|
+
failOpenTransaction(db, () => {
|
|
32294
|
+
historySync.markSynced(
|
|
32295
|
+
stampable.map((event) => event.id),
|
|
32296
|
+
atMs
|
|
32297
|
+
);
|
|
32298
|
+
});
|
|
32299
|
+
}
|
|
31334
32300
|
function recordCapture(event, detected) {
|
|
31335
32301
|
failOpenTransaction(db, () => {
|
|
31336
32302
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31417,7 +32383,7 @@ function openLocalDatabase(dir) {
|
|
|
31417
32383
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31418
32384
|
if (!definitionId) continue;
|
|
31419
32385
|
inspectionFindings.insertFinding({
|
|
31420
|
-
id:
|
|
32386
|
+
id: randomUUID11(),
|
|
31421
32387
|
auditEventId: record2.scanEvent.id,
|
|
31422
32388
|
inspectionDefinitionId: definitionId,
|
|
31423
32389
|
span: finding.span,
|
|
@@ -31497,252 +32463,40 @@ function openLocalDatabase(dir) {
|
|
|
31497
32463
|
historySync,
|
|
31498
32464
|
secretVault,
|
|
31499
32465
|
exceptions,
|
|
31500
|
-
resolutions,
|
|
31501
|
-
ruleProbeCache,
|
|
31502
|
-
security,
|
|
31503
|
-
detections,
|
|
31504
|
-
shares,
|
|
31505
|
-
policyCatalog,
|
|
31506
|
-
inventory,
|
|
31507
|
-
inventoryAssets,
|
|
31508
|
-
activity,
|
|
31509
|
-
sourceProject,
|
|
31510
|
-
auditEvents,
|
|
31511
|
-
classifiedData,
|
|
31512
|
-
inspectionDefinitions,
|
|
31513
|
-
inspectionFindings,
|
|
31514
|
-
recordCapture,
|
|
31515
|
-
markCaptureDelivered,
|
|
31516
|
-
|
|
31517
|
-
|
|
31518
|
-
|
|
31519
|
-
|
|
31520
|
-
|
|
31521
|
-
|
|
31522
|
-
|
|
31523
|
-
|
|
31524
|
-
|
|
31525
|
-
|
|
31526
|
-
|
|
31527
|
-
|
|
31528
|
-
|
|
31529
|
-
|
|
31530
|
-
|
|
31531
|
-
|
|
31532
|
-
|
|
31533
|
-
|
|
31534
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31535
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31536
|
-
import {
|
|
31537
|
-
closeSync,
|
|
31538
|
-
existsSync as existsSync2,
|
|
31539
|
-
openSync,
|
|
31540
|
-
readFileSync as readFileSync2,
|
|
31541
|
-
rmSync as rmSync5,
|
|
31542
|
-
statSync as statSync3,
|
|
31543
|
-
writeFileSync as writeFileSync2
|
|
31544
|
-
} from "fs";
|
|
31545
|
-
import { hostname as hostname3 } from "os";
|
|
31546
|
-
var LOCK_SUFFIX = ".lock";
|
|
31547
|
-
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
31548
|
-
var DEFAULT_STALE_MS = 2e3;
|
|
31549
|
-
var RETRY_INTERVAL_MS = 5;
|
|
31550
|
-
var RETRYABLE_CREATE_ERRNOS = /* @__PURE__ */ new Set(["EEXIST", "EACCES", "EPERM", "EBUSY"]);
|
|
31551
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31552
|
-
function sleepSync(ms) {
|
|
31553
|
-
Atomics.wait(PARK, 0, 0, ms);
|
|
31554
|
-
}
|
|
31555
|
-
var FileLockError = class extends Error {
|
|
31556
|
-
reason;
|
|
31557
|
-
file;
|
|
31558
|
-
holderPid;
|
|
31559
|
-
constructor(reason, file2, detail, holderPid, options) {
|
|
31560
|
-
super(
|
|
31561
|
-
`cannot lock ${file2} for writing: ${detail}` + (holderPid === void 0 ? "" : ` (held by pid ${String(holderPid)})`),
|
|
31562
|
-
options
|
|
31563
|
-
);
|
|
31564
|
-
this.name = "FileLockError";
|
|
31565
|
-
this.reason = reason;
|
|
31566
|
-
this.file = file2;
|
|
31567
|
-
this.holderPid = holderPid;
|
|
31568
|
-
}
|
|
31569
|
-
};
|
|
31570
|
-
function lockPathFor(file2) {
|
|
31571
|
-
return `${file2}${LOCK_SUFFIX}`;
|
|
31572
|
-
}
|
|
31573
|
-
function readLockBody(lock) {
|
|
31574
|
-
let raw;
|
|
31575
|
-
try {
|
|
31576
|
-
raw = readFileSync2(lock, "utf8");
|
|
31577
|
-
} catch {
|
|
31578
|
-
return null;
|
|
31579
|
-
}
|
|
31580
|
-
try {
|
|
31581
|
-
const parsed = JSON.parse(raw);
|
|
31582
|
-
const { pid, token, at, host } = parsed;
|
|
31583
|
-
if (typeof pid !== "number" || typeof token !== "string" || typeof at !== "number") return null;
|
|
31584
|
-
return { pid, token, at, host: typeof host === "string" ? host : "" };
|
|
31585
|
-
} catch {
|
|
31586
|
-
return null;
|
|
31587
|
-
}
|
|
31588
|
-
}
|
|
31589
|
-
function holderIsAlive(pid) {
|
|
31590
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
31591
|
-
try {
|
|
31592
|
-
process.kill(pid, 0);
|
|
31593
|
-
return true;
|
|
31594
|
-
} catch (err) {
|
|
31595
|
-
return err.code !== "ESRCH";
|
|
31596
|
-
}
|
|
31597
|
-
}
|
|
31598
|
-
function directoryAcceptsCreates(lock) {
|
|
31599
|
-
const probe = `${lock}.probe-${randomUUID11()}`;
|
|
31600
|
-
try {
|
|
31601
|
-
closeSync(openSync(probe, "wx", DATA_FILE_MODE));
|
|
31602
|
-
return true;
|
|
31603
|
-
} catch {
|
|
31604
|
-
return false;
|
|
31605
|
-
} finally {
|
|
31606
|
-
try {
|
|
31607
|
-
rmSync5(probe, { force: true });
|
|
31608
|
-
} catch {
|
|
31609
|
-
}
|
|
31610
|
-
}
|
|
31611
|
-
}
|
|
31612
|
-
function tryAcquire(lock, file2) {
|
|
31613
|
-
const token = randomUUID11();
|
|
31614
|
-
let fd;
|
|
31615
|
-
try {
|
|
31616
|
-
fd = openSync(lock, "wx", DATA_FILE_MODE);
|
|
31617
|
-
} catch (err) {
|
|
31618
|
-
const code = err.code ?? "";
|
|
31619
|
-
if (code === "EEXIST") return null;
|
|
31620
|
-
if (RETRYABLE_CREATE_ERRNOS.has(code) && (existsSync2(lock) || directoryAcceptsCreates(lock))) {
|
|
31621
|
-
return null;
|
|
31622
|
-
}
|
|
31623
|
-
throw new FileLockError(
|
|
31624
|
-
"unavailable",
|
|
31625
|
-
file2,
|
|
31626
|
-
err instanceof Error ? err.message : String(err),
|
|
31627
|
-
void 0,
|
|
31628
|
-
{ cause: err }
|
|
31629
|
-
);
|
|
31630
|
-
}
|
|
31631
|
-
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
31632
|
-
try {
|
|
31633
|
-
writeFileSync2(fd, `${JSON.stringify(body)}
|
|
31634
|
-
`);
|
|
31635
|
-
} catch {
|
|
31636
|
-
try {
|
|
31637
|
-
closeSync(fd);
|
|
31638
|
-
} catch {
|
|
31639
|
-
}
|
|
31640
|
-
rmSync5(lock, { force: true });
|
|
31641
|
-
return null;
|
|
31642
|
-
}
|
|
31643
|
-
try {
|
|
31644
|
-
closeSync(fd);
|
|
31645
|
-
} catch {
|
|
31646
|
-
}
|
|
31647
|
-
return token;
|
|
31648
|
-
}
|
|
31649
|
-
function isAbandoned(body, lock, staleMs) {
|
|
31650
|
-
if (!body) {
|
|
31651
|
-
try {
|
|
31652
|
-
return Date.now() - statSync3(lock).mtimeMs >= staleMs;
|
|
31653
|
-
} catch {
|
|
31654
|
-
return false;
|
|
31655
|
-
}
|
|
31656
|
-
}
|
|
31657
|
-
if (Date.now() - body.at < staleMs) return false;
|
|
31658
|
-
if (body.host !== hostname3() || !holderIsAlive(body.pid)) return true;
|
|
31659
|
-
return Date.now() - body.at >= abandonWindow(staleMs);
|
|
31660
|
-
}
|
|
31661
|
-
function breakIfStale(lock, staleMs) {
|
|
31662
|
-
const breaker2 = `${lock}.break`;
|
|
31663
|
-
let fd;
|
|
31664
|
-
try {
|
|
31665
|
-
fd = openSync(breaker2, "wx", DATA_FILE_MODE);
|
|
31666
|
-
} catch {
|
|
31667
|
-
reapAbandonedBreaker(breaker2);
|
|
31668
|
-
return false;
|
|
31669
|
-
}
|
|
31670
|
-
try {
|
|
31671
|
-
closeSync(fd);
|
|
31672
|
-
} catch {
|
|
31673
|
-
}
|
|
31674
|
-
try {
|
|
31675
|
-
if (!existsSync2(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
31676
|
-
rmSync5(lock, { force: true });
|
|
31677
|
-
return true;
|
|
31678
|
-
} catch {
|
|
31679
|
-
return false;
|
|
31680
|
-
} finally {
|
|
31681
|
-
try {
|
|
31682
|
-
rmSync5(breaker2, { force: true });
|
|
31683
|
-
} catch {
|
|
31684
|
-
}
|
|
31685
|
-
}
|
|
31686
|
-
}
|
|
31687
|
-
var BREAKER_ABANDONED_MS = 1e4;
|
|
31688
|
-
function reapAbandonedBreaker(breaker2) {
|
|
31689
|
-
try {
|
|
31690
|
-
if (Date.now() - statSync3(breaker2).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
31691
|
-
rmSync5(breaker2, { force: true });
|
|
31692
|
-
}
|
|
31693
|
-
} catch {
|
|
31694
|
-
}
|
|
31695
|
-
}
|
|
31696
|
-
function abandonWindow(staleMs) {
|
|
31697
|
-
return Math.max(staleMs * 30, 6e4);
|
|
31698
|
-
}
|
|
31699
|
-
function release(lock, token) {
|
|
31700
|
-
try {
|
|
31701
|
-
if (readLockBody(lock)?.token !== token) return;
|
|
31702
|
-
rmSync5(lock, { force: true });
|
|
31703
|
-
} catch {
|
|
31704
|
-
}
|
|
31705
|
-
}
|
|
31706
|
-
function withFileLock(file2, fn, options = {}) {
|
|
31707
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31708
|
-
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
31709
|
-
const lock = lockPathFor(file2);
|
|
31710
|
-
const deadline = Date.now() + timeoutMs;
|
|
31711
|
-
let token = tryAcquire(lock, file2);
|
|
31712
|
-
while (token === null) {
|
|
31713
|
-
if (breakIfStale(lock, staleMs)) {
|
|
31714
|
-
token = tryAcquire(lock, file2);
|
|
31715
|
-
continue;
|
|
31716
|
-
}
|
|
31717
|
-
if (Date.now() >= deadline) {
|
|
31718
|
-
throw new FileLockError(
|
|
31719
|
-
"timeout",
|
|
31720
|
-
file2,
|
|
31721
|
-
`still held after ${String(timeoutMs)}ms`,
|
|
31722
|
-
readLockBody(lock)?.pid
|
|
31723
|
-
);
|
|
31724
|
-
}
|
|
31725
|
-
sleepSync(RETRY_INTERVAL_MS);
|
|
31726
|
-
token = tryAcquire(lock, file2);
|
|
31727
|
-
}
|
|
31728
|
-
try {
|
|
31729
|
-
const result = fn();
|
|
31730
|
-
if (isThenable(result)) {
|
|
31731
|
-
void result.then(
|
|
31732
|
-
() => void 0,
|
|
31733
|
-
() => void 0
|
|
31734
|
-
);
|
|
31735
|
-
throw new TypeError(
|
|
31736
|
-
`withFileLock(${file2}) was given an async body; the lock is released as soon as it returns, so the awaited work would run unguarded. Pass a synchronous function.`
|
|
31737
|
-
);
|
|
31738
|
-
}
|
|
31739
|
-
return result;
|
|
31740
|
-
} finally {
|
|
31741
|
-
release(lock, token);
|
|
31742
|
-
}
|
|
31743
|
-
}
|
|
31744
|
-
function isThenable(value) {
|
|
31745
|
-
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
32466
|
+
resolutions,
|
|
32467
|
+
ruleProbeCache,
|
|
32468
|
+
security,
|
|
32469
|
+
detections,
|
|
32470
|
+
shares,
|
|
32471
|
+
policyCatalog,
|
|
32472
|
+
inventory,
|
|
32473
|
+
inventoryAssets,
|
|
32474
|
+
activity,
|
|
32475
|
+
sourceProject,
|
|
32476
|
+
auditEvents,
|
|
32477
|
+
classifiedData,
|
|
32478
|
+
inspectionDefinitions,
|
|
32479
|
+
inspectionFindings,
|
|
32480
|
+
recordCapture,
|
|
32481
|
+
markCaptureDelivered,
|
|
32482
|
+
markCaptureOwed,
|
|
32483
|
+
markAuditEventsDelivered,
|
|
32484
|
+
ensureInventory,
|
|
32485
|
+
recordConfigScan,
|
|
32486
|
+
recordProjectFiles,
|
|
32487
|
+
reconcileWorktreeProjects,
|
|
32488
|
+
configInventoryReport: () => configInventory.report(),
|
|
32489
|
+
facets,
|
|
32490
|
+
purgeSampleData: () => {
|
|
32491
|
+
purgeSampleData(db);
|
|
32492
|
+
},
|
|
32493
|
+
transaction,
|
|
32494
|
+
close: () => {
|
|
32495
|
+
db.close();
|
|
32496
|
+
},
|
|
32497
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
32498
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
32499
|
+
};
|
|
31746
32500
|
}
|
|
31747
32501
|
|
|
31748
32502
|
// ../../packages/persistence/src/finding-key.ts
|
|
@@ -31750,240 +32504,18 @@ import { createHash as createHash3 } from "crypto";
|
|
|
31750
32504
|
|
|
31751
32505
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31752
32506
|
import { createHmac, randomBytes } from "crypto";
|
|
31753
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31754
|
-
import { join as
|
|
32507
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32508
|
+
import { join as join8 } from "path";
|
|
31755
32509
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31756
32510
|
|
|
31757
32511
|
// ../../packages/persistence/src/history-preview.ts
|
|
31758
32512
|
import { existsSync as existsSync4 } from "fs";
|
|
31759
|
-
import { join as
|
|
32513
|
+
import { join as join9 } from "path";
|
|
31760
32514
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31761
32515
|
|
|
31762
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31763
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31764
|
-
import { mkdir } from "fs/promises";
|
|
31765
|
-
import { homedir } from "os";
|
|
31766
|
-
import { join as join7 } from "path";
|
|
31767
|
-
function defaultDataDir() {
|
|
31768
|
-
return join7(homedir(), ".aka");
|
|
31769
|
-
}
|
|
31770
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31771
|
-
return join7(base, "settings");
|
|
31772
|
-
}
|
|
31773
|
-
function dataDir(base = defaultDataDir()) {
|
|
31774
|
-
return join7(base, "data");
|
|
31775
|
-
}
|
|
31776
|
-
function dbPath(base = defaultDataDir()) {
|
|
31777
|
-
return join7(dataDir(base), "aka.db");
|
|
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 managedContextOf(managed) {
|
|
31829
|
-
if (!managed) return NO_MANAGED_CONTEXT;
|
|
31830
|
-
return {
|
|
31831
|
-
present: true,
|
|
31832
|
-
...managed.organization === void 0 ? {} : { organization: managed.organization },
|
|
31833
|
-
lockedFields: managed.lockedFields
|
|
31834
|
-
};
|
|
31835
|
-
}
|
|
31836
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31837
|
-
if (!managed) return settings;
|
|
31838
|
-
const { values } = managed;
|
|
31839
|
-
const merged = { ...settings };
|
|
31840
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31841
|
-
if (values.controlPlane !== void 0) {
|
|
31842
|
-
merged.controlPlane = {
|
|
31843
|
-
...values.controlPlane,
|
|
31844
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31845
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31846
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31847
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31848
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31849
|
-
};
|
|
31850
|
-
}
|
|
31851
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31852
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31853
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31854
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31855
|
-
if (values.vaultConsent !== void 0) {
|
|
31856
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31857
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31858
|
-
// at the current version otherwise.
|
|
31859
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31860
|
-
) : void 0;
|
|
31861
|
-
}
|
|
31862
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31863
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31864
|
-
acknowledgedAt: now().toISOString(),
|
|
31865
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31866
|
-
} : void 0;
|
|
31867
|
-
}
|
|
31868
|
-
return merged;
|
|
31869
|
-
}
|
|
31870
|
-
function lockedAmong(context, requested) {
|
|
31871
|
-
if (!context.present) return [];
|
|
31872
|
-
return requested.filter((key) => context.lockedFields.includes(key));
|
|
31873
|
-
}
|
|
31874
|
-
|
|
31875
|
-
// ../../packages/persistence/src/settings.ts
|
|
31876
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31877
|
-
import { join as join8 } from "path";
|
|
31878
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31879
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31880
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31881
|
-
}
|
|
31882
|
-
function readUserSettings(base) {
|
|
31883
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31884
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31885
|
-
try {
|
|
31886
|
-
return WorkspaceSettings.parse(record2);
|
|
31887
|
-
} catch {
|
|
31888
|
-
return defaultWorkspaceSettings();
|
|
31889
|
-
}
|
|
31890
|
-
}
|
|
31891
|
-
var ManagedFieldError = class extends Error {
|
|
31892
|
-
fields;
|
|
31893
|
-
constructor(fields) {
|
|
31894
|
-
super(`refusing to write administratively locked settings: ${fields.join(", ")}`);
|
|
31895
|
-
this.name = "ManagedFieldError";
|
|
31896
|
-
this.fields = fields;
|
|
31897
|
-
}
|
|
31898
|
-
};
|
|
31899
|
-
function lockableKeysTouched(current, applied) {
|
|
31900
|
-
const keys = [];
|
|
31901
|
-
const changed = (key) => key in applied && applied[key] !== current[key];
|
|
31902
|
-
const descriptorChanged = "controlPlane" in applied && (applied.controlPlane?.endpoint !== current.controlPlane?.endpoint || applied.controlPlane?.label !== current.controlPlane?.label);
|
|
31903
|
-
if (changed("runMode") || descriptorChanged) keys.push("runMode");
|
|
31904
|
-
if (changed("historicalAccess")) keys.push("historicalAccess");
|
|
31905
|
-
if (changed("vaultKeyCustody")) keys.push("vaultKeyCustody");
|
|
31906
|
-
if (changed("vaultInlineReveal")) keys.push("vaultInlineReveal");
|
|
31907
|
-
if (changed("dataSharesInPlace")) keys.push("dataSharesInPlace");
|
|
31908
|
-
if ("vaultConsent" in applied && isVaultConsentValid(applied.vaultConsent) !== isVaultConsentValid(current.vaultConsent)) {
|
|
31909
|
-
keys.push("vaultConsent");
|
|
31910
|
-
}
|
|
31911
|
-
if ("modelJudgeConsent" in applied && isModelJudgeConsentValid(applied.modelJudgeConsent) !== isModelJudgeConsentValid(current.modelJudgeConsent)) {
|
|
31912
|
-
keys.push("modelJudgeConsent");
|
|
31913
|
-
}
|
|
31914
|
-
return keys;
|
|
31915
|
-
}
|
|
31916
|
-
function pinnedKeys(managed) {
|
|
31917
|
-
if (!managed) return [];
|
|
31918
|
-
const { values } = managed;
|
|
31919
|
-
const keys = [];
|
|
31920
|
-
if (values.runMode !== void 0 || values.controlPlane !== void 0) keys.push("runMode");
|
|
31921
|
-
if (values.historicalAccess !== void 0) keys.push("historicalAccess");
|
|
31922
|
-
if (values.vaultConsent !== void 0) keys.push("vaultConsent");
|
|
31923
|
-
if (values.vaultKeyCustody !== void 0) keys.push("vaultKeyCustody");
|
|
31924
|
-
if (values.vaultInlineReveal !== void 0) keys.push("vaultInlineReveal");
|
|
31925
|
-
if (values.modelJudgeConsent !== void 0) keys.push("modelJudgeConsent");
|
|
31926
|
-
if (values.dataSharesInPlace !== void 0) keys.push("dataSharesInPlace");
|
|
31927
|
-
return keys;
|
|
31928
|
-
}
|
|
31929
|
-
function withoutManagedKeys(applied, managed, pinned, touched) {
|
|
31930
|
-
if (!managed.present) return applied;
|
|
31931
|
-
const strip = (key) => (managed.lockedFields.includes(key) || pinned.includes(key)) && !touched.includes(key);
|
|
31932
|
-
const out = { ...applied };
|
|
31933
|
-
if (strip("runMode")) {
|
|
31934
|
-
delete out.runMode;
|
|
31935
|
-
delete out.controlPlane;
|
|
31936
|
-
}
|
|
31937
|
-
if (strip("historicalAccess")) delete out.historicalAccess;
|
|
31938
|
-
if (strip("vaultConsent")) delete out.vaultConsent;
|
|
31939
|
-
if (strip("vaultKeyCustody")) delete out.vaultKeyCustody;
|
|
31940
|
-
if (strip("vaultInlineReveal")) delete out.vaultInlineReveal;
|
|
31941
|
-
if (strip("modelJudgeConsent")) delete out.modelJudgeConsent;
|
|
31942
|
-
if (strip("dataSharesInPlace")) delete out.dataSharesInPlace;
|
|
31943
|
-
return out;
|
|
31944
|
-
}
|
|
31945
|
-
function applyOnboarding(answers2, base = defaultDataDir(), managedOverride) {
|
|
31946
|
-
const dir = settingsDir(base);
|
|
31947
|
-
ensureDataDirSync(dir);
|
|
31948
|
-
const file2 = join8(dir, SETTINGS_FILENAME);
|
|
31949
|
-
const managedSettings = managedOverride === void 0 ? readManagedSettings() : managedOverride;
|
|
31950
|
-
const managed = managedContextOf(managedSettings);
|
|
31951
|
-
return withFileLock(file2, () => {
|
|
31952
|
-
const current = readUserSettings(base);
|
|
31953
|
-
const applied = typeof answers2 === "function" ? answers2(current) : answers2;
|
|
31954
|
-
const effective = overlayManagedSettings(current, managedSettings);
|
|
31955
|
-
const touched = lockableKeysTouched(effective, applied);
|
|
31956
|
-
const refused = lockedAmong(managed, touched);
|
|
31957
|
-
if (refused.length > 0) throw new ManagedFieldError(refused);
|
|
31958
|
-
const merged = WorkspaceSettings.parse({
|
|
31959
|
-
...current,
|
|
31960
|
-
// Locked keys are stripped rather than merged. Everything still here is,
|
|
31961
|
-
// by the refusal above, an unchanged ECHO of the administrator's value —
|
|
31962
|
-
// so dropping it discards no answer of the user's, and writing it would
|
|
31963
|
-
// persist the pin into their file, where it would outlive the managed
|
|
31964
|
-
// file and read as their own choice once the lock was gone.
|
|
31965
|
-
...withoutManagedKeys(applied, managed, pinnedKeys(managedSettings), touched),
|
|
31966
|
-
// First setup stamps the time; later edits keep the original completion mark.
|
|
31967
|
-
onboardedAt: applied.onboardedAt ?? current.onboardedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
31968
|
-
});
|
|
31969
|
-
writeOwnerOnlyFileSync(file2, `${JSON.stringify(merged, null, 2)}
|
|
31970
|
-
`);
|
|
31971
|
-
return merged;
|
|
31972
|
-
});
|
|
31973
|
-
}
|
|
31974
|
-
function readJson(file2) {
|
|
31975
|
-
let text;
|
|
31976
|
-
try {
|
|
31977
|
-
text = readFileSync5(file2, "utf8");
|
|
31978
|
-
} catch {
|
|
31979
|
-
return null;
|
|
31980
|
-
}
|
|
31981
|
-
return parseJsonObject(text) ?? null;
|
|
31982
|
-
}
|
|
31983
|
-
|
|
31984
32516
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31985
32517
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31986
|
-
import { dirname as
|
|
32518
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31987
32519
|
|
|
31988
32520
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
31989
32521
|
import {
|
|
@@ -31997,19 +32529,19 @@ import {
|
|
|
31997
32529
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
31998
32530
|
import { execFileSync } from "child_process";
|
|
31999
32531
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32000
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32001
|
-
import { join as
|
|
32532
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32533
|
+
import { join as join11 } from "path";
|
|
32002
32534
|
|
|
32003
32535
|
// ../../packages/persistence/src/vault/vault.ts
|
|
32004
32536
|
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
32005
32537
|
|
|
32006
32538
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32007
32539
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32008
|
-
import { join as
|
|
32540
|
+
import { join as join12 } from "path";
|
|
32009
32541
|
var MARKER = "warn-era-capped";
|
|
32010
32542
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32011
32543
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32012
|
-
const marker =
|
|
32544
|
+
const marker = join12(dataDir2, MARKER);
|
|
32013
32545
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
32014
32546
|
const capped = db.policies.capCategoryActions();
|
|
32015
32547
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -32019,7 +32551,7 @@ function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
|
32019
32551
|
|
|
32020
32552
|
// ../../packages/plugin-sdk/src/config.ts
|
|
32021
32553
|
import { existsSync as existsSync7 } from "fs";
|
|
32022
|
-
import { join as
|
|
32554
|
+
import { join as join13 } from "path";
|
|
32023
32555
|
|
|
32024
32556
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32025
32557
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32073,7 +32605,7 @@ function resolveProvider() {
|
|
|
32073
32605
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32074
32606
|
try {
|
|
32075
32607
|
ensureLayoutDirSync(base);
|
|
32076
|
-
const settingsFile =
|
|
32608
|
+
const settingsFile = join13(settingsDir(base), "settings.json");
|
|
32077
32609
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32078
32610
|
} catch {
|
|
32079
32611
|
}
|
|
@@ -32097,9 +32629,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32097
32629
|
}
|
|
32098
32630
|
|
|
32099
32631
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32100
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
32632
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32101
32633
|
import { homedir as homedir2 } from "os";
|
|
32102
|
-
import { basename as basename3, join as
|
|
32634
|
+
import { basename as basename3, join as join15 } from "path";
|
|
32103
32635
|
|
|
32104
32636
|
// ../../packages/detections/src/egress/registry.ts
|
|
32105
32637
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -32857,8 +33389,8 @@ var CPU_CORROBORATION_SHARE = 0.2;
|
|
|
32857
33389
|
var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
|
|
32858
33390
|
|
|
32859
33391
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
32860
|
-
import { existsSync as existsSync8, readFileSync as
|
|
32861
|
-
import { basename as basename2, dirname as
|
|
33392
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
33393
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join14, sep as sep2 } from "path";
|
|
32862
33394
|
|
|
32863
33395
|
// ../../packages/plugin-sdk/src/events.ts
|
|
32864
33396
|
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
@@ -32870,8 +33402,8 @@ import { Worker } from "worker_threads";
|
|
|
32870
33402
|
|
|
32871
33403
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
32872
33404
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
32873
|
-
import { readFileSync as
|
|
32874
|
-
import { join as
|
|
33405
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
33406
|
+
import { join as join16 } from "path";
|
|
32875
33407
|
|
|
32876
33408
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
32877
33409
|
import { arch, hostname as hostname4, platform, release as release2 } from "os";
|
|
@@ -32882,20 +33414,20 @@ import {
|
|
|
32882
33414
|
fstatSync,
|
|
32883
33415
|
mkdirSync as mkdirSync2,
|
|
32884
33416
|
openSync as openSync2,
|
|
32885
|
-
readFileSync as
|
|
33417
|
+
readFileSync as readFileSync11,
|
|
32886
33418
|
readSync,
|
|
32887
33419
|
writeFileSync as writeFileSync5
|
|
32888
33420
|
} from "fs";
|
|
32889
|
-
import { join as
|
|
33421
|
+
import { join as join17 } from "path";
|
|
32890
33422
|
var TAIL_BYTES = 256 * 1024;
|
|
32891
33423
|
|
|
32892
33424
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
32893
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
32894
|
-
import { join as
|
|
33425
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
33426
|
+
import { join as join18 } from "path";
|
|
32895
33427
|
|
|
32896
33428
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
32897
33429
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
32898
|
-
import { basename as basename4, dirname as
|
|
33430
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
32899
33431
|
|
|
32900
33432
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
32901
33433
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
@@ -32909,7 +33441,7 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
32909
33441
|
|
|
32910
33442
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
32911
33443
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
32912
|
-
import { basename as basename5, join as
|
|
33444
|
+
import { basename as basename5, join as join19 } from "path";
|
|
32913
33445
|
|
|
32914
33446
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
32915
33447
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -32945,7 +33477,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
32945
33477
|
|
|
32946
33478
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
32947
33479
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
32948
|
-
import { join as
|
|
33480
|
+
import { join as join20 } from "path";
|
|
32949
33481
|
|
|
32950
33482
|
// ../../packages/setup-wizard/src/onboard-posture.ts
|
|
32951
33483
|
function parsePosture(json2) {
|
|
@@ -32968,7 +33500,7 @@ function parsePosture(json2) {
|
|
|
32968
33500
|
|
|
32969
33501
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
32970
33502
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
32971
|
-
import { join as
|
|
33503
|
+
import { join as join21 } from "path";
|
|
32972
33504
|
|
|
32973
33505
|
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
32974
33506
|
var RANK = Object.fromEntries(
|
|
@@ -32976,9 +33508,9 @@ var RANK = Object.fromEntries(
|
|
|
32976
33508
|
);
|
|
32977
33509
|
|
|
32978
33510
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
32979
|
-
import { mkdtempSync, readFileSync as
|
|
33511
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmdirSync, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
32980
33512
|
import { tmpdir } from "os";
|
|
32981
|
-
import { basename as basename6, dirname as
|
|
33513
|
+
import { basename as basename6, dirname as dirname6, join as join22 } from "path";
|
|
32982
33514
|
var SuppressionEntrySchema = external_exports.object({
|
|
32983
33515
|
ruleId: external_exports.string(),
|
|
32984
33516
|
category: DetectionCategory,
|