@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/pre-tool-use.js
CHANGED
|
@@ -493,11 +493,12 @@ var require_ignore = __commonJS({
|
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
495
|
import { existsSync as existsSync7 } from "fs";
|
|
496
|
-
import { join as
|
|
496
|
+
import { join as join13 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/attached-derived.ts
|
|
499
499
|
import { rmSync } from "fs";
|
|
500
500
|
import { join } from "path";
|
|
501
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
501
502
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
502
503
|
var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
503
504
|
|
|
@@ -598,6 +599,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
598
599
|
{
|
|
599
600
|
tag: "0022_audit_inspection_ms",
|
|
600
601
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
tag: "0023_secret_vault_user_authorized",
|
|
605
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
609
|
+
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`);"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
613
|
+
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;"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
617
|
+
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;"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
tag: "0027_audit_llm_usage_index",
|
|
621
|
+
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;"
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
tag: "0028_activity_session_probe_indexes",
|
|
625
|
+
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"
|
|
601
626
|
}
|
|
602
627
|
];
|
|
603
628
|
|
|
@@ -22137,6 +22162,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22137
22162
|
AttachTokenExpired,
|
|
22138
22163
|
external_exports.object({ status: printable(64) })
|
|
22139
22164
|
]);
|
|
22165
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22166
|
+
var DeviceCommand = external_exports.object({
|
|
22167
|
+
id: printable(128).min(1),
|
|
22168
|
+
kind: DeviceCommandKind,
|
|
22169
|
+
issuedAt: printable(64).min(1),
|
|
22170
|
+
expiresAt: printable(64).min(1)
|
|
22171
|
+
}).strict();
|
|
22172
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22173
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22174
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22175
|
+
external_exports.object({
|
|
22176
|
+
outcome: external_exports.literal("reported"),
|
|
22177
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22178
|
+
}).strict(),
|
|
22179
|
+
external_exports.object({
|
|
22180
|
+
outcome: external_exports.literal("failed"),
|
|
22181
|
+
reason: DeviceCommandFailureReason,
|
|
22182
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22183
|
+
}).strict()
|
|
22184
|
+
]);
|
|
22140
22185
|
|
|
22141
22186
|
// ../../packages/schema/src/zod/registry.ts
|
|
22142
22187
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22303,7 +22348,7 @@ var PackManifest = external_exports.object({
|
|
|
22303
22348
|
}).meta({ id: "PackManifest" });
|
|
22304
22349
|
|
|
22305
22350
|
// ../../packages/schema/src/zod/detection.ts
|
|
22306
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22351
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22307
22352
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22308
22353
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22309
22354
|
var DetectionCounts = external_exports.object({
|
|
@@ -22440,14 +22485,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22440
22485
|
function isStringArray(value) {
|
|
22441
22486
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22442
22487
|
}
|
|
22488
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22489
|
+
function resolveOrigin(origin) {
|
|
22490
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22491
|
+
}
|
|
22443
22492
|
function summaryToDetectionListItem(s) {
|
|
22444
22493
|
return {
|
|
22445
22494
|
id: `${s.namespace}/${s.packId}`,
|
|
22446
22495
|
name: s.name,
|
|
22447
22496
|
version: s.version,
|
|
22448
22497
|
enabled: s.enabled,
|
|
22449
|
-
origin:
|
|
22450
|
-
// v1: every installed pack is library origin
|
|
22498
|
+
origin: resolveOrigin(s.origin),
|
|
22451
22499
|
namespace: s.namespace,
|
|
22452
22500
|
packId: s.packId,
|
|
22453
22501
|
ruleCount: s.ruleCount,
|
|
@@ -22499,7 +22547,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22499
22547
|
name: row.name,
|
|
22500
22548
|
version: row.version,
|
|
22501
22549
|
enabled: row.enabled,
|
|
22502
|
-
origin:
|
|
22550
|
+
origin: resolveOrigin(row.origin),
|
|
22503
22551
|
namespace: row.namespace,
|
|
22504
22552
|
packId: row.packId,
|
|
22505
22553
|
ruleCount: row.rules.length,
|
|
@@ -22519,16 +22567,20 @@ function splitDetectionId(id) {
|
|
|
22519
22567
|
}
|
|
22520
22568
|
function buildDetectionsList(summaries, query) {
|
|
22521
22569
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22570
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22522
22571
|
const counts = {
|
|
22523
22572
|
all: summaries.length,
|
|
22524
|
-
library: summaries.length,
|
|
22525
|
-
|
|
22526
|
-
|
|
22573
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22574
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22575
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22576
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22577
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22578
|
+
// OriginEnum.
|
|
22527
22579
|
customized: 0,
|
|
22528
22580
|
updates: withUpdate.length
|
|
22529
22581
|
};
|
|
22530
22582
|
const filter = query.filter;
|
|
22531
|
-
let filtered = filter === "custom"
|
|
22583
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22532
22584
|
if (query.q) {
|
|
22533
22585
|
const q = query.q.toLowerCase();
|
|
22534
22586
|
filtered = filtered.filter(
|
|
@@ -22608,8 +22660,9 @@ var Event = external_exports.object({
|
|
|
22608
22660
|
metadata: EventMetadata.optional()
|
|
22609
22661
|
}).meta({ id: "Event" });
|
|
22610
22662
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22663
|
+
var INGEST_BATCH_MAX = 100;
|
|
22611
22664
|
var IngestBatch = external_exports.object({
|
|
22612
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22665
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22613
22666
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22614
22667
|
// additionally rejects any event whose contentHash the store has already
|
|
22615
22668
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23165,6 +23218,252 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23165
23218
|
message: "At least one field must be provided"
|
|
23166
23219
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23167
23220
|
|
|
23221
|
+
// ../../packages/schema/src/zod/policy.ts
|
|
23222
|
+
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23223
|
+
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23224
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23225
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23226
|
+
var Policy = external_exports.object({
|
|
23227
|
+
id: external_exports.guid(),
|
|
23228
|
+
scope: PolicyScope,
|
|
23229
|
+
target: PolicyTarget,
|
|
23230
|
+
action: ActionTaken,
|
|
23231
|
+
enabled: external_exports.boolean().default(true),
|
|
23232
|
+
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23233
|
+
// Display name — optional so older policy rows without name still parse.
|
|
23234
|
+
// Added for the findings API (policy.name column migration).
|
|
23235
|
+
name: external_exports.string().optional(),
|
|
23236
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23237
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23238
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23239
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23240
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23241
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23242
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23243
|
+
//
|
|
23244
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23245
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23246
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23247
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23248
|
+
// — the CRUD routes neither accept nor set it.
|
|
23249
|
+
//
|
|
23250
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23251
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23252
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23253
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23254
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23255
|
+
provenance: PolicyProvenance.optional()
|
|
23256
|
+
}).meta({ id: "Policy" });
|
|
23257
|
+
var PolicyBundle = external_exports.object({
|
|
23258
|
+
version: external_exports.string(),
|
|
23259
|
+
policies: external_exports.array(Policy),
|
|
23260
|
+
// Rules from the installed marketplace packs (snapshotted by the
|
|
23261
|
+
// control plane). The plugin registers these in addition to its bundled
|
|
23262
|
+
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23263
|
+
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23264
|
+
rules: external_exports.array(Rule).optional(),
|
|
23265
|
+
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23266
|
+
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23267
|
+
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23268
|
+
// packs only), which is how detection updates stay manual: new bundled
|
|
23269
|
+
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23270
|
+
// the historical composition (bundled packs + rules) — older caches.
|
|
23271
|
+
rulesComplete: external_exports.boolean().optional(),
|
|
23272
|
+
// Active detection exceptions, evaluation subset only (see
|
|
23273
|
+
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23274
|
+
// on-disk caches — that omit the field still parse; consumers read
|
|
23275
|
+
// `bundle.exceptions ?? []`.
|
|
23276
|
+
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23277
|
+
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23278
|
+
// A second axis over the same `redact` action, carried beside the policies
|
|
23279
|
+
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23280
|
+
// widening Policy itself would change a persisted shape to express something
|
|
23281
|
+
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23282
|
+
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23283
|
+
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23284
|
+
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23285
|
+
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23286
|
+
// from a versioned installed pack. Optional so older backends — and older
|
|
23287
|
+
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23288
|
+
// the rule's own spec version. NOT the bundle version above — see
|
|
23289
|
+
// installedRuleset's ruleVersions for the source of truth.
|
|
23290
|
+
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23291
|
+
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23292
|
+
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23293
|
+
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23294
|
+
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23295
|
+
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23296
|
+
// unenforced behaviour that predates this field and the safe direction to
|
|
23297
|
+
// default.
|
|
23298
|
+
//
|
|
23299
|
+
// Ids, not display names: the governance decision is keyed on the exact
|
|
23300
|
+
// string the harness reports (`model_status_override.versionId` in the
|
|
23301
|
+
// control plane), so no name resolution stands between the decision and the
|
|
23302
|
+
// comparison.
|
|
23303
|
+
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23304
|
+
customKeywords: external_exports.array(external_exports.string()),
|
|
23305
|
+
fetchedAt: external_exports.iso.datetime()
|
|
23306
|
+
}).meta({ id: "PolicyBundle" });
|
|
23307
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23308
|
+
...Object.keys(PolicyBundle.shape),
|
|
23309
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23310
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23311
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23312
|
+
].sort().join(",");
|
|
23313
|
+
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23314
|
+
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23315
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
23316
|
+
secret: "critical",
|
|
23317
|
+
financial: "critical",
|
|
23318
|
+
// core-financial/credit-card
|
|
23319
|
+
code_flaw: "critical",
|
|
23320
|
+
pii: "high",
|
|
23321
|
+
phi: "high",
|
|
23322
|
+
custom: "high",
|
|
23323
|
+
// user-defined; conservative
|
|
23324
|
+
code_context: "low",
|
|
23325
|
+
config: "low"
|
|
23326
|
+
// observe-only; floors to monitor regardless
|
|
23327
|
+
};
|
|
23328
|
+
function severityFloorPolicy(category) {
|
|
23329
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23330
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23331
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23332
|
+
}
|
|
23333
|
+
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23334
|
+
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23335
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23336
|
+
id: "RedactFallback"
|
|
23337
|
+
});
|
|
23338
|
+
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23339
|
+
var BUILTIN_POLICY_SPECS = {
|
|
23340
|
+
monitor: {
|
|
23341
|
+
name: "Monitor",
|
|
23342
|
+
action: "log",
|
|
23343
|
+
reversible: false,
|
|
23344
|
+
description: "Log every match for audit. The request is allowed through untouched."
|
|
23345
|
+
},
|
|
23346
|
+
warn: {
|
|
23347
|
+
name: "Warn",
|
|
23348
|
+
action: "warn",
|
|
23349
|
+
reversible: false,
|
|
23350
|
+
description: "Allow the request, but warn the user inline before it is sent."
|
|
23351
|
+
},
|
|
23352
|
+
redact: {
|
|
23353
|
+
name: "Redact",
|
|
23354
|
+
action: "redact",
|
|
23355
|
+
reversible: false,
|
|
23356
|
+
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23357
|
+
},
|
|
23358
|
+
vault: {
|
|
23359
|
+
name: "Redact & Vault",
|
|
23360
|
+
action: "redact",
|
|
23361
|
+
reversible: true,
|
|
23362
|
+
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."
|
|
23363
|
+
},
|
|
23364
|
+
block: {
|
|
23365
|
+
name: "Block",
|
|
23366
|
+
action: "block",
|
|
23367
|
+
reversible: false,
|
|
23368
|
+
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23369
|
+
}
|
|
23370
|
+
};
|
|
23371
|
+
function builtinPolicyToAction(id) {
|
|
23372
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
23373
|
+
}
|
|
23374
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23375
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23376
|
+
];
|
|
23377
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23378
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23379
|
+
);
|
|
23380
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23381
|
+
...BELOW_PALETTE,
|
|
23382
|
+
...PALETTE_WEAKEST_FIRST
|
|
23383
|
+
];
|
|
23384
|
+
function actionRank(action) {
|
|
23385
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23386
|
+
}
|
|
23387
|
+
function isActionAtLeast(action, floor) {
|
|
23388
|
+
return actionRank(action) >= actionRank(floor);
|
|
23389
|
+
}
|
|
23390
|
+
function strongerAction(a, b) {
|
|
23391
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23392
|
+
}
|
|
23393
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23394
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23395
|
+
}
|
|
23396
|
+
var PackPolicyFloor = external_exports.object({
|
|
23397
|
+
/**
|
|
23398
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23399
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23400
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23401
|
+
*/
|
|
23402
|
+
floor: BuiltinPolicyId,
|
|
23403
|
+
/**
|
|
23404
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23405
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23406
|
+
* re-assignable locally in either direction.
|
|
23407
|
+
*/
|
|
23408
|
+
locked: external_exports.boolean()
|
|
23409
|
+
}).describe("PackPolicyFloor");
|
|
23410
|
+
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23411
|
+
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23412
|
+
);
|
|
23413
|
+
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23414
|
+
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23415
|
+
);
|
|
23416
|
+
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23417
|
+
function builtinPolicyIsReversible(id) {
|
|
23418
|
+
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23419
|
+
}
|
|
23420
|
+
function policyIdIsReversible(policyId) {
|
|
23421
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23422
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23423
|
+
return builtinPolicyIsReversible(id);
|
|
23424
|
+
}
|
|
23425
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23426
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23427
|
+
);
|
|
23428
|
+
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23429
|
+
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23430
|
+
);
|
|
23431
|
+
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23432
|
+
function policyIdToAction(policyId) {
|
|
23433
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23434
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23435
|
+
return BUILTIN_POLICIES[id].action;
|
|
23436
|
+
}
|
|
23437
|
+
var UsedByItem = external_exports.object({
|
|
23438
|
+
id: external_exports.string(),
|
|
23439
|
+
name: external_exports.string(),
|
|
23440
|
+
ruleCount: external_exports.number().int().nonnegative(),
|
|
23441
|
+
enabled: external_exports.boolean()
|
|
23442
|
+
}).meta({ id: "UsedByItem" });
|
|
23443
|
+
var PolicyListItem = external_exports.object({
|
|
23444
|
+
id: external_exports.string(),
|
|
23445
|
+
kind: PolicyKind,
|
|
23446
|
+
name: external_exports.string(),
|
|
23447
|
+
enabled: external_exports.boolean(),
|
|
23448
|
+
usedByCount: external_exports.number().int().nonnegative()
|
|
23449
|
+
}).meta({ id: "PolicyListItem" });
|
|
23450
|
+
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23451
|
+
var PolicyDetail = external_exports.object({
|
|
23452
|
+
specVersion: external_exports.literal(1),
|
|
23453
|
+
id: external_exports.string(),
|
|
23454
|
+
kind: PolicyKind,
|
|
23455
|
+
name: external_exports.string(),
|
|
23456
|
+
enabled: external_exports.boolean(),
|
|
23457
|
+
description: external_exports.string(),
|
|
23458
|
+
usedBy: external_exports.array(UsedByItem)
|
|
23459
|
+
}).meta({ id: "PolicyDetail" });
|
|
23460
|
+
var PolicyStatsResponse = external_exports.object({
|
|
23461
|
+
policies: external_exports.number().int().nonnegative(),
|
|
23462
|
+
builtin: external_exports.number().int().nonnegative(),
|
|
23463
|
+
custom: external_exports.number().int().nonnegative(),
|
|
23464
|
+
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23465
|
+
}).meta({ id: "PolicyStatsResponse" });
|
|
23466
|
+
|
|
23168
23467
|
// ../../packages/schema/src/zod/vault.ts
|
|
23169
23468
|
var POINTER_FORMAT_VERSION = 2;
|
|
23170
23469
|
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
@@ -23205,6 +23504,14 @@ var VaultEntry = external_exports.object({
|
|
|
23205
23504
|
// How many times this value has been detected on this machine — the reuse
|
|
23206
23505
|
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23207
23506
|
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23507
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23508
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23509
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23510
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23511
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23512
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23513
|
+
// the value does not expire.
|
|
23514
|
+
userAuthorized: external_exports.boolean(),
|
|
23208
23515
|
firstSeen: external_exports.string(),
|
|
23209
23516
|
lastSeen: external_exports.string()
|
|
23210
23517
|
});
|
|
@@ -23331,7 +23638,7 @@ function isVaultConsentValid(consent) {
|
|
|
23331
23638
|
}
|
|
23332
23639
|
|
|
23333
23640
|
// ../../packages/schema/src/zod/local.ts
|
|
23334
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
23641
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23335
23642
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23336
23643
|
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23337
23644
|
var ControlPlaneConnection = external_exports.object({
|
|
@@ -23373,6 +23680,19 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23373
23680
|
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23374
23681
|
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23375
23682
|
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23683
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23684
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23685
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23686
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23687
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23688
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23689
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23690
|
+
//
|
|
23691
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23692
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23693
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23694
|
+
// word and stays out of the stored value.
|
|
23695
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23376
23696
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23377
23697
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23378
23698
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -23380,10 +23700,12 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23380
23700
|
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23381
23701
|
// covers the current payload and must be re-granted.
|
|
23382
23702
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23383
|
-
// Records that the user consented to
|
|
23384
|
-
//
|
|
23385
|
-
//
|
|
23386
|
-
//
|
|
23703
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23704
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23705
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23706
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23707
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23708
|
+
// payload no longer counts.
|
|
23387
23709
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23388
23710
|
});
|
|
23389
23711
|
function defaultWorkspaceSettings() {
|
|
@@ -23511,7 +23833,8 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23511
23833
|
"vaultKeyCustody",
|
|
23512
23834
|
"vaultInlineReveal",
|
|
23513
23835
|
"modelJudgeConsent",
|
|
23514
|
-
"dataSharesInPlace"
|
|
23836
|
+
"dataSharesInPlace",
|
|
23837
|
+
"redactFallback"
|
|
23515
23838
|
]).meta({ id: "ManagedSettingKey" });
|
|
23516
23839
|
var ManagedSettingsValues = external_exports.object({
|
|
23517
23840
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
@@ -23524,7 +23847,8 @@ var ManagedSettingsValues = external_exports.object({
|
|
|
23524
23847
|
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23525
23848
|
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23526
23849
|
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23527
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23850
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23851
|
+
redactFallback: RedactFallback.optional()
|
|
23528
23852
|
}).meta({ id: "ManagedSettingsValues" });
|
|
23529
23853
|
var ManagedSettings = external_exports.object({
|
|
23530
23854
|
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
@@ -23539,186 +23863,6 @@ var ManagedSettings = external_exports.object({
|
|
|
23539
23863
|
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23540
23864
|
}).meta({ id: "ManagedSettings" });
|
|
23541
23865
|
|
|
23542
|
-
// ../../packages/schema/src/zod/policy.ts
|
|
23543
|
-
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23544
|
-
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23545
|
-
var Policy = external_exports.object({
|
|
23546
|
-
id: external_exports.guid(),
|
|
23547
|
-
scope: PolicyScope,
|
|
23548
|
-
target: PolicyTarget,
|
|
23549
|
-
action: ActionTaken,
|
|
23550
|
-
enabled: external_exports.boolean().default(true),
|
|
23551
|
-
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23552
|
-
// Display name — optional so older policy rows without name still parse.
|
|
23553
|
-
// Added for the findings API (policy.name column migration).
|
|
23554
|
-
name: external_exports.string().optional()
|
|
23555
|
-
}).meta({ id: "Policy" });
|
|
23556
|
-
var PolicyBundle = external_exports.object({
|
|
23557
|
-
version: external_exports.string(),
|
|
23558
|
-
policies: external_exports.array(Policy),
|
|
23559
|
-
// Rules from the installed marketplace packs (snapshotted by the
|
|
23560
|
-
// control plane). The plugin registers these in addition to its bundled
|
|
23561
|
-
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23562
|
-
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23563
|
-
rules: external_exports.array(Rule).optional(),
|
|
23564
|
-
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23565
|
-
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23566
|
-
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23567
|
-
// packs only), which is how detection updates stay manual: new bundled
|
|
23568
|
-
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23569
|
-
// the historical composition (bundled packs + rules) — older caches.
|
|
23570
|
-
rulesComplete: external_exports.boolean().optional(),
|
|
23571
|
-
// Active detection exceptions, evaluation subset only (see
|
|
23572
|
-
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23573
|
-
// on-disk caches — that omit the field still parse; consumers read
|
|
23574
|
-
// `bundle.exceptions ?? []`.
|
|
23575
|
-
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23576
|
-
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23577
|
-
// A second axis over the same `redact` action, carried beside the policies
|
|
23578
|
-
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23579
|
-
// widening Policy itself would change a persisted shape to express something
|
|
23580
|
-
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23581
|
-
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23582
|
-
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23583
|
-
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23584
|
-
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23585
|
-
// from a versioned installed pack. Optional so older backends — and older
|
|
23586
|
-
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23587
|
-
// the rule's own spec version. NOT the bundle version above — see
|
|
23588
|
-
// installedRuleset's ruleVersions for the source of truth.
|
|
23589
|
-
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23590
|
-
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23591
|
-
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23592
|
-
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23593
|
-
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23594
|
-
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23595
|
-
// unenforced behaviour that predates this field and the safe direction to
|
|
23596
|
-
// default.
|
|
23597
|
-
//
|
|
23598
|
-
// Ids, not display names: the governance decision is keyed on the exact
|
|
23599
|
-
// string the harness reports (`model_status_override.versionId` in the
|
|
23600
|
-
// control plane), so no name resolution stands between the decision and the
|
|
23601
|
-
// comparison.
|
|
23602
|
-
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23603
|
-
customKeywords: external_exports.array(external_exports.string()),
|
|
23604
|
-
fetchedAt: external_exports.iso.datetime()
|
|
23605
|
-
}).meta({ id: "PolicyBundle" });
|
|
23606
|
-
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23607
|
-
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23608
|
-
var CATEGORY_PEAK_SEVERITY = {
|
|
23609
|
-
secret: "critical",
|
|
23610
|
-
financial: "critical",
|
|
23611
|
-
// core-financial/credit-card
|
|
23612
|
-
code_flaw: "critical",
|
|
23613
|
-
pii: "high",
|
|
23614
|
-
phi: "high",
|
|
23615
|
-
custom: "high",
|
|
23616
|
-
// user-defined; conservative
|
|
23617
|
-
code_context: "low",
|
|
23618
|
-
config: "low"
|
|
23619
|
-
// observe-only; floors to monitor regardless
|
|
23620
|
-
};
|
|
23621
|
-
function severityFloorPolicy(category) {
|
|
23622
|
-
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23623
|
-
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23624
|
-
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23625
|
-
}
|
|
23626
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23627
|
-
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23628
|
-
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23629
|
-
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23630
|
-
var BUILTIN_POLICY_SPECS = {
|
|
23631
|
-
monitor: {
|
|
23632
|
-
name: "Monitor",
|
|
23633
|
-
action: "log",
|
|
23634
|
-
reversible: false,
|
|
23635
|
-
description: "Log every match for audit. The request is allowed through untouched."
|
|
23636
|
-
},
|
|
23637
|
-
warn: {
|
|
23638
|
-
name: "Warn",
|
|
23639
|
-
action: "warn",
|
|
23640
|
-
reversible: false,
|
|
23641
|
-
description: "Allow the request, but warn the user inline before it is sent."
|
|
23642
|
-
},
|
|
23643
|
-
redact: {
|
|
23644
|
-
name: "Redact",
|
|
23645
|
-
action: "redact",
|
|
23646
|
-
reversible: false,
|
|
23647
|
-
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23648
|
-
},
|
|
23649
|
-
vault: {
|
|
23650
|
-
name: "Redact & Vault",
|
|
23651
|
-
action: "redact",
|
|
23652
|
-
reversible: true,
|
|
23653
|
-
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."
|
|
23654
|
-
},
|
|
23655
|
-
block: {
|
|
23656
|
-
name: "Block",
|
|
23657
|
-
action: "block",
|
|
23658
|
-
reversible: false,
|
|
23659
|
-
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23660
|
-
}
|
|
23661
|
-
};
|
|
23662
|
-
function builtinPolicyToAction(id) {
|
|
23663
|
-
return BUILTIN_POLICY_SPECS[id].action;
|
|
23664
|
-
}
|
|
23665
|
-
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23666
|
-
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23667
|
-
);
|
|
23668
|
-
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23669
|
-
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23670
|
-
);
|
|
23671
|
-
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23672
|
-
function builtinPolicyIsReversible(id) {
|
|
23673
|
-
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23674
|
-
}
|
|
23675
|
-
function policyIdIsReversible(policyId) {
|
|
23676
|
-
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23677
|
-
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23678
|
-
return builtinPolicyIsReversible(id);
|
|
23679
|
-
}
|
|
23680
|
-
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23681
|
-
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23682
|
-
);
|
|
23683
|
-
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23684
|
-
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23685
|
-
);
|
|
23686
|
-
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23687
|
-
function policyIdToAction(policyId) {
|
|
23688
|
-
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23689
|
-
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23690
|
-
return BUILTIN_POLICIES[id].action;
|
|
23691
|
-
}
|
|
23692
|
-
var UsedByItem = external_exports.object({
|
|
23693
|
-
id: external_exports.string(),
|
|
23694
|
-
name: external_exports.string(),
|
|
23695
|
-
ruleCount: external_exports.number().int().nonnegative(),
|
|
23696
|
-
enabled: external_exports.boolean()
|
|
23697
|
-
}).meta({ id: "UsedByItem" });
|
|
23698
|
-
var PolicyListItem = external_exports.object({
|
|
23699
|
-
id: external_exports.string(),
|
|
23700
|
-
kind: PolicyKind,
|
|
23701
|
-
name: external_exports.string(),
|
|
23702
|
-
enabled: external_exports.boolean(),
|
|
23703
|
-
usedByCount: external_exports.number().int().nonnegative()
|
|
23704
|
-
}).meta({ id: "PolicyListItem" });
|
|
23705
|
-
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23706
|
-
var PolicyDetail = external_exports.object({
|
|
23707
|
-
specVersion: external_exports.literal(1),
|
|
23708
|
-
id: external_exports.string(),
|
|
23709
|
-
kind: PolicyKind,
|
|
23710
|
-
name: external_exports.string(),
|
|
23711
|
-
enabled: external_exports.boolean(),
|
|
23712
|
-
description: external_exports.string(),
|
|
23713
|
-
usedBy: external_exports.array(UsedByItem)
|
|
23714
|
-
}).meta({ id: "PolicyDetail" });
|
|
23715
|
-
var PolicyStatsResponse = external_exports.object({
|
|
23716
|
-
policies: external_exports.number().int().nonnegative(),
|
|
23717
|
-
builtin: external_exports.number().int().nonnegative(),
|
|
23718
|
-
custom: external_exports.number().int().nonnegative(),
|
|
23719
|
-
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23720
|
-
}).meta({ id: "PolicyStatsResponse" });
|
|
23721
|
-
|
|
23722
23866
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23723
23867
|
var ProjectFileInput = external_exports.object({
|
|
23724
23868
|
path: external_exports.string().min(1),
|
|
@@ -23964,10 +24108,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
23964
24108
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
23965
24109
|
|
|
23966
24110
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24111
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24112
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
23967
24113
|
var SaveSettingsInput = external_exports.object({
|
|
23968
24114
|
historicalAccess: external_exports.string(),
|
|
23969
|
-
modelJudgeConsent:
|
|
23970
|
-
historySyncConsent:
|
|
24115
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24116
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
23971
24117
|
vaultConsent: external_exports.string(),
|
|
23972
24118
|
vaultInlineReveal: external_exports.string()
|
|
23973
24119
|
});
|
|
@@ -24117,9 +24263,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24117
24263
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24118
24264
|
return reasons;
|
|
24119
24265
|
}
|
|
24120
|
-
function buildReviewInfo(trust, transports) {
|
|
24266
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24121
24267
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24122
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24268
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24123
24269
|
}
|
|
24124
24270
|
function distinctTransports(transports) {
|
|
24125
24271
|
return Array.from(new Set(transports));
|
|
@@ -24337,8 +24483,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
|
|
|
24337
24483
|
}
|
|
24338
24484
|
|
|
24339
24485
|
// ../../packages/persistence/src/database.ts
|
|
24340
|
-
import { randomUUID as
|
|
24341
|
-
import { join as
|
|
24486
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24487
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24342
24488
|
import { DatabaseSync } from "node:sqlite";
|
|
24343
24489
|
|
|
24344
24490
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24603,6 +24749,10 @@ function allRows(stmt, params) {
|
|
|
24603
24749
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24604
24750
|
return stmt.all(params);
|
|
24605
24751
|
}
|
|
24752
|
+
function* iterateRows(stmt, params) {
|
|
24753
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24754
|
+
for (const row of rows) yield row;
|
|
24755
|
+
}
|
|
24606
24756
|
function getRow(stmt, params) {
|
|
24607
24757
|
if (params === void 0) return stmt.get();
|
|
24608
24758
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -25071,10 +25221,17 @@ function ensureSyncedAtColumn(db, table) {
|
|
|
25071
25221
|
if (!columns.includes("sync_claimed_at")) {
|
|
25072
25222
|
db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
|
|
25073
25223
|
}
|
|
25224
|
+
if (!columns.includes("outbox_owed")) {
|
|
25225
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
|
|
25226
|
+
}
|
|
25074
25227
|
db.exec(
|
|
25075
25228
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
25076
25229
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
25077
25230
|
);
|
|
25231
|
+
db.exec(
|
|
25232
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25233
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25234
|
+
);
|
|
25078
25235
|
db.exec(
|
|
25079
25236
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
25080
25237
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25179,7 +25336,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25179
25336
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25180
25337
|
var DAY_MS = 864e5;
|
|
25181
25338
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25182
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25183
25339
|
function defaultTimeZone() {
|
|
25184
25340
|
try {
|
|
25185
25341
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25234,6 +25390,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25234
25390
|
error: "error",
|
|
25235
25391
|
active: "active"
|
|
25236
25392
|
};
|
|
25393
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25237
25394
|
function safeParseStringArray(raw) {
|
|
25238
25395
|
if (!raw) return [];
|
|
25239
25396
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25307,6 +25464,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25307
25464
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25308
25465
|
json_extract(attributes, '$.internal') AS internal,
|
|
25309
25466
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25467
|
+
var LLM_USAGE_SELECT = `
|
|
25468
|
+
SELECT root_session_id AS sessionId,
|
|
25469
|
+
provider,
|
|
25470
|
+
model,
|
|
25471
|
+
service_tier AS serviceTier,
|
|
25472
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25473
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25474
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25475
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25476
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25477
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25478
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25479
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25480
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25481
|
+
function usageLeaves(rows) {
|
|
25482
|
+
return rows.map((row) => {
|
|
25483
|
+
const attributes = {
|
|
25484
|
+
input_tokens: row.inputTokens,
|
|
25485
|
+
output_tokens: row.outputTokens,
|
|
25486
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25487
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25488
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25489
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25490
|
+
web_search_requests: row.webSearchRequests
|
|
25491
|
+
};
|
|
25492
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25493
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25494
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25495
|
+
return { sessionId: row.sessionId, attributes };
|
|
25496
|
+
});
|
|
25497
|
+
}
|
|
25310
25498
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25311
25499
|
var HAS_ACTIVITY = `EXISTS (
|
|
25312
25500
|
SELECT 1 FROM audit_events c
|
|
@@ -25332,16 +25520,17 @@ var SqliteActivityRepository = class {
|
|
|
25332
25520
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25333
25521
|
const liveNow = countScalar(
|
|
25334
25522
|
this.db,
|
|
25335
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25523
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25336
25524
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25337
|
-
AND
|
|
25338
|
-
|
|
25339
|
-
|
|
25340
|
-
|
|
25341
|
-
|
|
25342
|
-
|
|
25343
|
-
|
|
25344
|
-
|
|
25525
|
+
AND s.id IN (
|
|
25526
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25527
|
+
UNION
|
|
25528
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25529
|
+
WHERE started_at >= ?
|
|
25530
|
+
UNION
|
|
25531
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25532
|
+
WHERE ended_at >= ?)`,
|
|
25533
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25345
25534
|
);
|
|
25346
25535
|
const toolCallsToday = countScalar(
|
|
25347
25536
|
this.db,
|
|
@@ -25471,7 +25660,7 @@ var SqliteActivityRepository = class {
|
|
|
25471
25660
|
this.db.prepare(
|
|
25472
25661
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25473
25662
|
FROM audit_events
|
|
25474
|
-
WHERE id = ? OR root_session_id = ?
|
|
25663
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25475
25664
|
ORDER BY started_at ASC, id ASC`
|
|
25476
25665
|
),
|
|
25477
25666
|
[sessionId, sessionId]
|
|
@@ -25484,14 +25673,14 @@ var SqliteActivityRepository = class {
|
|
|
25484
25673
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25485
25674
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25486
25675
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25487
|
-
FROM audit_events
|
|
25676
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25488
25677
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25489
25678
|
),
|
|
25490
25679
|
[sessionId]
|
|
25491
25680
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25492
25681
|
const primaryModel = getRow(
|
|
25493
25682
|
this.db.prepare(
|
|
25494
|
-
`SELECT model, provider FROM audit_events
|
|
25683
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25495
25684
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25496
25685
|
ORDER BY started_at ASC, id ASC
|
|
25497
25686
|
LIMIT 1`
|
|
@@ -25502,7 +25691,7 @@ var SqliteActivityRepository = class {
|
|
|
25502
25691
|
this.db.prepare(
|
|
25503
25692
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25504
25693
|
count(*) AS n
|
|
25505
|
-
FROM audit_events
|
|
25694
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25506
25695
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25507
25696
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25508
25697
|
),
|
|
@@ -25510,7 +25699,7 @@ var SqliteActivityRepository = class {
|
|
|
25510
25699
|
);
|
|
25511
25700
|
const modelRows = allRows(
|
|
25512
25701
|
this.db.prepare(
|
|
25513
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25702
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25514
25703
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25515
25704
|
ORDER BY model`
|
|
25516
25705
|
),
|
|
@@ -25519,7 +25708,7 @@ var SqliteActivityRepository = class {
|
|
|
25519
25708
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25520
25709
|
const commits = countScalar(
|
|
25521
25710
|
this.db,
|
|
25522
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25711
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25523
25712
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25524
25713
|
[sessionId]
|
|
25525
25714
|
);
|
|
@@ -25555,25 +25744,57 @@ var SqliteActivityRepository = class {
|
|
|
25555
25744
|
return Promise.resolve(session);
|
|
25556
25745
|
}
|
|
25557
25746
|
/**
|
|
25558
|
-
* Cross-session token report — every `llm_call`
|
|
25559
|
-
* `started_at >= fromMs`
|
|
25560
|
-
* USD cost DERIVED at read time via the shared
|
|
25561
|
-
*
|
|
25562
|
-
*
|
|
25563
|
-
*
|
|
25747
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25748
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25749
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25750
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25751
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25752
|
+
*
|
|
25753
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25754
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25755
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25756
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25757
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25758
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25759
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25760
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25761
|
+
* recompute the columns it could have read. The index is one every open
|
|
25762
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25763
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25764
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25765
|
+
* per call, no bag parsed.
|
|
25564
25766
|
*/
|
|
25565
25767
|
tokenReports(fromMs) {
|
|
25566
|
-
const
|
|
25567
|
-
|
|
25768
|
+
const rows = allRows(
|
|
25769
|
+
this.db.prepare(
|
|
25770
|
+
`${LLM_USAGE_SELECT}
|
|
25771
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25772
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25773
|
+
${LLM_USAGE_GROUP}`
|
|
25774
|
+
),
|
|
25775
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25776
|
+
);
|
|
25777
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25568
25778
|
}
|
|
25569
25779
|
/**
|
|
25570
|
-
* One session's token report — its `llm_call`
|
|
25571
|
-
* model) with derived cost, or `null` when the session made no
|
|
25572
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25573
|
-
* breakdown + estimated cost.
|
|
25780
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25781
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25782
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25783
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25784
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25785
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25574
25786
|
*/
|
|
25575
25787
|
tokenReportForSession(sessionId) {
|
|
25576
|
-
const
|
|
25788
|
+
const rows = allRows(
|
|
25789
|
+
this.db.prepare(
|
|
25790
|
+
`${LLM_USAGE_SELECT}
|
|
25791
|
+
FROM audit_events
|
|
25792
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25793
|
+
${LLM_USAGE_GROUP}`
|
|
25794
|
+
),
|
|
25795
|
+
[sessionId]
|
|
25796
|
+
);
|
|
25797
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25577
25798
|
return Promise.resolve(reports[0] ?? null);
|
|
25578
25799
|
}
|
|
25579
25800
|
/**
|
|
@@ -25597,42 +25818,6 @@ var SqliteActivityRepository = class {
|
|
|
25597
25818
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25598
25819
|
return Promise.resolve([...seen]);
|
|
25599
25820
|
}
|
|
25600
|
-
/**
|
|
25601
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25602
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25603
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25604
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25605
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25606
|
-
*/
|
|
25607
|
-
readLlmCallLeaves(opts = {}) {
|
|
25608
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25609
|
-
const params = [];
|
|
25610
|
-
if (opts.sessionId !== void 0) {
|
|
25611
|
-
conditions.push("root_session_id = ?");
|
|
25612
|
-
params.push(opts.sessionId);
|
|
25613
|
-
}
|
|
25614
|
-
if (opts.fromMs !== void 0) {
|
|
25615
|
-
conditions.push("started_at >= ?");
|
|
25616
|
-
params.push(opts.fromMs);
|
|
25617
|
-
}
|
|
25618
|
-
const rows = allRows(
|
|
25619
|
-
this.db.prepare(
|
|
25620
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25621
|
-
FROM audit_events
|
|
25622
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25623
|
-
),
|
|
25624
|
-
params
|
|
25625
|
-
);
|
|
25626
|
-
return mapRowsTolerant(
|
|
25627
|
-
rows.filter(
|
|
25628
|
-
(row) => row.sessionId !== null
|
|
25629
|
-
),
|
|
25630
|
-
(row) => ({
|
|
25631
|
-
sessionId: row.sessionId,
|
|
25632
|
-
attributes: JSON.parse(row.attributes)
|
|
25633
|
-
})
|
|
25634
|
-
);
|
|
25635
|
-
}
|
|
25636
25821
|
/**
|
|
25637
25822
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25638
25823
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25647,20 +25832,23 @@ var SqliteActivityRepository = class {
|
|
|
25647
25832
|
const inClause = placeholders(sessionIds.length);
|
|
25648
25833
|
const lastActivityRows = allRows(
|
|
25649
25834
|
this.db.prepare(
|
|
25650
|
-
`SELECT
|
|
25651
|
-
|
|
25652
|
-
|
|
25835
|
+
`SELECT ids.value AS id,
|
|
25836
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25837
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25838
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25839
|
+
FROM json_each(?) AS ids`
|
|
25653
25840
|
),
|
|
25654
|
-
sessionIds
|
|
25841
|
+
[JSON.stringify(sessionIds)]
|
|
25655
25842
|
);
|
|
25656
25843
|
for (const row of lastActivityRows) {
|
|
25657
|
-
if (row.id === null) continue;
|
|
25658
25844
|
const entry = result.get(row.id);
|
|
25659
|
-
|
|
25845
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25846
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25660
25847
|
}
|
|
25661
25848
|
const turnsRows = allRows(
|
|
25662
25849
|
this.db.prepare(
|
|
25663
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25850
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25851
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25664
25852
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25665
25853
|
GROUP BY root_session_id`
|
|
25666
25854
|
),
|
|
@@ -25675,7 +25863,7 @@ var SqliteActivityRepository = class {
|
|
|
25675
25863
|
this.db.prepare(
|
|
25676
25864
|
`SELECT root_session_id AS id,
|
|
25677
25865
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25678
|
-
FROM audit_events
|
|
25866
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25679
25867
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25680
25868
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25681
25869
|
GROUP BY root_session_id`
|
|
@@ -25705,7 +25893,7 @@ var SqliteActivityRepository = class {
|
|
|
25705
25893
|
this.db.prepare(
|
|
25706
25894
|
`SELECT root_session_id AS id,
|
|
25707
25895
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25708
|
-
FROM audit_events
|
|
25896
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25709
25897
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25710
25898
|
GROUP BY root_session_id`
|
|
25711
25899
|
),
|
|
@@ -26734,7 +26922,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26734
26922
|
|
|
26735
26923
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26736
26924
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26737
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26738
26925
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26739
26926
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26740
26927
|
function compareLocationOrder(a, b) {
|
|
@@ -26763,6 +26950,25 @@ function deriveInstanceStatus(row) {
|
|
|
26763
26950
|
latestResolutionStatus: row.latest_status
|
|
26764
26951
|
});
|
|
26765
26952
|
}
|
|
26953
|
+
function toFlatFindingRow(r) {
|
|
26954
|
+
return {
|
|
26955
|
+
id: r.id,
|
|
26956
|
+
ruleId: r.rule_id,
|
|
26957
|
+
category: r.category,
|
|
26958
|
+
severity: r.severity,
|
|
26959
|
+
maskedMatch: r.masked_match,
|
|
26960
|
+
actionTaken: r.action_taken,
|
|
26961
|
+
confidence: r.confidence,
|
|
26962
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26963
|
+
sourceTool: r.source_tool,
|
|
26964
|
+
repo: r.repo ?? "",
|
|
26965
|
+
file: r.file ?? "",
|
|
26966
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26967
|
+
eventId: r.event_id,
|
|
26968
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26969
|
+
status: deriveInstanceStatus(r)
|
|
26970
|
+
};
|
|
26971
|
+
}
|
|
26766
26972
|
function encodeGroupCursor(group) {
|
|
26767
26973
|
const payload = {
|
|
26768
26974
|
sev: group.severity,
|
|
@@ -26838,7 +27044,7 @@ var SqliteFindingsRepository = class {
|
|
|
26838
27044
|
this.db.prepare(
|
|
26839
27045
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26840
27046
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26841
|
-
|
|
27047
|
+
e.source_tool AS source_tool,
|
|
26842
27048
|
e.event_type AS kind
|
|
26843
27049
|
FROM audit_events e
|
|
26844
27050
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26946,56 +27152,11 @@ var SqliteFindingsRepository = class {
|
|
|
26946
27152
|
predicate,
|
|
26947
27153
|
params: sessionParams
|
|
26948
27154
|
});
|
|
26949
|
-
const rows =
|
|
26950
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
26953
|
-
|
|
26954
|
-
FROM (
|
|
26955
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26956
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26957
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26958
|
-
e.started_at AS occurred_at,
|
|
26959
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26960
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26961
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26962
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26963
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26964
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26965
|
-
latest.status AS latest_status,
|
|
26966
|
-
ROW_NUMBER() OVER (
|
|
26967
|
-
PARTITION BY d.rule_id
|
|
26968
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26969
|
-
) AS rn
|
|
26970
|
-
FROM inspection_findings f
|
|
26971
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26972
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26973
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26974
|
-
ON latest.finding_key = f.finding_key
|
|
26975
|
-
${predicate}
|
|
26976
|
-
)
|
|
26977
|
-
WHERE rn <= :cap
|
|
26978
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26979
|
-
),
|
|
26980
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
26981
|
-
);
|
|
26982
|
-
const groupable = rows.map((r) => ({
|
|
26983
|
-
id: r.id,
|
|
26984
|
-
ruleId: r.rule_id,
|
|
26985
|
-
category: r.category,
|
|
26986
|
-
severity: r.severity,
|
|
26987
|
-
maskedMatch: r.masked_match,
|
|
26988
|
-
actionTaken: r.action_taken,
|
|
26989
|
-
confidence: r.confidence,
|
|
26990
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26991
|
-
sourceTool: r.source_tool,
|
|
26992
|
-
repo: r.repo ?? "",
|
|
26993
|
-
file: r.file ?? "",
|
|
26994
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26995
|
-
eventId: r.event_id,
|
|
26996
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26997
|
-
status: deriveInstanceStatus(r)
|
|
26998
|
-
}));
|
|
27155
|
+
const rows = this.previewRows(aggregates, {
|
|
27156
|
+
sessionId: query.sessionId,
|
|
27157
|
+
from: query.from
|
|
27158
|
+
});
|
|
27159
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
26999
27160
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27000
27161
|
const filterOpts = {
|
|
27001
27162
|
severity: query.severity,
|
|
@@ -27081,8 +27242,10 @@ var SqliteFindingsRepository = class {
|
|
|
27081
27242
|
*
|
|
27082
27243
|
* The scan runs from the top of the scope on every request, not from the
|
|
27083
27244
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
27084
|
-
* move as the caller pages. Rows
|
|
27085
|
-
*
|
|
27245
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27246
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27247
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27248
|
+
* fetched batches; only the page itself is retained.
|
|
27086
27249
|
*/
|
|
27087
27250
|
listFindingInstances(query) {
|
|
27088
27251
|
const opts = {
|
|
@@ -27098,6 +27261,10 @@ var SqliteFindingsRepository = class {
|
|
|
27098
27261
|
};
|
|
27099
27262
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27100
27263
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27264
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27265
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27266
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27267
|
+
};
|
|
27101
27268
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27102
27269
|
const items = [];
|
|
27103
27270
|
let total = 0;
|
|
@@ -27110,6 +27277,7 @@ var SqliteFindingsRepository = class {
|
|
|
27110
27277
|
accumulator.add(row);
|
|
27111
27278
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27112
27279
|
total += 1;
|
|
27280
|
+
if (!isPastCursor(row)) continue;
|
|
27113
27281
|
if (items.length < limit) {
|
|
27114
27282
|
items.push(toInstanceDetail(row));
|
|
27115
27283
|
last = row;
|
|
@@ -27118,15 +27286,6 @@ var SqliteFindingsRepository = class {
|
|
|
27118
27286
|
}
|
|
27119
27287
|
}
|
|
27120
27288
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27121
|
-
if (cursor !== null) {
|
|
27122
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27123
|
-
return Promise.resolve({
|
|
27124
|
-
totals: { findings: total },
|
|
27125
|
-
facets: accumulator.facets(),
|
|
27126
|
-
items: resumed.items,
|
|
27127
|
-
nextCursor: resumed.nextCursor
|
|
27128
|
-
});
|
|
27129
|
-
}
|
|
27130
27289
|
return Promise.resolve({
|
|
27131
27290
|
totals: { findings: total },
|
|
27132
27291
|
facets: accumulator.facets(),
|
|
@@ -27134,35 +27293,6 @@ var SqliteFindingsRepository = class {
|
|
|
27134
27293
|
nextCursor
|
|
27135
27294
|
});
|
|
27136
27295
|
}
|
|
27137
|
-
/**
|
|
27138
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27139
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27140
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27141
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27142
|
-
*/
|
|
27143
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27144
|
-
const items = [];
|
|
27145
|
-
let last;
|
|
27146
|
-
let hasMore = false;
|
|
27147
|
-
for (const row of this.scanFindingRows({
|
|
27148
|
-
sessionId: query.sessionId,
|
|
27149
|
-
from: query.from,
|
|
27150
|
-
after: cursor
|
|
27151
|
-
})) {
|
|
27152
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27153
|
-
if (items.length < limit) {
|
|
27154
|
-
items.push(toInstanceDetail(row));
|
|
27155
|
-
last = row;
|
|
27156
|
-
} else {
|
|
27157
|
-
hasMore = true;
|
|
27158
|
-
break;
|
|
27159
|
-
}
|
|
27160
|
-
}
|
|
27161
|
-
return {
|
|
27162
|
-
items,
|
|
27163
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27164
|
-
};
|
|
27165
|
-
}
|
|
27166
27296
|
/**
|
|
27167
27297
|
* The same findings folded by location: repository, then file within it.
|
|
27168
27298
|
*
|
|
@@ -27245,25 +27375,111 @@ var SqliteFindingsRepository = class {
|
|
|
27245
27375
|
});
|
|
27246
27376
|
}
|
|
27247
27377
|
/**
|
|
27248
|
-
*
|
|
27378
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27379
|
+
*
|
|
27380
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27381
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27382
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27383
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27384
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27385
|
+
* store while the answer does not.
|
|
27386
|
+
*
|
|
27387
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27388
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27389
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27390
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27391
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27392
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27393
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27394
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27395
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27396
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27397
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27398
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27399
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27400
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27401
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27402
|
+
* sort of the scope, which is still that floor.
|
|
27403
|
+
*
|
|
27404
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27405
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27406
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27407
|
+
* built from.
|
|
27408
|
+
*/
|
|
27409
|
+
previewRows(aggregates, scope) {
|
|
27410
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27411
|
+
let remaining = 0;
|
|
27412
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27413
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27414
|
+
wanted.set(ruleId, n);
|
|
27415
|
+
remaining += n;
|
|
27416
|
+
}
|
|
27417
|
+
const rows = [];
|
|
27418
|
+
if (remaining === 0) return rows;
|
|
27419
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27420
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27421
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27422
|
+
const want = wanted.get(r.rule_id);
|
|
27423
|
+
if (want === void 0) continue;
|
|
27424
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27425
|
+
if (have >= want) continue;
|
|
27426
|
+
taken.set(r.rule_id, have + 1);
|
|
27427
|
+
rows.push(r);
|
|
27428
|
+
remaining -= 1;
|
|
27429
|
+
if (remaining === 0) break;
|
|
27430
|
+
}
|
|
27431
|
+
return rows;
|
|
27432
|
+
}
|
|
27433
|
+
/**
|
|
27434
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27249
27435
|
*
|
|
27250
27436
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27251
27437
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27252
|
-
* store is far more rows than any page.
|
|
27253
|
-
*
|
|
27254
|
-
*
|
|
27255
|
-
*
|
|
27256
|
-
*
|
|
27257
|
-
*
|
|
27258
|
-
* makes it a point lookup per row, and the derived table would re-materialize
|
|
27259
|
-
* a window over the whole resolution table once per batch.
|
|
27438
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27439
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27440
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27441
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27442
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27443
|
+
* batch and cost the square of the scope.
|
|
27260
27444
|
*
|
|
27261
|
-
* `
|
|
27262
|
-
* would be missing from its own facet, which is
|
|
27263
|
-
* dimension
|
|
27445
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27446
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27447
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27448
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27449
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27450
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27451
|
+
* page-2+ request would otherwise re-seek for.
|
|
27264
27452
|
*/
|
|
27265
27453
|
*scanFindingRows(scope) {
|
|
27266
|
-
const
|
|
27454
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27455
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27456
|
+
yield toFlatFindingRow(r);
|
|
27457
|
+
}
|
|
27458
|
+
}
|
|
27459
|
+
/**
|
|
27460
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27461
|
+
* joined to its event and definition, newest first.
|
|
27462
|
+
*
|
|
27463
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27464
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27465
|
+
*
|
|
27466
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27467
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27468
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27469
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27470
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27471
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27472
|
+
* ordered within the session — and the order falls out of the index.
|
|
27473
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27474
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27475
|
+
*
|
|
27476
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27477
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27478
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27479
|
+
* table would be materialized before the first row streamed.
|
|
27480
|
+
*/
|
|
27481
|
+
findingScanSql(scope) {
|
|
27482
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27267
27483
|
const params = [];
|
|
27268
27484
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27269
27485
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27277,58 +27493,24 @@ var SqliteFindingsRepository = class {
|
|
|
27277
27493
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27278
27494
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27279
27495
|
e.started_at AS occurred_at,
|
|
27280
|
-
|
|
27281
|
-
|
|
27282
|
-
|
|
27283
|
-
|
|
27496
|
+
e.source_tool AS source_tool,
|
|
27497
|
+
e.repo AS repo,
|
|
27498
|
+
e.file_path AS file,
|
|
27499
|
+
e.tool_name AS tool_name,
|
|
27284
27500
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27285
27501
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27286
27502
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27287
|
-
FROM
|
|
27288
|
-
JOIN
|
|
27289
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27503
|
+
FROM audit_events e
|
|
27504
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27505
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27290
27506
|
WHERE ${conditions.join(" AND ")}
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
LIMIT ?`;
|
|
27294
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27295
|
-
for (; ; ) {
|
|
27296
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27297
|
-
...params,
|
|
27298
|
-
after.startedAtMs,
|
|
27299
|
-
after.startedAtMs,
|
|
27300
|
-
after.id,
|
|
27301
|
-
SCAN_BATCH_ROWS
|
|
27302
|
-
]);
|
|
27303
|
-
for (const r of rows) {
|
|
27304
|
-
yield {
|
|
27305
|
-
id: r.id,
|
|
27306
|
-
ruleId: r.rule_id,
|
|
27307
|
-
category: r.category,
|
|
27308
|
-
severity: r.severity,
|
|
27309
|
-
maskedMatch: r.masked_match,
|
|
27310
|
-
actionTaken: r.action_taken,
|
|
27311
|
-
confidence: r.confidence,
|
|
27312
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27313
|
-
sourceTool: r.source_tool,
|
|
27314
|
-
repo: r.repo ?? "",
|
|
27315
|
-
file: r.file ?? "",
|
|
27316
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27317
|
-
eventId: r.event_id,
|
|
27318
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27319
|
-
status: deriveInstanceStatus(r)
|
|
27320
|
-
};
|
|
27321
|
-
}
|
|
27322
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27323
|
-
const lastRow = rows[rows.length - 1];
|
|
27324
|
-
if (lastRow === void 0) return;
|
|
27325
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27326
|
-
}
|
|
27507
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27508
|
+
return { sql, params };
|
|
27327
27509
|
}
|
|
27328
27510
|
groupAggregates(withSearchText, scope) {
|
|
27329
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27330
|
-
group_concat(DISTINCT
|
|
27331
|
-
group_concat(DISTINCT 'via ' ||
|
|
27511
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27512
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27513
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27332
27514
|
const rows = this.db.prepare(
|
|
27333
27515
|
`SELECT rule_id,
|
|
27334
27516
|
sum(tuple_count) AS instance_count,
|
|
@@ -27346,7 +27528,7 @@ var SqliteFindingsRepository = class {
|
|
|
27346
27528
|
coalesce(latest.status, '') AS status_tuple,
|
|
27347
27529
|
count(*) AS tuple_count,
|
|
27348
27530
|
max(e.started_at) AS latest_at,
|
|
27349
|
-
group_concat(DISTINCT
|
|
27531
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27350
27532
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27351
27533
|
${innerSearchColumns}
|
|
27352
27534
|
FROM inspection_findings f
|
|
@@ -27477,6 +27659,8 @@ function isoDay(ms) {
|
|
|
27477
27659
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27478
27660
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27479
27661
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27662
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27663
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27480
27664
|
var SKIPPED = -1;
|
|
27481
27665
|
var ROW_COLUMNS = `id,
|
|
27482
27666
|
parent_id AS parentId,
|
|
@@ -27516,6 +27700,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27516
27700
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27517
27701
|
LIMIT :limit`
|
|
27518
27702
|
);
|
|
27703
|
+
this.captureRowsStmt = db.prepare(
|
|
27704
|
+
`SELECT ${ROW_COLUMNS}
|
|
27705
|
+
FROM audit_events
|
|
27706
|
+
WHERE synced_at IS NULL
|
|
27707
|
+
AND sync_claimed_at IS NULL
|
|
27708
|
+
AND outbox_owed = 1
|
|
27709
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27710
|
+
AND started_at < :before
|
|
27711
|
+
ORDER BY started_at
|
|
27712
|
+
LIMIT :limit`
|
|
27713
|
+
);
|
|
27714
|
+
this.markOwedStmt = db.prepare(
|
|
27715
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27716
|
+
);
|
|
27519
27717
|
this.stampStmt = db.prepare(
|
|
27520
27718
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27521
27719
|
);
|
|
@@ -27547,6 +27745,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27547
27745
|
FROM audit_events
|
|
27548
27746
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27549
27747
|
);
|
|
27748
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27749
|
+
`SELECT COUNT(*) AS skipped
|
|
27750
|
+
FROM audit_events
|
|
27751
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27752
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27753
|
+
);
|
|
27550
27754
|
this.fingerprintStmt = db.prepare(
|
|
27551
27755
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27552
27756
|
FROM history_sync WHERE id = 1`
|
|
@@ -27556,6 +27760,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27556
27760
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27557
27761
|
WHERE id = 1`
|
|
27558
27762
|
);
|
|
27763
|
+
this.disownCapturesStmt = db.prepare(
|
|
27764
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27765
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27766
|
+
);
|
|
27559
27767
|
this.rearmStmt = db.prepare(
|
|
27560
27768
|
`UPDATE audit_events SET synced_at = NULL
|
|
27561
27769
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27628,6 +27836,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27628
27836
|
closeWindowStmt;
|
|
27629
27837
|
releaseBoundaryStmt;
|
|
27630
27838
|
freezeBoundaryStmt;
|
|
27839
|
+
captureRowsStmt;
|
|
27840
|
+
markOwedStmt;
|
|
27841
|
+
captureSkipCountStmt;
|
|
27842
|
+
disownCapturesStmt;
|
|
27631
27843
|
partitionStmt;
|
|
27632
27844
|
claimRowStmt;
|
|
27633
27845
|
releaseRowStmt;
|
|
@@ -27661,6 +27873,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27661
27873
|
pendingRows(sessionId, limit, before) {
|
|
27662
27874
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27663
27875
|
}
|
|
27876
|
+
/**
|
|
27877
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27878
|
+
*
|
|
27879
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27880
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27881
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27882
|
+
* the live path.
|
|
27883
|
+
*/
|
|
27884
|
+
pendingCaptureRows(limit, before) {
|
|
27885
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27886
|
+
}
|
|
27887
|
+
/**
|
|
27888
|
+
* Record that a capture is OWED to the deployment.
|
|
27889
|
+
*
|
|
27890
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27891
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27892
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27893
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27894
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27895
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27896
|
+
* offered to anyone.
|
|
27897
|
+
*
|
|
27898
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27899
|
+
* out of the drain's read.
|
|
27900
|
+
*/
|
|
27901
|
+
markCaptureOwed(id) {
|
|
27902
|
+
this.markOwedStmt.run({ id });
|
|
27903
|
+
}
|
|
27664
27904
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27665
27905
|
markSynced(ids, atMs) {
|
|
27666
27906
|
this.stampAll(ids, atMs);
|
|
@@ -27744,10 +27984,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27744
27984
|
this.countsStmt,
|
|
27745
27985
|
{ before }
|
|
27746
27986
|
);
|
|
27987
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27747
27988
|
return {
|
|
27748
27989
|
pending: row?.pending ?? 0,
|
|
27749
27990
|
sent: row?.sent ?? 0,
|
|
27750
|
-
skipped: row?.skipped ?? 0
|
|
27991
|
+
skipped: row?.skipped ?? 0,
|
|
27992
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27751
27993
|
};
|
|
27752
27994
|
}
|
|
27753
27995
|
/**
|
|
@@ -27788,7 +28030,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27788
28030
|
withTransaction(
|
|
27789
28031
|
this.db,
|
|
27790
28032
|
() => {
|
|
28033
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27791
28034
|
this.rearmStmt.run();
|
|
28035
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28036
|
+
this.disownCapturesStmt.run();
|
|
28037
|
+
}
|
|
27792
28038
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27793
28039
|
},
|
|
27794
28040
|
"IMMEDIATE"
|
|
@@ -27985,7 +28231,259 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
27985
28231
|
};
|
|
27986
28232
|
|
|
27987
28233
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
27988
|
-
import { createHash as createHash2, randomUUID as
|
|
28234
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28235
|
+
|
|
28236
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28237
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28238
|
+
import { join as join6 } from "path";
|
|
28239
|
+
|
|
28240
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28241
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28242
|
+
import { mkdir } from "fs/promises";
|
|
28243
|
+
import { homedir } from "os";
|
|
28244
|
+
import { join as join4 } from "path";
|
|
28245
|
+
function defaultDataDir() {
|
|
28246
|
+
return join4(homedir(), ".aka");
|
|
28247
|
+
}
|
|
28248
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28249
|
+
return join4(base, "settings");
|
|
28250
|
+
}
|
|
28251
|
+
function dataDir(base = defaultDataDir()) {
|
|
28252
|
+
return join4(base, "data");
|
|
28253
|
+
}
|
|
28254
|
+
function dbPath(base = defaultDataDir()) {
|
|
28255
|
+
return join4(dataDir(base), "aka.db");
|
|
28256
|
+
}
|
|
28257
|
+
function keysDir(base = defaultDataDir()) {
|
|
28258
|
+
return join4(base, "keys");
|
|
28259
|
+
}
|
|
28260
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
28261
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
28262
|
+
tightenDir(dir);
|
|
28263
|
+
}
|
|
28264
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28265
|
+
ensureDataDirSync(dir);
|
|
28266
|
+
}
|
|
28267
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28268
|
+
const moves = [
|
|
28269
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28270
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28271
|
+
];
|
|
28272
|
+
for (const { name, dest } of moves) {
|
|
28273
|
+
try {
|
|
28274
|
+
ensureDataDirSync(dest);
|
|
28275
|
+
const moved = join4(dest, name);
|
|
28276
|
+
renameSync3(join4(base, name), moved);
|
|
28277
|
+
tightenFile(moved);
|
|
28278
|
+
} catch {
|
|
28279
|
+
}
|
|
28280
|
+
}
|
|
28281
|
+
}
|
|
28282
|
+
|
|
28283
|
+
// ../../packages/persistence/src/settings.ts
|
|
28284
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28285
|
+
import { join as join5 } from "path";
|
|
28286
|
+
|
|
28287
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28288
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28289
|
+
import {
|
|
28290
|
+
closeSync,
|
|
28291
|
+
existsSync as existsSync2,
|
|
28292
|
+
openSync,
|
|
28293
|
+
readFileSync as readFileSync2,
|
|
28294
|
+
rmSync as rmSync5,
|
|
28295
|
+
statSync as statSync3,
|
|
28296
|
+
writeFileSync as writeFileSync2
|
|
28297
|
+
} from "fs";
|
|
28298
|
+
import { hostname as hostname3 } from "os";
|
|
28299
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28300
|
+
|
|
28301
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28302
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28303
|
+
import { posix, win32 } from "path";
|
|
28304
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28305
|
+
if (platform2 === "darwin") {
|
|
28306
|
+
return [
|
|
28307
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28308
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28309
|
+
];
|
|
28310
|
+
}
|
|
28311
|
+
if (platform2 === "win32") {
|
|
28312
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28313
|
+
}
|
|
28314
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28315
|
+
}
|
|
28316
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28317
|
+
for (const path of paths) {
|
|
28318
|
+
let text;
|
|
28319
|
+
try {
|
|
28320
|
+
text = readFileSync3(path, "utf8");
|
|
28321
|
+
} catch {
|
|
28322
|
+
continue;
|
|
28323
|
+
}
|
|
28324
|
+
const record2 = parseJsonObject(text);
|
|
28325
|
+
if (!record2) continue;
|
|
28326
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28327
|
+
if (parsed2.success) return parsed2.data;
|
|
28328
|
+
}
|
|
28329
|
+
return null;
|
|
28330
|
+
}
|
|
28331
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28332
|
+
if (!managed) return settings;
|
|
28333
|
+
const { values } = managed;
|
|
28334
|
+
const merged = { ...settings };
|
|
28335
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28336
|
+
if (values.controlPlane !== void 0) {
|
|
28337
|
+
merged.controlPlane = {
|
|
28338
|
+
...values.controlPlane,
|
|
28339
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28340
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28341
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28342
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28343
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28344
|
+
};
|
|
28345
|
+
}
|
|
28346
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28347
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28348
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28349
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28350
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28351
|
+
if (values.vaultConsent !== void 0) {
|
|
28352
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28353
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28354
|
+
// at the current version otherwise.
|
|
28355
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28356
|
+
) : void 0;
|
|
28357
|
+
}
|
|
28358
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28359
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28360
|
+
acknowledgedAt: now().toISOString(),
|
|
28361
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28362
|
+
} : void 0;
|
|
28363
|
+
}
|
|
28364
|
+
return merged;
|
|
28365
|
+
}
|
|
28366
|
+
|
|
28367
|
+
// ../../packages/persistence/src/settings.ts
|
|
28368
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28369
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28370
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28371
|
+
}
|
|
28372
|
+
function readUserSettings(base) {
|
|
28373
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28374
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28375
|
+
try {
|
|
28376
|
+
return WorkspaceSettings.parse(record2);
|
|
28377
|
+
} catch {
|
|
28378
|
+
return defaultWorkspaceSettings();
|
|
28379
|
+
}
|
|
28380
|
+
}
|
|
28381
|
+
function readJson(file2) {
|
|
28382
|
+
let text;
|
|
28383
|
+
try {
|
|
28384
|
+
text = readFileSync4(file2, "utf8");
|
|
28385
|
+
} catch {
|
|
28386
|
+
return null;
|
|
28387
|
+
}
|
|
28388
|
+
return parseJsonObject(text) ?? null;
|
|
28389
|
+
}
|
|
28390
|
+
|
|
28391
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28392
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28393
|
+
switch (refusal) {
|
|
28394
|
+
case "lock":
|
|
28395
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28396
|
+
case "disable":
|
|
28397
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28398
|
+
case "floor":
|
|
28399
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28400
|
+
}
|
|
28401
|
+
}
|
|
28402
|
+
var PolicyFloorError = class extends Error {
|
|
28403
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28404
|
+
pack;
|
|
28405
|
+
/**
|
|
28406
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28407
|
+
* clearing the assignment, or switching the detection off.
|
|
28408
|
+
*/
|
|
28409
|
+
attempted;
|
|
28410
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28411
|
+
floor;
|
|
28412
|
+
refusal;
|
|
28413
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28414
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28415
|
+
this.name = "PolicyFloorError";
|
|
28416
|
+
this.pack = pack;
|
|
28417
|
+
this.attempted = attempted;
|
|
28418
|
+
this.floor = floor;
|
|
28419
|
+
this.refusal = refusal;
|
|
28420
|
+
}
|
|
28421
|
+
};
|
|
28422
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28423
|
+
try {
|
|
28424
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28425
|
+
const parsed2 = JSON.parse(raw);
|
|
28426
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28427
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28428
|
+
} catch {
|
|
28429
|
+
return null;
|
|
28430
|
+
}
|
|
28431
|
+
}
|
|
28432
|
+
function indexEnabled(policies) {
|
|
28433
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28434
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28435
|
+
for (const policy of policies) {
|
|
28436
|
+
if (!policy.enabled) continue;
|
|
28437
|
+
if ("ruleId" in policy.target) {
|
|
28438
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28439
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28440
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28441
|
+
}
|
|
28442
|
+
}
|
|
28443
|
+
return { byRuleId, byCategory };
|
|
28444
|
+
}
|
|
28445
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28446
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28447
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28448
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28449
|
+
return policies.some((policy) => {
|
|
28450
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28451
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28452
|
+
});
|
|
28453
|
+
}
|
|
28454
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28455
|
+
const floors = openControlPlaneFloors(base);
|
|
28456
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28457
|
+
}
|
|
28458
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28459
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28460
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28461
|
+
if (bundle === null) return null;
|
|
28462
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28463
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28464
|
+
}
|
|
28465
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28466
|
+
let action = null;
|
|
28467
|
+
for (const rule of rules) {
|
|
28468
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28469
|
+
if (resolved === void 0) continue;
|
|
28470
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28471
|
+
}
|
|
28472
|
+
if (action === null) return null;
|
|
28473
|
+
return {
|
|
28474
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28475
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28476
|
+
};
|
|
28477
|
+
}
|
|
28478
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28479
|
+
if (floor.locked) return "lock";
|
|
28480
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28481
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28482
|
+
}
|
|
28483
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28484
|
+
if (floor === null || enabled) return null;
|
|
28485
|
+
return "disable";
|
|
28486
|
+
}
|
|
27989
28487
|
|
|
27990
28488
|
// ../../packages/persistence/src/semver.ts
|
|
27991
28489
|
function parse3(version2) {
|
|
@@ -28079,8 +28577,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
28079
28577
|
return ids;
|
|
28080
28578
|
}
|
|
28081
28579
|
var SqliteInstalledPacksRepository = class {
|
|
28082
|
-
|
|
28580
|
+
/**
|
|
28581
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28582
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28583
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28584
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28585
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28586
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28587
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28588
|
+
* real `~/.aka`.
|
|
28589
|
+
*/
|
|
28590
|
+
constructor(db, baseDir) {
|
|
28083
28591
|
this.db = db;
|
|
28592
|
+
this.baseDir = baseDir;
|
|
28084
28593
|
this.insertMissingStmt = db.prepare(
|
|
28085
28594
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
28086
28595
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28102,11 +28611,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28102
28611
|
this.signatureStmt = db.prepare(
|
|
28103
28612
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28104
28613
|
);
|
|
28614
|
+
this.packRulesStmt = db.prepare(
|
|
28615
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28616
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28617
|
+
);
|
|
28105
28618
|
}
|
|
28106
28619
|
db;
|
|
28620
|
+
baseDir;
|
|
28107
28621
|
insertMissingStmt;
|
|
28108
28622
|
upsertAvailableStmt;
|
|
28109
28623
|
signatureStmt;
|
|
28624
|
+
packRulesStmt;
|
|
28110
28625
|
/**
|
|
28111
28626
|
* Record the running binary's detection inventory. Refreshes the
|
|
28112
28627
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28148,7 +28663,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28148
28663
|
let behind = false;
|
|
28149
28664
|
for (const row of rows) {
|
|
28150
28665
|
const params = {
|
|
28151
|
-
id:
|
|
28666
|
+
id: randomUUID4(),
|
|
28152
28667
|
namespace: row.namespace,
|
|
28153
28668
|
packId: row.packId,
|
|
28154
28669
|
version: row.version,
|
|
@@ -28160,7 +28675,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28160
28675
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28161
28676
|
this.upsertAvailableStmt.run({
|
|
28162
28677
|
...params,
|
|
28163
|
-
id:
|
|
28678
|
+
id: randomUUID4(),
|
|
28164
28679
|
recordedBy: meta4?.recordedBy ?? null
|
|
28165
28680
|
});
|
|
28166
28681
|
} else {
|
|
@@ -28406,9 +28921,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28406
28921
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28407
28922
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28408
28923
|
// caller can tell an edit from a no-such-detection.
|
|
28924
|
+
/**
|
|
28925
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
28926
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
28927
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
28928
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
28929
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
28930
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
28931
|
+
*/
|
|
28932
|
+
packFloorRules(namespace, packId) {
|
|
28933
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
28934
|
+
if (!row) return [];
|
|
28935
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
28936
|
+
}
|
|
28937
|
+
/**
|
|
28938
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
28939
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
28940
|
+
* repository constructed without a layout base).
|
|
28941
|
+
*
|
|
28942
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
28943
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
28944
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
28945
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
28946
|
+
*/
|
|
28947
|
+
policyFloor(namespace, packId) {
|
|
28948
|
+
if (this.baseDir === void 0) return null;
|
|
28949
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
28950
|
+
}
|
|
28951
|
+
/**
|
|
28952
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
28953
|
+
* entry only for a pack the control plane actually governs.
|
|
28954
|
+
*
|
|
28955
|
+
* A surface listing every detection asks per pack, and asking through
|
|
28956
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
28957
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
28958
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
28959
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
28960
|
+
* exactly as the single-pack read returns null for them.
|
|
28961
|
+
*/
|
|
28962
|
+
policyFloors(packs2) {
|
|
28963
|
+
const floors = /* @__PURE__ */ new Map();
|
|
28964
|
+
if (this.baseDir === void 0) return floors;
|
|
28965
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
28966
|
+
if (source === null) return floors;
|
|
28967
|
+
for (const pack of packs2) {
|
|
28968
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
28969
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
28970
|
+
}
|
|
28971
|
+
return floors;
|
|
28972
|
+
}
|
|
28409
28973
|
/**
|
|
28410
28974
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28411
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
28975
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
28976
|
+
*
|
|
28977
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
28978
|
+
* write below, and a detection the organization has authored a policy for is
|
|
28979
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
28980
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
28981
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
28982
|
+
* surface that offers the choice.
|
|
28412
28983
|
*/
|
|
28413
28984
|
setPolicy(namespace, packId, policyId) {
|
|
28414
28985
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28416,14 +28987,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28416
28987
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28417
28988
|
);
|
|
28418
28989
|
}
|
|
28990
|
+
const requested = policyId;
|
|
28991
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28992
|
+
if (floor !== null) {
|
|
28993
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
28994
|
+
if (refusal !== null) {
|
|
28995
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
28996
|
+
}
|
|
28997
|
+
}
|
|
28419
28998
|
const res = this.db.prepare(
|
|
28420
28999
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28421
29000
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28422
29001
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28423
29002
|
return Number(res.changes) > 0;
|
|
28424
29003
|
}
|
|
28425
|
-
/**
|
|
29004
|
+
/**
|
|
29005
|
+
* Enable or disable one installed pack.
|
|
29006
|
+
*
|
|
29007
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29008
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29009
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29010
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29011
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29012
|
+
* that lived in a page would leave the CLI free.
|
|
29013
|
+
*/
|
|
28426
29014
|
setEnabled(namespace, packId, enabled) {
|
|
29015
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29016
|
+
if (floor !== null) {
|
|
29017
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29018
|
+
if (refusal !== null) {
|
|
29019
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29020
|
+
}
|
|
29021
|
+
}
|
|
28427
29022
|
const res = this.db.prepare(
|
|
28428
29023
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28429
29024
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28509,7 +29104,7 @@ var SqliteInventoryRepository = class {
|
|
|
28509
29104
|
};
|
|
28510
29105
|
|
|
28511
29106
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28512
|
-
import { randomUUID as
|
|
29107
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28513
29108
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28514
29109
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28515
29110
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -28998,7 +29593,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28998
29593
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
28999
29594
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
29000
29595
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
29001
|
-
).run({ id:
|
|
29596
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
29002
29597
|
}
|
|
29003
29598
|
return true;
|
|
29004
29599
|
}
|
|
@@ -29019,7 +29614,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29019
29614
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
29020
29615
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
29021
29616
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
29022
|
-
).run({ id:
|
|
29617
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
29023
29618
|
}
|
|
29024
29619
|
this.configRowsCache = void 0;
|
|
29025
29620
|
return "ok";
|
|
@@ -29316,7 +29911,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29316
29911
|
};
|
|
29317
29912
|
|
|
29318
29913
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29319
|
-
import { randomUUID as
|
|
29914
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29320
29915
|
var SqlitePoliciesRepository = class {
|
|
29321
29916
|
constructor(db) {
|
|
29322
29917
|
this.db = db;
|
|
@@ -29351,7 +29946,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29351
29946
|
failOpenTransaction(this.db, () => {
|
|
29352
29947
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29353
29948
|
stmt.run({
|
|
29354
|
-
id:
|
|
29949
|
+
id: randomUUID6(),
|
|
29355
29950
|
target: JSON.stringify({ category }),
|
|
29356
29951
|
action,
|
|
29357
29952
|
now: Date.now()
|
|
@@ -29371,7 +29966,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29371
29966
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29372
29967
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29373
29968
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29374
|
-
).run({ id:
|
|
29969
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29375
29970
|
}
|
|
29376
29971
|
// Caps every global per-category policy currently set to block/redact down
|
|
29377
29972
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29439,7 +30034,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29439
30034
|
};
|
|
29440
30035
|
|
|
29441
30036
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29442
|
-
import { randomUUID as
|
|
30037
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29443
30038
|
var SqliteProjectFilesRepository = class {
|
|
29444
30039
|
constructor(db) {
|
|
29445
30040
|
this.db = db;
|
|
@@ -29471,7 +30066,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29471
30066
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29472
30067
|
for (const file2 of scan2.files) {
|
|
29473
30068
|
this.upsertStmt.run({
|
|
29474
|
-
id:
|
|
30069
|
+
id: randomUUID7(),
|
|
29475
30070
|
projectId,
|
|
29476
30071
|
path: file2.path,
|
|
29477
30072
|
name: file2.name,
|
|
@@ -29485,9 +30080,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29485
30080
|
};
|
|
29486
30081
|
|
|
29487
30082
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29488
|
-
import { randomUUID as
|
|
30083
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29489
30084
|
var SqliteResolutionsRepository = class {
|
|
29490
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30085
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29491
30086
|
this.db = db;
|
|
29492
30087
|
this.now = now;
|
|
29493
30088
|
this.newId = newId;
|
|
@@ -29700,7 +30295,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29700
30295
|
};
|
|
29701
30296
|
|
|
29702
30297
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29703
|
-
import { randomUUID as
|
|
30298
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29704
30299
|
function pageLimit(requested, fallback) {
|
|
29705
30300
|
if (requested === void 0) return fallback;
|
|
29706
30301
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29746,12 +30341,14 @@ var SELECT_COLUMNS = `
|
|
|
29746
30341
|
ciphertext,
|
|
29747
30342
|
nonce,
|
|
29748
30343
|
auth_tag AS authTag,
|
|
30344
|
+
user_authorized AS userAuthorized,
|
|
29749
30345
|
occurrence_count AS occurrenceCount,
|
|
29750
30346
|
first_seen AS firstSeen,
|
|
29751
30347
|
last_seen AS lastSeen`;
|
|
29752
30348
|
function toRow(raw) {
|
|
29753
|
-
const { provider, ...rest } = raw;
|
|
29754
|
-
|
|
30349
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30350
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30351
|
+
return provider === null ? row : { ...row, provider };
|
|
29755
30352
|
}
|
|
29756
30353
|
var SqliteSecretVaultRepository = class {
|
|
29757
30354
|
constructor(db) {
|
|
@@ -29761,17 +30358,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29761
30358
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29762
30359
|
format_version, category, rule_id, masked_match, provider,
|
|
29763
30360
|
ciphertext, nonce, auth_tag,
|
|
29764
|
-
occurrence_count, first_seen, last_seen
|
|
30361
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29765
30362
|
) VALUES (
|
|
29766
30363
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29767
30364
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29768
30365
|
:ciphertext, :nonce, :authTag,
|
|
29769
|
-
1, :now, :now
|
|
30366
|
+
:userAuthorized, 1, :now, :now
|
|
29770
30367
|
)`
|
|
29771
30368
|
);
|
|
29772
30369
|
this.bumpStmt = db.prepare(
|
|
29773
30370
|
`UPDATE secret_vault
|
|
29774
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30371
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30372
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29775
30373
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29776
30374
|
);
|
|
29777
30375
|
this.byPointerStmt = db.prepare(
|
|
@@ -29791,6 +30389,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29791
30389
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29792
30390
|
WHERE pointer_id = :pointerId`
|
|
29793
30391
|
);
|
|
30392
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29794
30393
|
this.derefStmt = db.prepare(
|
|
29795
30394
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29796
30395
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29804,6 +30403,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29804
30403
|
listStmt;
|
|
29805
30404
|
replaceCiphertextStmt;
|
|
29806
30405
|
refreshFingerprintStmt;
|
|
30406
|
+
deleteByPointerStmt;
|
|
29807
30407
|
derefStmt;
|
|
29808
30408
|
/**
|
|
29809
30409
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29812,6 +30412,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29812
30412
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29813
30413
|
* wire token. `minted` is true only when this call created the row.
|
|
29814
30414
|
*
|
|
30415
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30416
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30417
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30418
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30419
|
+
*
|
|
29815
30420
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29816
30421
|
* writers cannot both decide they are minting.
|
|
29817
30422
|
*/
|
|
@@ -29838,13 +30443,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29838
30443
|
ciphertext: input2.ciphertext,
|
|
29839
30444
|
nonce: input2.nonce,
|
|
29840
30445
|
authTag: input2.authTag,
|
|
30446
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29841
30447
|
now
|
|
29842
30448
|
})
|
|
29843
30449
|
);
|
|
29844
30450
|
minted = true;
|
|
29845
30451
|
return;
|
|
29846
30452
|
}
|
|
29847
|
-
this.bumpStmt.run({
|
|
30453
|
+
this.bumpStmt.run({
|
|
30454
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30455
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30456
|
+
now
|
|
30457
|
+
});
|
|
29848
30458
|
},
|
|
29849
30459
|
"IMMEDIATE"
|
|
29850
30460
|
);
|
|
@@ -29904,6 +30514,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29904
30514
|
);
|
|
29905
30515
|
return destroyed;
|
|
29906
30516
|
}
|
|
30517
|
+
/**
|
|
30518
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30519
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30520
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30521
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30522
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30523
|
+
* it.
|
|
30524
|
+
*
|
|
30525
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30526
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30527
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30528
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30529
|
+
* entry still sitting in the vault.
|
|
30530
|
+
*
|
|
30531
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30532
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30533
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30534
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30535
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30536
|
+
*/
|
|
30537
|
+
deleteByPointerIds(pointerIds) {
|
|
30538
|
+
if (pointerIds.length === 0) return [];
|
|
30539
|
+
const deleted = [];
|
|
30540
|
+
withTransaction(
|
|
30541
|
+
this.db,
|
|
30542
|
+
() => {
|
|
30543
|
+
for (const pointerId of pointerIds) {
|
|
30544
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30545
|
+
deleted.push(pointerId);
|
|
30546
|
+
}
|
|
30547
|
+
}
|
|
30548
|
+
},
|
|
30549
|
+
"IMMEDIATE"
|
|
30550
|
+
);
|
|
30551
|
+
return deleted;
|
|
30552
|
+
}
|
|
29907
30553
|
/**
|
|
29908
30554
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29909
30555
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29916,7 +30562,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29916
30562
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29917
30563
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29918
30564
|
).run({
|
|
29919
|
-
id:
|
|
30565
|
+
id: randomUUID9(),
|
|
29920
30566
|
pointerId: entry.pointerId,
|
|
29921
30567
|
location: entry.location,
|
|
29922
30568
|
kind: entry.kind,
|
|
@@ -30429,15 +31075,15 @@ var SqliteSecurityRepository = class {
|
|
|
30429
31075
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30430
31076
|
const rows = allRows(
|
|
30431
31077
|
this.db.prepare(
|
|
30432
|
-
`SELECT
|
|
31078
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30433
31079
|
FROM inspection_findings f
|
|
30434
31080
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30435
31081
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30436
31082
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30437
|
-
AND
|
|
30438
|
-
AND
|
|
30439
|
-
GROUP BY repo
|
|
30440
|
-
ORDER BY c DESC, repo
|
|
31083
|
+
AND e.repo IS NOT NULL
|
|
31084
|
+
AND e.repo != ''
|
|
31085
|
+
GROUP BY e.repo
|
|
31086
|
+
ORDER BY c DESC, e.repo
|
|
30441
31087
|
LIMIT :limit`
|
|
30442
31088
|
),
|
|
30443
31089
|
{ from, to: now, limit }
|
|
@@ -30499,7 +31145,7 @@ var SqliteSecurityRepository = class {
|
|
|
30499
31145
|
`SELECT f.finding_key AS finding_key,
|
|
30500
31146
|
d.rule_id AS rule_id,
|
|
30501
31147
|
d.severity AS severity,
|
|
30502
|
-
|
|
31148
|
+
e.file_path AS path,
|
|
30503
31149
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30504
31150
|
latest.resolved_at AS latest_resolved_at
|
|
30505
31151
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30552,7 +31198,7 @@ var SqliteSecurityRepository = class {
|
|
|
30552
31198
|
};
|
|
30553
31199
|
|
|
30554
31200
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30555
|
-
import { randomUUID as
|
|
31201
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30556
31202
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30557
31203
|
var IN_CHUNK = 500;
|
|
30558
31204
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30640,7 +31286,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30640
31286
|
callSiteCount,
|
|
30641
31287
|
transports: distinctTransports(transports),
|
|
30642
31288
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30643
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31289
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30644
31290
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30645
31291
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30646
31292
|
};
|
|
@@ -30667,7 +31313,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30667
31313
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30668
31314
|
transports: distinctTransports(transports),
|
|
30669
31315
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30670
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31316
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30671
31317
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30672
31318
|
note: dest.note,
|
|
30673
31319
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30696,7 +31342,11 @@ var SqliteSharesRepository = class {
|
|
|
30696
31342
|
FROM share_destination d
|
|
30697
31343
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30698
31344
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30699
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31345
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31346
|
+
AND NOT EXISTS (
|
|
31347
|
+
SELECT 1 FROM egress_decision_override o
|
|
31348
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31349
|
+
)`
|
|
30700
31350
|
);
|
|
30701
31351
|
const kindCounts = countBy(
|
|
30702
31352
|
this.db,
|
|
@@ -30808,7 +31458,7 @@ var SqliteSharesRepository = class {
|
|
|
30808
31458
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30809
31459
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30810
31460
|
).run({
|
|
30811
|
-
id:
|
|
31461
|
+
id: randomUUID10(),
|
|
30812
31462
|
destinationId,
|
|
30813
31463
|
host: dest.host,
|
|
30814
31464
|
decision,
|
|
@@ -30957,7 +31607,7 @@ var SqliteSharesRepository = class {
|
|
|
30957
31607
|
let destinationId = destIds.get(hit.host);
|
|
30958
31608
|
if (destinationId === void 0) {
|
|
30959
31609
|
destStmt.run({
|
|
30960
|
-
id:
|
|
31610
|
+
id: randomUUID10(),
|
|
30961
31611
|
kind: hit.kind,
|
|
30962
31612
|
name: hit.name,
|
|
30963
31613
|
host: hit.host,
|
|
@@ -30973,7 +31623,7 @@ var SqliteSharesRepository = class {
|
|
|
30973
31623
|
let endpointId = endpointIds.get(endpointKey);
|
|
30974
31624
|
if (endpointId === void 0) {
|
|
30975
31625
|
endpointStmt.run({
|
|
30976
|
-
id:
|
|
31626
|
+
id: randomUUID10(),
|
|
30977
31627
|
destinationId,
|
|
30978
31628
|
method: hit.method,
|
|
30979
31629
|
transport: hit.transport,
|
|
@@ -30986,7 +31636,7 @@ var SqliteSharesRepository = class {
|
|
|
30986
31636
|
endpointIds.set(endpointKey, endpointId);
|
|
30987
31637
|
}
|
|
30988
31638
|
siteStmt.run({
|
|
30989
|
-
id:
|
|
31639
|
+
id: randomUUID10(),
|
|
30990
31640
|
endpointId,
|
|
30991
31641
|
project: input2.project,
|
|
30992
31642
|
projectKey: input2.projectKey,
|
|
@@ -31351,6 +32001,7 @@ function purgeSampleData(db) {
|
|
|
31351
32001
|
}
|
|
31352
32002
|
|
|
31353
32003
|
// ../../packages/persistence/src/database.ts
|
|
32004
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31354
32005
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31355
32006
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31356
32007
|
);
|
|
@@ -31398,7 +32049,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31398
32049
|
discardStore(file2, backup);
|
|
31399
32050
|
return backup;
|
|
31400
32051
|
}
|
|
31401
|
-
function openAndInitialize(file2) {
|
|
32052
|
+
function openAndInitialize(file2, base) {
|
|
31402
32053
|
let db = openWithPragmas(file2);
|
|
31403
32054
|
try {
|
|
31404
32055
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31411,7 +32062,7 @@ function openAndInitialize(file2) {
|
|
|
31411
32062
|
applyMigrations(db, file2);
|
|
31412
32063
|
tightenPerms(file2);
|
|
31413
32064
|
const policies = new SqlitePoliciesRepository(db);
|
|
31414
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32065
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31415
32066
|
const repositories = {
|
|
31416
32067
|
events: new SqliteEventsRepository(db),
|
|
31417
32068
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31447,7 +32098,7 @@ function openAndInitialize(file2) {
|
|
|
31447
32098
|
}
|
|
31448
32099
|
function openLocalDatabase(dir) {
|
|
31449
32100
|
ensureDataDirSync(dir);
|
|
31450
|
-
const file2 =
|
|
32101
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31451
32102
|
reapStalePartials(file2);
|
|
31452
32103
|
const {
|
|
31453
32104
|
db,
|
|
@@ -31475,7 +32126,13 @@ function openLocalDatabase(dir) {
|
|
|
31475
32126
|
inspectionDefinitions,
|
|
31476
32127
|
inspectionFindings,
|
|
31477
32128
|
configInventory
|
|
31478
|
-
} = openAndInitialize(
|
|
32129
|
+
} = openAndInitialize(
|
|
32130
|
+
file2,
|
|
32131
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32132
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32133
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32134
|
+
dirname2(dir)
|
|
32135
|
+
);
|
|
31479
32136
|
function captureRowId(event) {
|
|
31480
32137
|
return captureId(
|
|
31481
32138
|
event.metadata?.sessionId ?? null,
|
|
@@ -31488,6 +32145,21 @@ function openLocalDatabase(dir) {
|
|
|
31488
32145
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31489
32146
|
});
|
|
31490
32147
|
}
|
|
32148
|
+
function markCaptureOwed(event) {
|
|
32149
|
+
failOpenTransaction(db, () => {
|
|
32150
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32151
|
+
});
|
|
32152
|
+
}
|
|
32153
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32154
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32155
|
+
if (stampable.length === 0) return;
|
|
32156
|
+
failOpenTransaction(db, () => {
|
|
32157
|
+
historySync.markSynced(
|
|
32158
|
+
stampable.map((event) => event.id),
|
|
32159
|
+
atMs
|
|
32160
|
+
);
|
|
32161
|
+
});
|
|
32162
|
+
}
|
|
31491
32163
|
function recordCapture(event, detected) {
|
|
31492
32164
|
failOpenTransaction(db, () => {
|
|
31493
32165
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31574,7 +32246,7 @@ function openLocalDatabase(dir) {
|
|
|
31574
32246
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31575
32247
|
if (!definitionId) continue;
|
|
31576
32248
|
inspectionFindings.insertFinding({
|
|
31577
|
-
id:
|
|
32249
|
+
id: randomUUID11(),
|
|
31578
32250
|
auditEventId: record2.scanEvent.id,
|
|
31579
32251
|
inspectionDefinitionId: definitionId,
|
|
31580
32252
|
span: finding.span,
|
|
@@ -31670,6 +32342,8 @@ function openLocalDatabase(dir) {
|
|
|
31670
32342
|
inspectionFindings,
|
|
31671
32343
|
recordCapture,
|
|
31672
32344
|
markCaptureDelivered,
|
|
32345
|
+
markCaptureOwed,
|
|
32346
|
+
markAuditEventsDelivered,
|
|
31673
32347
|
ensureInventory,
|
|
31674
32348
|
recordConfigScan,
|
|
31675
32349
|
recordProjectFiles,
|
|
@@ -31708,20 +32382,6 @@ var UserGrantPolicyProvider = class {
|
|
|
31708
32382
|
}
|
|
31709
32383
|
};
|
|
31710
32384
|
|
|
31711
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31712
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31713
|
-
import {
|
|
31714
|
-
closeSync,
|
|
31715
|
-
existsSync as existsSync2,
|
|
31716
|
-
openSync,
|
|
31717
|
-
readFileSync as readFileSync2,
|
|
31718
|
-
rmSync as rmSync5,
|
|
31719
|
-
statSync as statSync3,
|
|
31720
|
-
writeFileSync as writeFileSync2
|
|
31721
|
-
} from "fs";
|
|
31722
|
-
import { hostname as hostname3 } from "os";
|
|
31723
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31724
|
-
|
|
31725
32385
|
// ../../packages/persistence/src/finding-key.ts
|
|
31726
32386
|
import { createHash as createHash3 } from "crypto";
|
|
31727
32387
|
function normalizeFilePath(filePath) {
|
|
@@ -31734,13 +32394,13 @@ function computeFindingKey(input2) {
|
|
|
31734
32394
|
|
|
31735
32395
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31736
32396
|
import { createHmac, randomBytes } from "crypto";
|
|
31737
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31738
|
-
import { join as
|
|
32397
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32398
|
+
import { join as join8 } from "path";
|
|
31739
32399
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31740
32400
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31741
32401
|
var KEY_MATERIAL_BYTES = 32;
|
|
31742
32402
|
function keyFilePath(dataDir2) {
|
|
31743
|
-
return
|
|
32403
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31744
32404
|
}
|
|
31745
32405
|
function parseKeyFile(raw) {
|
|
31746
32406
|
const parsed2 = JSON.parse(raw);
|
|
@@ -31778,7 +32438,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
31778
32438
|
}
|
|
31779
32439
|
};
|
|
31780
32440
|
function storedKeyVersionFloor(dataDir2) {
|
|
31781
|
-
const file2 =
|
|
32441
|
+
const file2 = join8(dataDir2, DB_FILENAME);
|
|
31782
32442
|
if (!existsSync3(file2)) return 0;
|
|
31783
32443
|
let db;
|
|
31784
32444
|
try {
|
|
@@ -31833,7 +32493,7 @@ function occupantMessage(file2, kind) {
|
|
|
31833
32493
|
function readFingerprintKey(dataDir2) {
|
|
31834
32494
|
let raw;
|
|
31835
32495
|
try {
|
|
31836
|
-
raw =
|
|
32496
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31837
32497
|
} catch (err) {
|
|
31838
32498
|
if (err.code === "ENOENT") return null;
|
|
31839
32499
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31857,146 +32517,12 @@ function fingerprintValue(key, raw) {
|
|
|
31857
32517
|
|
|
31858
32518
|
// ../../packages/persistence/src/history-preview.ts
|
|
31859
32519
|
import { existsSync as existsSync4 } from "fs";
|
|
31860
|
-
import { join as
|
|
32520
|
+
import { join as join9 } from "path";
|
|
31861
32521
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31862
32522
|
|
|
31863
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31864
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31865
|
-
import { mkdir } from "fs/promises";
|
|
31866
|
-
import { homedir } from "os";
|
|
31867
|
-
import { join as join7 } from "path";
|
|
31868
|
-
function defaultDataDir() {
|
|
31869
|
-
return join7(homedir(), ".aka");
|
|
31870
|
-
}
|
|
31871
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31872
|
-
return join7(base, "settings");
|
|
31873
|
-
}
|
|
31874
|
-
function dataDir(base = defaultDataDir()) {
|
|
31875
|
-
return join7(base, "data");
|
|
31876
|
-
}
|
|
31877
|
-
function dbPath(base = defaultDataDir()) {
|
|
31878
|
-
return join7(dataDir(base), "aka.db");
|
|
31879
|
-
}
|
|
31880
|
-
function keysDir(base = defaultDataDir()) {
|
|
31881
|
-
return join7(base, "keys");
|
|
31882
|
-
}
|
|
31883
|
-
async function ensureDataDir(dir = defaultDataDir()) {
|
|
31884
|
-
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
31885
|
-
tightenDir(dir);
|
|
31886
|
-
}
|
|
31887
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31888
|
-
ensureDataDirSync(dir);
|
|
31889
|
-
}
|
|
31890
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31891
|
-
const moves = [
|
|
31892
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31893
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31894
|
-
];
|
|
31895
|
-
for (const { name, dest } of moves) {
|
|
31896
|
-
try {
|
|
31897
|
-
ensureDataDirSync(dest);
|
|
31898
|
-
const moved = join7(dest, name);
|
|
31899
|
-
renameSync3(join7(base, name), moved);
|
|
31900
|
-
tightenFile(moved);
|
|
31901
|
-
} catch {
|
|
31902
|
-
}
|
|
31903
|
-
}
|
|
31904
|
-
}
|
|
31905
|
-
|
|
31906
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
31907
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
31908
|
-
import { posix, win32 } from "path";
|
|
31909
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
31910
|
-
if (platform2 === "darwin") {
|
|
31911
|
-
return [
|
|
31912
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31913
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31914
|
-
];
|
|
31915
|
-
}
|
|
31916
|
-
if (platform2 === "win32") {
|
|
31917
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31918
|
-
}
|
|
31919
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31920
|
-
}
|
|
31921
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
31922
|
-
for (const path of paths) {
|
|
31923
|
-
let text;
|
|
31924
|
-
try {
|
|
31925
|
-
text = readFileSync4(path, "utf8");
|
|
31926
|
-
} catch {
|
|
31927
|
-
continue;
|
|
31928
|
-
}
|
|
31929
|
-
const record2 = parseJsonObject(text);
|
|
31930
|
-
if (!record2) continue;
|
|
31931
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
31932
|
-
if (parsed2.success) return parsed2.data;
|
|
31933
|
-
}
|
|
31934
|
-
return null;
|
|
31935
|
-
}
|
|
31936
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31937
|
-
if (!managed) return settings;
|
|
31938
|
-
const { values } = managed;
|
|
31939
|
-
const merged = { ...settings };
|
|
31940
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31941
|
-
if (values.controlPlane !== void 0) {
|
|
31942
|
-
merged.controlPlane = {
|
|
31943
|
-
...values.controlPlane,
|
|
31944
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31945
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31946
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31947
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31948
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31949
|
-
};
|
|
31950
|
-
}
|
|
31951
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31952
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31953
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31954
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31955
|
-
if (values.vaultConsent !== void 0) {
|
|
31956
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31957
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31958
|
-
// at the current version otherwise.
|
|
31959
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31960
|
-
) : void 0;
|
|
31961
|
-
}
|
|
31962
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31963
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31964
|
-
acknowledgedAt: now().toISOString(),
|
|
31965
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31966
|
-
} : void 0;
|
|
31967
|
-
}
|
|
31968
|
-
return merged;
|
|
31969
|
-
}
|
|
31970
|
-
|
|
31971
|
-
// ../../packages/persistence/src/settings.ts
|
|
31972
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31973
|
-
import { join as join8 } from "path";
|
|
31974
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31975
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31976
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31977
|
-
}
|
|
31978
|
-
function readUserSettings(base) {
|
|
31979
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31980
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31981
|
-
try {
|
|
31982
|
-
return WorkspaceSettings.parse(record2);
|
|
31983
|
-
} catch {
|
|
31984
|
-
return defaultWorkspaceSettings();
|
|
31985
|
-
}
|
|
31986
|
-
}
|
|
31987
|
-
function readJson(file2) {
|
|
31988
|
-
let text;
|
|
31989
|
-
try {
|
|
31990
|
-
text = readFileSync5(file2, "utf8");
|
|
31991
|
-
} catch {
|
|
31992
|
-
return null;
|
|
31993
|
-
}
|
|
31994
|
-
return parseJsonObject(text) ?? null;
|
|
31995
|
-
}
|
|
31996
|
-
|
|
31997
32523
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31998
32524
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31999
|
-
import { dirname as
|
|
32525
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
32000
32526
|
var STORE_DB = "the store database (including the prompt corpus)";
|
|
32001
32527
|
var STORE_SETTINGS = "your settings file";
|
|
32002
32528
|
function storeContents(home) {
|
|
@@ -32005,7 +32531,7 @@ function storeContents(home) {
|
|
|
32005
32531
|
[settingsDir(home), STORE_SETTINGS],
|
|
32006
32532
|
[dataDir(home), STORE_DB],
|
|
32007
32533
|
[keysDir(home), "the vault key"],
|
|
32008
|
-
[
|
|
32534
|
+
[join10(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
32009
32535
|
[dbPath(home), STORE_DB]
|
|
32010
32536
|
]);
|
|
32011
32537
|
}
|
|
@@ -32033,7 +32559,7 @@ function linkTarget(path) {
|
|
|
32033
32559
|
try {
|
|
32034
32560
|
return realpathSync(path);
|
|
32035
32561
|
} catch {
|
|
32036
|
-
return resolve(
|
|
32562
|
+
return resolve(dirname3(path), readlinkSync(path));
|
|
32037
32563
|
}
|
|
32038
32564
|
}
|
|
32039
32565
|
function targetMode(path, platform2) {
|
|
@@ -32156,8 +32682,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
32156
32682
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32157
32683
|
import { execFileSync } from "child_process";
|
|
32158
32684
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32159
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32160
|
-
import { join as
|
|
32685
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32686
|
+
import { join as join11 } from "path";
|
|
32161
32687
|
var VAULT_OCCUPANT_REASON = {
|
|
32162
32688
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
32163
32689
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -32256,7 +32782,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32256
32782
|
throw asError(err);
|
|
32257
32783
|
}
|
|
32258
32784
|
try {
|
|
32259
|
-
writeFileSync3(
|
|
32785
|
+
writeFileSync3(join11(lock, LOCK_OWNER_FILE), `${owner}
|
|
32260
32786
|
`, { mode: DATA_FILE_MODE });
|
|
32261
32787
|
return true;
|
|
32262
32788
|
} catch (err) {
|
|
@@ -32265,7 +32791,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32265
32791
|
}
|
|
32266
32792
|
}
|
|
32267
32793
|
function acquireRotationLock(keysDir2) {
|
|
32268
|
-
const lock =
|
|
32794
|
+
const lock = join11(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
32269
32795
|
const owner = randomBytes2(16).toString("hex");
|
|
32270
32796
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
32271
32797
|
let held;
|
|
@@ -32292,7 +32818,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
32292
32818
|
}
|
|
32293
32819
|
function releaseRotationLock(lease) {
|
|
32294
32820
|
try {
|
|
32295
|
-
if (
|
|
32821
|
+
if (readFileSync7(join11(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
32296
32822
|
} catch {
|
|
32297
32823
|
return;
|
|
32298
32824
|
}
|
|
@@ -32313,7 +32839,7 @@ var FileKeyProvider = class {
|
|
|
32313
32839
|
this.#keysDir = keysDir2;
|
|
32314
32840
|
}
|
|
32315
32841
|
get filePath() {
|
|
32316
|
-
return
|
|
32842
|
+
return join11(this.#keysDir, VAULT_KEY_FILENAME);
|
|
32317
32843
|
}
|
|
32318
32844
|
loadOrCreate() {
|
|
32319
32845
|
return asAsync(() => {
|
|
@@ -32343,7 +32869,7 @@ var FileKeyProvider = class {
|
|
|
32343
32869
|
#read() {
|
|
32344
32870
|
let raw;
|
|
32345
32871
|
try {
|
|
32346
|
-
raw =
|
|
32872
|
+
raw = readFileSync7(this.filePath, "utf8");
|
|
32347
32873
|
} catch (err) {
|
|
32348
32874
|
if (err.code === "ENOENT") return null;
|
|
32349
32875
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -32630,7 +33156,14 @@ var SecretVault = class {
|
|
|
32630
33156
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
32631
33157
|
const now = this.#now();
|
|
32632
33158
|
if (existing) {
|
|
32633
|
-
this.#repo.upsert(
|
|
33159
|
+
this.#repo.upsert(
|
|
33160
|
+
{
|
|
33161
|
+
...existing,
|
|
33162
|
+
provider: existing.provider ?? void 0,
|
|
33163
|
+
userAuthorized: meta4.userAuthorized === true
|
|
33164
|
+
},
|
|
33165
|
+
now
|
|
33166
|
+
);
|
|
32634
33167
|
return await this.#emitToken(existing.keyVersion, existing.pointerId, existing.category);
|
|
32635
33168
|
}
|
|
32636
33169
|
const { material, version: version2 } = await this.#keys.loadOrCreate();
|
|
@@ -32652,6 +33185,7 @@ var SecretVault = class {
|
|
|
32652
33185
|
ruleId: meta4.ruleId,
|
|
32653
33186
|
maskedMatch: meta4.maskedMatch,
|
|
32654
33187
|
provider: meta4.provider,
|
|
33188
|
+
userAuthorized: meta4.userAuthorized === true,
|
|
32655
33189
|
ciphertext: sealed.ciphertext.toString("base64"),
|
|
32656
33190
|
nonce: sealed.nonce.toString("base64"),
|
|
32657
33191
|
authTag: sealed.authTag.toString("base64")
|
|
@@ -32971,11 +33505,11 @@ var SecretVault = class {
|
|
|
32971
33505
|
|
|
32972
33506
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32973
33507
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32974
|
-
import { join as
|
|
33508
|
+
import { join as join12 } from "path";
|
|
32975
33509
|
var MARKER = "warn-era-capped";
|
|
32976
33510
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32977
33511
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32978
|
-
const marker =
|
|
33512
|
+
const marker = join12(dataDir2, MARKER);
|
|
32979
33513
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
32980
33514
|
const capped = db.policies.capCategoryActions();
|
|
32981
33515
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -33035,7 +33569,7 @@ function resolveProvider() {
|
|
|
33035
33569
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
33036
33570
|
try {
|
|
33037
33571
|
ensureLayoutDirSync(base);
|
|
33038
|
-
const settingsFile =
|
|
33572
|
+
const settingsFile = join13(settingsDir(base), "settings.json");
|
|
33039
33573
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
33040
33574
|
} catch {
|
|
33041
33575
|
}
|
|
@@ -33059,9 +33593,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
33059
33593
|
}
|
|
33060
33594
|
|
|
33061
33595
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
33062
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
33596
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
33063
33597
|
import { homedir as homedir2 } from "os";
|
|
33064
|
-
import { basename as basename3, join as
|
|
33598
|
+
import { basename as basename3, join as join15 } from "path";
|
|
33065
33599
|
|
|
33066
33600
|
// ../../packages/detections/src/egress/registry.ts
|
|
33067
33601
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -36150,8 +36684,8 @@ function bundledDetections() {
|
|
|
36150
36684
|
}
|
|
36151
36685
|
|
|
36152
36686
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
36153
|
-
import { existsSync as existsSync8, readFileSync as
|
|
36154
|
-
import { basename as basename2, dirname as
|
|
36687
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
36688
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join14, sep as sep2 } from "path";
|
|
36155
36689
|
function resolveRepo(cwd) {
|
|
36156
36690
|
try {
|
|
36157
36691
|
const root = findGitRoot(cwd);
|
|
@@ -36166,36 +36700,36 @@ function resolveRepo(cwd) {
|
|
|
36166
36700
|
function findGitRoot(start) {
|
|
36167
36701
|
let dir = start;
|
|
36168
36702
|
for (; ; ) {
|
|
36169
|
-
if (existsSync8(
|
|
36170
|
-
const parent =
|
|
36703
|
+
if (existsSync8(join14(dir, ".git"))) return dir;
|
|
36704
|
+
const parent = dirname4(dir);
|
|
36171
36705
|
if (parent === dir) return void 0;
|
|
36172
36706
|
dir = parent;
|
|
36173
36707
|
}
|
|
36174
36708
|
}
|
|
36175
36709
|
function resolveGitContext(root) {
|
|
36176
|
-
const dotGit =
|
|
36710
|
+
const dotGit = join14(root, ".git");
|
|
36177
36711
|
try {
|
|
36178
36712
|
if (statSync6(dotGit).isDirectory()) {
|
|
36179
|
-
return { configPath:
|
|
36713
|
+
return { configPath: join14(dotGit, "config"), headRoot: root };
|
|
36180
36714
|
}
|
|
36181
36715
|
} catch {
|
|
36182
36716
|
return void 0;
|
|
36183
36717
|
}
|
|
36184
36718
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
36185
36719
|
if (!target) return void 0;
|
|
36186
|
-
const gitdir = isAbsolute(target) ? target :
|
|
36187
|
-
if (existsSync8(
|
|
36188
|
-
return { configPath:
|
|
36720
|
+
const gitdir = isAbsolute(target) ? target : join14(root, target);
|
|
36721
|
+
if (existsSync8(join14(gitdir, "config"))) {
|
|
36722
|
+
return { configPath: join14(gitdir, "config"), headRoot: root };
|
|
36189
36723
|
}
|
|
36190
|
-
const commonRaw = safeRead(
|
|
36724
|
+
const commonRaw = safeRead(join14(gitdir, "commondir"))?.trim();
|
|
36191
36725
|
if (!commonRaw) return void 0;
|
|
36192
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
36193
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
36194
|
-
return { configPath:
|
|
36726
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join14(gitdir, commonRaw);
|
|
36727
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
|
|
36728
|
+
return { configPath: join14(commonGitDir, "config"), headRoot };
|
|
36195
36729
|
}
|
|
36196
36730
|
function safeRead(path) {
|
|
36197
36731
|
try {
|
|
36198
|
-
return
|
|
36732
|
+
return readFileSync8(path, "utf8");
|
|
36199
36733
|
} catch {
|
|
36200
36734
|
return void 0;
|
|
36201
36735
|
}
|
|
@@ -36740,8 +37274,8 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
36740
37274
|
|
|
36741
37275
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
36742
37276
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
36743
|
-
import { readFileSync as
|
|
36744
|
-
import { join as
|
|
37277
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
37278
|
+
import { join as join16 } from "path";
|
|
36745
37279
|
|
|
36746
37280
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
36747
37281
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -36752,24 +37286,115 @@ import {
|
|
|
36752
37286
|
fstatSync,
|
|
36753
37287
|
mkdirSync as mkdirSync2,
|
|
36754
37288
|
openSync as openSync2,
|
|
36755
|
-
readFileSync as
|
|
37289
|
+
readFileSync as readFileSync11,
|
|
36756
37290
|
readSync,
|
|
36757
37291
|
writeFileSync as writeFileSync5
|
|
36758
37292
|
} from "fs";
|
|
36759
|
-
import { join as
|
|
37293
|
+
import { join as join17 } from "path";
|
|
37294
|
+
var DATE_SUFFIX = /-\d{8}$/;
|
|
37295
|
+
function normalizeModelId(model) {
|
|
37296
|
+
return model.trim().toLowerCase().replace(DATE_SUFFIX, "");
|
|
37297
|
+
}
|
|
37298
|
+
var SPAWN_INHERIT_WORDS = /* @__PURE__ */ new Set(["inherit", "default"]);
|
|
37299
|
+
function isBoundary(char) {
|
|
37300
|
+
return char === "" || /[^a-z0-9]/.test(char);
|
|
37301
|
+
}
|
|
37302
|
+
function matchProhibitedSpawnModel(requested, prohibited) {
|
|
37303
|
+
if (requested === void 0 || requested === "") return void 0;
|
|
37304
|
+
if (prohibited === void 0 || prohibited.length === 0) return void 0;
|
|
37305
|
+
const needle = normalizeModelId(requested);
|
|
37306
|
+
if (needle === "" || SPAWN_INHERIT_WORDS.has(needle)) return void 0;
|
|
37307
|
+
const exact = prohibited.find((p) => normalizeModelId(p) === needle);
|
|
37308
|
+
if (exact !== void 0) return exact;
|
|
37309
|
+
if (!needle.includes("-")) {
|
|
37310
|
+
return prohibited.find((p) => normalizeModelId(p).split("-").includes(needle));
|
|
37311
|
+
}
|
|
37312
|
+
return prohibited.find((p) => {
|
|
37313
|
+
const base = normalizeModelId(p);
|
|
37314
|
+
if (base === "") return false;
|
|
37315
|
+
let at = needle.indexOf(base);
|
|
37316
|
+
while (at !== -1) {
|
|
37317
|
+
if (isBoundary(at === 0 ? "" : needle.charAt(at - 1)) && isBoundary(needle.charAt(at + base.length))) {
|
|
37318
|
+
return true;
|
|
37319
|
+
}
|
|
37320
|
+
at = needle.indexOf(base, at + 1);
|
|
37321
|
+
}
|
|
37322
|
+
return false;
|
|
37323
|
+
});
|
|
37324
|
+
}
|
|
36760
37325
|
var TAIL_BYTES = 256 * 1024;
|
|
37326
|
+
function prohibitedModelMessage(model, action) {
|
|
37327
|
+
const subject = action === "switch" ? `Cannot switch to ${model}` : action === "spawn" ? `Cannot start a subagent on ${model}` : `This session is running on ${model}, which cannot be used`;
|
|
37328
|
+
const remedy = action === "spawn" ? "Name an approved model on the subagent" : "Switch to an approved model with /model";
|
|
37329
|
+
return `${subject} \u2014 your organization has prohibited this model. ${remedy}, or ask an administrator to change its status in AKA under Govern \u2192 LLM Providers.`;
|
|
37330
|
+
}
|
|
37331
|
+
function buildModelRefusalEvent(input2) {
|
|
37332
|
+
return {
|
|
37333
|
+
id: input2.id,
|
|
37334
|
+
eventType: "model_refusal",
|
|
37335
|
+
startedAt: input2.occurredAt,
|
|
37336
|
+
// Omitted rather than nulled when unknown: `root_session_id` is a self-FK,
|
|
37337
|
+
// and a session id naming no row would fail the insert outright.
|
|
37338
|
+
...input2.sessionId === void 0 || input2.sessionId === "" ? {} : { rootSessionId: input2.sessionId },
|
|
37339
|
+
attributes: {
|
|
37340
|
+
// `model` is a generated column on audit_events, so the refused model is
|
|
37341
|
+
// queryable without unpacking the bag — which is what lets the control
|
|
37342
|
+
// plane group refusals by the same id the prohibition was keyed on.
|
|
37343
|
+
model: input2.model,
|
|
37344
|
+
refusal_seam: input2.seam,
|
|
37345
|
+
source_tool: input2.sourceTool,
|
|
37346
|
+
// Omitted rather than duplicated when the caller named the id itself.
|
|
37347
|
+
...input2.requestedModel === void 0 || input2.requestedModel === input2.model ? {} : { requested_model: input2.requestedModel }
|
|
37348
|
+
}
|
|
37349
|
+
};
|
|
37350
|
+
}
|
|
36761
37351
|
|
|
36762
37352
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
36763
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
36764
|
-
import { join as
|
|
37353
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
37354
|
+
import { join as join18 } from "path";
|
|
36765
37355
|
|
|
36766
37356
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
36767
37357
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
36768
|
-
import { basename as basename4, dirname as
|
|
37358
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
37359
|
+
|
|
37360
|
+
// ../../packages/plugin-sdk/src/policy-resolver.ts
|
|
37361
|
+
function createPolicyResolver(bundle) {
|
|
37362
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
37363
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
37364
|
+
let reversible = /* @__PURE__ */ new Set();
|
|
37365
|
+
try {
|
|
37366
|
+
for (const policy of bundle.policies) {
|
|
37367
|
+
if (!policy.enabled) continue;
|
|
37368
|
+
if ("ruleId" in policy.target) {
|
|
37369
|
+
if (!byRule.has(policy.target.ruleId)) byRule.set(policy.target.ruleId, policy.action);
|
|
37370
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
37371
|
+
byCategory.set(policy.target.category, policy.action);
|
|
37372
|
+
}
|
|
37373
|
+
}
|
|
37374
|
+
reversible = new Set(bundle.reversibleRuleIds ?? []);
|
|
37375
|
+
} catch {
|
|
37376
|
+
byRule.clear();
|
|
37377
|
+
byCategory.clear();
|
|
37378
|
+
reversible = /* @__PURE__ */ new Set();
|
|
37379
|
+
}
|
|
37380
|
+
return {
|
|
37381
|
+
actionFor(ruleId, category) {
|
|
37382
|
+
const byRuleAction = byRule.get(ruleId);
|
|
37383
|
+
if (byRuleAction !== void 0) return byRuleAction;
|
|
37384
|
+
const byCategoryAction = byCategory.get(category);
|
|
37385
|
+
if (byCategoryAction !== void 0) return byCategoryAction;
|
|
37386
|
+
const fallback = DEFAULT_ACTIONS[category];
|
|
37387
|
+
return fallback ?? "log";
|
|
37388
|
+
},
|
|
37389
|
+
isReversible(ruleId) {
|
|
37390
|
+
return reversible.has(ruleId);
|
|
37391
|
+
}
|
|
37392
|
+
};
|
|
37393
|
+
}
|
|
36769
37394
|
|
|
36770
37395
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
36771
37396
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
36772
|
-
import { basename as basename5, join as
|
|
37397
|
+
import { basename as basename5, join as join19 } from "path";
|
|
36773
37398
|
|
|
36774
37399
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
36775
37400
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -36800,7 +37425,6 @@ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
|
36800
37425
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
36801
37426
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
36802
37427
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
36803
|
-
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
36804
37428
|
function startTiming() {
|
|
36805
37429
|
try {
|
|
36806
37430
|
return performance.now();
|
|
@@ -36837,28 +37461,22 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
36837
37461
|
bundlesPacked = true;
|
|
36838
37462
|
}
|
|
36839
37463
|
const policyMode = settings.policy;
|
|
37464
|
+
const redactFallback = settings.redactFallback;
|
|
36840
37465
|
const dataDir2 = opts?.dataDir;
|
|
36841
|
-
let policies = [];
|
|
36842
37466
|
let rules = [];
|
|
36843
37467
|
let scanner;
|
|
36844
37468
|
let bundleExceptions = [];
|
|
36845
37469
|
let initialized = false;
|
|
36846
|
-
|
|
36847
|
-
|
|
36848
|
-
|
|
37470
|
+
let resolver = createPolicyResolver({
|
|
37471
|
+
version: "",
|
|
37472
|
+
policies: [],
|
|
37473
|
+
customKeywords: [],
|
|
37474
|
+
fetchedAt: ""
|
|
37475
|
+
});
|
|
36849
37476
|
async function ensureInitialized() {
|
|
36850
37477
|
if (initialized) return;
|
|
36851
37478
|
const bundle = await gateway.getPolicyBundle();
|
|
36852
|
-
|
|
36853
|
-
for (const p of policies) {
|
|
36854
|
-
if (!p.enabled) continue;
|
|
36855
|
-
if ("ruleId" in p.target) {
|
|
36856
|
-
if (!ruleActionIndex.has(p.target.ruleId)) ruleActionIndex.set(p.target.ruleId, p.action);
|
|
36857
|
-
} else if (!categoryActionIndex.has(p.target.category)) {
|
|
36858
|
-
categoryActionIndex.set(p.target.category, p.action);
|
|
36859
|
-
}
|
|
36860
|
-
}
|
|
36861
|
-
reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
|
|
37479
|
+
resolver = createPolicyResolver(bundle);
|
|
36862
37480
|
const bundledProbeKeys = new Set(
|
|
36863
37481
|
getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
|
|
36864
37482
|
);
|
|
@@ -36911,33 +37529,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
36911
37529
|
return cachedKey;
|
|
36912
37530
|
}
|
|
36913
37531
|
function resolveAction(ruleId, category) {
|
|
36914
|
-
|
|
36915
|
-
|
|
36916
|
-
|
|
36917
|
-
if (byCategory !== void 0) return byCategory;
|
|
36918
|
-
const fallback = DEFAULT_ACTIONS[category];
|
|
36919
|
-
return fallback ?? "log";
|
|
36920
|
-
}
|
|
36921
|
-
function actionForFinding(finding, excepted) {
|
|
37532
|
+
return resolver.actionFor(ruleId, category);
|
|
37533
|
+
}
|
|
37534
|
+
function actionForFinding(finding, excepted, rewritable = true) {
|
|
36922
37535
|
if (excepted?.has(finding)) return "allow";
|
|
36923
37536
|
const action = resolveAction(finding.ruleId, finding.category);
|
|
37537
|
+
if (!rewritable && action === "redact") return builtinPolicyToAction(redactFallback);
|
|
36924
37538
|
if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
|
|
36925
37539
|
return "warn";
|
|
36926
37540
|
}
|
|
36927
37541
|
return action;
|
|
36928
37542
|
}
|
|
36929
|
-
function decide(findings, text, excepted) {
|
|
37543
|
+
function decide(findings, text, excepted, rewritable = true) {
|
|
36930
37544
|
if (findings.length === 0) return { action: "log", text, findings: [] };
|
|
36931
|
-
const actionFor = (finding) => actionForFinding(finding, excepted);
|
|
37545
|
+
const actionFor = (finding) => actionForFinding(finding, excepted, rewritable);
|
|
36932
37546
|
let worst = "log";
|
|
36933
37547
|
for (const finding of findings) {
|
|
36934
|
-
|
|
36935
|
-
if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
|
|
37548
|
+
worst = strongerAction(worst, actionFor(finding));
|
|
36936
37549
|
}
|
|
36937
37550
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
36938
37551
|
if (worst === "redact") {
|
|
36939
37552
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
36940
|
-
const reversibleFindings = redactFindings.filter((f) =>
|
|
37553
|
+
const reversibleFindings = redactFindings.filter((f) => resolver.isReversible(f.ruleId));
|
|
36941
37554
|
return {
|
|
36942
37555
|
action: "redact",
|
|
36943
37556
|
text: redact(text, redactFindings),
|
|
@@ -37013,7 +37626,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37013
37626
|
return { excepted: /* @__PURE__ */ new Set(), exceptionIds: [] };
|
|
37014
37627
|
}
|
|
37015
37628
|
}
|
|
37016
|
-
async function recordBlockedDetections(decision, excepted, ctx, fpCache) {
|
|
37629
|
+
async function recordBlockedDetections(decision, excepted, ctx, fpCache, rewritable = true) {
|
|
37017
37630
|
const references = [];
|
|
37018
37631
|
try {
|
|
37019
37632
|
if (decision.action !== "block" && decision.action !== "redact") return references;
|
|
@@ -37021,7 +37634,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37021
37634
|
if (!key) return references;
|
|
37022
37635
|
const seen = /* @__PURE__ */ new Set();
|
|
37023
37636
|
for (const finding of decision.findings) {
|
|
37024
|
-
const action = actionForFinding(finding, excepted);
|
|
37637
|
+
const action = actionForFinding(finding, excepted, rewritable);
|
|
37025
37638
|
if (action !== "block" && action !== "redact") continue;
|
|
37026
37639
|
const fp = fingerprintOf(key, finding, fpCache);
|
|
37027
37640
|
const pair = `${finding.ruleId}:${fp}`;
|
|
@@ -37048,7 +37661,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37048
37661
|
}
|
|
37049
37662
|
return references;
|
|
37050
37663
|
}
|
|
37051
|
-
async function evaluate(text, context, ctx) {
|
|
37664
|
+
async function evaluate(text, context, ctx, rewritable = true) {
|
|
37052
37665
|
try {
|
|
37053
37666
|
await ensureInitialized();
|
|
37054
37667
|
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
@@ -37057,8 +37670,14 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37057
37670
|
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
37058
37671
|
const fpCache = /* @__PURE__ */ new Map();
|
|
37059
37672
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
37060
|
-
const decision = decide(findings, text, excepted);
|
|
37061
|
-
const blockedReferences = await recordBlockedDetections(
|
|
37673
|
+
const decision = decide(findings, text, excepted, rewritable);
|
|
37674
|
+
const blockedReferences = await recordBlockedDetections(
|
|
37675
|
+
decision,
|
|
37676
|
+
excepted,
|
|
37677
|
+
ctx,
|
|
37678
|
+
fpCache,
|
|
37679
|
+
rewritable
|
|
37680
|
+
);
|
|
37062
37681
|
if (blockedReferences.length > 0) decision.blockedReferences = blockedReferences;
|
|
37063
37682
|
return { decision, excepted, exceptionIds };
|
|
37064
37683
|
} catch {
|
|
@@ -37082,12 +37701,16 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37082
37701
|
sourceTool: input2.sourceTool,
|
|
37083
37702
|
metadata: input2.metadata,
|
|
37084
37703
|
preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
|
|
37085
|
-
}
|
|
37704
|
+
},
|
|
37705
|
+
opts2.rewritable
|
|
37086
37706
|
);
|
|
37087
37707
|
if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
|
|
37088
37708
|
try {
|
|
37089
37709
|
const contentHash = contentHashOf(input2.text);
|
|
37090
|
-
const
|
|
37710
|
+
const maskedFindings = decision.findings.filter(
|
|
37711
|
+
(match) => isActionAtLeast(actionForFinding(match, excepted, opts2.rewritable), "redact")
|
|
37712
|
+
);
|
|
37713
|
+
const storedContent = maskedFindings.length > 0 ? redact(input2.text, maskedFindings) : input2.text;
|
|
37091
37714
|
const inspectionMs = elapsedMs(timingStartedAt);
|
|
37092
37715
|
const metadata = exceptionIds.length > 0 || inspectionMs !== void 0 ? {
|
|
37093
37716
|
...input2.metadata,
|
|
@@ -37124,7 +37747,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37124
37747
|
severity: match.severity,
|
|
37125
37748
|
span: match.span,
|
|
37126
37749
|
maskedMatch,
|
|
37127
|
-
actionTaken: actionForFinding(match, excepted),
|
|
37750
|
+
actionTaken: actionForFinding(match, excepted, opts2.rewritable),
|
|
37128
37751
|
confidence: match.confidence,
|
|
37129
37752
|
...findingKey ? { findingKey } : {}
|
|
37130
37753
|
};
|
|
@@ -37164,7 +37787,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
37164
37787
|
|
|
37165
37788
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
37166
37789
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
37167
|
-
import { join as
|
|
37790
|
+
import { join as join20 } from "path";
|
|
37168
37791
|
|
|
37169
37792
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
37170
37793
|
function redactedPlaceholder(category) {
|
|
@@ -37226,14 +37849,26 @@ var SecretVaultGlue = class {
|
|
|
37226
37849
|
}
|
|
37227
37850
|
async tokenizeText(text, opts) {
|
|
37228
37851
|
try {
|
|
37229
|
-
const
|
|
37230
|
-
const
|
|
37231
|
-
const
|
|
37232
|
-
if (
|
|
37233
|
-
|
|
37852
|
+
const supplied = opts?.findings;
|
|
37853
|
+
const resolver = opts?.resolver;
|
|
37854
|
+
const scanned = supplied ?? this.#selfScan(text);
|
|
37855
|
+
if (scanned === null) {
|
|
37856
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
37857
|
+
}
|
|
37858
|
+
const findings = supplied === void 0 && resolver !== void 0 ? scanned.filter(
|
|
37859
|
+
(f) => isActionAtLeast(resolver.actionFor(f.ruleId, f.category), "redact")
|
|
37860
|
+
) : scanned;
|
|
37861
|
+
let reversible = opts?.reversible;
|
|
37862
|
+
if (resolver !== void 0 && reversible === void 0) {
|
|
37863
|
+
reversible = new Set(findings.filter((f) => resolver.isReversible(f.ruleId)));
|
|
37864
|
+
}
|
|
37865
|
+
const reversibleSet = reversible;
|
|
37866
|
+
const keeps = (finding) => reversibleSet === void 0 || reversibleSet.has(finding);
|
|
37867
|
+
if (findings.length === 0) return { text, pointers: [], degraded: [], redacted: [] };
|
|
37234
37868
|
const groups = groupSpans(text, findings);
|
|
37235
37869
|
const pointers = [];
|
|
37236
37870
|
const degraded = [];
|
|
37871
|
+
const redacted = [];
|
|
37237
37872
|
let out = text;
|
|
37238
37873
|
for (const group of [...groups].reverse()) {
|
|
37239
37874
|
const original = text.slice(group.start, group.end);
|
|
@@ -37247,6 +37882,7 @@ var SecretVaultGlue = class {
|
|
|
37247
37882
|
degraded.unshift({ category: group.category });
|
|
37248
37883
|
} else if (!keeps(finding)) {
|
|
37249
37884
|
replacement = redactedPlaceholder(finding.category);
|
|
37885
|
+
redacted.unshift({ category: finding.category });
|
|
37250
37886
|
} else {
|
|
37251
37887
|
replacement = await this.tokenizeValue(finding.rawMatch, {
|
|
37252
37888
|
ruleId: finding.ruleId,
|
|
@@ -37267,9 +37903,9 @@ var SecretVaultGlue = class {
|
|
|
37267
37903
|
}
|
|
37268
37904
|
}
|
|
37269
37905
|
}
|
|
37270
|
-
return { text: out, pointers, degraded };
|
|
37906
|
+
return { text: out, pointers, degraded, redacted };
|
|
37271
37907
|
} catch {
|
|
37272
|
-
return { text: "[REDACTED]", pointers: [], degraded: [] };
|
|
37908
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
37273
37909
|
}
|
|
37274
37910
|
}
|
|
37275
37911
|
async detokenizeText(text, opts) {
|
|
@@ -37471,17 +38107,17 @@ var UNOPENABLE_VAULT = {
|
|
|
37471
38107
|
|
|
37472
38108
|
// src/protocol/marker.ts
|
|
37473
38109
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
37474
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
37475
|
-
import { join as
|
|
38110
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
38111
|
+
import { join as join21 } from "path";
|
|
37476
38112
|
var MARKER_FILE = "protocol-marker";
|
|
37477
38113
|
function mintMarker() {
|
|
37478
38114
|
return randomBytes4(8).toString("hex");
|
|
37479
38115
|
}
|
|
37480
38116
|
function sessionProtocolMarker(dataDir2, sessionId) {
|
|
37481
38117
|
if (!sessionId) return mintMarker();
|
|
37482
|
-
const path =
|
|
38118
|
+
const path = join21(dataDir2, MARKER_FILE);
|
|
37483
38119
|
try {
|
|
37484
|
-
const stored = JSON.parse(
|
|
38120
|
+
const stored = JSON.parse(readFileSync13(path, "utf8"));
|
|
37485
38121
|
if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
|
|
37486
38122
|
return stored.marker;
|
|
37487
38123
|
}
|
|
@@ -37490,7 +38126,7 @@ function sessionProtocolMarker(dataDir2, sessionId) {
|
|
|
37490
38126
|
const marker = mintMarker();
|
|
37491
38127
|
try {
|
|
37492
38128
|
mkdirSync5(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
37493
|
-
const tmp =
|
|
38129
|
+
const tmp = join21(dataDir2, `${MARKER_FILE}.tmp`);
|
|
37494
38130
|
writeFileSync8(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
|
|
37495
38131
|
renameSync5(tmp, path);
|
|
37496
38132
|
} catch {
|
|
@@ -37543,6 +38179,117 @@ function userDisclosure(opts) {
|
|
|
37543
38179
|
return sentences.join(" ");
|
|
37544
38180
|
}
|
|
37545
38181
|
|
|
38182
|
+
// src/hooks/model-guard.ts
|
|
38183
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
38184
|
+
import { readFileSync as readFileSync14, statSync as statSync9 } from "fs";
|
|
38185
|
+
import { homedir as homedir3 } from "os";
|
|
38186
|
+
import { dirname as dirname6, join as join22 } from "path";
|
|
38187
|
+
var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "Agent"]);
|
|
38188
|
+
var SAFE_SUBAGENT_TYPE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
38189
|
+
function projectAgentsRoot(from) {
|
|
38190
|
+
if (from === void 0 || from === "") return void 0;
|
|
38191
|
+
const home = homedir3();
|
|
38192
|
+
let dir = from;
|
|
38193
|
+
for (let depth = 0; depth < 24; depth += 1) {
|
|
38194
|
+
if (dir === home) return void 0;
|
|
38195
|
+
try {
|
|
38196
|
+
if (statSync9(join22(dir, ".claude", "agents")).isDirectory()) return dir;
|
|
38197
|
+
} catch {
|
|
38198
|
+
}
|
|
38199
|
+
const parent = dirname6(dir);
|
|
38200
|
+
if (parent === dir) return void 0;
|
|
38201
|
+
dir = parent;
|
|
38202
|
+
}
|
|
38203
|
+
return void 0;
|
|
38204
|
+
}
|
|
38205
|
+
function modelFromAgentDefinition(subagentType, cwd) {
|
|
38206
|
+
if (subagentType === void 0 || !SAFE_SUBAGENT_TYPE.test(subagentType)) return void 0;
|
|
38207
|
+
const roots = [projectAgentsRoot(cwd), homedir3()].filter(
|
|
38208
|
+
(r) => r !== void 0 && r !== ""
|
|
38209
|
+
);
|
|
38210
|
+
for (const root of roots) {
|
|
38211
|
+
try {
|
|
38212
|
+
const raw = readFileSync14(join22(root, ".claude", "agents", `${subagentType}.md`), "utf8");
|
|
38213
|
+
const lines = raw.split("\n");
|
|
38214
|
+
if (lines[0]?.trim() !== "---") continue;
|
|
38215
|
+
for (const line of lines.slice(1)) {
|
|
38216
|
+
if (line.trim() === "---") break;
|
|
38217
|
+
const sep4 = line.indexOf(":");
|
|
38218
|
+
if (sep4 === -1) continue;
|
|
38219
|
+
if (line.slice(0, sep4).trim() !== "model") continue;
|
|
38220
|
+
const value = line.slice(sep4 + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
38221
|
+
if (value !== "") return value;
|
|
38222
|
+
}
|
|
38223
|
+
} catch {
|
|
38224
|
+
}
|
|
38225
|
+
}
|
|
38226
|
+
return void 0;
|
|
38227
|
+
}
|
|
38228
|
+
function resolveSpawnModel(toolInput, cwd) {
|
|
38229
|
+
const explicit = toolInput.model;
|
|
38230
|
+
if (typeof explicit === "string" && explicit !== "") return explicit;
|
|
38231
|
+
const subagentType = toolInput.subagent_type;
|
|
38232
|
+
return modelFromAgentDefinition(typeof subagentType === "string" ? subagentType : void 0, cwd);
|
|
38233
|
+
}
|
|
38234
|
+
function decideSubagentSpawn(requested, prohibitedModels) {
|
|
38235
|
+
if (requested === void 0 || requested === "") return null;
|
|
38236
|
+
const matched = matchProhibitedSpawnModel(requested, prohibitedModels);
|
|
38237
|
+
if (matched === void 0) return null;
|
|
38238
|
+
return {
|
|
38239
|
+
matched,
|
|
38240
|
+
output: {
|
|
38241
|
+
hookSpecificOutput: {
|
|
38242
|
+
hookEventName: "PreToolUse",
|
|
38243
|
+
permissionDecision: "deny",
|
|
38244
|
+
permissionDecisionReason: prohibitedModelMessage(requested, "spawn")
|
|
38245
|
+
}
|
|
38246
|
+
}
|
|
38247
|
+
};
|
|
38248
|
+
}
|
|
38249
|
+
async function handleSubagentSpawn(openGateway, toolName, toolInput, sessionId, cwd, emit2) {
|
|
38250
|
+
if (!SUBAGENT_TOOLS.has(toolName)) return false;
|
|
38251
|
+
const gateway = openGateway();
|
|
38252
|
+
if (gateway === null) return false;
|
|
38253
|
+
let decision;
|
|
38254
|
+
let requested;
|
|
38255
|
+
try {
|
|
38256
|
+
const { prohibitedModels } = await gateway.getPolicyBundle();
|
|
38257
|
+
if (prohibitedModels === void 0 || prohibitedModels.length === 0) {
|
|
38258
|
+
await gateway.close();
|
|
38259
|
+
return false;
|
|
38260
|
+
}
|
|
38261
|
+
requested = resolveSpawnModel(toolInput, cwd);
|
|
38262
|
+
decision = decideSubagentSpawn(requested, prohibitedModels);
|
|
38263
|
+
} catch {
|
|
38264
|
+
await gateway.close();
|
|
38265
|
+
return false;
|
|
38266
|
+
}
|
|
38267
|
+
if (decision === null || requested === void 0) {
|
|
38268
|
+
await gateway.close();
|
|
38269
|
+
return false;
|
|
38270
|
+
}
|
|
38271
|
+
try {
|
|
38272
|
+
await gateway.recordAuditEvent(
|
|
38273
|
+
buildModelRefusalEvent({
|
|
38274
|
+
id: randomUUID15(),
|
|
38275
|
+
sessionId,
|
|
38276
|
+
model: decision.matched,
|
|
38277
|
+
requestedModel: requested,
|
|
38278
|
+
seam: "spawn",
|
|
38279
|
+
sourceTool: SOURCE_TOOL.ClaudeCode,
|
|
38280
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
38281
|
+
})
|
|
38282
|
+
);
|
|
38283
|
+
} catch {
|
|
38284
|
+
}
|
|
38285
|
+
await emit2(decision.output);
|
|
38286
|
+
try {
|
|
38287
|
+
await gateway.close();
|
|
38288
|
+
} catch {
|
|
38289
|
+
}
|
|
38290
|
+
return true;
|
|
38291
|
+
}
|
|
38292
|
+
|
|
37546
38293
|
// src/hooks/paths.ts
|
|
37547
38294
|
function stringAtPath(root, path) {
|
|
37548
38295
|
let current = root;
|
|
@@ -37774,7 +38521,13 @@ var STATIC_FIELDS = {
|
|
|
37774
38521
|
// machine, but it is still a channel a secret can ride into a context the
|
|
37775
38522
|
// user never sees. Scanned as data: the masked prompt is a coherent
|
|
37776
38523
|
// instruction, so redaction is the intended end state and block still blocks.
|
|
37777
|
-
|
|
38524
|
+
//
|
|
38525
|
+
// BOTH spellings. The harness renamed this tool — older builds send `Task`,
|
|
38526
|
+
// current ones send `Agent` — and a table naming only the old one scans
|
|
38527
|
+
// nothing at all on a current build, silently, because an unknown tool yields
|
|
38528
|
+
// no fields and the hook returns before any decision.
|
|
38529
|
+
Task: [{ path: ["prompt"], executable: false }],
|
|
38530
|
+
Agent: [{ path: ["prompt"], executable: false }]
|
|
37778
38531
|
};
|
|
37779
38532
|
var MCP_MAX_DEPTH = 6;
|
|
37780
38533
|
var MCP_MAX_LEAF_CHARS = 1e6;
|
|
@@ -37811,6 +38564,7 @@ function multiEditFields(toolInput) {
|
|
|
37811
38564
|
executable: false
|
|
37812
38565
|
}));
|
|
37813
38566
|
}
|
|
38567
|
+
var SCANNED_TOOL_NAMES = Object.keys(STATIC_FIELDS);
|
|
37814
38568
|
function scannableInputFields(toolName, toolInput) {
|
|
37815
38569
|
const candidates = toolName.startsWith("mcp__") ? mcpFields(toolInput) : toolName === "MultiEdit" ? multiEditFields(toolInput) : (
|
|
37816
38570
|
// hasOwn guard: a bare index would resolve Object.prototype members for
|
|
@@ -37886,8 +38640,8 @@ function baseMetadata(input2) {
|
|
|
37886
38640
|
}
|
|
37887
38641
|
|
|
37888
38642
|
// src/hooks/store-health.ts
|
|
37889
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
37890
|
-
import { dirname as
|
|
38643
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
|
|
38644
|
+
import { dirname as dirname7, join as join29 } from "path";
|
|
37891
38645
|
|
|
37892
38646
|
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
37893
38647
|
import { createHash as createHash5 } from "crypto";
|
|
@@ -37926,6 +38680,307 @@ function toEgressIngestRequest(input2) {
|
|
|
37926
38680
|
};
|
|
37927
38681
|
}
|
|
37928
38682
|
|
|
38683
|
+
// ../../packages/remote/src/http.ts
|
|
38684
|
+
import { request as httpRequest } from "http";
|
|
38685
|
+
import { request as httpsRequest } from "https";
|
|
38686
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
38687
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
38688
|
+
var RemoteRequestError = class extends Error {
|
|
38689
|
+
constructor(status) {
|
|
38690
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
38691
|
+
this.status = status;
|
|
38692
|
+
this.name = "RemoteRequestError";
|
|
38693
|
+
}
|
|
38694
|
+
status;
|
|
38695
|
+
};
|
|
38696
|
+
var RemoteRouteAbsent = class extends Error {
|
|
38697
|
+
constructor(route) {
|
|
38698
|
+
super(`control plane does not serve ${route}`);
|
|
38699
|
+
this.route = route;
|
|
38700
|
+
this.name = "RemoteRouteAbsent";
|
|
38701
|
+
}
|
|
38702
|
+
route;
|
|
38703
|
+
};
|
|
38704
|
+
var RemoteRequestInvalid = class extends Error {
|
|
38705
|
+
constructor(route, cause) {
|
|
38706
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
38707
|
+
this.cause = cause;
|
|
38708
|
+
this.name = "RemoteRequestInvalid";
|
|
38709
|
+
}
|
|
38710
|
+
cause;
|
|
38711
|
+
};
|
|
38712
|
+
var RemoteResponseInvalid = class extends Error {
|
|
38713
|
+
constructor(route, detail) {
|
|
38714
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
38715
|
+
this.name = "RemoteResponseInvalid";
|
|
38716
|
+
}
|
|
38717
|
+
};
|
|
38718
|
+
var RemoteTransportError = class extends Error {
|
|
38719
|
+
/**
|
|
38720
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
38721
|
+
* refused.
|
|
38722
|
+
*
|
|
38723
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
38724
|
+
* all. It exists because two paths reject after a status has already been
|
|
38725
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
38726
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
38727
|
+
* outage, which sends the reader to look at their network instead of their
|
|
38728
|
+
* credential.
|
|
38729
|
+
*/
|
|
38730
|
+
constructor(reason, status) {
|
|
38731
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
38732
|
+
this.status = status;
|
|
38733
|
+
this.name = "RemoteTransportError";
|
|
38734
|
+
}
|
|
38735
|
+
status;
|
|
38736
|
+
};
|
|
38737
|
+
async function send(options) {
|
|
38738
|
+
const url2 = new URL(options.url);
|
|
38739
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38740
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
38741
|
+
const requestOptions = {
|
|
38742
|
+
method: options.method,
|
|
38743
|
+
headers: {
|
|
38744
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
38745
|
+
// last they win, and two of the values below are ones no caller may
|
|
38746
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
38747
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
38748
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
38749
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
38750
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
38751
|
+
// conditional GET — is untouched by this order.
|
|
38752
|
+
...options.headers,
|
|
38753
|
+
// The credential. One header, matching what the deployment authenticates
|
|
38754
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
38755
|
+
// it can be logged by an intermediary for no gain.
|
|
38756
|
+
//
|
|
38757
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
38758
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
38759
|
+
// key differently from an absent one, and "the header is not there" is
|
|
38760
|
+
// the property the attach flow needs.
|
|
38761
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
38762
|
+
accept: "application/json",
|
|
38763
|
+
...options.body === void 0 ? {} : {
|
|
38764
|
+
"content-type": "application/json",
|
|
38765
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
38766
|
+
// character count is truncated by the receiver.
|
|
38767
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
38768
|
+
}
|
|
38769
|
+
}
|
|
38770
|
+
};
|
|
38771
|
+
return new Promise((resolve2, reject) => {
|
|
38772
|
+
let settled = false;
|
|
38773
|
+
const fail = (reason, status) => {
|
|
38774
|
+
if (settled) return;
|
|
38775
|
+
settled = true;
|
|
38776
|
+
reject(new RemoteTransportError(reason, status));
|
|
38777
|
+
};
|
|
38778
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
38779
|
+
const chunks = [];
|
|
38780
|
+
let size = 0;
|
|
38781
|
+
res.on("data", (chunk) => {
|
|
38782
|
+
size += chunk.length;
|
|
38783
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
38784
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
38785
|
+
res.destroy();
|
|
38786
|
+
req.destroy();
|
|
38787
|
+
return;
|
|
38788
|
+
}
|
|
38789
|
+
chunks.push(chunk);
|
|
38790
|
+
});
|
|
38791
|
+
res.on("aborted", () => {
|
|
38792
|
+
fail("the response was aborted", res.statusCode);
|
|
38793
|
+
});
|
|
38794
|
+
res.on("end", () => {
|
|
38795
|
+
if (settled) return;
|
|
38796
|
+
settled = true;
|
|
38797
|
+
resolve2({
|
|
38798
|
+
status: res.statusCode ?? 0,
|
|
38799
|
+
headers: res.headers,
|
|
38800
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
38801
|
+
});
|
|
38802
|
+
});
|
|
38803
|
+
});
|
|
38804
|
+
const deadline = setTimeout(() => {
|
|
38805
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
38806
|
+
req.destroy();
|
|
38807
|
+
}, timeoutMs);
|
|
38808
|
+
deadline.unref();
|
|
38809
|
+
req.on("upgrade", (_res, socket) => {
|
|
38810
|
+
fail("the deployment answered with a protocol upgrade");
|
|
38811
|
+
socket.destroy();
|
|
38812
|
+
});
|
|
38813
|
+
req.on("close", () => {
|
|
38814
|
+
fail("the connection closed before a response was read");
|
|
38815
|
+
clearTimeout(deadline);
|
|
38816
|
+
});
|
|
38817
|
+
req.on("error", (err) => {
|
|
38818
|
+
fail(err.message);
|
|
38819
|
+
});
|
|
38820
|
+
if (options.body !== void 0) req.write(options.body);
|
|
38821
|
+
req.end();
|
|
38822
|
+
});
|
|
38823
|
+
}
|
|
38824
|
+
|
|
38825
|
+
// ../../packages/remote/src/client.ts
|
|
38826
|
+
var ROUTES = {
|
|
38827
|
+
events: "/v1/events",
|
|
38828
|
+
auditEvents: "/v1/audit-events",
|
|
38829
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
38830
|
+
inventory: "/v1/inventory",
|
|
38831
|
+
storePosture: "/v1/store-posture",
|
|
38832
|
+
policyBundle: "/v1/policy-bundle",
|
|
38833
|
+
whoami: "/v1/plugin/whoami",
|
|
38834
|
+
shares: "/v1/shares",
|
|
38835
|
+
commands: "/v1/plugin/commands"
|
|
38836
|
+
};
|
|
38837
|
+
function ackRoute(id) {
|
|
38838
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
38839
|
+
}
|
|
38840
|
+
function headerValue(response, name) {
|
|
38841
|
+
const raw = response.headers[name];
|
|
38842
|
+
if (raw === void 0) return void 0;
|
|
38843
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
38844
|
+
}
|
|
38845
|
+
function okBody(response) {
|
|
38846
|
+
if (response.status < 200 || response.status >= 300) {
|
|
38847
|
+
throw new RemoteRequestError(response.status);
|
|
38848
|
+
}
|
|
38849
|
+
return response.body;
|
|
38850
|
+
}
|
|
38851
|
+
function parsed(schema, body, route) {
|
|
38852
|
+
let json2;
|
|
38853
|
+
try {
|
|
38854
|
+
json2 = JSON.parse(body);
|
|
38855
|
+
} catch {
|
|
38856
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
38857
|
+
}
|
|
38858
|
+
const result = schema.safeParse(json2);
|
|
38859
|
+
if (!result.success) {
|
|
38860
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
38861
|
+
}
|
|
38862
|
+
return result.data;
|
|
38863
|
+
}
|
|
38864
|
+
function withoutTrailingSlashes(endpoint) {
|
|
38865
|
+
let end = endpoint.length;
|
|
38866
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
38867
|
+
return endpoint.slice(0, end);
|
|
38868
|
+
}
|
|
38869
|
+
var SLASH = "/".charCodeAt(0);
|
|
38870
|
+
function createRemoteClient(options) {
|
|
38871
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
38872
|
+
const url2 = (route) => `${base}${route}`;
|
|
38873
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
38874
|
+
const sendOne = async (event) => {
|
|
38875
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
38876
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
38877
|
+
const response = await send({
|
|
38878
|
+
...common,
|
|
38879
|
+
method: "POST",
|
|
38880
|
+
url: url2(ROUTES.auditEvents),
|
|
38881
|
+
body: JSON.stringify(validated.data)
|
|
38882
|
+
});
|
|
38883
|
+
okBody(response);
|
|
38884
|
+
};
|
|
38885
|
+
return {
|
|
38886
|
+
async ingestEvents(batch) {
|
|
38887
|
+
const response = await send({
|
|
38888
|
+
...common,
|
|
38889
|
+
method: "POST",
|
|
38890
|
+
url: url2(ROUTES.events),
|
|
38891
|
+
body: JSON.stringify(batch)
|
|
38892
|
+
});
|
|
38893
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
38894
|
+
},
|
|
38895
|
+
async ingestInventory(context) {
|
|
38896
|
+
const response = await send({
|
|
38897
|
+
...common,
|
|
38898
|
+
method: "POST",
|
|
38899
|
+
url: url2(ROUTES.inventory),
|
|
38900
|
+
body: JSON.stringify(context)
|
|
38901
|
+
});
|
|
38902
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
38903
|
+
},
|
|
38904
|
+
async recordAuditEvent(event) {
|
|
38905
|
+
await sendOne(event);
|
|
38906
|
+
},
|
|
38907
|
+
async recordAuditEvents(events, opts) {
|
|
38908
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
38909
|
+
if (!validated.success) {
|
|
38910
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
38911
|
+
}
|
|
38912
|
+
const response = await send({
|
|
38913
|
+
...common,
|
|
38914
|
+
method: "POST",
|
|
38915
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
38916
|
+
body: JSON.stringify(validated.data)
|
|
38917
|
+
});
|
|
38918
|
+
if (response.status === 404) {
|
|
38919
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
38920
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
38921
|
+
}
|
|
38922
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
38923
|
+
return { accepted: validated.data.events.length };
|
|
38924
|
+
}
|
|
38925
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
38926
|
+
},
|
|
38927
|
+
async reportStorePosture(snapshot) {
|
|
38928
|
+
const response = await send({
|
|
38929
|
+
...common,
|
|
38930
|
+
method: "POST",
|
|
38931
|
+
url: url2(ROUTES.storePosture),
|
|
38932
|
+
body: JSON.stringify(snapshot)
|
|
38933
|
+
});
|
|
38934
|
+
okBody(response);
|
|
38935
|
+
},
|
|
38936
|
+
async getPolicyBundle(etag) {
|
|
38937
|
+
const response = await send({
|
|
38938
|
+
...common,
|
|
38939
|
+
method: "GET",
|
|
38940
|
+
url: url2(ROUTES.policyBundle),
|
|
38941
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
38942
|
+
});
|
|
38943
|
+
if (response.status === 304) {
|
|
38944
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
38945
|
+
}
|
|
38946
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
38947
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
38948
|
+
},
|
|
38949
|
+
async whoami() {
|
|
38950
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
38951
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
38952
|
+
},
|
|
38953
|
+
async recordProjectEgress(request) {
|
|
38954
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
38955
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
38956
|
+
const response = await send({
|
|
38957
|
+
...common,
|
|
38958
|
+
method: "POST",
|
|
38959
|
+
url: url2(ROUTES.shares),
|
|
38960
|
+
body: JSON.stringify(validated.data)
|
|
38961
|
+
});
|
|
38962
|
+
okBody(response);
|
|
38963
|
+
},
|
|
38964
|
+
async pollCommand() {
|
|
38965
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
38966
|
+
if (response.status === 404) return null;
|
|
38967
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
38968
|
+
},
|
|
38969
|
+
async ackCommand(id, body) {
|
|
38970
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
38971
|
+
const route = ackRoute(id);
|
|
38972
|
+
if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
|
|
38973
|
+
const response = await send({
|
|
38974
|
+
...common,
|
|
38975
|
+
method: "POST",
|
|
38976
|
+
url: url2(route),
|
|
38977
|
+
body: JSON.stringify(validated.data)
|
|
38978
|
+
});
|
|
38979
|
+
okBody(response);
|
|
38980
|
+
}
|
|
38981
|
+
};
|
|
38982
|
+
}
|
|
38983
|
+
|
|
37929
38984
|
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
37930
38985
|
function statusOf(err) {
|
|
37931
38986
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
@@ -37944,12 +38999,27 @@ function classifyFailure(err) {
|
|
|
37944
38999
|
}
|
|
37945
39000
|
}
|
|
37946
39001
|
|
|
39002
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
39003
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
39004
|
+
function withTimeout(promise2, ms) {
|
|
39005
|
+
let timer;
|
|
39006
|
+
const timeout = new Promise((_, reject) => {
|
|
39007
|
+
timer = setTimeout(() => {
|
|
39008
|
+
reject(new Error("attached gateway request timed out"));
|
|
39009
|
+
}, ms);
|
|
39010
|
+
});
|
|
39011
|
+
promise2.catch(() => void 0);
|
|
39012
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
39013
|
+
clearTimeout(timer);
|
|
39014
|
+
});
|
|
39015
|
+
}
|
|
39016
|
+
|
|
37947
39017
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
37948
|
-
import { readFileSync as
|
|
37949
|
-
import { join as
|
|
39018
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
39019
|
+
import { join as join23 } from "path";
|
|
37950
39020
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
37951
39021
|
function forwardDropsPath(dataDir2) {
|
|
37952
|
-
return
|
|
39022
|
+
return join23(dataDir2, FORWARD_DROPS_FILENAME);
|
|
37953
39023
|
}
|
|
37954
39024
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
37955
39025
|
if (count <= 0) return;
|
|
@@ -37967,7 +39037,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
37967
39037
|
}
|
|
37968
39038
|
function readForwardDrops(dataDir2) {
|
|
37969
39039
|
try {
|
|
37970
|
-
const parsed2 = JSON.parse(
|
|
39040
|
+
const parsed2 = JSON.parse(readFileSync15(forwardDropsPath(dataDir2), "utf8"));
|
|
37971
39041
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
37972
39042
|
const record2 = parsed2;
|
|
37973
39043
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -37984,30 +39054,20 @@ function readForwardDrops(dataDir2) {
|
|
|
37984
39054
|
}
|
|
37985
39055
|
|
|
37986
39056
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
37987
|
-
import { randomUUID as
|
|
37988
|
-
import { readFileSync as
|
|
39057
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
39058
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
37989
39059
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
37990
|
-
import { join as
|
|
37991
|
-
|
|
37992
|
-
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
37993
|
-
var REQUEST_TIMEOUT_MS = 2e3;
|
|
37994
|
-
function withTimeout(promise2, ms) {
|
|
37995
|
-
let timer;
|
|
37996
|
-
const timeout = new Promise((_, reject) => {
|
|
37997
|
-
timer = setTimeout(() => {
|
|
37998
|
-
reject(new Error("attached gateway request timed out"));
|
|
37999
|
-
}, ms);
|
|
38000
|
-
});
|
|
38001
|
-
promise2.catch(() => void 0);
|
|
38002
|
-
return Promise.race([promise2, timeout]).finally(() => {
|
|
38003
|
-
clearTimeout(timer);
|
|
38004
|
-
});
|
|
38005
|
-
}
|
|
38006
|
-
|
|
38007
|
-
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
39060
|
+
import { join as join24 } from "path";
|
|
38008
39061
|
function isInvalidRequest(err) {
|
|
38009
39062
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
38010
39063
|
}
|
|
39064
|
+
function isRouteAbsent(err) {
|
|
39065
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
|
|
39066
|
+
}
|
|
39067
|
+
function isServerRejection(err) {
|
|
39068
|
+
const status = statusOf(err);
|
|
39069
|
+
return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
|
|
39070
|
+
}
|
|
38011
39071
|
var FORWARD_BUDGET_MS = 1500;
|
|
38012
39072
|
var DECISION_PATH_BUDGET_MS = 800;
|
|
38013
39073
|
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
@@ -38035,7 +39095,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
38035
39095
|
}
|
|
38036
39096
|
function createForwardPolicy(deps) {
|
|
38037
39097
|
const now = deps.now ?? (() => Date.now());
|
|
38038
|
-
const file2 =
|
|
39098
|
+
const file2 = join24(deps.dir, STATE_FILENAME);
|
|
38039
39099
|
let state = null;
|
|
38040
39100
|
let loading = null;
|
|
38041
39101
|
async function readState() {
|
|
@@ -38060,7 +39120,7 @@ function createForwardPolicy(deps) {
|
|
|
38060
39120
|
state = next;
|
|
38061
39121
|
try {
|
|
38062
39122
|
await ensureDataDir(deps.dir);
|
|
38063
|
-
const tmp = `${file2}.${
|
|
39123
|
+
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
38064
39124
|
await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
38065
39125
|
await rename(tmp, file2);
|
|
38066
39126
|
} catch {
|
|
@@ -38075,6 +39135,20 @@ function createForwardPolicy(deps) {
|
|
|
38075
39135
|
} catch {
|
|
38076
39136
|
current = { ...CLOSED };
|
|
38077
39137
|
}
|
|
39138
|
+
const restoreOpenedAtMs = (openedAtMs) => persist({
|
|
39139
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
39140
|
+
openedAtMs,
|
|
39141
|
+
lastFailure: current.lastFailure
|
|
39142
|
+
});
|
|
39143
|
+
const recordFailure = (cause) => {
|
|
39144
|
+
const failures = current.consecutiveFailures + 1;
|
|
39145
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
39146
|
+
return persist({
|
|
39147
|
+
consecutiveFailures: failures,
|
|
39148
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
39149
|
+
lastFailure: cause
|
|
39150
|
+
});
|
|
39151
|
+
};
|
|
38078
39152
|
const at = now();
|
|
38079
39153
|
if (current.openedAtMs !== null) {
|
|
38080
39154
|
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
@@ -38093,15 +39167,20 @@ function createForwardPolicy(deps) {
|
|
|
38093
39167
|
}
|
|
38094
39168
|
return { ok: true, value };
|
|
38095
39169
|
} catch (err) {
|
|
38096
|
-
if (isInvalidRequest(err))
|
|
39170
|
+
if (isInvalidRequest(err)) {
|
|
39171
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
|
|
39172
|
+
return { ok: false, reason: "invalid-request" };
|
|
39173
|
+
}
|
|
39174
|
+
if (isRouteAbsent(err)) {
|
|
39175
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
|
|
39176
|
+
return { ok: false, reason: "route-absent" };
|
|
39177
|
+
}
|
|
39178
|
+
if (isServerRejection(err)) {
|
|
39179
|
+
await recordFailure("unreachable");
|
|
39180
|
+
return { ok: false, reason: "rejected" };
|
|
39181
|
+
}
|
|
38097
39182
|
const reason = classifyFailure(err);
|
|
38098
|
-
|
|
38099
|
-
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
38100
|
-
await persist({
|
|
38101
|
-
consecutiveFailures: failures,
|
|
38102
|
-
openedAtMs: shouldOpen ? now() : null,
|
|
38103
|
-
lastFailure: reason
|
|
38104
|
-
});
|
|
39183
|
+
await recordFailure(reason);
|
|
38105
39184
|
return { ok: false, reason };
|
|
38106
39185
|
}
|
|
38107
39186
|
}
|
|
@@ -38109,13 +39188,11 @@ function createForwardPolicy(deps) {
|
|
|
38109
39188
|
}
|
|
38110
39189
|
|
|
38111
39190
|
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
38112
|
-
|
|
38113
|
-
|
|
38114
|
-
|
|
38115
|
-
|
|
38116
|
-
|
|
38117
|
-
block: 4
|
|
38118
|
-
};
|
|
39191
|
+
function strongerOf(a, b) {
|
|
39192
|
+
if (a === null) return b;
|
|
39193
|
+
if (b === null) return a;
|
|
39194
|
+
return strongerAction(a, b);
|
|
39195
|
+
}
|
|
38119
39196
|
function ruleCategoryMap(wireRules, localRules) {
|
|
38120
39197
|
const map2 = /* @__PURE__ */ new Map();
|
|
38121
39198
|
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
@@ -38125,11 +39202,6 @@ function ruleCategoryMap(wireRules, localRules) {
|
|
|
38125
39202
|
}
|
|
38126
39203
|
return map2;
|
|
38127
39204
|
}
|
|
38128
|
-
function strongerOf(a, b) {
|
|
38129
|
-
if (a === null) return b;
|
|
38130
|
-
if (b === null) return a;
|
|
38131
|
-
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
38132
|
-
}
|
|
38133
39205
|
function policyKey(policy) {
|
|
38134
39206
|
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
38135
39207
|
}
|
|
@@ -38148,7 +39220,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
38148
39220
|
const floor = floorFor(policy, categoryByRuleId);
|
|
38149
39221
|
remoteCategoryAction.set(
|
|
38150
39222
|
policy.target.category,
|
|
38151
|
-
floor !== null &&
|
|
39223
|
+
floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
|
|
38152
39224
|
);
|
|
38153
39225
|
}
|
|
38154
39226
|
for (const policy of localPolicies) {
|
|
@@ -38165,7 +39237,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
38165
39237
|
}
|
|
38166
39238
|
merged.set(
|
|
38167
39239
|
key,
|
|
38168
|
-
remoteFloor !== null &&
|
|
39240
|
+
remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
|
|
38169
39241
|
);
|
|
38170
39242
|
}
|
|
38171
39243
|
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
@@ -38185,13 +39257,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
38185
39257
|
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
38186
39258
|
}
|
|
38187
39259
|
const effectiveFloor = strongerOf(floor, localFloor);
|
|
38188
|
-
const clamped = effectiveFloor !== null &&
|
|
39260
|
+
const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
|
|
38189
39261
|
const existing = merged.get(key);
|
|
38190
39262
|
if (existing === void 0) {
|
|
38191
39263
|
merged.set(key, clamped);
|
|
38192
39264
|
continue;
|
|
38193
39265
|
}
|
|
38194
|
-
if (
|
|
39266
|
+
if (actionRank(clamped.action) > actionRank(existing.action)) {
|
|
38195
39267
|
merged.set(key, clamped);
|
|
38196
39268
|
}
|
|
38197
39269
|
}
|
|
@@ -38224,6 +39296,8 @@ var AttachedDataGateway = class {
|
|
|
38224
39296
|
);
|
|
38225
39297
|
if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
|
|
38226
39298
|
this.deps.local.markCaptureDelivered(record2.event, Date.now());
|
|
39299
|
+
} else {
|
|
39300
|
+
this.deps.local.markCaptureOwed(record2.event);
|
|
38227
39301
|
}
|
|
38228
39302
|
}
|
|
38229
39303
|
async ensureInventory(ctx) {
|
|
@@ -38260,9 +39334,10 @@ var AttachedDataGateway = class {
|
|
|
38260
39334
|
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
38261
39335
|
async recordAuditEvent(event) {
|
|
38262
39336
|
await this.deps.local.recordAuditEvent(event);
|
|
38263
|
-
await this.deps.forward.run(
|
|
39337
|
+
const forwarded = await this.deps.forward.run(
|
|
38264
39338
|
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
38265
39339
|
);
|
|
39340
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
38266
39341
|
}
|
|
38267
39342
|
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
38268
39343
|
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
@@ -38271,44 +39346,170 @@ var AttachedDataGateway = class {
|
|
|
38271
39346
|
// which would write the event to the local store a second time.
|
|
38272
39347
|
async recordLlmCall(input2) {
|
|
38273
39348
|
await this.deps.local.recordLlmCall(input2);
|
|
38274
|
-
|
|
38275
|
-
|
|
38276
|
-
|
|
38277
|
-
)
|
|
39349
|
+
const event = llmAuditEvent(input2);
|
|
39350
|
+
const forwarded = await this.deps.forward.run(
|
|
39351
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
38278
39352
|
);
|
|
39353
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
38279
39354
|
}
|
|
38280
39355
|
/**
|
|
38281
|
-
* Forward one batch
|
|
39356
|
+
* Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
|
|
39357
|
+
*
|
|
39358
|
+
* This used to send one HTTP request per event, which is what made the batch
|
|
39359
|
+
* budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
|
|
39360
|
+
* away everything after them. The same rows now cross 50 at a time over
|
|
39361
|
+
* `POST /v1/audit-events/batch` — the route the attach-time drain has always
|
|
39362
|
+
* used — so the same budget admits ~750. The wire cap is the server's own
|
|
39363
|
+
* constant, sized against server cost, and the client REFUSES a longer array
|
|
39364
|
+
* client-side, so the chunking here is not a convention.
|
|
39365
|
+
*
|
|
39366
|
+
* Still serial, and still for the original reason: firing N requests at once
|
|
39367
|
+
* would trade a latency problem for a burst the plane's per-key rate limiting
|
|
39368
|
+
* answers with the refusals the breaker then counts. Fewer, fuller requests is
|
|
39369
|
+
* the fix; more concurrent ones is not.
|
|
39370
|
+
*
|
|
39371
|
+
* When the deadline passes the remainder is dropped rather than sent: the
|
|
39372
|
+
* local write has already succeeded, so every caller has a correct result to
|
|
39373
|
+
* return. What is dropped is COUNTED, everywhere it can happen — this path
|
|
39374
|
+
* returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
|
|
39375
|
+
* `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
|
|
39376
|
+
* the breaker closed, renders a healthy block, and discards the tail of every
|
|
39377
|
+
* batch indefinitely. The SAME tally also covers a single that fails inside
|
|
39378
|
+
* the per-item retry below — the breaker opening mid-retry is a failure the
|
|
39379
|
+
* breaker's own state DOES capture, but the events still in this chunk once
|
|
39380
|
+
* that happens are neither delivered nor otherwise counted anywhere, which is
|
|
39381
|
+
* the same invisibility with a different cause.
|
|
38282
39382
|
*
|
|
38283
|
-
*
|
|
38284
|
-
*
|
|
38285
|
-
*
|
|
38286
|
-
*
|
|
38287
|
-
*
|
|
39383
|
+
* `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
|
|
39384
|
+
* single-event ack and at fifty times the blast radius here:
|
|
39385
|
+
* `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
|
|
39386
|
+
* not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
|
|
39387
|
+
* fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
|
|
39388
|
+
* delivered and never re-offer the twenty the plane did not take. So success
|
|
39389
|
+
* is checked against `chunk.length`; anything short of it falls into the same
|
|
39390
|
+
* per-item pass as a refused chunk, which is the only way to recover the
|
|
39391
|
+
* rows that did not land, since the ack carries no per-row verdict to
|
|
39392
|
+
* resend by.
|
|
38288
39393
|
*
|
|
38289
|
-
*
|
|
38290
|
-
*
|
|
38291
|
-
*
|
|
39394
|
+
* That fallback ASSUMES a re-send of an already-landed row is a harmless
|
|
39395
|
+
* no-op rather than a second cost — an assumption this file cannot verify.
|
|
39396
|
+
* `AuditEventBatchAck` carries only `accepted`, unlike its sibling
|
|
39397
|
+
* `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
|
|
39398
|
+
* the batch size as the invariant `recordCapture` reads), so whether a
|
|
39399
|
+
* duplicate counts toward THIS route's `accepted` is not expressed
|
|
39400
|
+
* anywhere in this repo. If it follows its sibling's convention and does
|
|
39401
|
+
* NOT, a chunk containing even one already-delivered row — the ordinary
|
|
39402
|
+
* consequence of a lost stamp, which this file already treats as cheap —
|
|
39403
|
+
* answers short forever and enters the per-item pass on every pass it is
|
|
39404
|
+
* offered again. The cost of that is bounded rather than silent: the
|
|
39405
|
+
* pass converges (every row lands and stamps), so it is one wasted round
|
|
39406
|
+
* of singles rather than a stall, and it errs toward an extra resend
|
|
39407
|
+
* rather than toward the lost row the alternative risks.
|
|
38292
39408
|
*
|
|
38293
|
-
*
|
|
38294
|
-
*
|
|
38295
|
-
*
|
|
38296
|
-
*
|
|
38297
|
-
*
|
|
38298
|
-
*
|
|
39409
|
+
* BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
|
|
39410
|
+
* one transaction, so a full 2xx settles every event in it and a non-2xx
|
|
39411
|
+
* settles none — which is why the whole chunk is stamped together on a FULL
|
|
39412
|
+
* accept and none of it otherwise. THREE reasons do not deserve whole-chunk
|
|
39413
|
+
* treatment, alongside a short accept, and all are re-sent one event at a
|
|
39414
|
+
* time:
|
|
39415
|
+
*
|
|
39416
|
+
* `invalid-request` a chunk the client refused to send at all. One malformed
|
|
39417
|
+
* event would otherwise cost the 49 good ones beside it —
|
|
39418
|
+
* a new way to lose data introduced by the very change
|
|
39419
|
+
* meant to stop losing it.
|
|
39420
|
+
* `route-absent` a deployment that predates the batch route. The
|
|
39421
|
+
* single-event route is the one it serves, and re-sending
|
|
39422
|
+
* here rather than inside the client is what gives each
|
|
39423
|
+
* request its own budget instead of 50 inside one.
|
|
39424
|
+
* `rejected` the deployment's SERVER-side twin of `invalid-request` —
|
|
39425
|
+
* a 4xx body refusal from schema drift on the other side
|
|
39426
|
+
* of the wire. Settlement is batch-atomic on this reason
|
|
39427
|
+
* exactly as on the others, so leaving it out would cost
|
|
39428
|
+
* the whole chunk for one event the DEPLOYMENT considers
|
|
39429
|
+
* malformed, where the per-item form cost only that one.
|
|
39430
|
+
*
|
|
39431
|
+
* Every other reason (breaker-open, a refusal, a timeout) applies to the whole
|
|
39432
|
+
* chunk, and re-sending it item by item would just spend the budget failing 50
|
|
39433
|
+
* more times — for those, the blast radius stays exactly what it was before
|
|
39434
|
+
* batching.
|
|
38299
39435
|
*/
|
|
38300
39436
|
async forwardBatch(inputs, toEvent) {
|
|
38301
39437
|
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
38302
|
-
|
|
38303
|
-
|
|
38304
|
-
|
|
38305
|
-
|
|
38306
|
-
|
|
39438
|
+
const delivered = [];
|
|
39439
|
+
try {
|
|
39440
|
+
for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
|
|
39441
|
+
const now = Date.now();
|
|
39442
|
+
if (now >= deadline) {
|
|
39443
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
39444
|
+
return;
|
|
39445
|
+
}
|
|
39446
|
+
const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
|
|
39447
|
+
const forwarded = await this.deps.forward.run(
|
|
39448
|
+
() => this.deps.client.recordAuditEvents(
|
|
39449
|
+
chunk.map((event) => reKeyForForward(event, this.remoteInventory))
|
|
39450
|
+
)
|
|
39451
|
+
);
|
|
39452
|
+
if (forwarded.ok) {
|
|
39453
|
+
if (forwarded.value.accepted === chunk.length) {
|
|
39454
|
+
delivered.push(...chunk);
|
|
39455
|
+
continue;
|
|
39456
|
+
}
|
|
39457
|
+
} else if (
|
|
39458
|
+
// THREE reasons are worth a second pass, one at a time, and they are
|
|
39459
|
+
// the three settled BEFORE the control plane refused anything, or
|
|
39460
|
+
// (for `rejected`) refused the BODY rather than the connection.
|
|
39461
|
+
//
|
|
39462
|
+
// `invalid-request` — the CLIENT refused the body before any request
|
|
39463
|
+
// went out: a defect in one event, not an outage. Re-sending singly
|
|
39464
|
+
// isolates the bad one instead of charging its 49 neighbours for it.
|
|
39465
|
+
//
|
|
39466
|
+
// `route-absent` — the deployment predates the batch route and serves
|
|
39467
|
+
// only the single-event one. The retry IS the compatibility path, and
|
|
39468
|
+
// it has to live HERE rather than inside the client: each single gets
|
|
39469
|
+
// its own FORWARD_BUDGET_MS through `run`, whereas the client's own
|
|
39470
|
+
// fallback would spend 50 sequential round trips inside the ONE
|
|
39471
|
+
// budget wrapping this call — turning a working older deployment into
|
|
39472
|
+
// a timeout, three of those into an open breaker, and every row into
|
|
39473
|
+
// a silent drop while the status surface called an answering
|
|
39474
|
+
// deployment down.
|
|
39475
|
+
//
|
|
39476
|
+
// `rejected` — the deployment's own 4xx refusal of the body, the
|
|
39477
|
+
// server-side twin of `invalid-request`: isolating it the same way
|
|
39478
|
+
// costs one event instead of the whole chunk for a defect the
|
|
39479
|
+
// deployment considers local to one row.
|
|
39480
|
+
//
|
|
39481
|
+
// Every other reason (breaker-open, a refusal, a timeout) applies to
|
|
39482
|
+
// the whole chunk; re-sending it item by item would just spend the
|
|
39483
|
+
// budget failing 50 more times.
|
|
39484
|
+
forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
|
|
39485
|
+
) {
|
|
39486
|
+
continue;
|
|
39487
|
+
}
|
|
39488
|
+
for (const [j, event] of chunk.entries()) {
|
|
39489
|
+
const at = Date.now();
|
|
39490
|
+
if (at >= deadline) {
|
|
39491
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
39492
|
+
return;
|
|
39493
|
+
}
|
|
39494
|
+
const single = await this.deps.forward.run(
|
|
39495
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
39496
|
+
);
|
|
39497
|
+
if (single.ok) {
|
|
39498
|
+
delivered.push(event);
|
|
39499
|
+
continue;
|
|
39500
|
+
}
|
|
39501
|
+
if (single.reason === "breaker-open") {
|
|
39502
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
39503
|
+
return;
|
|
39504
|
+
}
|
|
39505
|
+
recordForwardDrops(this.deps.dataDir, 1, at);
|
|
39506
|
+
}
|
|
39507
|
+
}
|
|
39508
|
+
} finally {
|
|
39509
|
+
try {
|
|
39510
|
+
this.deps.local.markAuditEventsDelivered(delivered, Date.now());
|
|
39511
|
+
} catch {
|
|
38307
39512
|
}
|
|
38308
|
-
const input2 = inputs[i];
|
|
38309
|
-
await this.deps.forward.run(
|
|
38310
|
-
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
|
|
38311
|
-
);
|
|
38312
39513
|
}
|
|
38313
39514
|
}
|
|
38314
39515
|
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
@@ -38351,9 +39552,10 @@ var AttachedDataGateway = class {
|
|
|
38351
39552
|
// local store.
|
|
38352
39553
|
async recordConfigScan(record2) {
|
|
38353
39554
|
await this.deps.local.recordConfigScan(record2);
|
|
38354
|
-
await this.deps.forward.run(
|
|
39555
|
+
const forwarded = await this.deps.forward.run(
|
|
38355
39556
|
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
38356
39557
|
);
|
|
39558
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
|
|
38357
39559
|
}
|
|
38358
39560
|
async recordBlockedDetection(entry) {
|
|
38359
39561
|
return this.deps.local.recordBlockedDetection(entry);
|
|
@@ -38487,6 +39689,18 @@ var AttachedDataGateway = class {
|
|
|
38487
39689
|
// exactly what it did, leaving the whole control inert on every device
|
|
38488
39690
|
// while every test around it stayed green.
|
|
38489
39691
|
prohibitedModels: cached2.prohibitedModels
|
|
39692
|
+
// ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
|
|
39693
|
+
// merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
|
|
39694
|
+
// it emits, so an 'authored' policy arriving from the control plane
|
|
39695
|
+
// keeps that marker even where the clamp rebuilds it with a stronger
|
|
39696
|
+
// action. The device reads it in exactly one direction — the rules such a
|
|
39697
|
+
// policy targets are not locally re-assignable — so it sits on the
|
|
39698
|
+
// `prohibitedModels` side of the line for the same reason that field
|
|
39699
|
+
// does: it can only ever ADD a refusal, never relax one, and an unsigned
|
|
39700
|
+
// cache therefore has no relaxation to grant by carrying it. Dropping it
|
|
39701
|
+
// would be the silent failure rather than the safe one — the action would
|
|
39702
|
+
// still be enforced while the local override the organization authored
|
|
39703
|
+
// away quietly came back.
|
|
38490
39704
|
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
38491
39705
|
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
38492
39706
|
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
@@ -38526,10 +39740,10 @@ var AttachedDataGateway = class {
|
|
|
38526
39740
|
//
|
|
38527
39741
|
// Implementing these is what actually closes the skipped-local-maintenance
|
|
38528
39742
|
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
38529
|
-
// any object carrying all
|
|
39743
|
+
// any object carrying them all, so the composite qualifies and SessionStart
|
|
38530
39744
|
// runs maintenance on the device's real store.
|
|
38531
39745
|
//
|
|
38532
|
-
// ⚠
|
|
39746
|
+
// ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
38533
39747
|
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
38534
39748
|
// return value directly; declaring them `async` here would hand those call
|
|
38535
39749
|
// sites a Promise and silently break both.
|
|
@@ -38552,9 +39766,15 @@ var AttachedDataGateway = class {
|
|
|
38552
39766
|
// Delegated like the rest, and SYNCHRONOUS for the reason the note above
|
|
38553
39767
|
// gives: `recordCapture` calls it after the forward has already settled, on a
|
|
38554
39768
|
// path that has nothing left to await.
|
|
39769
|
+
markCaptureOwed(event) {
|
|
39770
|
+
this.deps.local.markCaptureOwed(event);
|
|
39771
|
+
}
|
|
38555
39772
|
markCaptureDelivered(event, atMs) {
|
|
38556
39773
|
this.deps.local.markCaptureDelivered(event, atMs);
|
|
38557
39774
|
}
|
|
39775
|
+
markAuditEventsDelivered(events, atMs) {
|
|
39776
|
+
this.deps.local.markAuditEventsDelivered(events, atMs);
|
|
39777
|
+
}
|
|
38558
39778
|
};
|
|
38559
39779
|
function reKeyForForward(event, remote) {
|
|
38560
39780
|
if (remote === null) {
|
|
@@ -38597,281 +39817,17 @@ function toolAuditEvent(input2) {
|
|
|
38597
39817
|
}
|
|
38598
39818
|
|
|
38599
39819
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
38600
|
-
import { readFileSync as
|
|
38601
|
-
import { join as
|
|
39820
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
39821
|
+
import { join as join25 } from "path";
|
|
38602
39822
|
|
|
38603
39823
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
38604
39824
|
import { createHash as createHash6 } from "crypto";
|
|
38605
39825
|
import { hostname as hostname5 } from "os";
|
|
38606
39826
|
|
|
38607
|
-
// ../../packages/
|
|
38608
|
-
|
|
38609
|
-
|
|
38610
|
-
var
|
|
38611
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
38612
|
-
var RemoteRequestError = class extends Error {
|
|
38613
|
-
constructor(status) {
|
|
38614
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
38615
|
-
this.status = status;
|
|
38616
|
-
this.name = "RemoteRequestError";
|
|
38617
|
-
}
|
|
38618
|
-
status;
|
|
38619
|
-
};
|
|
38620
|
-
var RemoteRequestInvalid = class extends Error {
|
|
38621
|
-
constructor(route, cause) {
|
|
38622
|
-
super(`refusing to send a malformed body to ${route}`);
|
|
38623
|
-
this.cause = cause;
|
|
38624
|
-
this.name = "RemoteRequestInvalid";
|
|
38625
|
-
}
|
|
38626
|
-
cause;
|
|
38627
|
-
};
|
|
38628
|
-
var RemoteResponseInvalid = class extends Error {
|
|
38629
|
-
constructor(route, detail) {
|
|
38630
|
-
super(`control plane answered ${route} with ${detail}`);
|
|
38631
|
-
this.name = "RemoteResponseInvalid";
|
|
38632
|
-
}
|
|
38633
|
-
};
|
|
38634
|
-
var RemoteTransportError = class extends Error {
|
|
38635
|
-
/**
|
|
38636
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
38637
|
-
* refused.
|
|
38638
|
-
*
|
|
38639
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
38640
|
-
* all. It exists because two paths reject after a status has already been
|
|
38641
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
38642
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
38643
|
-
* outage, which sends the reader to look at their network instead of their
|
|
38644
|
-
* credential.
|
|
38645
|
-
*/
|
|
38646
|
-
constructor(reason, status) {
|
|
38647
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
38648
|
-
this.status = status;
|
|
38649
|
-
this.name = "RemoteTransportError";
|
|
38650
|
-
}
|
|
38651
|
-
status;
|
|
38652
|
-
};
|
|
38653
|
-
async function send(options) {
|
|
38654
|
-
const url2 = new URL(options.url);
|
|
38655
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38656
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
38657
|
-
const requestOptions = {
|
|
38658
|
-
method: options.method,
|
|
38659
|
-
headers: {
|
|
38660
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
38661
|
-
// last they win, and two of the values below are ones no caller may
|
|
38662
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
38663
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
38664
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
38665
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
38666
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
38667
|
-
// conditional GET — is untouched by this order.
|
|
38668
|
-
...options.headers,
|
|
38669
|
-
// The credential. One header, matching what the deployment authenticates
|
|
38670
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
38671
|
-
// it can be logged by an intermediary for no gain.
|
|
38672
|
-
//
|
|
38673
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
38674
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
38675
|
-
// key differently from an absent one, and "the header is not there" is
|
|
38676
|
-
// the property the attach flow needs.
|
|
38677
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
38678
|
-
accept: "application/json",
|
|
38679
|
-
...options.body === void 0 ? {} : {
|
|
38680
|
-
"content-type": "application/json",
|
|
38681
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
38682
|
-
// character count is truncated by the receiver.
|
|
38683
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
38684
|
-
}
|
|
38685
|
-
}
|
|
38686
|
-
};
|
|
38687
|
-
return new Promise((resolve2, reject) => {
|
|
38688
|
-
let settled = false;
|
|
38689
|
-
const fail = (reason, status) => {
|
|
38690
|
-
if (settled) return;
|
|
38691
|
-
settled = true;
|
|
38692
|
-
reject(new RemoteTransportError(reason, status));
|
|
38693
|
-
};
|
|
38694
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
38695
|
-
const chunks = [];
|
|
38696
|
-
let size = 0;
|
|
38697
|
-
res.on("data", (chunk) => {
|
|
38698
|
-
size += chunk.length;
|
|
38699
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
38700
|
-
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
38701
|
-
res.destroy();
|
|
38702
|
-
req.destroy();
|
|
38703
|
-
return;
|
|
38704
|
-
}
|
|
38705
|
-
chunks.push(chunk);
|
|
38706
|
-
});
|
|
38707
|
-
res.on("aborted", () => {
|
|
38708
|
-
fail("the response was aborted", res.statusCode);
|
|
38709
|
-
});
|
|
38710
|
-
res.on("end", () => {
|
|
38711
|
-
if (settled) return;
|
|
38712
|
-
settled = true;
|
|
38713
|
-
resolve2({
|
|
38714
|
-
status: res.statusCode ?? 0,
|
|
38715
|
-
headers: res.headers,
|
|
38716
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
38717
|
-
});
|
|
38718
|
-
});
|
|
38719
|
-
});
|
|
38720
|
-
const deadline = setTimeout(() => {
|
|
38721
|
-
fail(`no response within ${String(timeoutMs)}ms`);
|
|
38722
|
-
req.destroy();
|
|
38723
|
-
}, timeoutMs);
|
|
38724
|
-
deadline.unref();
|
|
38725
|
-
req.on("upgrade", (_res, socket) => {
|
|
38726
|
-
fail("the deployment answered with a protocol upgrade");
|
|
38727
|
-
socket.destroy();
|
|
38728
|
-
});
|
|
38729
|
-
req.on("close", () => {
|
|
38730
|
-
fail("the connection closed before a response was read");
|
|
38731
|
-
clearTimeout(deadline);
|
|
38732
|
-
});
|
|
38733
|
-
req.on("error", (err) => {
|
|
38734
|
-
fail(err.message);
|
|
38735
|
-
});
|
|
38736
|
-
if (options.body !== void 0) req.write(options.body);
|
|
38737
|
-
req.end();
|
|
38738
|
-
});
|
|
38739
|
-
}
|
|
38740
|
-
|
|
38741
|
-
// ../../packages/remote/src/client.ts
|
|
38742
|
-
var ROUTES = {
|
|
38743
|
-
events: "/v1/events",
|
|
38744
|
-
auditEvents: "/v1/audit-events",
|
|
38745
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
38746
|
-
inventory: "/v1/inventory",
|
|
38747
|
-
storePosture: "/v1/store-posture",
|
|
38748
|
-
policyBundle: "/v1/policy-bundle",
|
|
38749
|
-
whoami: "/v1/plugin/whoami",
|
|
38750
|
-
shares: "/v1/shares"
|
|
38751
|
-
};
|
|
38752
|
-
function headerValue(response, name) {
|
|
38753
|
-
const raw = response.headers[name];
|
|
38754
|
-
if (raw === void 0) return void 0;
|
|
38755
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
38756
|
-
}
|
|
38757
|
-
function okBody(response) {
|
|
38758
|
-
if (response.status < 200 || response.status >= 300) {
|
|
38759
|
-
throw new RemoteRequestError(response.status);
|
|
38760
|
-
}
|
|
38761
|
-
return response.body;
|
|
38762
|
-
}
|
|
38763
|
-
function parsed(schema, body, route) {
|
|
38764
|
-
let json2;
|
|
38765
|
-
try {
|
|
38766
|
-
json2 = JSON.parse(body);
|
|
38767
|
-
} catch {
|
|
38768
|
-
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
38769
|
-
}
|
|
38770
|
-
const result = schema.safeParse(json2);
|
|
38771
|
-
if (!result.success) {
|
|
38772
|
-
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
38773
|
-
}
|
|
38774
|
-
return result.data;
|
|
38775
|
-
}
|
|
38776
|
-
function withoutTrailingSlashes(endpoint) {
|
|
38777
|
-
let end = endpoint.length;
|
|
38778
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
38779
|
-
return endpoint.slice(0, end);
|
|
38780
|
-
}
|
|
38781
|
-
var SLASH = "/".charCodeAt(0);
|
|
38782
|
-
function createRemoteClient(options) {
|
|
38783
|
-
const base = withoutTrailingSlashes(options.endpoint);
|
|
38784
|
-
const url2 = (route) => `${base}${route}`;
|
|
38785
|
-
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
38786
|
-
const sendOne = async (event) => {
|
|
38787
|
-
const validated = RecordAuditEventRequest.safeParse(event);
|
|
38788
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
38789
|
-
const response = await send({
|
|
38790
|
-
...common,
|
|
38791
|
-
method: "POST",
|
|
38792
|
-
url: url2(ROUTES.auditEvents),
|
|
38793
|
-
body: JSON.stringify(validated.data)
|
|
38794
|
-
});
|
|
38795
|
-
okBody(response);
|
|
38796
|
-
};
|
|
38797
|
-
return {
|
|
38798
|
-
async ingestEvents(batch) {
|
|
38799
|
-
const response = await send({
|
|
38800
|
-
...common,
|
|
38801
|
-
method: "POST",
|
|
38802
|
-
url: url2(ROUTES.events),
|
|
38803
|
-
body: JSON.stringify(batch)
|
|
38804
|
-
});
|
|
38805
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
38806
|
-
},
|
|
38807
|
-
async ingestInventory(context) {
|
|
38808
|
-
const response = await send({
|
|
38809
|
-
...common,
|
|
38810
|
-
method: "POST",
|
|
38811
|
-
url: url2(ROUTES.inventory),
|
|
38812
|
-
body: JSON.stringify(context)
|
|
38813
|
-
});
|
|
38814
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
38815
|
-
},
|
|
38816
|
-
async recordAuditEvent(event) {
|
|
38817
|
-
await sendOne(event);
|
|
38818
|
-
},
|
|
38819
|
-
async recordAuditEvents(events) {
|
|
38820
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
38821
|
-
if (!validated.success) {
|
|
38822
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
38823
|
-
}
|
|
38824
|
-
const response = await send({
|
|
38825
|
-
...common,
|
|
38826
|
-
method: "POST",
|
|
38827
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
38828
|
-
body: JSON.stringify(validated.data)
|
|
38829
|
-
});
|
|
38830
|
-
if (response.status === 404) {
|
|
38831
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
38832
|
-
return { accepted: validated.data.events.length };
|
|
38833
|
-
}
|
|
38834
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
38835
|
-
},
|
|
38836
|
-
async reportStorePosture(snapshot) {
|
|
38837
|
-
const response = await send({
|
|
38838
|
-
...common,
|
|
38839
|
-
method: "POST",
|
|
38840
|
-
url: url2(ROUTES.storePosture),
|
|
38841
|
-
body: JSON.stringify(snapshot)
|
|
38842
|
-
});
|
|
38843
|
-
okBody(response);
|
|
38844
|
-
},
|
|
38845
|
-
async getPolicyBundle(etag) {
|
|
38846
|
-
const response = await send({
|
|
38847
|
-
...common,
|
|
38848
|
-
method: "GET",
|
|
38849
|
-
url: url2(ROUTES.policyBundle),
|
|
38850
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
38851
|
-
});
|
|
38852
|
-
if (response.status === 304) {
|
|
38853
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
38854
|
-
}
|
|
38855
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
38856
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
38857
|
-
},
|
|
38858
|
-
async whoami() {
|
|
38859
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
38860
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
38861
|
-
},
|
|
38862
|
-
async recordProjectEgress(request) {
|
|
38863
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
38864
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
38865
|
-
const response = await send({
|
|
38866
|
-
...common,
|
|
38867
|
-
method: "POST",
|
|
38868
|
-
url: url2(ROUTES.shares),
|
|
38869
|
-
body: JSON.stringify(validated.data)
|
|
38870
|
-
});
|
|
38871
|
-
okBody(response);
|
|
38872
|
-
}
|
|
38873
|
-
};
|
|
38874
|
-
}
|
|
39827
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
39828
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
39829
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
39830
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
38875
39831
|
|
|
38876
39832
|
// ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
|
|
38877
39833
|
import { spawn } from "child_process";
|
|
@@ -38879,7 +39835,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
38879
39835
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
38880
39836
|
|
|
38881
39837
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
38882
|
-
import { readFileSync as
|
|
39838
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
38883
39839
|
function createPluginBlock(build, policyStore) {
|
|
38884
39840
|
return async () => {
|
|
38885
39841
|
const cached2 = await policyStore.read();
|
|
@@ -38896,9 +39852,9 @@ function createPluginBlock(build, policyStore) {
|
|
|
38896
39852
|
}
|
|
38897
39853
|
|
|
38898
39854
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
38899
|
-
import { randomUUID as
|
|
39855
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
38900
39856
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
38901
|
-
import { join as
|
|
39857
|
+
import { join as join26 } from "path";
|
|
38902
39858
|
|
|
38903
39859
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
38904
39860
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -38922,7 +39878,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
38922
39878
|
|
|
38923
39879
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
38924
39880
|
function createPolicyStore(dir = dataDir()) {
|
|
38925
|
-
const file2 =
|
|
39881
|
+
const file2 = join26(dir, "policy-cache.json");
|
|
38926
39882
|
async function read() {
|
|
38927
39883
|
try {
|
|
38928
39884
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -38931,22 +39887,32 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
38931
39887
|
const record2 = parsed2;
|
|
38932
39888
|
const bundle = PolicyBundle.parse(record2.bundle);
|
|
38933
39889
|
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
38934
|
-
const
|
|
39890
|
+
const stored = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
39891
|
+
const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
|
|
39892
|
+
const etag = replayable ? stored : void 0;
|
|
38935
39893
|
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
38936
39894
|
} catch {
|
|
38937
39895
|
return null;
|
|
38938
39896
|
}
|
|
38939
39897
|
}
|
|
38940
|
-
|
|
38941
|
-
|
|
38942
|
-
const
|
|
38943
|
-
|
|
38944
|
-
|
|
38945
|
-
|
|
38946
|
-
|
|
38947
|
-
|
|
39898
|
+
function knowsMoreThanThisBuild(shapeId) {
|
|
39899
|
+
if (typeof shapeId !== "string" || shapeId === "") return false;
|
|
39900
|
+
const theirs = new Set(shapeId.split(","));
|
|
39901
|
+
const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
|
|
39902
|
+
return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
|
|
39903
|
+
}
|
|
39904
|
+
async function priorRecord() {
|
|
39905
|
+
try {
|
|
39906
|
+
const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
|
|
39907
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
39908
|
+
} catch {
|
|
39909
|
+
return null;
|
|
39910
|
+
}
|
|
39911
|
+
}
|
|
39912
|
+
async function publishRecord(record2) {
|
|
39913
|
+
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
38948
39914
|
try {
|
|
38949
|
-
await writeFile2(tmp, JSON.stringify(
|
|
39915
|
+
await writeFile2(tmp, JSON.stringify(record2), {
|
|
38950
39916
|
encoding: "utf8",
|
|
38951
39917
|
mode: DATA_FILE_MODE,
|
|
38952
39918
|
flag: "wx"
|
|
@@ -38957,6 +39923,27 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
38957
39923
|
throw err;
|
|
38958
39924
|
}
|
|
38959
39925
|
}
|
|
39926
|
+
async function write(bundle, etag) {
|
|
39927
|
+
await ensureDataDir(dir);
|
|
39928
|
+
const prior = await priorRecord();
|
|
39929
|
+
const priorVersion = prior?.bundle?.version;
|
|
39930
|
+
if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
|
|
39931
|
+
await publishRecord({
|
|
39932
|
+
...prior,
|
|
39933
|
+
fetchedAtMs: Date.now()
|
|
39934
|
+
});
|
|
39935
|
+
return;
|
|
39936
|
+
}
|
|
39937
|
+
await publishRecord({
|
|
39938
|
+
bundle,
|
|
39939
|
+
fetchedAtMs: Date.now(),
|
|
39940
|
+
// Stamped on EVERY write, the 304 arm's included: that arm hands back the
|
|
39941
|
+
// bundle it already holds, and the point of the stamp is to describe the
|
|
39942
|
+
// build that last narrowed those bytes, which is this one.
|
|
39943
|
+
shapeId: POLICY_BUNDLE_SHAPE_ID,
|
|
39944
|
+
...etag === void 0 ? {} : { etag }
|
|
39945
|
+
});
|
|
39946
|
+
}
|
|
38960
39947
|
return { read, write, file: file2 };
|
|
38961
39948
|
}
|
|
38962
39949
|
|
|
@@ -39009,7 +39996,7 @@ function createPostureReporter(deps) {
|
|
|
39009
39996
|
}
|
|
39010
39997
|
|
|
39011
39998
|
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
39012
|
-
import { statSync as
|
|
39999
|
+
import { statSync as statSync10 } from "fs";
|
|
39013
40000
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
39014
40001
|
|
|
39015
40002
|
// ../../packages/plugin-runtime/src/attached/action-counts.ts
|
|
@@ -39040,7 +40027,7 @@ function emptyReadout(readError = false) {
|
|
|
39040
40027
|
}
|
|
39041
40028
|
function readStorePosture(dbPath2) {
|
|
39042
40029
|
try {
|
|
39043
|
-
|
|
40030
|
+
statSync10(dbPath2);
|
|
39044
40031
|
} catch (err) {
|
|
39045
40032
|
const code = err.code;
|
|
39046
40033
|
if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
|
|
@@ -39120,16 +40107,16 @@ function readStorePosture(dbPath2) {
|
|
|
39120
40107
|
}
|
|
39121
40108
|
|
|
39122
40109
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
39123
|
-
import { randomUUID as
|
|
40110
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
39124
40111
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
39125
|
-
import { join as
|
|
40112
|
+
import { join as join27 } from "path";
|
|
39126
40113
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
39127
40114
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
39128
|
-
const file2 =
|
|
39129
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
40115
|
+
const file2 = join27(dir, "posture-state.json");
|
|
40116
|
+
const legacyFile = legacyDir === void 0 ? null : join27(legacyDir, "posture-state.json");
|
|
39130
40117
|
async function persist(state) {
|
|
39131
40118
|
await ensureDataDir(dir);
|
|
39132
|
-
const tmp = `${file2}.${
|
|
40119
|
+
const tmp = `${file2}.${randomUUID18()}.tmp`;
|
|
39133
40120
|
try {
|
|
39134
40121
|
await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
39135
40122
|
await publishByRename(tmp, file2);
|
|
@@ -39171,7 +40158,7 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
39171
40158
|
}
|
|
39172
40159
|
return legacy;
|
|
39173
40160
|
}
|
|
39174
|
-
const fresh = { deviceId:
|
|
40161
|
+
const fresh = { deviceId: randomUUID18(), lastAttemptedAtMs: 0 };
|
|
39175
40162
|
try {
|
|
39176
40163
|
await ensureDataDir(dir);
|
|
39177
40164
|
if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
|
|
@@ -39194,8 +40181,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
39194
40181
|
}
|
|
39195
40182
|
|
|
39196
40183
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
39197
|
-
import { readFileSync as
|
|
39198
|
-
import { join as
|
|
40184
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
40185
|
+
import { join as join28 } from "path";
|
|
39199
40186
|
|
|
39200
40187
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
39201
40188
|
var REFUSAL_LINES = {
|
|
@@ -39220,7 +40207,7 @@ var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
|
|
|
39220
40207
|
import { hostname as hostname6 } from "os";
|
|
39221
40208
|
|
|
39222
40209
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
39223
|
-
import { randomUUID as
|
|
40210
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
39224
40211
|
|
|
39225
40212
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
39226
40213
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -39428,7 +40415,7 @@ var StandaloneDataGateway = class {
|
|
|
39428
40415
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
39429
40416
|
const installed = this.installedScanRules();
|
|
39430
40417
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
39431
|
-
id:
|
|
40418
|
+
id: randomUUID19(),
|
|
39432
40419
|
scope: "global",
|
|
39433
40420
|
target: { ruleId },
|
|
39434
40421
|
action,
|
|
@@ -39513,9 +40500,21 @@ var StandaloneDataGateway = class {
|
|
|
39513
40500
|
// for the whole of it, so a member that threw would make that answer a lie
|
|
39514
40501
|
// the moment a composite delegated to it. A store-level no-op is the honest
|
|
39515
40502
|
// shape — a standalone machine has nothing delivered to record.
|
|
40503
|
+
markCaptureOwed(event) {
|
|
40504
|
+
this.db.markCaptureOwed(event);
|
|
40505
|
+
}
|
|
39516
40506
|
markCaptureDelivered(event, atMs) {
|
|
39517
40507
|
this.db.markCaptureDelivered(event, atMs);
|
|
39518
40508
|
}
|
|
40509
|
+
// Implemented, not stubbed, for the same reason its sibling above is: the
|
|
40510
|
+
// attached gateway is a DECORATOR over an instance of this class
|
|
40511
|
+
// (`attached/factory.ts` builds one and passes it as `deps.local`), so every
|
|
40512
|
+
// stamp the live forward makes lands here with a non-empty array. This is the
|
|
40513
|
+
// production write path for that feature, not a shape-satisfying no-op — a
|
|
40514
|
+
// machine that is merely standalone simply never calls it.
|
|
40515
|
+
markAuditEventsDelivered(events, atMs) {
|
|
40516
|
+
this.db.markAuditEventsDelivered(events, atMs);
|
|
40517
|
+
}
|
|
39519
40518
|
staleBinaryNotice(currentVersion) {
|
|
39520
40519
|
try {
|
|
39521
40520
|
const newest = this.db.installedPacks.newestRecordedBinary();
|
|
@@ -39650,7 +40649,7 @@ function resolveDataGateway(config2, meta4, gatewayFactory = defaultGatewayFacto
|
|
|
39650
40649
|
}
|
|
39651
40650
|
|
|
39652
40651
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
39653
|
-
import { randomUUID as
|
|
40652
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
39654
40653
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
39655
40654
|
|
|
39656
40655
|
// src/hooks/store-health.ts
|
|
@@ -39674,12 +40673,12 @@ function claimStoreUnavailableWarning(dataDir2, sessionId) {
|
|
|
39674
40673
|
return true;
|
|
39675
40674
|
}
|
|
39676
40675
|
function markerDirs(dataDir2) {
|
|
39677
|
-
return [dataDir2,
|
|
40676
|
+
return [dataDir2, dirname7(dataDir2)];
|
|
39678
40677
|
}
|
|
39679
40678
|
function alreadyClaimed(dirs, marker, sessionId) {
|
|
39680
40679
|
return dirs.some((dir) => {
|
|
39681
40680
|
try {
|
|
39682
|
-
return
|
|
40681
|
+
return readFileSync20(join29(dir, marker), "utf8") === sessionId;
|
|
39683
40682
|
} catch {
|
|
39684
40683
|
return false;
|
|
39685
40684
|
}
|
|
@@ -39689,7 +40688,7 @@ function recordClaim(dirs, marker, sessionId) {
|
|
|
39689
40688
|
for (const dir of dirs) {
|
|
39690
40689
|
try {
|
|
39691
40690
|
mkdirSync6(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
39692
|
-
writeFileSync9(
|
|
40691
|
+
writeFileSync9(join29(dir, marker), sessionId, { mode: DATA_FILE_MODE });
|
|
39693
40692
|
return;
|
|
39694
40693
|
} catch {
|
|
39695
40694
|
}
|
|
@@ -39714,7 +40713,7 @@ function formatMode(mode) {
|
|
|
39714
40713
|
}
|
|
39715
40714
|
function warnIfStoreRedirected(config2, sessionId, write = (message) => void process.stderr.write(message)) {
|
|
39716
40715
|
try {
|
|
39717
|
-
const paths = symlinkedStorePaths(
|
|
40716
|
+
const paths = symlinkedStorePaths(dirname7(config2.dataDir));
|
|
39718
40717
|
if (paths.length === 0) return;
|
|
39719
40718
|
if (!sessionId) {
|
|
39720
40719
|
write(storeRedirectedMessage(paths));
|
|
@@ -39736,10 +40735,22 @@ async function main() {
|
|
|
39736
40735
|
const rawToolInput = input2.tool_input;
|
|
39737
40736
|
if (typeof rawToolInput !== "object" || rawToolInput === null) return;
|
|
39738
40737
|
const toolInput = rawToolInput;
|
|
40738
|
+
const sessionId = getString(input2, "session_id");
|
|
40739
|
+
let configMemo;
|
|
40740
|
+
const loadConfigOnce = () => configMemo ??= loadConfig();
|
|
40741
|
+
if (await handleSubagentSpawn(
|
|
40742
|
+
() => openGatewayOrNull(loadConfigOnce()),
|
|
40743
|
+
toolName,
|
|
40744
|
+
toolInput,
|
|
40745
|
+
sessionId,
|
|
40746
|
+
getString(input2, "cwd"),
|
|
40747
|
+
emit
|
|
40748
|
+
)) {
|
|
40749
|
+
return;
|
|
40750
|
+
}
|
|
39739
40751
|
const fields = scannableInputFields(toolName, toolInput);
|
|
39740
40752
|
if (fields.length === 0) return;
|
|
39741
|
-
const config2 =
|
|
39742
|
-
const sessionId = getString(input2, "session_id");
|
|
40753
|
+
const config2 = loadConfigOnce();
|
|
39743
40754
|
warnIfStoreRedirected(config2, sessionId);
|
|
39744
40755
|
const consented = isVaultConsentValid(config2.settings.vaultConsent);
|
|
39745
40756
|
const vaultGlue = consented ? createVaultGlue() : null;
|