@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/history-sync.js
CHANGED
|
@@ -497,6 +497,7 @@ import { createHash as createHash4 } from "crypto";
|
|
|
497
497
|
// ../../packages/persistence/src/attached-derived.ts
|
|
498
498
|
import { rmSync } from "fs";
|
|
499
499
|
import { join } from "path";
|
|
500
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
500
501
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
501
502
|
var ATTACHED_HISTORY_SYNC_STATE_FILENAME = "attached-history-sync.json";
|
|
502
503
|
|
|
@@ -597,6 +598,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
597
598
|
{
|
|
598
599
|
tag: "0022_audit_inspection_ms",
|
|
599
600
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
tag: "0023_secret_vault_user_authorized",
|
|
604
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
608
|
+
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`);"
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
612
|
+
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;"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
616
|
+
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;"
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
tag: "0027_audit_llm_usage_index",
|
|
620
|
+
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;"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
tag: "0028_activity_session_probe_indexes",
|
|
624
|
+
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"
|
|
600
625
|
}
|
|
601
626
|
];
|
|
602
627
|
|
|
@@ -22136,6 +22161,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22136
22161
|
AttachTokenExpired,
|
|
22137
22162
|
external_exports.object({ status: printable(64) })
|
|
22138
22163
|
]);
|
|
22164
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22165
|
+
var DeviceCommand = external_exports.object({
|
|
22166
|
+
id: printable(128).min(1),
|
|
22167
|
+
kind: DeviceCommandKind,
|
|
22168
|
+
issuedAt: printable(64).min(1),
|
|
22169
|
+
expiresAt: printable(64).min(1)
|
|
22170
|
+
}).strict();
|
|
22171
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22172
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22173
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22174
|
+
external_exports.object({
|
|
22175
|
+
outcome: external_exports.literal("reported"),
|
|
22176
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22177
|
+
}).strict(),
|
|
22178
|
+
external_exports.object({
|
|
22179
|
+
outcome: external_exports.literal("failed"),
|
|
22180
|
+
reason: DeviceCommandFailureReason,
|
|
22181
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22182
|
+
}).strict()
|
|
22183
|
+
]);
|
|
22139
22184
|
|
|
22140
22185
|
// ../../packages/schema/src/zod/registry.ts
|
|
22141
22186
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22302,7 +22347,7 @@ var PackManifest = external_exports.object({
|
|
|
22302
22347
|
}).meta({ id: "PackManifest" });
|
|
22303
22348
|
|
|
22304
22349
|
// ../../packages/schema/src/zod/detection.ts
|
|
22305
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22350
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22306
22351
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22307
22352
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22308
22353
|
var DetectionCounts = external_exports.object({
|
|
@@ -22439,14 +22484,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22439
22484
|
function isStringArray(value) {
|
|
22440
22485
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22441
22486
|
}
|
|
22487
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22488
|
+
function resolveOrigin(origin) {
|
|
22489
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22490
|
+
}
|
|
22442
22491
|
function summaryToDetectionListItem(s) {
|
|
22443
22492
|
return {
|
|
22444
22493
|
id: `${s.namespace}/${s.packId}`,
|
|
22445
22494
|
name: s.name,
|
|
22446
22495
|
version: s.version,
|
|
22447
22496
|
enabled: s.enabled,
|
|
22448
|
-
origin:
|
|
22449
|
-
// v1: every installed pack is library origin
|
|
22497
|
+
origin: resolveOrigin(s.origin),
|
|
22450
22498
|
namespace: s.namespace,
|
|
22451
22499
|
packId: s.packId,
|
|
22452
22500
|
ruleCount: s.ruleCount,
|
|
@@ -22498,7 +22546,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22498
22546
|
name: row.name,
|
|
22499
22547
|
version: row.version,
|
|
22500
22548
|
enabled: row.enabled,
|
|
22501
|
-
origin:
|
|
22549
|
+
origin: resolveOrigin(row.origin),
|
|
22502
22550
|
namespace: row.namespace,
|
|
22503
22551
|
packId: row.packId,
|
|
22504
22552
|
ruleCount: row.rules.length,
|
|
@@ -22518,16 +22566,20 @@ function splitDetectionId(id) {
|
|
|
22518
22566
|
}
|
|
22519
22567
|
function buildDetectionsList(summaries, query) {
|
|
22520
22568
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22569
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22521
22570
|
const counts = {
|
|
22522
22571
|
all: summaries.length,
|
|
22523
|
-
library: summaries.length,
|
|
22524
|
-
|
|
22525
|
-
|
|
22572
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22573
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22574
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22575
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22576
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22577
|
+
// OriginEnum.
|
|
22526
22578
|
customized: 0,
|
|
22527
22579
|
updates: withUpdate.length
|
|
22528
22580
|
};
|
|
22529
22581
|
const filter = query.filter;
|
|
22530
|
-
let filtered = filter === "custom"
|
|
22582
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22531
22583
|
if (query.q) {
|
|
22532
22584
|
const q = query.q.toLowerCase();
|
|
22533
22585
|
filtered = filtered.filter(
|
|
@@ -22607,8 +22659,9 @@ var Event = external_exports.object({
|
|
|
22607
22659
|
metadata: EventMetadata.optional()
|
|
22608
22660
|
}).meta({ id: "Event" });
|
|
22609
22661
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22662
|
+
var INGEST_BATCH_MAX = 100;
|
|
22610
22663
|
var IngestBatch = external_exports.object({
|
|
22611
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22664
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22612
22665
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22613
22666
|
// additionally rejects any event whose contentHash the store has already
|
|
22614
22667
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23164,6 +23217,252 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23164
23217
|
message: "At least one field must be provided"
|
|
23165
23218
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23166
23219
|
|
|
23220
|
+
// ../../packages/schema/src/zod/policy.ts
|
|
23221
|
+
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23222
|
+
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23223
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23224
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23225
|
+
var Policy = external_exports.object({
|
|
23226
|
+
id: external_exports.guid(),
|
|
23227
|
+
scope: PolicyScope,
|
|
23228
|
+
target: PolicyTarget,
|
|
23229
|
+
action: ActionTaken,
|
|
23230
|
+
enabled: external_exports.boolean().default(true),
|
|
23231
|
+
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23232
|
+
// Display name — optional so older policy rows without name still parse.
|
|
23233
|
+
// Added for the findings API (policy.name column migration).
|
|
23234
|
+
name: external_exports.string().optional(),
|
|
23235
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23236
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23237
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23238
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23239
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23240
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23241
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23242
|
+
//
|
|
23243
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23244
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23245
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23246
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23247
|
+
// — the CRUD routes neither accept nor set it.
|
|
23248
|
+
//
|
|
23249
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23250
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23251
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23252
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23253
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23254
|
+
provenance: PolicyProvenance.optional()
|
|
23255
|
+
}).meta({ id: "Policy" });
|
|
23256
|
+
var PolicyBundle = external_exports.object({
|
|
23257
|
+
version: external_exports.string(),
|
|
23258
|
+
policies: external_exports.array(Policy),
|
|
23259
|
+
// Rules from the installed marketplace packs (snapshotted by the
|
|
23260
|
+
// control plane). The plugin registers these in addition to its bundled
|
|
23261
|
+
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23262
|
+
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23263
|
+
rules: external_exports.array(Rule).optional(),
|
|
23264
|
+
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23265
|
+
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23266
|
+
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23267
|
+
// packs only), which is how detection updates stay manual: new bundled
|
|
23268
|
+
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23269
|
+
// the historical composition (bundled packs + rules) — older caches.
|
|
23270
|
+
rulesComplete: external_exports.boolean().optional(),
|
|
23271
|
+
// Active detection exceptions, evaluation subset only (see
|
|
23272
|
+
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23273
|
+
// on-disk caches — that omit the field still parse; consumers read
|
|
23274
|
+
// `bundle.exceptions ?? []`.
|
|
23275
|
+
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23276
|
+
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23277
|
+
// A second axis over the same `redact` action, carried beside the policies
|
|
23278
|
+
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23279
|
+
// widening Policy itself would change a persisted shape to express something
|
|
23280
|
+
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23281
|
+
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23282
|
+
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23283
|
+
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23284
|
+
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23285
|
+
// from a versioned installed pack. Optional so older backends — and older
|
|
23286
|
+
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23287
|
+
// the rule's own spec version. NOT the bundle version above — see
|
|
23288
|
+
// installedRuleset's ruleVersions for the source of truth.
|
|
23289
|
+
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23290
|
+
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23291
|
+
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23292
|
+
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23293
|
+
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23294
|
+
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23295
|
+
// unenforced behaviour that predates this field and the safe direction to
|
|
23296
|
+
// default.
|
|
23297
|
+
//
|
|
23298
|
+
// Ids, not display names: the governance decision is keyed on the exact
|
|
23299
|
+
// string the harness reports (`model_status_override.versionId` in the
|
|
23300
|
+
// control plane), so no name resolution stands between the decision and the
|
|
23301
|
+
// comparison.
|
|
23302
|
+
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23303
|
+
customKeywords: external_exports.array(external_exports.string()),
|
|
23304
|
+
fetchedAt: external_exports.iso.datetime()
|
|
23305
|
+
}).meta({ id: "PolicyBundle" });
|
|
23306
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23307
|
+
...Object.keys(PolicyBundle.shape),
|
|
23308
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23309
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23310
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23311
|
+
].sort().join(",");
|
|
23312
|
+
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23313
|
+
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23314
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
23315
|
+
secret: "critical",
|
|
23316
|
+
financial: "critical",
|
|
23317
|
+
// core-financial/credit-card
|
|
23318
|
+
code_flaw: "critical",
|
|
23319
|
+
pii: "high",
|
|
23320
|
+
phi: "high",
|
|
23321
|
+
custom: "high",
|
|
23322
|
+
// user-defined; conservative
|
|
23323
|
+
code_context: "low",
|
|
23324
|
+
config: "low"
|
|
23325
|
+
// observe-only; floors to monitor regardless
|
|
23326
|
+
};
|
|
23327
|
+
function severityFloorPolicy(category) {
|
|
23328
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23329
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23330
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23331
|
+
}
|
|
23332
|
+
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23333
|
+
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23334
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23335
|
+
id: "RedactFallback"
|
|
23336
|
+
});
|
|
23337
|
+
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23338
|
+
var BUILTIN_POLICY_SPECS = {
|
|
23339
|
+
monitor: {
|
|
23340
|
+
name: "Monitor",
|
|
23341
|
+
action: "log",
|
|
23342
|
+
reversible: false,
|
|
23343
|
+
description: "Log every match for audit. The request is allowed through untouched."
|
|
23344
|
+
},
|
|
23345
|
+
warn: {
|
|
23346
|
+
name: "Warn",
|
|
23347
|
+
action: "warn",
|
|
23348
|
+
reversible: false,
|
|
23349
|
+
description: "Allow the request, but warn the user inline before it is sent."
|
|
23350
|
+
},
|
|
23351
|
+
redact: {
|
|
23352
|
+
name: "Redact",
|
|
23353
|
+
action: "redact",
|
|
23354
|
+
reversible: false,
|
|
23355
|
+
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23356
|
+
},
|
|
23357
|
+
vault: {
|
|
23358
|
+
name: "Redact & Vault",
|
|
23359
|
+
action: "redact",
|
|
23360
|
+
reversible: true,
|
|
23361
|
+
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."
|
|
23362
|
+
},
|
|
23363
|
+
block: {
|
|
23364
|
+
name: "Block",
|
|
23365
|
+
action: "block",
|
|
23366
|
+
reversible: false,
|
|
23367
|
+
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23368
|
+
}
|
|
23369
|
+
};
|
|
23370
|
+
function builtinPolicyToAction(id) {
|
|
23371
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
23372
|
+
}
|
|
23373
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23374
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23375
|
+
];
|
|
23376
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23377
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23378
|
+
);
|
|
23379
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23380
|
+
...BELOW_PALETTE,
|
|
23381
|
+
...PALETTE_WEAKEST_FIRST
|
|
23382
|
+
];
|
|
23383
|
+
function actionRank(action) {
|
|
23384
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23385
|
+
}
|
|
23386
|
+
function isActionAtLeast(action, floor) {
|
|
23387
|
+
return actionRank(action) >= actionRank(floor);
|
|
23388
|
+
}
|
|
23389
|
+
function strongerAction(a, b) {
|
|
23390
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23391
|
+
}
|
|
23392
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23393
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23394
|
+
}
|
|
23395
|
+
var PackPolicyFloor = external_exports.object({
|
|
23396
|
+
/**
|
|
23397
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23398
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23399
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23400
|
+
*/
|
|
23401
|
+
floor: BuiltinPolicyId,
|
|
23402
|
+
/**
|
|
23403
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23404
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23405
|
+
* re-assignable locally in either direction.
|
|
23406
|
+
*/
|
|
23407
|
+
locked: external_exports.boolean()
|
|
23408
|
+
}).describe("PackPolicyFloor");
|
|
23409
|
+
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23410
|
+
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23411
|
+
);
|
|
23412
|
+
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23413
|
+
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23414
|
+
);
|
|
23415
|
+
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23416
|
+
function builtinPolicyIsReversible(id) {
|
|
23417
|
+
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23418
|
+
}
|
|
23419
|
+
function policyIdIsReversible(policyId) {
|
|
23420
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23421
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23422
|
+
return builtinPolicyIsReversible(id);
|
|
23423
|
+
}
|
|
23424
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23425
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23426
|
+
);
|
|
23427
|
+
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23428
|
+
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23429
|
+
);
|
|
23430
|
+
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23431
|
+
function policyIdToAction(policyId) {
|
|
23432
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23433
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23434
|
+
return BUILTIN_POLICIES[id].action;
|
|
23435
|
+
}
|
|
23436
|
+
var UsedByItem = external_exports.object({
|
|
23437
|
+
id: external_exports.string(),
|
|
23438
|
+
name: external_exports.string(),
|
|
23439
|
+
ruleCount: external_exports.number().int().nonnegative(),
|
|
23440
|
+
enabled: external_exports.boolean()
|
|
23441
|
+
}).meta({ id: "UsedByItem" });
|
|
23442
|
+
var PolicyListItem = external_exports.object({
|
|
23443
|
+
id: external_exports.string(),
|
|
23444
|
+
kind: PolicyKind,
|
|
23445
|
+
name: external_exports.string(),
|
|
23446
|
+
enabled: external_exports.boolean(),
|
|
23447
|
+
usedByCount: external_exports.number().int().nonnegative()
|
|
23448
|
+
}).meta({ id: "PolicyListItem" });
|
|
23449
|
+
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23450
|
+
var PolicyDetail = external_exports.object({
|
|
23451
|
+
specVersion: external_exports.literal(1),
|
|
23452
|
+
id: external_exports.string(),
|
|
23453
|
+
kind: PolicyKind,
|
|
23454
|
+
name: external_exports.string(),
|
|
23455
|
+
enabled: external_exports.boolean(),
|
|
23456
|
+
description: external_exports.string(),
|
|
23457
|
+
usedBy: external_exports.array(UsedByItem)
|
|
23458
|
+
}).meta({ id: "PolicyDetail" });
|
|
23459
|
+
var PolicyStatsResponse = external_exports.object({
|
|
23460
|
+
policies: external_exports.number().int().nonnegative(),
|
|
23461
|
+
builtin: external_exports.number().int().nonnegative(),
|
|
23462
|
+
custom: external_exports.number().int().nonnegative(),
|
|
23463
|
+
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23464
|
+
}).meta({ id: "PolicyStatsResponse" });
|
|
23465
|
+
|
|
23167
23466
|
// ../../packages/schema/src/zod/vault.ts
|
|
23168
23467
|
var POINTER_FORMAT_VERSION = 2;
|
|
23169
23468
|
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
@@ -23201,6 +23500,14 @@ var VaultEntry = external_exports.object({
|
|
|
23201
23500
|
// How many times this value has been detected on this machine — the reuse
|
|
23202
23501
|
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23203
23502
|
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23503
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23504
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23505
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23506
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23507
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23508
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23509
|
+
// the value does not expire.
|
|
23510
|
+
userAuthorized: external_exports.boolean(),
|
|
23204
23511
|
firstSeen: external_exports.string(),
|
|
23205
23512
|
lastSeen: external_exports.string()
|
|
23206
23513
|
});
|
|
@@ -23319,9 +23626,9 @@ var VaultConsent = external_exports.object({
|
|
|
23319
23626
|
});
|
|
23320
23627
|
|
|
23321
23628
|
// ../../packages/schema/src/zod/local.ts
|
|
23322
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
23629
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23323
23630
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23324
|
-
var HISTORY_SYNC_PAYLOAD_VERSION =
|
|
23631
|
+
var HISTORY_SYNC_PAYLOAD_VERSION = 2;
|
|
23325
23632
|
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23326
23633
|
var ControlPlaneConnection = external_exports.object({
|
|
23327
23634
|
endpoint: external_exports.string().min(1),
|
|
@@ -23366,6 +23673,19 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23366
23673
|
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23367
23674
|
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23368
23675
|
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23676
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23677
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23678
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23679
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23680
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23681
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23682
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23683
|
+
//
|
|
23684
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23685
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23686
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23687
|
+
// word and stays out of the stored value.
|
|
23688
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23369
23689
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23370
23690
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23371
23691
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -23373,10 +23693,12 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23373
23693
|
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23374
23694
|
// covers the current payload and must be re-granted.
|
|
23375
23695
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23376
|
-
// Records that the user consented to
|
|
23377
|
-
//
|
|
23378
|
-
//
|
|
23379
|
-
//
|
|
23696
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23697
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23698
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23699
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23700
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23701
|
+
// payload no longer counts.
|
|
23380
23702
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23381
23703
|
});
|
|
23382
23704
|
function defaultWorkspaceSettings() {
|
|
@@ -23504,7 +23826,8 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23504
23826
|
"vaultKeyCustody",
|
|
23505
23827
|
"vaultInlineReveal",
|
|
23506
23828
|
"modelJudgeConsent",
|
|
23507
|
-
"dataSharesInPlace"
|
|
23829
|
+
"dataSharesInPlace",
|
|
23830
|
+
"redactFallback"
|
|
23508
23831
|
]).meta({ id: "ManagedSettingKey" });
|
|
23509
23832
|
var ManagedSettingsValues = external_exports.object({
|
|
23510
23833
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
@@ -23517,7 +23840,8 @@ var ManagedSettingsValues = external_exports.object({
|
|
|
23517
23840
|
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23518
23841
|
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23519
23842
|
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23520
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23843
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23844
|
+
redactFallback: RedactFallback.optional()
|
|
23521
23845
|
}).meta({ id: "ManagedSettingsValues" });
|
|
23522
23846
|
var ManagedSettings = external_exports.object({
|
|
23523
23847
|
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
@@ -23532,186 +23856,6 @@ var ManagedSettings = external_exports.object({
|
|
|
23532
23856
|
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23533
23857
|
}).meta({ id: "ManagedSettings" });
|
|
23534
23858
|
|
|
23535
|
-
// ../../packages/schema/src/zod/policy.ts
|
|
23536
|
-
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23537
|
-
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23538
|
-
var Policy = external_exports.object({
|
|
23539
|
-
id: external_exports.guid(),
|
|
23540
|
-
scope: PolicyScope,
|
|
23541
|
-
target: PolicyTarget,
|
|
23542
|
-
action: ActionTaken,
|
|
23543
|
-
enabled: external_exports.boolean().default(true),
|
|
23544
|
-
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23545
|
-
// Display name — optional so older policy rows without name still parse.
|
|
23546
|
-
// Added for the findings API (policy.name column migration).
|
|
23547
|
-
name: external_exports.string().optional()
|
|
23548
|
-
}).meta({ id: "Policy" });
|
|
23549
|
-
var PolicyBundle = external_exports.object({
|
|
23550
|
-
version: external_exports.string(),
|
|
23551
|
-
policies: external_exports.array(Policy),
|
|
23552
|
-
// Rules from the installed marketplace packs (snapshotted by the
|
|
23553
|
-
// control plane). The plugin registers these in addition to its bundled
|
|
23554
|
-
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
23555
|
-
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
23556
|
-
rules: external_exports.array(Rule).optional(),
|
|
23557
|
-
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
23558
|
-
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
23559
|
-
// after reading the user's installed snapshot (installed_packs, enabled
|
|
23560
|
-
// packs only), which is how detection updates stay manual: new bundled
|
|
23561
|
-
// rules run only after the user applies the pack update. Absent/false keeps
|
|
23562
|
-
// the historical composition (bundled packs + rules) — older caches.
|
|
23563
|
-
rulesComplete: external_exports.boolean().optional(),
|
|
23564
|
-
// Active detection exceptions, evaluation subset only (see
|
|
23565
|
-
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
23566
|
-
// on-disk caches — that omit the field still parse; consumers read
|
|
23567
|
-
// `bundle.exceptions ?? []`.
|
|
23568
|
-
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
23569
|
-
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
23570
|
-
// A second axis over the same `redact` action, carried beside the policies
|
|
23571
|
-
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
23572
|
-
// widening Policy itself would change a persisted shape to express something
|
|
23573
|
-
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
23574
|
-
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
23575
|
-
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
23576
|
-
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
23577
|
-
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
23578
|
-
// from a versioned installed pack. Optional so older backends — and older
|
|
23579
|
-
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
23580
|
-
// the rule's own spec version. NOT the bundle version above — see
|
|
23581
|
-
// installedRuleset's ruleVersions for the source of truth.
|
|
23582
|
-
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
23583
|
-
// Model ids (the raw `model` string a harness reports, e.g.
|
|
23584
|
-
// `claude-opus-4-1`) the tenant has PROHIBITED. The plugin refuses to switch
|
|
23585
|
-
// a session onto one (PreModelSwitch) and refuses a turn that would run on
|
|
23586
|
-
// one (UserPromptSubmit). Optional so an older backend — and an older
|
|
23587
|
-
// on-disk cache — still parses; consumers read `?? []`, which is the
|
|
23588
|
-
// unenforced behaviour that predates this field and the safe direction to
|
|
23589
|
-
// default.
|
|
23590
|
-
//
|
|
23591
|
-
// Ids, not display names: the governance decision is keyed on the exact
|
|
23592
|
-
// string the harness reports (`model_status_override.versionId` in the
|
|
23593
|
-
// control plane), so no name resolution stands between the decision and the
|
|
23594
|
-
// comparison.
|
|
23595
|
-
prohibitedModels: external_exports.array(external_exports.string()).optional(),
|
|
23596
|
-
customKeywords: external_exports.array(external_exports.string()),
|
|
23597
|
-
fetchedAt: external_exports.iso.datetime()
|
|
23598
|
-
}).meta({ id: "PolicyBundle" });
|
|
23599
|
-
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23600
|
-
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23601
|
-
var CATEGORY_PEAK_SEVERITY = {
|
|
23602
|
-
secret: "critical",
|
|
23603
|
-
financial: "critical",
|
|
23604
|
-
// core-financial/credit-card
|
|
23605
|
-
code_flaw: "critical",
|
|
23606
|
-
pii: "high",
|
|
23607
|
-
phi: "high",
|
|
23608
|
-
custom: "high",
|
|
23609
|
-
// user-defined; conservative
|
|
23610
|
-
code_context: "low",
|
|
23611
|
-
config: "low"
|
|
23612
|
-
// observe-only; floors to monitor regardless
|
|
23613
|
-
};
|
|
23614
|
-
function severityFloorPolicy(category) {
|
|
23615
|
-
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
23616
|
-
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23617
|
-
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23618
|
-
}
|
|
23619
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23620
|
-
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23621
|
-
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23622
|
-
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23623
|
-
var BUILTIN_POLICY_SPECS = {
|
|
23624
|
-
monitor: {
|
|
23625
|
-
name: "Monitor",
|
|
23626
|
-
action: "log",
|
|
23627
|
-
reversible: false,
|
|
23628
|
-
description: "Log every match for audit. The request is allowed through untouched."
|
|
23629
|
-
},
|
|
23630
|
-
warn: {
|
|
23631
|
-
name: "Warn",
|
|
23632
|
-
action: "warn",
|
|
23633
|
-
reversible: false,
|
|
23634
|
-
description: "Allow the request, but warn the user inline before it is sent."
|
|
23635
|
-
},
|
|
23636
|
-
redact: {
|
|
23637
|
-
name: "Redact",
|
|
23638
|
-
action: "redact",
|
|
23639
|
-
reversible: false,
|
|
23640
|
-
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
23641
|
-
},
|
|
23642
|
-
vault: {
|
|
23643
|
-
name: "Redact & Vault",
|
|
23644
|
-
action: "redact",
|
|
23645
|
-
reversible: true,
|
|
23646
|
-
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."
|
|
23647
|
-
},
|
|
23648
|
-
block: {
|
|
23649
|
-
name: "Block",
|
|
23650
|
-
action: "block",
|
|
23651
|
-
reversible: false,
|
|
23652
|
-
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
23653
|
-
}
|
|
23654
|
-
};
|
|
23655
|
-
function builtinPolicyToAction(id) {
|
|
23656
|
-
return BUILTIN_POLICY_SPECS[id].action;
|
|
23657
|
-
}
|
|
23658
|
-
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23659
|
-
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23660
|
-
);
|
|
23661
|
-
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23662
|
-
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
23663
|
-
);
|
|
23664
|
-
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
23665
|
-
function builtinPolicyIsReversible(id) {
|
|
23666
|
-
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
23667
|
-
}
|
|
23668
|
-
function policyIdIsReversible(policyId) {
|
|
23669
|
-
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23670
|
-
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23671
|
-
return builtinPolicyIsReversible(id);
|
|
23672
|
-
}
|
|
23673
|
-
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
23674
|
-
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
23675
|
-
);
|
|
23676
|
-
var BUILTIN_POLICIES = Object.fromEntries(
|
|
23677
|
-
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
23678
|
-
);
|
|
23679
|
-
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
23680
|
-
function policyIdToAction(policyId) {
|
|
23681
|
-
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
23682
|
-
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
23683
|
-
return BUILTIN_POLICIES[id].action;
|
|
23684
|
-
}
|
|
23685
|
-
var UsedByItem = external_exports.object({
|
|
23686
|
-
id: external_exports.string(),
|
|
23687
|
-
name: external_exports.string(),
|
|
23688
|
-
ruleCount: external_exports.number().int().nonnegative(),
|
|
23689
|
-
enabled: external_exports.boolean()
|
|
23690
|
-
}).meta({ id: "UsedByItem" });
|
|
23691
|
-
var PolicyListItem = external_exports.object({
|
|
23692
|
-
id: external_exports.string(),
|
|
23693
|
-
kind: PolicyKind,
|
|
23694
|
-
name: external_exports.string(),
|
|
23695
|
-
enabled: external_exports.boolean(),
|
|
23696
|
-
usedByCount: external_exports.number().int().nonnegative()
|
|
23697
|
-
}).meta({ id: "PolicyListItem" });
|
|
23698
|
-
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
23699
|
-
var PolicyDetail = external_exports.object({
|
|
23700
|
-
specVersion: external_exports.literal(1),
|
|
23701
|
-
id: external_exports.string(),
|
|
23702
|
-
kind: PolicyKind,
|
|
23703
|
-
name: external_exports.string(),
|
|
23704
|
-
enabled: external_exports.boolean(),
|
|
23705
|
-
description: external_exports.string(),
|
|
23706
|
-
usedBy: external_exports.array(UsedByItem)
|
|
23707
|
-
}).meta({ id: "PolicyDetail" });
|
|
23708
|
-
var PolicyStatsResponse = external_exports.object({
|
|
23709
|
-
policies: external_exports.number().int().nonnegative(),
|
|
23710
|
-
builtin: external_exports.number().int().nonnegative(),
|
|
23711
|
-
custom: external_exports.number().int().nonnegative(),
|
|
23712
|
-
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23713
|
-
}).meta({ id: "PolicyStatsResponse" });
|
|
23714
|
-
|
|
23715
23859
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23716
23860
|
var ProjectFileInput = external_exports.object({
|
|
23717
23861
|
path: external_exports.string().min(1),
|
|
@@ -23957,10 +24101,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
23957
24101
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
23958
24102
|
|
|
23959
24103
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24104
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24105
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
23960
24106
|
var SaveSettingsInput = external_exports.object({
|
|
23961
24107
|
historicalAccess: external_exports.string(),
|
|
23962
|
-
modelJudgeConsent:
|
|
23963
|
-
historySyncConsent:
|
|
24108
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24109
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
23964
24110
|
vaultConsent: external_exports.string(),
|
|
23965
24111
|
vaultInlineReveal: external_exports.string()
|
|
23966
24112
|
});
|
|
@@ -24110,9 +24256,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24110
24256
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24111
24257
|
return reasons;
|
|
24112
24258
|
}
|
|
24113
|
-
function buildReviewInfo(trust, transports) {
|
|
24259
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24114
24260
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24115
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24261
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24116
24262
|
}
|
|
24117
24263
|
function distinctTransports(transports) {
|
|
24118
24264
|
return Array.from(new Set(transports));
|
|
@@ -24269,8 +24415,8 @@ function readControlPlaneCredentialFile(settingsDir2, connection) {
|
|
|
24269
24415
|
}
|
|
24270
24416
|
|
|
24271
24417
|
// ../../packages/persistence/src/database.ts
|
|
24272
|
-
import { randomUUID as
|
|
24273
|
-
import { join as
|
|
24418
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24419
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24274
24420
|
import { DatabaseSync } from "node:sqlite";
|
|
24275
24421
|
|
|
24276
24422
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24303,6 +24449,16 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
24303
24449
|
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
24304
24450
|
);
|
|
24305
24451
|
}
|
|
24452
|
+
function uuidFromDigest(digest) {
|
|
24453
|
+
const h = digest.slice(0, 32);
|
|
24454
|
+
const version2 = "8";
|
|
24455
|
+
const variant = (Number.parseInt(h.charAt(16), 16) & 3 | 8).toString(16);
|
|
24456
|
+
const b = h.slice(0, 12) + version2 + h.slice(13, 16) + variant + h.slice(17, 32);
|
|
24457
|
+
return `${b.slice(0, 8)}-${b.slice(8, 12)}-${b.slice(12, 16)}-${b.slice(16, 20)}-${b.slice(20, 32)}`;
|
|
24458
|
+
}
|
|
24459
|
+
function captureWireId(sessionId, contentHash, filePath = null) {
|
|
24460
|
+
return uuidFromDigest(captureId(sessionId, contentHash, filePath));
|
|
24461
|
+
}
|
|
24306
24462
|
|
|
24307
24463
|
// ../../packages/persistence/src/internal/snapshot.ts
|
|
24308
24464
|
import { randomUUID } from "crypto";
|
|
@@ -24514,6 +24670,10 @@ function allRows(stmt, params) {
|
|
|
24514
24670
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24515
24671
|
return stmt.all(params);
|
|
24516
24672
|
}
|
|
24673
|
+
function* iterateRows(stmt, params) {
|
|
24674
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24675
|
+
for (const row of rows) yield row;
|
|
24676
|
+
}
|
|
24517
24677
|
function getRow(stmt, params) {
|
|
24518
24678
|
if (params === void 0) return stmt.get();
|
|
24519
24679
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -24982,10 +25142,17 @@ function ensureSyncedAtColumn(db, table) {
|
|
|
24982
25142
|
if (!columns.includes("sync_claimed_at")) {
|
|
24983
25143
|
db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
|
|
24984
25144
|
}
|
|
25145
|
+
if (!columns.includes("outbox_owed")) {
|
|
25146
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
|
|
25147
|
+
}
|
|
24985
25148
|
db.exec(
|
|
24986
25149
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
24987
25150
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
24988
25151
|
);
|
|
25152
|
+
db.exec(
|
|
25153
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25154
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25155
|
+
);
|
|
24989
25156
|
db.exec(
|
|
24990
25157
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
24991
25158
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25090,7 +25257,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25090
25257
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25091
25258
|
var DAY_MS = 864e5;
|
|
25092
25259
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25093
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25094
25260
|
function defaultTimeZone() {
|
|
25095
25261
|
try {
|
|
25096
25262
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25145,6 +25311,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25145
25311
|
error: "error",
|
|
25146
25312
|
active: "active"
|
|
25147
25313
|
};
|
|
25314
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25148
25315
|
function safeParseStringArray(raw) {
|
|
25149
25316
|
if (!raw) return [];
|
|
25150
25317
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25218,6 +25385,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25218
25385
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25219
25386
|
json_extract(attributes, '$.internal') AS internal,
|
|
25220
25387
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25388
|
+
var LLM_USAGE_SELECT = `
|
|
25389
|
+
SELECT root_session_id AS sessionId,
|
|
25390
|
+
provider,
|
|
25391
|
+
model,
|
|
25392
|
+
service_tier AS serviceTier,
|
|
25393
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25394
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25395
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25396
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25397
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25398
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25399
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25400
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25401
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25402
|
+
function usageLeaves(rows) {
|
|
25403
|
+
return rows.map((row) => {
|
|
25404
|
+
const attributes = {
|
|
25405
|
+
input_tokens: row.inputTokens,
|
|
25406
|
+
output_tokens: row.outputTokens,
|
|
25407
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25408
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25409
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25410
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25411
|
+
web_search_requests: row.webSearchRequests
|
|
25412
|
+
};
|
|
25413
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25414
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25415
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25416
|
+
return { sessionId: row.sessionId, attributes };
|
|
25417
|
+
});
|
|
25418
|
+
}
|
|
25221
25419
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25222
25420
|
var HAS_ACTIVITY = `EXISTS (
|
|
25223
25421
|
SELECT 1 FROM audit_events c
|
|
@@ -25243,16 +25441,17 @@ var SqliteActivityRepository = class {
|
|
|
25243
25441
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25244
25442
|
const liveNow = countScalar(
|
|
25245
25443
|
this.db,
|
|
25246
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25444
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25247
25445
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25248
|
-
AND
|
|
25249
|
-
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25446
|
+
AND s.id IN (
|
|
25447
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25448
|
+
UNION
|
|
25449
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25450
|
+
WHERE started_at >= ?
|
|
25451
|
+
UNION
|
|
25452
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25453
|
+
WHERE ended_at >= ?)`,
|
|
25454
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25256
25455
|
);
|
|
25257
25456
|
const toolCallsToday = countScalar(
|
|
25258
25457
|
this.db,
|
|
@@ -25382,7 +25581,7 @@ var SqliteActivityRepository = class {
|
|
|
25382
25581
|
this.db.prepare(
|
|
25383
25582
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25384
25583
|
FROM audit_events
|
|
25385
|
-
WHERE id = ? OR root_session_id = ?
|
|
25584
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25386
25585
|
ORDER BY started_at ASC, id ASC`
|
|
25387
25586
|
),
|
|
25388
25587
|
[sessionId, sessionId]
|
|
@@ -25395,14 +25594,14 @@ var SqliteActivityRepository = class {
|
|
|
25395
25594
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25396
25595
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25397
25596
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25398
|
-
FROM audit_events
|
|
25597
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25399
25598
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25400
25599
|
),
|
|
25401
25600
|
[sessionId]
|
|
25402
25601
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25403
25602
|
const primaryModel = getRow(
|
|
25404
25603
|
this.db.prepare(
|
|
25405
|
-
`SELECT model, provider FROM audit_events
|
|
25604
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25406
25605
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25407
25606
|
ORDER BY started_at ASC, id ASC
|
|
25408
25607
|
LIMIT 1`
|
|
@@ -25413,7 +25612,7 @@ var SqliteActivityRepository = class {
|
|
|
25413
25612
|
this.db.prepare(
|
|
25414
25613
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25415
25614
|
count(*) AS n
|
|
25416
|
-
FROM audit_events
|
|
25615
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25417
25616
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25418
25617
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25419
25618
|
),
|
|
@@ -25421,7 +25620,7 @@ var SqliteActivityRepository = class {
|
|
|
25421
25620
|
);
|
|
25422
25621
|
const modelRows = allRows(
|
|
25423
25622
|
this.db.prepare(
|
|
25424
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25623
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25425
25624
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25426
25625
|
ORDER BY model`
|
|
25427
25626
|
),
|
|
@@ -25430,7 +25629,7 @@ var SqliteActivityRepository = class {
|
|
|
25430
25629
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25431
25630
|
const commits = countScalar(
|
|
25432
25631
|
this.db,
|
|
25433
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25632
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25434
25633
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25435
25634
|
[sessionId]
|
|
25436
25635
|
);
|
|
@@ -25466,25 +25665,57 @@ var SqliteActivityRepository = class {
|
|
|
25466
25665
|
return Promise.resolve(session);
|
|
25467
25666
|
}
|
|
25468
25667
|
/**
|
|
25469
|
-
* Cross-session token report — every `llm_call`
|
|
25470
|
-
* `started_at >= fromMs`
|
|
25471
|
-
* USD cost DERIVED at read time via the shared
|
|
25472
|
-
*
|
|
25473
|
-
*
|
|
25474
|
-
*
|
|
25668
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25669
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25670
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25671
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25672
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25673
|
+
*
|
|
25674
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25675
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25676
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25677
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25678
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25679
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25680
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25681
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25682
|
+
* recompute the columns it could have read. The index is one every open
|
|
25683
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25684
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25685
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25686
|
+
* per call, no bag parsed.
|
|
25475
25687
|
*/
|
|
25476
25688
|
tokenReports(fromMs) {
|
|
25477
|
-
const
|
|
25478
|
-
|
|
25689
|
+
const rows = allRows(
|
|
25690
|
+
this.db.prepare(
|
|
25691
|
+
`${LLM_USAGE_SELECT}
|
|
25692
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25693
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25694
|
+
${LLM_USAGE_GROUP}`
|
|
25695
|
+
),
|
|
25696
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25697
|
+
);
|
|
25698
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25479
25699
|
}
|
|
25480
25700
|
/**
|
|
25481
|
-
* One session's token report — its `llm_call`
|
|
25482
|
-
* model) with derived cost, or `null` when the session made no
|
|
25483
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25484
|
-
* breakdown + estimated cost.
|
|
25701
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25702
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25703
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25704
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25705
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25706
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25485
25707
|
*/
|
|
25486
25708
|
tokenReportForSession(sessionId) {
|
|
25487
|
-
const
|
|
25709
|
+
const rows = allRows(
|
|
25710
|
+
this.db.prepare(
|
|
25711
|
+
`${LLM_USAGE_SELECT}
|
|
25712
|
+
FROM audit_events
|
|
25713
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25714
|
+
${LLM_USAGE_GROUP}`
|
|
25715
|
+
),
|
|
25716
|
+
[sessionId]
|
|
25717
|
+
);
|
|
25718
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25488
25719
|
return Promise.resolve(reports[0] ?? null);
|
|
25489
25720
|
}
|
|
25490
25721
|
/**
|
|
@@ -25508,42 +25739,6 @@ var SqliteActivityRepository = class {
|
|
|
25508
25739
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25509
25740
|
return Promise.resolve([...seen]);
|
|
25510
25741
|
}
|
|
25511
|
-
/**
|
|
25512
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25513
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25514
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25515
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25516
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25517
|
-
*/
|
|
25518
|
-
readLlmCallLeaves(opts = {}) {
|
|
25519
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25520
|
-
const params = [];
|
|
25521
|
-
if (opts.sessionId !== void 0) {
|
|
25522
|
-
conditions.push("root_session_id = ?");
|
|
25523
|
-
params.push(opts.sessionId);
|
|
25524
|
-
}
|
|
25525
|
-
if (opts.fromMs !== void 0) {
|
|
25526
|
-
conditions.push("started_at >= ?");
|
|
25527
|
-
params.push(opts.fromMs);
|
|
25528
|
-
}
|
|
25529
|
-
const rows = allRows(
|
|
25530
|
-
this.db.prepare(
|
|
25531
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25532
|
-
FROM audit_events
|
|
25533
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25534
|
-
),
|
|
25535
|
-
params
|
|
25536
|
-
);
|
|
25537
|
-
return mapRowsTolerant(
|
|
25538
|
-
rows.filter(
|
|
25539
|
-
(row) => row.sessionId !== null
|
|
25540
|
-
),
|
|
25541
|
-
(row) => ({
|
|
25542
|
-
sessionId: row.sessionId,
|
|
25543
|
-
attributes: JSON.parse(row.attributes)
|
|
25544
|
-
})
|
|
25545
|
-
);
|
|
25546
|
-
}
|
|
25547
25742
|
/**
|
|
25548
25743
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25549
25744
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25558,20 +25753,23 @@ var SqliteActivityRepository = class {
|
|
|
25558
25753
|
const inClause = placeholders(sessionIds.length);
|
|
25559
25754
|
const lastActivityRows = allRows(
|
|
25560
25755
|
this.db.prepare(
|
|
25561
|
-
`SELECT
|
|
25562
|
-
|
|
25563
|
-
|
|
25756
|
+
`SELECT ids.value AS id,
|
|
25757
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25758
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25759
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25760
|
+
FROM json_each(?) AS ids`
|
|
25564
25761
|
),
|
|
25565
|
-
sessionIds
|
|
25762
|
+
[JSON.stringify(sessionIds)]
|
|
25566
25763
|
);
|
|
25567
25764
|
for (const row of lastActivityRows) {
|
|
25568
|
-
if (row.id === null) continue;
|
|
25569
25765
|
const entry = result.get(row.id);
|
|
25570
|
-
|
|
25766
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25767
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25571
25768
|
}
|
|
25572
25769
|
const turnsRows = allRows(
|
|
25573
25770
|
this.db.prepare(
|
|
25574
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25771
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25772
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25575
25773
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25576
25774
|
GROUP BY root_session_id`
|
|
25577
25775
|
),
|
|
@@ -25586,7 +25784,7 @@ var SqliteActivityRepository = class {
|
|
|
25586
25784
|
this.db.prepare(
|
|
25587
25785
|
`SELECT root_session_id AS id,
|
|
25588
25786
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25589
|
-
FROM audit_events
|
|
25787
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25590
25788
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25591
25789
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25592
25790
|
GROUP BY root_session_id`
|
|
@@ -25616,7 +25814,7 @@ var SqliteActivityRepository = class {
|
|
|
25616
25814
|
this.db.prepare(
|
|
25617
25815
|
`SELECT root_session_id AS id,
|
|
25618
25816
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25619
|
-
FROM audit_events
|
|
25817
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25620
25818
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25621
25819
|
GROUP BY root_session_id`
|
|
25622
25820
|
),
|
|
@@ -26645,7 +26843,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26645
26843
|
|
|
26646
26844
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26647
26845
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26648
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26649
26846
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26650
26847
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26651
26848
|
function compareLocationOrder(a, b) {
|
|
@@ -26674,6 +26871,25 @@ function deriveInstanceStatus(row) {
|
|
|
26674
26871
|
latestResolutionStatus: row.latest_status
|
|
26675
26872
|
});
|
|
26676
26873
|
}
|
|
26874
|
+
function toFlatFindingRow(r) {
|
|
26875
|
+
return {
|
|
26876
|
+
id: r.id,
|
|
26877
|
+
ruleId: r.rule_id,
|
|
26878
|
+
category: r.category,
|
|
26879
|
+
severity: r.severity,
|
|
26880
|
+
maskedMatch: r.masked_match,
|
|
26881
|
+
actionTaken: r.action_taken,
|
|
26882
|
+
confidence: r.confidence,
|
|
26883
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26884
|
+
sourceTool: r.source_tool,
|
|
26885
|
+
repo: r.repo ?? "",
|
|
26886
|
+
file: r.file ?? "",
|
|
26887
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26888
|
+
eventId: r.event_id,
|
|
26889
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26890
|
+
status: deriveInstanceStatus(r)
|
|
26891
|
+
};
|
|
26892
|
+
}
|
|
26677
26893
|
function encodeGroupCursor(group) {
|
|
26678
26894
|
const payload = {
|
|
26679
26895
|
sev: group.severity,
|
|
@@ -26749,7 +26965,7 @@ var SqliteFindingsRepository = class {
|
|
|
26749
26965
|
this.db.prepare(
|
|
26750
26966
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26751
26967
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26752
|
-
|
|
26968
|
+
e.source_tool AS source_tool,
|
|
26753
26969
|
e.event_type AS kind
|
|
26754
26970
|
FROM audit_events e
|
|
26755
26971
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26857,56 +27073,11 @@ var SqliteFindingsRepository = class {
|
|
|
26857
27073
|
predicate,
|
|
26858
27074
|
params: sessionParams
|
|
26859
27075
|
});
|
|
26860
|
-
const rows =
|
|
26861
|
-
|
|
26862
|
-
|
|
26863
|
-
|
|
26864
|
-
|
|
26865
|
-
FROM (
|
|
26866
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26867
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26868
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26869
|
-
e.started_at AS occurred_at,
|
|
26870
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26871
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26872
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26873
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26874
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26875
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26876
|
-
latest.status AS latest_status,
|
|
26877
|
-
ROW_NUMBER() OVER (
|
|
26878
|
-
PARTITION BY d.rule_id
|
|
26879
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26880
|
-
) AS rn
|
|
26881
|
-
FROM inspection_findings f
|
|
26882
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26883
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26884
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26885
|
-
ON latest.finding_key = f.finding_key
|
|
26886
|
-
${predicate}
|
|
26887
|
-
)
|
|
26888
|
-
WHERE rn <= :cap
|
|
26889
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26890
|
-
),
|
|
26891
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
26892
|
-
);
|
|
26893
|
-
const groupable = rows.map((r) => ({
|
|
26894
|
-
id: r.id,
|
|
26895
|
-
ruleId: r.rule_id,
|
|
26896
|
-
category: r.category,
|
|
26897
|
-
severity: r.severity,
|
|
26898
|
-
maskedMatch: r.masked_match,
|
|
26899
|
-
actionTaken: r.action_taken,
|
|
26900
|
-
confidence: r.confidence,
|
|
26901
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26902
|
-
sourceTool: r.source_tool,
|
|
26903
|
-
repo: r.repo ?? "",
|
|
26904
|
-
file: r.file ?? "",
|
|
26905
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26906
|
-
eventId: r.event_id,
|
|
26907
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26908
|
-
status: deriveInstanceStatus(r)
|
|
26909
|
-
}));
|
|
27076
|
+
const rows = this.previewRows(aggregates, {
|
|
27077
|
+
sessionId: query.sessionId,
|
|
27078
|
+
from: query.from
|
|
27079
|
+
});
|
|
27080
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
26910
27081
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
26911
27082
|
const filterOpts = {
|
|
26912
27083
|
severity: query.severity,
|
|
@@ -26992,8 +27163,10 @@ var SqliteFindingsRepository = class {
|
|
|
26992
27163
|
*
|
|
26993
27164
|
* The scan runs from the top of the scope on every request, not from the
|
|
26994
27165
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
26995
|
-
* move as the caller pages. Rows
|
|
26996
|
-
*
|
|
27166
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27167
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27168
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27169
|
+
* fetched batches; only the page itself is retained.
|
|
26997
27170
|
*/
|
|
26998
27171
|
listFindingInstances(query) {
|
|
26999
27172
|
const opts = {
|
|
@@ -27009,6 +27182,10 @@ var SqliteFindingsRepository = class {
|
|
|
27009
27182
|
};
|
|
27010
27183
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27011
27184
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27185
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27186
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27187
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27188
|
+
};
|
|
27012
27189
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27013
27190
|
const items = [];
|
|
27014
27191
|
let total = 0;
|
|
@@ -27021,6 +27198,7 @@ var SqliteFindingsRepository = class {
|
|
|
27021
27198
|
accumulator.add(row);
|
|
27022
27199
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27023
27200
|
total += 1;
|
|
27201
|
+
if (!isPastCursor(row)) continue;
|
|
27024
27202
|
if (items.length < limit) {
|
|
27025
27203
|
items.push(toInstanceDetail(row));
|
|
27026
27204
|
last = row;
|
|
@@ -27029,15 +27207,6 @@ var SqliteFindingsRepository = class {
|
|
|
27029
27207
|
}
|
|
27030
27208
|
}
|
|
27031
27209
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27032
|
-
if (cursor !== null) {
|
|
27033
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27034
|
-
return Promise.resolve({
|
|
27035
|
-
totals: { findings: total },
|
|
27036
|
-
facets: accumulator.facets(),
|
|
27037
|
-
items: resumed.items,
|
|
27038
|
-
nextCursor: resumed.nextCursor
|
|
27039
|
-
});
|
|
27040
|
-
}
|
|
27041
27210
|
return Promise.resolve({
|
|
27042
27211
|
totals: { findings: total },
|
|
27043
27212
|
facets: accumulator.facets(),
|
|
@@ -27045,35 +27214,6 @@ var SqliteFindingsRepository = class {
|
|
|
27045
27214
|
nextCursor
|
|
27046
27215
|
});
|
|
27047
27216
|
}
|
|
27048
|
-
/**
|
|
27049
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27050
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27051
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27052
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27053
|
-
*/
|
|
27054
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27055
|
-
const items = [];
|
|
27056
|
-
let last;
|
|
27057
|
-
let hasMore = false;
|
|
27058
|
-
for (const row of this.scanFindingRows({
|
|
27059
|
-
sessionId: query.sessionId,
|
|
27060
|
-
from: query.from,
|
|
27061
|
-
after: cursor
|
|
27062
|
-
})) {
|
|
27063
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27064
|
-
if (items.length < limit) {
|
|
27065
|
-
items.push(toInstanceDetail(row));
|
|
27066
|
-
last = row;
|
|
27067
|
-
} else {
|
|
27068
|
-
hasMore = true;
|
|
27069
|
-
break;
|
|
27070
|
-
}
|
|
27071
|
-
}
|
|
27072
|
-
return {
|
|
27073
|
-
items,
|
|
27074
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27075
|
-
};
|
|
27076
|
-
}
|
|
27077
27217
|
/**
|
|
27078
27218
|
* The same findings folded by location: repository, then file within it.
|
|
27079
27219
|
*
|
|
@@ -27156,25 +27296,111 @@ var SqliteFindingsRepository = class {
|
|
|
27156
27296
|
});
|
|
27157
27297
|
}
|
|
27158
27298
|
/**
|
|
27159
|
-
*
|
|
27299
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27300
|
+
*
|
|
27301
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27302
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27303
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27304
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27305
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27306
|
+
* store while the answer does not.
|
|
27307
|
+
*
|
|
27308
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27309
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27310
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27311
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27312
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27313
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27314
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27315
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27316
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27317
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27318
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27319
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27320
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27321
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27322
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27323
|
+
* sort of the scope, which is still that floor.
|
|
27324
|
+
*
|
|
27325
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27326
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27327
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27328
|
+
* built from.
|
|
27329
|
+
*/
|
|
27330
|
+
previewRows(aggregates, scope) {
|
|
27331
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27332
|
+
let remaining = 0;
|
|
27333
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27334
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27335
|
+
wanted.set(ruleId, n);
|
|
27336
|
+
remaining += n;
|
|
27337
|
+
}
|
|
27338
|
+
const rows = [];
|
|
27339
|
+
if (remaining === 0) return rows;
|
|
27340
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27341
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27342
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27343
|
+
const want = wanted.get(r.rule_id);
|
|
27344
|
+
if (want === void 0) continue;
|
|
27345
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27346
|
+
if (have >= want) continue;
|
|
27347
|
+
taken.set(r.rule_id, have + 1);
|
|
27348
|
+
rows.push(r);
|
|
27349
|
+
remaining -= 1;
|
|
27350
|
+
if (remaining === 0) break;
|
|
27351
|
+
}
|
|
27352
|
+
return rows;
|
|
27353
|
+
}
|
|
27354
|
+
/**
|
|
27355
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27160
27356
|
*
|
|
27161
27357
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27162
27358
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27163
|
-
* store is far more rows than any page.
|
|
27164
|
-
*
|
|
27165
|
-
*
|
|
27359
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27360
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27361
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27362
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27363
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27364
|
+
* batch and cost the square of the scope.
|
|
27166
27365
|
*
|
|
27167
|
-
*
|
|
27168
|
-
*
|
|
27169
|
-
*
|
|
27170
|
-
*
|
|
27171
|
-
*
|
|
27172
|
-
*
|
|
27173
|
-
*
|
|
27174
|
-
* dimension — see listFindingInstances.
|
|
27366
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27367
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27368
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27369
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27370
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27371
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27372
|
+
* page-2+ request would otherwise re-seek for.
|
|
27175
27373
|
*/
|
|
27176
27374
|
*scanFindingRows(scope) {
|
|
27177
|
-
const
|
|
27375
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27376
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27377
|
+
yield toFlatFindingRow(r);
|
|
27378
|
+
}
|
|
27379
|
+
}
|
|
27380
|
+
/**
|
|
27381
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27382
|
+
* joined to its event and definition, newest first.
|
|
27383
|
+
*
|
|
27384
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27385
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27386
|
+
*
|
|
27387
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27388
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27389
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27390
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27391
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27392
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27393
|
+
* ordered within the session — and the order falls out of the index.
|
|
27394
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27395
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27396
|
+
*
|
|
27397
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27398
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27399
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27400
|
+
* table would be materialized before the first row streamed.
|
|
27401
|
+
*/
|
|
27402
|
+
findingScanSql(scope) {
|
|
27403
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27178
27404
|
const params = [];
|
|
27179
27405
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27180
27406
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27188,58 +27414,24 @@ var SqliteFindingsRepository = class {
|
|
|
27188
27414
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27189
27415
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27190
27416
|
e.started_at AS occurred_at,
|
|
27191
|
-
|
|
27192
|
-
|
|
27193
|
-
|
|
27194
|
-
|
|
27417
|
+
e.source_tool AS source_tool,
|
|
27418
|
+
e.repo AS repo,
|
|
27419
|
+
e.file_path AS file,
|
|
27420
|
+
e.tool_name AS tool_name,
|
|
27195
27421
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27196
27422
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27197
27423
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27198
|
-
FROM
|
|
27199
|
-
JOIN
|
|
27200
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27424
|
+
FROM audit_events e
|
|
27425
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27426
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27201
27427
|
WHERE ${conditions.join(" AND ")}
|
|
27202
|
-
|
|
27203
|
-
|
|
27204
|
-
LIMIT ?`;
|
|
27205
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27206
|
-
for (; ; ) {
|
|
27207
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27208
|
-
...params,
|
|
27209
|
-
after.startedAtMs,
|
|
27210
|
-
after.startedAtMs,
|
|
27211
|
-
after.id,
|
|
27212
|
-
SCAN_BATCH_ROWS
|
|
27213
|
-
]);
|
|
27214
|
-
for (const r of rows) {
|
|
27215
|
-
yield {
|
|
27216
|
-
id: r.id,
|
|
27217
|
-
ruleId: r.rule_id,
|
|
27218
|
-
category: r.category,
|
|
27219
|
-
severity: r.severity,
|
|
27220
|
-
maskedMatch: r.masked_match,
|
|
27221
|
-
actionTaken: r.action_taken,
|
|
27222
|
-
confidence: r.confidence,
|
|
27223
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27224
|
-
sourceTool: r.source_tool,
|
|
27225
|
-
repo: r.repo ?? "",
|
|
27226
|
-
file: r.file ?? "",
|
|
27227
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27228
|
-
eventId: r.event_id,
|
|
27229
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27230
|
-
status: deriveInstanceStatus(r)
|
|
27231
|
-
};
|
|
27232
|
-
}
|
|
27233
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27234
|
-
const lastRow = rows[rows.length - 1];
|
|
27235
|
-
if (lastRow === void 0) return;
|
|
27236
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27237
|
-
}
|
|
27428
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27429
|
+
return { sql, params };
|
|
27238
27430
|
}
|
|
27239
27431
|
groupAggregates(withSearchText, scope) {
|
|
27240
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27241
|
-
group_concat(DISTINCT
|
|
27242
|
-
group_concat(DISTINCT 'via ' ||
|
|
27432
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27433
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27434
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27243
27435
|
const rows = this.db.prepare(
|
|
27244
27436
|
`SELECT rule_id,
|
|
27245
27437
|
sum(tuple_count) AS instance_count,
|
|
@@ -27257,7 +27449,7 @@ var SqliteFindingsRepository = class {
|
|
|
27257
27449
|
coalesce(latest.status, '') AS status_tuple,
|
|
27258
27450
|
count(*) AS tuple_count,
|
|
27259
27451
|
max(e.started_at) AS latest_at,
|
|
27260
|
-
group_concat(DISTINCT
|
|
27452
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27261
27453
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27262
27454
|
${innerSearchColumns}
|
|
27263
27455
|
FROM inspection_findings f
|
|
@@ -27388,6 +27580,8 @@ function isoDay(ms) {
|
|
|
27388
27580
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27389
27581
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27390
27582
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27583
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27584
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27391
27585
|
var SKIPPED = -1;
|
|
27392
27586
|
var ROW_COLUMNS = `id,
|
|
27393
27587
|
parent_id AS parentId,
|
|
@@ -27427,9 +27621,23 @@ var SqliteHistorySyncRepository = class {
|
|
|
27427
27621
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27428
27622
|
LIMIT :limit`
|
|
27429
27623
|
);
|
|
27430
|
-
this.
|
|
27431
|
-
`
|
|
27432
|
-
|
|
27624
|
+
this.captureRowsStmt = db.prepare(
|
|
27625
|
+
`SELECT ${ROW_COLUMNS}
|
|
27626
|
+
FROM audit_events
|
|
27627
|
+
WHERE synced_at IS NULL
|
|
27628
|
+
AND sync_claimed_at IS NULL
|
|
27629
|
+
AND outbox_owed = 1
|
|
27630
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27631
|
+
AND started_at < :before
|
|
27632
|
+
ORDER BY started_at
|
|
27633
|
+
LIMIT :limit`
|
|
27634
|
+
);
|
|
27635
|
+
this.markOwedStmt = db.prepare(
|
|
27636
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27637
|
+
);
|
|
27638
|
+
this.stampStmt = db.prepare(
|
|
27639
|
+
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27640
|
+
);
|
|
27433
27641
|
this.claimRowStmt = db.prepare(
|
|
27434
27642
|
`UPDATE audit_events SET sync_claimed_at = :at WHERE id = :id AND synced_at IS NULL`
|
|
27435
27643
|
);
|
|
@@ -27458,6 +27666,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27458
27666
|
FROM audit_events
|
|
27459
27667
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27460
27668
|
);
|
|
27669
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27670
|
+
`SELECT COUNT(*) AS skipped
|
|
27671
|
+
FROM audit_events
|
|
27672
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27673
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27674
|
+
);
|
|
27461
27675
|
this.fingerprintStmt = db.prepare(
|
|
27462
27676
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27463
27677
|
FROM history_sync WHERE id = 1`
|
|
@@ -27467,6 +27681,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27467
27681
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27468
27682
|
WHERE id = 1`
|
|
27469
27683
|
);
|
|
27684
|
+
this.disownCapturesStmt = db.prepare(
|
|
27685
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27686
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27687
|
+
);
|
|
27470
27688
|
this.rearmStmt = db.prepare(
|
|
27471
27689
|
`UPDATE audit_events SET synced_at = NULL
|
|
27472
27690
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27539,6 +27757,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27539
27757
|
closeWindowStmt;
|
|
27540
27758
|
releaseBoundaryStmt;
|
|
27541
27759
|
freezeBoundaryStmt;
|
|
27760
|
+
captureRowsStmt;
|
|
27761
|
+
markOwedStmt;
|
|
27762
|
+
captureSkipCountStmt;
|
|
27763
|
+
disownCapturesStmt;
|
|
27542
27764
|
partitionStmt;
|
|
27543
27765
|
claimRowStmt;
|
|
27544
27766
|
releaseRowStmt;
|
|
@@ -27572,6 +27794,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27572
27794
|
pendingRows(sessionId, limit, before) {
|
|
27573
27795
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27574
27796
|
}
|
|
27797
|
+
/**
|
|
27798
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27799
|
+
*
|
|
27800
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27801
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27802
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27803
|
+
* the live path.
|
|
27804
|
+
*/
|
|
27805
|
+
pendingCaptureRows(limit, before) {
|
|
27806
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27807
|
+
}
|
|
27808
|
+
/**
|
|
27809
|
+
* Record that a capture is OWED to the deployment.
|
|
27810
|
+
*
|
|
27811
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27812
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27813
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27814
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27815
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27816
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27817
|
+
* offered to anyone.
|
|
27818
|
+
*
|
|
27819
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27820
|
+
* out of the drain's read.
|
|
27821
|
+
*/
|
|
27822
|
+
markCaptureOwed(id) {
|
|
27823
|
+
this.markOwedStmt.run({ id });
|
|
27824
|
+
}
|
|
27575
27825
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27576
27826
|
markSynced(ids, atMs) {
|
|
27577
27827
|
this.stampAll(ids, atMs);
|
|
@@ -27655,10 +27905,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27655
27905
|
this.countsStmt,
|
|
27656
27906
|
{ before }
|
|
27657
27907
|
);
|
|
27908
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27658
27909
|
return {
|
|
27659
27910
|
pending: row?.pending ?? 0,
|
|
27660
27911
|
sent: row?.sent ?? 0,
|
|
27661
|
-
skipped: row?.skipped ?? 0
|
|
27912
|
+
skipped: row?.skipped ?? 0,
|
|
27913
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27662
27914
|
};
|
|
27663
27915
|
}
|
|
27664
27916
|
/**
|
|
@@ -27699,7 +27951,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27699
27951
|
withTransaction(
|
|
27700
27952
|
this.db,
|
|
27701
27953
|
() => {
|
|
27954
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27702
27955
|
this.rearmStmt.run();
|
|
27956
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
27957
|
+
this.disownCapturesStmt.run();
|
|
27958
|
+
}
|
|
27703
27959
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27704
27960
|
},
|
|
27705
27961
|
"IMMEDIATE"
|
|
@@ -27896,7 +28152,231 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
27896
28152
|
};
|
|
27897
28153
|
|
|
27898
28154
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
27899
|
-
import { createHash as createHash2, randomUUID as
|
|
28155
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28156
|
+
|
|
28157
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28158
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28159
|
+
import { join as join6 } from "path";
|
|
28160
|
+
|
|
28161
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28162
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28163
|
+
import { mkdir } from "fs/promises";
|
|
28164
|
+
import { homedir } from "os";
|
|
28165
|
+
import { join as join4 } from "path";
|
|
28166
|
+
function defaultDataDir() {
|
|
28167
|
+
return join4(homedir(), ".aka");
|
|
28168
|
+
}
|
|
28169
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28170
|
+
return join4(base, "settings");
|
|
28171
|
+
}
|
|
28172
|
+
function dataDir(base = defaultDataDir()) {
|
|
28173
|
+
return join4(base, "data");
|
|
28174
|
+
}
|
|
28175
|
+
|
|
28176
|
+
// ../../packages/persistence/src/settings.ts
|
|
28177
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28178
|
+
import { join as join5 } from "path";
|
|
28179
|
+
|
|
28180
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28181
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28182
|
+
import {
|
|
28183
|
+
closeSync,
|
|
28184
|
+
existsSync as existsSync2,
|
|
28185
|
+
openSync,
|
|
28186
|
+
readFileSync as readFileSync2,
|
|
28187
|
+
rmSync as rmSync5,
|
|
28188
|
+
statSync as statSync3,
|
|
28189
|
+
writeFileSync as writeFileSync2
|
|
28190
|
+
} from "fs";
|
|
28191
|
+
import { hostname as hostname3 } from "os";
|
|
28192
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28193
|
+
|
|
28194
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28195
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28196
|
+
import { posix, win32 } from "path";
|
|
28197
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28198
|
+
if (platform2 === "darwin") {
|
|
28199
|
+
return [
|
|
28200
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28201
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28202
|
+
];
|
|
28203
|
+
}
|
|
28204
|
+
if (platform2 === "win32") {
|
|
28205
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28206
|
+
}
|
|
28207
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28208
|
+
}
|
|
28209
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28210
|
+
for (const path of paths) {
|
|
28211
|
+
let text;
|
|
28212
|
+
try {
|
|
28213
|
+
text = readFileSync3(path, "utf8");
|
|
28214
|
+
} catch {
|
|
28215
|
+
continue;
|
|
28216
|
+
}
|
|
28217
|
+
const record2 = parseJsonObject(text);
|
|
28218
|
+
if (!record2) continue;
|
|
28219
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28220
|
+
if (parsed2.success) return parsed2.data;
|
|
28221
|
+
}
|
|
28222
|
+
return null;
|
|
28223
|
+
}
|
|
28224
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28225
|
+
if (!managed) return settings;
|
|
28226
|
+
const { values } = managed;
|
|
28227
|
+
const merged = { ...settings };
|
|
28228
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28229
|
+
if (values.controlPlane !== void 0) {
|
|
28230
|
+
merged.controlPlane = {
|
|
28231
|
+
...values.controlPlane,
|
|
28232
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28233
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28234
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28235
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28236
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28237
|
+
};
|
|
28238
|
+
}
|
|
28239
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28240
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28241
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28242
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28243
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28244
|
+
if (values.vaultConsent !== void 0) {
|
|
28245
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28246
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28247
|
+
// at the current version otherwise.
|
|
28248
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28249
|
+
) : void 0;
|
|
28250
|
+
}
|
|
28251
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28252
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28253
|
+
acknowledgedAt: now().toISOString(),
|
|
28254
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28255
|
+
} : void 0;
|
|
28256
|
+
}
|
|
28257
|
+
return merged;
|
|
28258
|
+
}
|
|
28259
|
+
|
|
28260
|
+
// ../../packages/persistence/src/settings.ts
|
|
28261
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28262
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28263
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28264
|
+
}
|
|
28265
|
+
function readUserSettings(base) {
|
|
28266
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28267
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28268
|
+
try {
|
|
28269
|
+
return WorkspaceSettings.parse(record2);
|
|
28270
|
+
} catch {
|
|
28271
|
+
return defaultWorkspaceSettings();
|
|
28272
|
+
}
|
|
28273
|
+
}
|
|
28274
|
+
function readJson(file2) {
|
|
28275
|
+
let text;
|
|
28276
|
+
try {
|
|
28277
|
+
text = readFileSync4(file2, "utf8");
|
|
28278
|
+
} catch {
|
|
28279
|
+
return null;
|
|
28280
|
+
}
|
|
28281
|
+
return parseJsonObject(text) ?? null;
|
|
28282
|
+
}
|
|
28283
|
+
|
|
28284
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28285
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28286
|
+
switch (refusal) {
|
|
28287
|
+
case "lock":
|
|
28288
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28289
|
+
case "disable":
|
|
28290
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28291
|
+
case "floor":
|
|
28292
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28293
|
+
}
|
|
28294
|
+
}
|
|
28295
|
+
var PolicyFloorError = class extends Error {
|
|
28296
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28297
|
+
pack;
|
|
28298
|
+
/**
|
|
28299
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28300
|
+
* clearing the assignment, or switching the detection off.
|
|
28301
|
+
*/
|
|
28302
|
+
attempted;
|
|
28303
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28304
|
+
floor;
|
|
28305
|
+
refusal;
|
|
28306
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28307
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28308
|
+
this.name = "PolicyFloorError";
|
|
28309
|
+
this.pack = pack;
|
|
28310
|
+
this.attempted = attempted;
|
|
28311
|
+
this.floor = floor;
|
|
28312
|
+
this.refusal = refusal;
|
|
28313
|
+
}
|
|
28314
|
+
};
|
|
28315
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28316
|
+
try {
|
|
28317
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28318
|
+
const parsed2 = JSON.parse(raw);
|
|
28319
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28320
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28321
|
+
} catch {
|
|
28322
|
+
return null;
|
|
28323
|
+
}
|
|
28324
|
+
}
|
|
28325
|
+
function indexEnabled(policies) {
|
|
28326
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28327
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28328
|
+
for (const policy of policies) {
|
|
28329
|
+
if (!policy.enabled) continue;
|
|
28330
|
+
if ("ruleId" in policy.target) {
|
|
28331
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28332
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28333
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28334
|
+
}
|
|
28335
|
+
}
|
|
28336
|
+
return { byRuleId, byCategory };
|
|
28337
|
+
}
|
|
28338
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28339
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28340
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28341
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28342
|
+
return policies.some((policy) => {
|
|
28343
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28344
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28345
|
+
});
|
|
28346
|
+
}
|
|
28347
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28348
|
+
const floors = openControlPlaneFloors(base);
|
|
28349
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28350
|
+
}
|
|
28351
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28352
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28353
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28354
|
+
if (bundle === null) return null;
|
|
28355
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28356
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28357
|
+
}
|
|
28358
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28359
|
+
let action = null;
|
|
28360
|
+
for (const rule of rules) {
|
|
28361
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28362
|
+
if (resolved === void 0) continue;
|
|
28363
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28364
|
+
}
|
|
28365
|
+
if (action === null) return null;
|
|
28366
|
+
return {
|
|
28367
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28368
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28369
|
+
};
|
|
28370
|
+
}
|
|
28371
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28372
|
+
if (floor.locked) return "lock";
|
|
28373
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28374
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28375
|
+
}
|
|
28376
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28377
|
+
if (floor === null || enabled) return null;
|
|
28378
|
+
return "disable";
|
|
28379
|
+
}
|
|
27900
28380
|
|
|
27901
28381
|
// ../../packages/persistence/src/semver.ts
|
|
27902
28382
|
function parse3(version2) {
|
|
@@ -27990,8 +28470,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
27990
28470
|
return ids;
|
|
27991
28471
|
}
|
|
27992
28472
|
var SqliteInstalledPacksRepository = class {
|
|
27993
|
-
|
|
28473
|
+
/**
|
|
28474
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28475
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28476
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28477
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28478
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28479
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28480
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28481
|
+
* real `~/.aka`.
|
|
28482
|
+
*/
|
|
28483
|
+
constructor(db, baseDir) {
|
|
27994
28484
|
this.db = db;
|
|
28485
|
+
this.baseDir = baseDir;
|
|
27995
28486
|
this.insertMissingStmt = db.prepare(
|
|
27996
28487
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
27997
28488
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28013,11 +28504,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28013
28504
|
this.signatureStmt = db.prepare(
|
|
28014
28505
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28015
28506
|
);
|
|
28507
|
+
this.packRulesStmt = db.prepare(
|
|
28508
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28509
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28510
|
+
);
|
|
28016
28511
|
}
|
|
28017
28512
|
db;
|
|
28513
|
+
baseDir;
|
|
28018
28514
|
insertMissingStmt;
|
|
28019
28515
|
upsertAvailableStmt;
|
|
28020
28516
|
signatureStmt;
|
|
28517
|
+
packRulesStmt;
|
|
28021
28518
|
/**
|
|
28022
28519
|
* Record the running binary's detection inventory. Refreshes the
|
|
28023
28520
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28059,7 +28556,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28059
28556
|
let behind = false;
|
|
28060
28557
|
for (const row of rows) {
|
|
28061
28558
|
const params = {
|
|
28062
|
-
id:
|
|
28559
|
+
id: randomUUID4(),
|
|
28063
28560
|
namespace: row.namespace,
|
|
28064
28561
|
packId: row.packId,
|
|
28065
28562
|
version: row.version,
|
|
@@ -28071,7 +28568,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28071
28568
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28072
28569
|
this.upsertAvailableStmt.run({
|
|
28073
28570
|
...params,
|
|
28074
|
-
id:
|
|
28571
|
+
id: randomUUID4(),
|
|
28075
28572
|
recordedBy: meta4?.recordedBy ?? null
|
|
28076
28573
|
});
|
|
28077
28574
|
} else {
|
|
@@ -28317,9 +28814,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28317
28814
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28318
28815
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28319
28816
|
// caller can tell an edit from a no-such-detection.
|
|
28817
|
+
/**
|
|
28818
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
28819
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
28820
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
28821
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
28822
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
28823
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
28824
|
+
*/
|
|
28825
|
+
packFloorRules(namespace, packId) {
|
|
28826
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
28827
|
+
if (!row) return [];
|
|
28828
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
28829
|
+
}
|
|
28830
|
+
/**
|
|
28831
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
28832
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
28833
|
+
* repository constructed without a layout base).
|
|
28834
|
+
*
|
|
28835
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
28836
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
28837
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
28838
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
28839
|
+
*/
|
|
28840
|
+
policyFloor(namespace, packId) {
|
|
28841
|
+
if (this.baseDir === void 0) return null;
|
|
28842
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
28843
|
+
}
|
|
28844
|
+
/**
|
|
28845
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
28846
|
+
* entry only for a pack the control plane actually governs.
|
|
28847
|
+
*
|
|
28848
|
+
* A surface listing every detection asks per pack, and asking through
|
|
28849
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
28850
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
28851
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
28852
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
28853
|
+
* exactly as the single-pack read returns null for them.
|
|
28854
|
+
*/
|
|
28855
|
+
policyFloors(packs) {
|
|
28856
|
+
const floors = /* @__PURE__ */ new Map();
|
|
28857
|
+
if (this.baseDir === void 0) return floors;
|
|
28858
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
28859
|
+
if (source === null) return floors;
|
|
28860
|
+
for (const pack of packs) {
|
|
28861
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
28862
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
28863
|
+
}
|
|
28864
|
+
return floors;
|
|
28865
|
+
}
|
|
28320
28866
|
/**
|
|
28321
28867
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28322
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
28868
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
28869
|
+
*
|
|
28870
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
28871
|
+
* write below, and a detection the organization has authored a policy for is
|
|
28872
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
28873
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
28874
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
28875
|
+
* surface that offers the choice.
|
|
28323
28876
|
*/
|
|
28324
28877
|
setPolicy(namespace, packId, policyId) {
|
|
28325
28878
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28327,14 +28880,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28327
28880
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28328
28881
|
);
|
|
28329
28882
|
}
|
|
28883
|
+
const requested = policyId;
|
|
28884
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28885
|
+
if (floor !== null) {
|
|
28886
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
28887
|
+
if (refusal !== null) {
|
|
28888
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
28889
|
+
}
|
|
28890
|
+
}
|
|
28330
28891
|
const res = this.db.prepare(
|
|
28331
28892
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28332
28893
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28333
28894
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28334
28895
|
return Number(res.changes) > 0;
|
|
28335
28896
|
}
|
|
28336
|
-
/**
|
|
28897
|
+
/**
|
|
28898
|
+
* Enable or disable one installed pack.
|
|
28899
|
+
*
|
|
28900
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
28901
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
28902
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
28903
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
28904
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
28905
|
+
* that lived in a page would leave the CLI free.
|
|
28906
|
+
*/
|
|
28337
28907
|
setEnabled(namespace, packId, enabled) {
|
|
28908
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28909
|
+
if (floor !== null) {
|
|
28910
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
28911
|
+
if (refusal !== null) {
|
|
28912
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
28913
|
+
}
|
|
28914
|
+
}
|
|
28338
28915
|
const res = this.db.prepare(
|
|
28339
28916
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28340
28917
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28420,7 +28997,7 @@ var SqliteInventoryRepository = class {
|
|
|
28420
28997
|
};
|
|
28421
28998
|
|
|
28422
28999
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28423
|
-
import { randomUUID as
|
|
29000
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28424
29001
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28425
29002
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28426
29003
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -28909,7 +29486,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28909
29486
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
28910
29487
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
28911
29488
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
28912
|
-
).run({ id:
|
|
29489
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
28913
29490
|
}
|
|
28914
29491
|
return true;
|
|
28915
29492
|
}
|
|
@@ -28930,7 +29507,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28930
29507
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
28931
29508
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
28932
29509
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
28933
|
-
).run({ id:
|
|
29510
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
28934
29511
|
}
|
|
28935
29512
|
this.configRowsCache = void 0;
|
|
28936
29513
|
return "ok";
|
|
@@ -29227,7 +29804,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29227
29804
|
};
|
|
29228
29805
|
|
|
29229
29806
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29230
|
-
import { randomUUID as
|
|
29807
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29231
29808
|
var SqlitePoliciesRepository = class {
|
|
29232
29809
|
constructor(db) {
|
|
29233
29810
|
this.db = db;
|
|
@@ -29262,7 +29839,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29262
29839
|
failOpenTransaction(this.db, () => {
|
|
29263
29840
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29264
29841
|
stmt.run({
|
|
29265
|
-
id:
|
|
29842
|
+
id: randomUUID6(),
|
|
29266
29843
|
target: JSON.stringify({ category }),
|
|
29267
29844
|
action,
|
|
29268
29845
|
now: Date.now()
|
|
@@ -29282,7 +29859,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29282
29859
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29283
29860
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29284
29861
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29285
|
-
).run({ id:
|
|
29862
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29286
29863
|
}
|
|
29287
29864
|
// Caps every global per-category policy currently set to block/redact down
|
|
29288
29865
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29350,7 +29927,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29350
29927
|
};
|
|
29351
29928
|
|
|
29352
29929
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29353
|
-
import { randomUUID as
|
|
29930
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29354
29931
|
var SqliteProjectFilesRepository = class {
|
|
29355
29932
|
constructor(db) {
|
|
29356
29933
|
this.db = db;
|
|
@@ -29382,7 +29959,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29382
29959
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29383
29960
|
for (const file2 of scan2.files) {
|
|
29384
29961
|
this.upsertStmt.run({
|
|
29385
|
-
id:
|
|
29962
|
+
id: randomUUID7(),
|
|
29386
29963
|
projectId,
|
|
29387
29964
|
path: file2.path,
|
|
29388
29965
|
name: file2.name,
|
|
@@ -29396,9 +29973,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29396
29973
|
};
|
|
29397
29974
|
|
|
29398
29975
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29399
|
-
import { randomUUID as
|
|
29976
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29400
29977
|
var SqliteResolutionsRepository = class {
|
|
29401
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
29978
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29402
29979
|
this.db = db;
|
|
29403
29980
|
this.now = now;
|
|
29404
29981
|
this.newId = newId;
|
|
@@ -29611,7 +30188,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29611
30188
|
};
|
|
29612
30189
|
|
|
29613
30190
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29614
|
-
import { randomUUID as
|
|
30191
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29615
30192
|
function pageLimit(requested, fallback) {
|
|
29616
30193
|
if (requested === void 0) return fallback;
|
|
29617
30194
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29657,12 +30234,14 @@ var SELECT_COLUMNS = `
|
|
|
29657
30234
|
ciphertext,
|
|
29658
30235
|
nonce,
|
|
29659
30236
|
auth_tag AS authTag,
|
|
30237
|
+
user_authorized AS userAuthorized,
|
|
29660
30238
|
occurrence_count AS occurrenceCount,
|
|
29661
30239
|
first_seen AS firstSeen,
|
|
29662
30240
|
last_seen AS lastSeen`;
|
|
29663
30241
|
function toRow(raw) {
|
|
29664
|
-
const { provider, ...rest } = raw;
|
|
29665
|
-
|
|
30242
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30243
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30244
|
+
return provider === null ? row : { ...row, provider };
|
|
29666
30245
|
}
|
|
29667
30246
|
var SqliteSecretVaultRepository = class {
|
|
29668
30247
|
constructor(db) {
|
|
@@ -29672,17 +30251,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29672
30251
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29673
30252
|
format_version, category, rule_id, masked_match, provider,
|
|
29674
30253
|
ciphertext, nonce, auth_tag,
|
|
29675
|
-
occurrence_count, first_seen, last_seen
|
|
30254
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29676
30255
|
) VALUES (
|
|
29677
30256
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29678
30257
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29679
30258
|
:ciphertext, :nonce, :authTag,
|
|
29680
|
-
1, :now, :now
|
|
30259
|
+
:userAuthorized, 1, :now, :now
|
|
29681
30260
|
)`
|
|
29682
30261
|
);
|
|
29683
30262
|
this.bumpStmt = db.prepare(
|
|
29684
30263
|
`UPDATE secret_vault
|
|
29685
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30264
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30265
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29686
30266
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29687
30267
|
);
|
|
29688
30268
|
this.byPointerStmt = db.prepare(
|
|
@@ -29702,6 +30282,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29702
30282
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29703
30283
|
WHERE pointer_id = :pointerId`
|
|
29704
30284
|
);
|
|
30285
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29705
30286
|
this.derefStmt = db.prepare(
|
|
29706
30287
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29707
30288
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29715,6 +30296,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29715
30296
|
listStmt;
|
|
29716
30297
|
replaceCiphertextStmt;
|
|
29717
30298
|
refreshFingerprintStmt;
|
|
30299
|
+
deleteByPointerStmt;
|
|
29718
30300
|
derefStmt;
|
|
29719
30301
|
/**
|
|
29720
30302
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29723,6 +30305,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29723
30305
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29724
30306
|
* wire token. `minted` is true only when this call created the row.
|
|
29725
30307
|
*
|
|
30308
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30309
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30310
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30311
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30312
|
+
*
|
|
29726
30313
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29727
30314
|
* writers cannot both decide they are minting.
|
|
29728
30315
|
*/
|
|
@@ -29749,13 +30336,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29749
30336
|
ciphertext: input2.ciphertext,
|
|
29750
30337
|
nonce: input2.nonce,
|
|
29751
30338
|
authTag: input2.authTag,
|
|
30339
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29752
30340
|
now
|
|
29753
30341
|
})
|
|
29754
30342
|
);
|
|
29755
30343
|
minted = true;
|
|
29756
30344
|
return;
|
|
29757
30345
|
}
|
|
29758
|
-
this.bumpStmt.run({
|
|
30346
|
+
this.bumpStmt.run({
|
|
30347
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30348
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30349
|
+
now
|
|
30350
|
+
});
|
|
29759
30351
|
},
|
|
29760
30352
|
"IMMEDIATE"
|
|
29761
30353
|
);
|
|
@@ -29815,6 +30407,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29815
30407
|
);
|
|
29816
30408
|
return destroyed;
|
|
29817
30409
|
}
|
|
30410
|
+
/**
|
|
30411
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30412
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30413
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30414
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30415
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30416
|
+
* it.
|
|
30417
|
+
*
|
|
30418
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30419
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30420
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30421
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30422
|
+
* entry still sitting in the vault.
|
|
30423
|
+
*
|
|
30424
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30425
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30426
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30427
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30428
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30429
|
+
*/
|
|
30430
|
+
deleteByPointerIds(pointerIds) {
|
|
30431
|
+
if (pointerIds.length === 0) return [];
|
|
30432
|
+
const deleted = [];
|
|
30433
|
+
withTransaction(
|
|
30434
|
+
this.db,
|
|
30435
|
+
() => {
|
|
30436
|
+
for (const pointerId of pointerIds) {
|
|
30437
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30438
|
+
deleted.push(pointerId);
|
|
30439
|
+
}
|
|
30440
|
+
}
|
|
30441
|
+
},
|
|
30442
|
+
"IMMEDIATE"
|
|
30443
|
+
);
|
|
30444
|
+
return deleted;
|
|
30445
|
+
}
|
|
29818
30446
|
/**
|
|
29819
30447
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29820
30448
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29827,7 +30455,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29827
30455
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29828
30456
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29829
30457
|
).run({
|
|
29830
|
-
id:
|
|
30458
|
+
id: randomUUID9(),
|
|
29831
30459
|
pointerId: entry.pointerId,
|
|
29832
30460
|
location: entry.location,
|
|
29833
30461
|
kind: entry.kind,
|
|
@@ -30340,15 +30968,15 @@ var SqliteSecurityRepository = class {
|
|
|
30340
30968
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30341
30969
|
const rows = allRows(
|
|
30342
30970
|
this.db.prepare(
|
|
30343
|
-
`SELECT
|
|
30971
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30344
30972
|
FROM inspection_findings f
|
|
30345
30973
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30346
30974
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30347
30975
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30348
|
-
AND
|
|
30349
|
-
AND
|
|
30350
|
-
GROUP BY repo
|
|
30351
|
-
ORDER BY c DESC, repo
|
|
30976
|
+
AND e.repo IS NOT NULL
|
|
30977
|
+
AND e.repo != ''
|
|
30978
|
+
GROUP BY e.repo
|
|
30979
|
+
ORDER BY c DESC, e.repo
|
|
30352
30980
|
LIMIT :limit`
|
|
30353
30981
|
),
|
|
30354
30982
|
{ from, to: now, limit }
|
|
@@ -30410,7 +31038,7 @@ var SqliteSecurityRepository = class {
|
|
|
30410
31038
|
`SELECT f.finding_key AS finding_key,
|
|
30411
31039
|
d.rule_id AS rule_id,
|
|
30412
31040
|
d.severity AS severity,
|
|
30413
|
-
|
|
31041
|
+
e.file_path AS path,
|
|
30414
31042
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30415
31043
|
latest.resolved_at AS latest_resolved_at
|
|
30416
31044
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30463,7 +31091,7 @@ var SqliteSecurityRepository = class {
|
|
|
30463
31091
|
};
|
|
30464
31092
|
|
|
30465
31093
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30466
|
-
import { randomUUID as
|
|
31094
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30467
31095
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30468
31096
|
var IN_CHUNK = 500;
|
|
30469
31097
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30551,7 +31179,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30551
31179
|
callSiteCount,
|
|
30552
31180
|
transports: distinctTransports(transports),
|
|
30553
31181
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30554
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31182
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30555
31183
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30556
31184
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30557
31185
|
};
|
|
@@ -30578,7 +31206,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30578
31206
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30579
31207
|
transports: distinctTransports(transports),
|
|
30580
31208
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30581
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31209
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30582
31210
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30583
31211
|
note: dest.note,
|
|
30584
31212
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30607,7 +31235,11 @@ var SqliteSharesRepository = class {
|
|
|
30607
31235
|
FROM share_destination d
|
|
30608
31236
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30609
31237
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30610
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31238
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31239
|
+
AND NOT EXISTS (
|
|
31240
|
+
SELECT 1 FROM egress_decision_override o
|
|
31241
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31242
|
+
)`
|
|
30611
31243
|
);
|
|
30612
31244
|
const kindCounts = countBy(
|
|
30613
31245
|
this.db,
|
|
@@ -30719,7 +31351,7 @@ var SqliteSharesRepository = class {
|
|
|
30719
31351
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30720
31352
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30721
31353
|
).run({
|
|
30722
|
-
id:
|
|
31354
|
+
id: randomUUID10(),
|
|
30723
31355
|
destinationId,
|
|
30724
31356
|
host: dest.host,
|
|
30725
31357
|
decision,
|
|
@@ -30868,7 +31500,7 @@ var SqliteSharesRepository = class {
|
|
|
30868
31500
|
let destinationId = destIds.get(hit.host);
|
|
30869
31501
|
if (destinationId === void 0) {
|
|
30870
31502
|
destStmt.run({
|
|
30871
|
-
id:
|
|
31503
|
+
id: randomUUID10(),
|
|
30872
31504
|
kind: hit.kind,
|
|
30873
31505
|
name: hit.name,
|
|
30874
31506
|
host: hit.host,
|
|
@@ -30884,7 +31516,7 @@ var SqliteSharesRepository = class {
|
|
|
30884
31516
|
let endpointId = endpointIds.get(endpointKey);
|
|
30885
31517
|
if (endpointId === void 0) {
|
|
30886
31518
|
endpointStmt.run({
|
|
30887
|
-
id:
|
|
31519
|
+
id: randomUUID10(),
|
|
30888
31520
|
destinationId,
|
|
30889
31521
|
method: hit.method,
|
|
30890
31522
|
transport: hit.transport,
|
|
@@ -30897,7 +31529,7 @@ var SqliteSharesRepository = class {
|
|
|
30897
31529
|
endpointIds.set(endpointKey, endpointId);
|
|
30898
31530
|
}
|
|
30899
31531
|
siteStmt.run({
|
|
30900
|
-
id:
|
|
31532
|
+
id: randomUUID10(),
|
|
30901
31533
|
endpointId,
|
|
30902
31534
|
project: input2.project,
|
|
30903
31535
|
projectKey: input2.projectKey,
|
|
@@ -31262,6 +31894,7 @@ function purgeSampleData(db) {
|
|
|
31262
31894
|
}
|
|
31263
31895
|
|
|
31264
31896
|
// ../../packages/persistence/src/database.ts
|
|
31897
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31265
31898
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31266
31899
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31267
31900
|
);
|
|
@@ -31309,7 +31942,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31309
31942
|
discardStore(file2, backup);
|
|
31310
31943
|
return backup;
|
|
31311
31944
|
}
|
|
31312
|
-
function openAndInitialize(file2) {
|
|
31945
|
+
function openAndInitialize(file2, base) {
|
|
31313
31946
|
let db = openWithPragmas(file2);
|
|
31314
31947
|
try {
|
|
31315
31948
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31322,7 +31955,7 @@ function openAndInitialize(file2) {
|
|
|
31322
31955
|
applyMigrations(db, file2);
|
|
31323
31956
|
tightenPerms(file2);
|
|
31324
31957
|
const policies = new SqlitePoliciesRepository(db);
|
|
31325
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
31958
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31326
31959
|
const repositories = {
|
|
31327
31960
|
events: new SqliteEventsRepository(db),
|
|
31328
31961
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31358,7 +31991,7 @@ function openAndInitialize(file2) {
|
|
|
31358
31991
|
}
|
|
31359
31992
|
function openLocalDatabase(dir) {
|
|
31360
31993
|
ensureDataDirSync(dir);
|
|
31361
|
-
const file2 =
|
|
31994
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31362
31995
|
reapStalePartials(file2);
|
|
31363
31996
|
const {
|
|
31364
31997
|
db,
|
|
@@ -31386,7 +32019,13 @@ function openLocalDatabase(dir) {
|
|
|
31386
32019
|
inspectionDefinitions,
|
|
31387
32020
|
inspectionFindings,
|
|
31388
32021
|
configInventory
|
|
31389
|
-
} = openAndInitialize(
|
|
32022
|
+
} = openAndInitialize(
|
|
32023
|
+
file2,
|
|
32024
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32025
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32026
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32027
|
+
dirname2(dir)
|
|
32028
|
+
);
|
|
31390
32029
|
function captureRowId(event) {
|
|
31391
32030
|
return captureId(
|
|
31392
32031
|
event.metadata?.sessionId ?? null,
|
|
@@ -31399,6 +32038,21 @@ function openLocalDatabase(dir) {
|
|
|
31399
32038
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31400
32039
|
});
|
|
31401
32040
|
}
|
|
32041
|
+
function markCaptureOwed(event) {
|
|
32042
|
+
failOpenTransaction(db, () => {
|
|
32043
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32044
|
+
});
|
|
32045
|
+
}
|
|
32046
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32047
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32048
|
+
if (stampable.length === 0) return;
|
|
32049
|
+
failOpenTransaction(db, () => {
|
|
32050
|
+
historySync.markSynced(
|
|
32051
|
+
stampable.map((event) => event.id),
|
|
32052
|
+
atMs
|
|
32053
|
+
);
|
|
32054
|
+
});
|
|
32055
|
+
}
|
|
31402
32056
|
function recordCapture(event, detected) {
|
|
31403
32057
|
failOpenTransaction(db, () => {
|
|
31404
32058
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31485,7 +32139,7 @@ function openLocalDatabase(dir) {
|
|
|
31485
32139
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31486
32140
|
if (!definitionId) continue;
|
|
31487
32141
|
inspectionFindings.insertFinding({
|
|
31488
|
-
id:
|
|
32142
|
+
id: randomUUID11(),
|
|
31489
32143
|
auditEventId: record2.scanEvent.id,
|
|
31490
32144
|
inspectionDefinitionId: definitionId,
|
|
31491
32145
|
span: finding.span,
|
|
@@ -31581,6 +32235,8 @@ function openLocalDatabase(dir) {
|
|
|
31581
32235
|
inspectionFindings,
|
|
31582
32236
|
recordCapture,
|
|
31583
32237
|
markCaptureDelivered,
|
|
32238
|
+
markCaptureOwed,
|
|
32239
|
+
markAuditEventsDelivered,
|
|
31584
32240
|
ensureInventory,
|
|
31585
32241
|
recordConfigScan,
|
|
31586
32242
|
recordProjectFiles,
|
|
@@ -31599,179 +32255,360 @@ function openLocalDatabase(dir) {
|
|
|
31599
32255
|
};
|
|
31600
32256
|
}
|
|
31601
32257
|
|
|
31602
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31603
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31604
|
-
import {
|
|
31605
|
-
closeSync,
|
|
31606
|
-
existsSync as existsSync2,
|
|
31607
|
-
openSync,
|
|
31608
|
-
readFileSync as readFileSync2,
|
|
31609
|
-
rmSync as rmSync5,
|
|
31610
|
-
statSync as statSync3,
|
|
31611
|
-
writeFileSync as writeFileSync2
|
|
31612
|
-
} from "fs";
|
|
31613
|
-
import { hostname as hostname3 } from "os";
|
|
31614
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31615
|
-
|
|
31616
32258
|
// ../../packages/persistence/src/finding-key.ts
|
|
31617
32259
|
import { createHash as createHash3 } from "crypto";
|
|
31618
32260
|
|
|
31619
32261
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31620
32262
|
import { createHmac, randomBytes } from "crypto";
|
|
31621
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31622
|
-
import { join as
|
|
32263
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32264
|
+
import { join as join8 } from "path";
|
|
31623
32265
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31624
32266
|
|
|
31625
32267
|
// ../../packages/persistence/src/history-preview.ts
|
|
31626
32268
|
import { existsSync as existsSync4 } from "fs";
|
|
31627
|
-
import { join as
|
|
32269
|
+
import { join as join9 } from "path";
|
|
31628
32270
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31629
32271
|
|
|
31630
|
-
// ../../packages/persistence/src/
|
|
31631
|
-
import {
|
|
31632
|
-
import {
|
|
31633
|
-
import { homedir } from "os";
|
|
31634
|
-
import { join as join7 } from "path";
|
|
31635
|
-
function defaultDataDir() {
|
|
31636
|
-
return join7(homedir(), ".aka");
|
|
31637
|
-
}
|
|
31638
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31639
|
-
return join7(base, "settings");
|
|
31640
|
-
}
|
|
31641
|
-
function dataDir(base = defaultDataDir()) {
|
|
31642
|
-
return join7(base, "data");
|
|
31643
|
-
}
|
|
32272
|
+
// ../../packages/persistence/src/store-symlinks.ts
|
|
32273
|
+
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
32274
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31644
32275
|
|
|
31645
|
-
// ../../packages/persistence/src/
|
|
31646
|
-
import {
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
31652
|
-
|
|
31653
|
-
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
32276
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
32277
|
+
import {
|
|
32278
|
+
createCipheriv,
|
|
32279
|
+
createDecipheriv,
|
|
32280
|
+
createHmac as createHmac2,
|
|
32281
|
+
hkdfSync,
|
|
32282
|
+
timingSafeEqual
|
|
32283
|
+
} from "crypto";
|
|
32284
|
+
|
|
32285
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32286
|
+
import { execFileSync } from "child_process";
|
|
32287
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
32288
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32289
|
+
import { join as join11 } from "path";
|
|
32290
|
+
|
|
32291
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
32292
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
32293
|
+
|
|
32294
|
+
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32295
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32296
|
+
import { join as join12 } from "path";
|
|
32297
|
+
|
|
32298
|
+
// ../../packages/remote/src/http.ts
|
|
32299
|
+
import { request as httpRequest } from "http";
|
|
32300
|
+
import { request as httpsRequest } from "https";
|
|
32301
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32302
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32303
|
+
var RemoteRequestError = class extends Error {
|
|
32304
|
+
constructor(status) {
|
|
32305
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32306
|
+
this.status = status;
|
|
32307
|
+
this.name = "RemoteRequestError";
|
|
31657
32308
|
}
|
|
31658
|
-
|
|
31659
|
-
}
|
|
31660
|
-
|
|
31661
|
-
|
|
31662
|
-
|
|
31663
|
-
|
|
31664
|
-
|
|
31665
|
-
} catch {
|
|
31666
|
-
continue;
|
|
31667
|
-
}
|
|
31668
|
-
const record2 = parseJsonObject(text);
|
|
31669
|
-
if (!record2) continue;
|
|
31670
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
31671
|
-
if (parsed2.success) return parsed2.data;
|
|
32309
|
+
status;
|
|
32310
|
+
};
|
|
32311
|
+
var RemoteRouteAbsent = class extends Error {
|
|
32312
|
+
constructor(route) {
|
|
32313
|
+
super(`control plane does not serve ${route}`);
|
|
32314
|
+
this.route = route;
|
|
32315
|
+
this.name = "RemoteRouteAbsent";
|
|
31672
32316
|
}
|
|
31673
|
-
|
|
31674
|
-
}
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
31679
|
-
|
|
31680
|
-
if (values.controlPlane !== void 0) {
|
|
31681
|
-
merged.controlPlane = {
|
|
31682
|
-
...values.controlPlane,
|
|
31683
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31684
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31685
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31686
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31687
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31688
|
-
};
|
|
32317
|
+
route;
|
|
32318
|
+
};
|
|
32319
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32320
|
+
constructor(route, cause) {
|
|
32321
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32322
|
+
this.cause = cause;
|
|
32323
|
+
this.name = "RemoteRequestInvalid";
|
|
31689
32324
|
}
|
|
31690
|
-
|
|
31691
|
-
|
|
31692
|
-
|
|
31693
|
-
|
|
31694
|
-
|
|
31695
|
-
|
|
31696
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31697
|
-
// at the current version otherwise.
|
|
31698
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31699
|
-
) : void 0;
|
|
32325
|
+
cause;
|
|
32326
|
+
};
|
|
32327
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32328
|
+
constructor(route, detail) {
|
|
32329
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32330
|
+
this.name = "RemoteResponseInvalid";
|
|
31700
32331
|
}
|
|
31701
|
-
|
|
31702
|
-
|
|
31703
|
-
|
|
31704
|
-
|
|
31705
|
-
|
|
32332
|
+
};
|
|
32333
|
+
var RemoteTransportError = class extends Error {
|
|
32334
|
+
/**
|
|
32335
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32336
|
+
* refused.
|
|
32337
|
+
*
|
|
32338
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32339
|
+
* all. It exists because two paths reject after a status has already been
|
|
32340
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32341
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32342
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32343
|
+
* credential.
|
|
32344
|
+
*/
|
|
32345
|
+
constructor(reason, status) {
|
|
32346
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32347
|
+
this.status = status;
|
|
32348
|
+
this.name = "RemoteTransportError";
|
|
31706
32349
|
}
|
|
31707
|
-
|
|
32350
|
+
status;
|
|
32351
|
+
};
|
|
32352
|
+
async function send(options) {
|
|
32353
|
+
const url2 = new URL(options.url);
|
|
32354
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32355
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32356
|
+
const requestOptions = {
|
|
32357
|
+
method: options.method,
|
|
32358
|
+
headers: {
|
|
32359
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32360
|
+
// last they win, and two of the values below are ones no caller may
|
|
32361
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32362
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32363
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32364
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32365
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32366
|
+
// conditional GET — is untouched by this order.
|
|
32367
|
+
...options.headers,
|
|
32368
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32369
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32370
|
+
// it can be logged by an intermediary for no gain.
|
|
32371
|
+
//
|
|
32372
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
32373
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
32374
|
+
// key differently from an absent one, and "the header is not there" is
|
|
32375
|
+
// the property the attach flow needs.
|
|
32376
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
32377
|
+
accept: "application/json",
|
|
32378
|
+
...options.body === void 0 ? {} : {
|
|
32379
|
+
"content-type": "application/json",
|
|
32380
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32381
|
+
// character count is truncated by the receiver.
|
|
32382
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32383
|
+
}
|
|
32384
|
+
}
|
|
32385
|
+
};
|
|
32386
|
+
return new Promise((resolve2, reject) => {
|
|
32387
|
+
let settled = false;
|
|
32388
|
+
const fail = (reason, status) => {
|
|
32389
|
+
if (settled) return;
|
|
32390
|
+
settled = true;
|
|
32391
|
+
reject(new RemoteTransportError(reason, status));
|
|
32392
|
+
};
|
|
32393
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32394
|
+
const chunks = [];
|
|
32395
|
+
let size = 0;
|
|
32396
|
+
res.on("data", (chunk) => {
|
|
32397
|
+
size += chunk.length;
|
|
32398
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32399
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32400
|
+
res.destroy();
|
|
32401
|
+
req.destroy();
|
|
32402
|
+
return;
|
|
32403
|
+
}
|
|
32404
|
+
chunks.push(chunk);
|
|
32405
|
+
});
|
|
32406
|
+
res.on("aborted", () => {
|
|
32407
|
+
fail("the response was aborted", res.statusCode);
|
|
32408
|
+
});
|
|
32409
|
+
res.on("end", () => {
|
|
32410
|
+
if (settled) return;
|
|
32411
|
+
settled = true;
|
|
32412
|
+
resolve2({
|
|
32413
|
+
status: res.statusCode ?? 0,
|
|
32414
|
+
headers: res.headers,
|
|
32415
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32416
|
+
});
|
|
32417
|
+
});
|
|
32418
|
+
});
|
|
32419
|
+
const deadline = setTimeout(() => {
|
|
32420
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32421
|
+
req.destroy();
|
|
32422
|
+
}, timeoutMs);
|
|
32423
|
+
deadline.unref();
|
|
32424
|
+
req.on("upgrade", (_res, socket) => {
|
|
32425
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32426
|
+
socket.destroy();
|
|
32427
|
+
});
|
|
32428
|
+
req.on("close", () => {
|
|
32429
|
+
fail("the connection closed before a response was read");
|
|
32430
|
+
clearTimeout(deadline);
|
|
32431
|
+
});
|
|
32432
|
+
req.on("error", (err) => {
|
|
32433
|
+
fail(err.message);
|
|
32434
|
+
});
|
|
32435
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32436
|
+
req.end();
|
|
32437
|
+
});
|
|
31708
32438
|
}
|
|
31709
32439
|
|
|
31710
|
-
// ../../packages/
|
|
31711
|
-
|
|
31712
|
-
|
|
31713
|
-
|
|
31714
|
-
|
|
31715
|
-
|
|
32440
|
+
// ../../packages/remote/src/client.ts
|
|
32441
|
+
var ROUTES = {
|
|
32442
|
+
events: "/v1/events",
|
|
32443
|
+
auditEvents: "/v1/audit-events",
|
|
32444
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
32445
|
+
inventory: "/v1/inventory",
|
|
32446
|
+
storePosture: "/v1/store-posture",
|
|
32447
|
+
policyBundle: "/v1/policy-bundle",
|
|
32448
|
+
whoami: "/v1/plugin/whoami",
|
|
32449
|
+
shares: "/v1/shares",
|
|
32450
|
+
commands: "/v1/plugin/commands"
|
|
32451
|
+
};
|
|
32452
|
+
function ackRoute(id) {
|
|
32453
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
31716
32454
|
}
|
|
31717
|
-
function
|
|
31718
|
-
const
|
|
31719
|
-
if (
|
|
31720
|
-
|
|
31721
|
-
|
|
31722
|
-
|
|
31723
|
-
|
|
32455
|
+
function headerValue(response, name) {
|
|
32456
|
+
const raw = response.headers[name];
|
|
32457
|
+
if (raw === void 0) return void 0;
|
|
32458
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32459
|
+
}
|
|
32460
|
+
function okBody(response) {
|
|
32461
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32462
|
+
throw new RemoteRequestError(response.status);
|
|
31724
32463
|
}
|
|
32464
|
+
return response.body;
|
|
31725
32465
|
}
|
|
31726
|
-
function
|
|
31727
|
-
let
|
|
32466
|
+
function parsed(schema, body, route) {
|
|
32467
|
+
let json2;
|
|
31728
32468
|
try {
|
|
31729
|
-
|
|
32469
|
+
json2 = JSON.parse(body);
|
|
31730
32470
|
} catch {
|
|
31731
|
-
|
|
32471
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
31732
32472
|
}
|
|
31733
|
-
|
|
32473
|
+
const result = schema.safeParse(json2);
|
|
32474
|
+
if (!result.success) {
|
|
32475
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32476
|
+
}
|
|
32477
|
+
return result.data;
|
|
32478
|
+
}
|
|
32479
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32480
|
+
let end = endpoint.length;
|
|
32481
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32482
|
+
return endpoint.slice(0, end);
|
|
32483
|
+
}
|
|
32484
|
+
var SLASH = "/".charCodeAt(0);
|
|
32485
|
+
function createRemoteClient(options) {
|
|
32486
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32487
|
+
const url2 = (route) => `${base}${route}`;
|
|
32488
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32489
|
+
const sendOne = async (event) => {
|
|
32490
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32491
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32492
|
+
const response = await send({
|
|
32493
|
+
...common,
|
|
32494
|
+
method: "POST",
|
|
32495
|
+
url: url2(ROUTES.auditEvents),
|
|
32496
|
+
body: JSON.stringify(validated.data)
|
|
32497
|
+
});
|
|
32498
|
+
okBody(response);
|
|
32499
|
+
};
|
|
32500
|
+
return {
|
|
32501
|
+
async ingestEvents(batch) {
|
|
32502
|
+
const response = await send({
|
|
32503
|
+
...common,
|
|
32504
|
+
method: "POST",
|
|
32505
|
+
url: url2(ROUTES.events),
|
|
32506
|
+
body: JSON.stringify(batch)
|
|
32507
|
+
});
|
|
32508
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32509
|
+
},
|
|
32510
|
+
async ingestInventory(context) {
|
|
32511
|
+
const response = await send({
|
|
32512
|
+
...common,
|
|
32513
|
+
method: "POST",
|
|
32514
|
+
url: url2(ROUTES.inventory),
|
|
32515
|
+
body: JSON.stringify(context)
|
|
32516
|
+
});
|
|
32517
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32518
|
+
},
|
|
32519
|
+
async recordAuditEvent(event) {
|
|
32520
|
+
await sendOne(event);
|
|
32521
|
+
},
|
|
32522
|
+
async recordAuditEvents(events, opts) {
|
|
32523
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
32524
|
+
if (!validated.success) {
|
|
32525
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
32526
|
+
}
|
|
32527
|
+
const response = await send({
|
|
32528
|
+
...common,
|
|
32529
|
+
method: "POST",
|
|
32530
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
32531
|
+
body: JSON.stringify(validated.data)
|
|
32532
|
+
});
|
|
32533
|
+
if (response.status === 404) {
|
|
32534
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
32535
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
32536
|
+
}
|
|
32537
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
32538
|
+
return { accepted: validated.data.events.length };
|
|
32539
|
+
}
|
|
32540
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
32541
|
+
},
|
|
32542
|
+
async reportStorePosture(snapshot) {
|
|
32543
|
+
const response = await send({
|
|
32544
|
+
...common,
|
|
32545
|
+
method: "POST",
|
|
32546
|
+
url: url2(ROUTES.storePosture),
|
|
32547
|
+
body: JSON.stringify(snapshot)
|
|
32548
|
+
});
|
|
32549
|
+
okBody(response);
|
|
32550
|
+
},
|
|
32551
|
+
async getPolicyBundle(etag) {
|
|
32552
|
+
const response = await send({
|
|
32553
|
+
...common,
|
|
32554
|
+
method: "GET",
|
|
32555
|
+
url: url2(ROUTES.policyBundle),
|
|
32556
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32557
|
+
});
|
|
32558
|
+
if (response.status === 304) {
|
|
32559
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32560
|
+
}
|
|
32561
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32562
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32563
|
+
},
|
|
32564
|
+
async whoami() {
|
|
32565
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32566
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32567
|
+
},
|
|
32568
|
+
async recordProjectEgress(request) {
|
|
32569
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
32570
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
32571
|
+
const response = await send({
|
|
32572
|
+
...common,
|
|
32573
|
+
method: "POST",
|
|
32574
|
+
url: url2(ROUTES.shares),
|
|
32575
|
+
body: JSON.stringify(validated.data)
|
|
32576
|
+
});
|
|
32577
|
+
okBody(response);
|
|
32578
|
+
},
|
|
32579
|
+
async pollCommand() {
|
|
32580
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
32581
|
+
if (response.status === 404) return null;
|
|
32582
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
32583
|
+
},
|
|
32584
|
+
async ackCommand(id, body) {
|
|
32585
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
32586
|
+
const route = ackRoute(id);
|
|
32587
|
+
if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
|
|
32588
|
+
const response = await send({
|
|
32589
|
+
...common,
|
|
32590
|
+
method: "POST",
|
|
32591
|
+
url: url2(route),
|
|
32592
|
+
body: JSON.stringify(validated.data)
|
|
32593
|
+
});
|
|
32594
|
+
okBody(response);
|
|
32595
|
+
}
|
|
32596
|
+
};
|
|
31734
32597
|
}
|
|
31735
32598
|
|
|
31736
|
-
// ../../packages/persistence/src/store-symlinks.ts
|
|
31737
|
-
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31738
|
-
import { dirname as dirname2, join as join9, resolve } from "path";
|
|
31739
|
-
|
|
31740
|
-
// ../../packages/persistence/src/vault/crypto.ts
|
|
31741
|
-
import {
|
|
31742
|
-
createCipheriv,
|
|
31743
|
-
createDecipheriv,
|
|
31744
|
-
createHmac as createHmac2,
|
|
31745
|
-
hkdfSync,
|
|
31746
|
-
timingSafeEqual
|
|
31747
|
-
} from "crypto";
|
|
31748
|
-
|
|
31749
|
-
// ../../packages/persistence/src/vault/key-provider.ts
|
|
31750
|
-
import { execFileSync } from "child_process";
|
|
31751
|
-
import { randomBytes as randomBytes2 } from "crypto";
|
|
31752
|
-
import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
31753
|
-
import { join as join10 } from "path";
|
|
31754
|
-
|
|
31755
|
-
// ../../packages/persistence/src/vault/vault.ts
|
|
31756
|
-
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
31757
|
-
|
|
31758
|
-
// ../../packages/persistence/src/warn-era-cap.ts
|
|
31759
|
-
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
31760
|
-
import { join as join11 } from "path";
|
|
31761
|
-
|
|
31762
32599
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
31763
|
-
import { readFileSync as
|
|
31764
|
-
import { join as
|
|
32600
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
32601
|
+
import { join as join13 } from "path";
|
|
31765
32602
|
|
|
31766
32603
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
31767
32604
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
31768
|
-
import { readFileSync as
|
|
32605
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
31769
32606
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
31770
|
-
import { join as
|
|
32607
|
+
import { join as join22 } from "path";
|
|
31771
32608
|
|
|
31772
32609
|
// ../../packages/plugin-sdk/src/config.ts
|
|
31773
32610
|
import { existsSync as existsSync7 } from "fs";
|
|
31774
|
-
import { join as
|
|
32611
|
+
import { join as join14 } from "path";
|
|
31775
32612
|
|
|
31776
32613
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
31777
32614
|
var booleanish = external_exports.string().optional().transform((v) => {
|
|
@@ -31792,9 +32629,9 @@ var providerEnvShape = {
|
|
|
31792
32629
|
var ProviderEnvSchema = external_exports.object(providerEnvShape);
|
|
31793
32630
|
|
|
31794
32631
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
31795
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
32632
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
31796
32633
|
import { homedir as homedir2 } from "os";
|
|
31797
|
-
import { basename as basename3, join as
|
|
32634
|
+
import { basename as basename3, join as join16 } from "path";
|
|
31798
32635
|
|
|
31799
32636
|
// ../../packages/detections/src/egress/registry.ts
|
|
31800
32637
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -32552,8 +33389,8 @@ var CPU_CORROBORATION_SHARE = 0.2;
|
|
|
32552
33389
|
var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
|
|
32553
33390
|
|
|
32554
33391
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
32555
|
-
import { existsSync as existsSync8, readFileSync as
|
|
32556
|
-
import { basename as basename2, dirname as
|
|
33392
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
33393
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join15, sep as sep2 } from "path";
|
|
32557
33394
|
|
|
32558
33395
|
// ../../packages/plugin-sdk/src/events.ts
|
|
32559
33396
|
import { createHash as createHash5, randomUUID as randomUUID13 } from "crypto";
|
|
@@ -32565,8 +33402,8 @@ import { Worker } from "worker_threads";
|
|
|
32565
33402
|
|
|
32566
33403
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
32567
33404
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
32568
|
-
import { readFileSync as
|
|
32569
|
-
import { join as
|
|
33405
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
33406
|
+
import { join as join17 } from "path";
|
|
32570
33407
|
|
|
32571
33408
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
32572
33409
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -32577,24 +33414,24 @@ import {
|
|
|
32577
33414
|
fstatSync,
|
|
32578
33415
|
mkdirSync as mkdirSync2,
|
|
32579
33416
|
openSync as openSync2,
|
|
32580
|
-
readFileSync as
|
|
33417
|
+
readFileSync as readFileSync12,
|
|
32581
33418
|
readSync,
|
|
32582
33419
|
writeFileSync as writeFileSync5
|
|
32583
33420
|
} from "fs";
|
|
32584
|
-
import { join as
|
|
33421
|
+
import { join as join18 } from "path";
|
|
32585
33422
|
var TAIL_BYTES = 256 * 1024;
|
|
32586
33423
|
|
|
32587
33424
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
32588
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
32589
|
-
import { join as
|
|
33425
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
33426
|
+
import { join as join19 } from "path";
|
|
32590
33427
|
|
|
32591
33428
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
32592
33429
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
32593
|
-
import { basename as basename4, dirname as
|
|
33430
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
32594
33431
|
|
|
32595
33432
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
32596
33433
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
32597
|
-
import { basename as basename5, join as
|
|
33434
|
+
import { basename as basename5, join as join20 } from "path";
|
|
32598
33435
|
|
|
32599
33436
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
32600
33437
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -32630,7 +33467,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
32630
33467
|
|
|
32631
33468
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
32632
33469
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
32633
|
-
import { join as
|
|
33470
|
+
import { join as join21 } from "path";
|
|
32634
33471
|
|
|
32635
33472
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
32636
33473
|
var BREAKER_COOLDOWN_MS = 3e4;
|
|
@@ -32656,15 +33493,15 @@ function parseBreakerState(raw, nowMs) {
|
|
|
32656
33493
|
}
|
|
32657
33494
|
function readForwardHealth(dir, nowMs = Date.now()) {
|
|
32658
33495
|
try {
|
|
32659
|
-
return parseBreakerState(
|
|
33496
|
+
return parseBreakerState(readFileSync14(join22(dir, STATE_FILENAME), "utf8"), nowMs);
|
|
32660
33497
|
} catch {
|
|
32661
33498
|
return null;
|
|
32662
33499
|
}
|
|
32663
33500
|
}
|
|
32664
33501
|
|
|
32665
33502
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
32666
|
-
import { readFileSync as
|
|
32667
|
-
import { join as
|
|
33503
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
33504
|
+
import { join as join23 } from "path";
|
|
32668
33505
|
var HISTORY_SYNC_STATE_FILENAME = ATTACHED_HISTORY_SYNC_STATE_FILENAME;
|
|
32669
33506
|
var PHASES = /* @__PURE__ */ new Set(["filling", "complete"]);
|
|
32670
33507
|
var OUTCOMES = /* @__PURE__ */ new Set([
|
|
@@ -32672,332 +33509,159 @@ var OUTCOMES = /* @__PURE__ */ new Set([
|
|
|
32672
33509
|
"unreachable",
|
|
32673
33510
|
"refused",
|
|
32674
33511
|
"interrupted"
|
|
32675
|
-
]);
|
|
32676
|
-
var SPEC_VERSION = 1;
|
|
32677
|
-
function historySyncStatePath(dataDir2) {
|
|
32678
|
-
return
|
|
32679
|
-
}
|
|
32680
|
-
function writeHistorySyncState(dataDir2, state) {
|
|
32681
|
-
try {
|
|
32682
|
-
ensureDataDirSync(dataDir2);
|
|
32683
|
-
const persisted = { specVersion: SPEC_VERSION, ...state };
|
|
32684
|
-
writeOwnerOnlyFileSync(historySyncStatePath(dataDir2), `${JSON.stringify(persisted)}
|
|
32685
|
-
`);
|
|
32686
|
-
} catch {
|
|
32687
|
-
}
|
|
32688
|
-
}
|
|
32689
|
-
function readHistorySyncState(dataDir2) {
|
|
32690
|
-
try {
|
|
32691
|
-
const parsed2 = JSON.parse(readFileSync14(historySyncStatePath(dataDir2), "utf8"));
|
|
32692
|
-
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32693
|
-
const r = parsed2;
|
|
32694
|
-
if (r.specVersion !== SPEC_VERSION) return null;
|
|
32695
|
-
if (typeof r.phase !== "string" || !PHASES.has(r.phase)) return null;
|
|
32696
|
-
if (typeof r.lastOutcome !== "string" || !OUTCOMES.has(r.lastOutcome)) return null;
|
|
32697
|
-
if (!isCount(r.lastPassAtMs)) return null;
|
|
32698
|
-
if (!isCount(r.sentTotal) || !isCount(r.pendingTotal) || !isCount(r.skippedTotal)) return null;
|
|
32699
|
-
if (!isNullableCount(r.startedAtMs) || !isNullableCount(r.completedAtMs)) return null;
|
|
32700
|
-
return {
|
|
32701
|
-
specVersion: SPEC_VERSION,
|
|
32702
|
-
phase: r.phase,
|
|
32703
|
-
lastOutcome: r.lastOutcome,
|
|
32704
|
-
lastPassAtMs: r.lastPassAtMs,
|
|
32705
|
-
sentTotal: r.sentTotal,
|
|
32706
|
-
pendingTotal: r.pendingTotal,
|
|
32707
|
-
skippedTotal: r.skippedTotal,
|
|
32708
|
-
startedAtMs: r.startedAtMs,
|
|
32709
|
-
completedAtMs: r.completedAtMs
|
|
32710
|
-
};
|
|
32711
|
-
} catch {
|
|
32712
|
-
return null;
|
|
32713
|
-
}
|
|
32714
|
-
}
|
|
32715
|
-
function isCount(v) {
|
|
32716
|
-
return typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
32717
|
-
}
|
|
32718
|
-
function isNullableCount(v) {
|
|
32719
|
-
return v === null || isCount(v);
|
|
32720
|
-
}
|
|
32721
|
-
|
|
32722
|
-
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
32723
|
-
import { createHash as createHash6 } from "crypto";
|
|
32724
|
-
import { hostname as hostname5 } from "os";
|
|
32725
|
-
|
|
32726
|
-
// ../../packages/remote/src/http.ts
|
|
32727
|
-
import { request as httpRequest } from "http";
|
|
32728
|
-
import { request as httpsRequest } from "https";
|
|
32729
|
-
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32730
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32731
|
-
var RemoteRequestError = class extends Error {
|
|
32732
|
-
constructor(status) {
|
|
32733
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
32734
|
-
this.status = status;
|
|
32735
|
-
this.name = "RemoteRequestError";
|
|
32736
|
-
}
|
|
32737
|
-
status;
|
|
32738
|
-
};
|
|
32739
|
-
var RemoteRequestInvalid = class extends Error {
|
|
32740
|
-
constructor(route, cause) {
|
|
32741
|
-
super(`refusing to send a malformed body to ${route}`);
|
|
32742
|
-
this.cause = cause;
|
|
32743
|
-
this.name = "RemoteRequestInvalid";
|
|
32744
|
-
}
|
|
32745
|
-
cause;
|
|
32746
|
-
};
|
|
32747
|
-
var RemoteResponseInvalid = class extends Error {
|
|
32748
|
-
constructor(route, detail) {
|
|
32749
|
-
super(`control plane answered ${route} with ${detail}`);
|
|
32750
|
-
this.name = "RemoteResponseInvalid";
|
|
32751
|
-
}
|
|
32752
|
-
};
|
|
32753
|
-
var RemoteTransportError = class extends Error {
|
|
32754
|
-
/**
|
|
32755
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
32756
|
-
* refused.
|
|
32757
|
-
*
|
|
32758
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
32759
|
-
* all. It exists because two paths reject after a status has already been
|
|
32760
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32761
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
32762
|
-
* outage, which sends the reader to look at their network instead of their
|
|
32763
|
-
* credential.
|
|
32764
|
-
*/
|
|
32765
|
-
constructor(reason, status) {
|
|
32766
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
32767
|
-
this.status = status;
|
|
32768
|
-
this.name = "RemoteTransportError";
|
|
32769
|
-
}
|
|
32770
|
-
status;
|
|
32771
|
-
};
|
|
32772
|
-
async function send(options) {
|
|
32773
|
-
const url2 = new URL(options.url);
|
|
32774
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32775
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32776
|
-
const requestOptions = {
|
|
32777
|
-
method: options.method,
|
|
32778
|
-
headers: {
|
|
32779
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32780
|
-
// last they win, and two of the values below are ones no caller may
|
|
32781
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32782
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
32783
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32784
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
32785
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32786
|
-
// conditional GET — is untouched by this order.
|
|
32787
|
-
...options.headers,
|
|
32788
|
-
// The credential. One header, matching what the deployment authenticates
|
|
32789
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
32790
|
-
// it can be logged by an intermediary for no gain.
|
|
32791
|
-
//
|
|
32792
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
32793
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
32794
|
-
// key differently from an absent one, and "the header is not there" is
|
|
32795
|
-
// the property the attach flow needs.
|
|
32796
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
32797
|
-
accept: "application/json",
|
|
32798
|
-
...options.body === void 0 ? {} : {
|
|
32799
|
-
"content-type": "application/json",
|
|
32800
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
32801
|
-
// character count is truncated by the receiver.
|
|
32802
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
32803
|
-
}
|
|
32804
|
-
}
|
|
32805
|
-
};
|
|
32806
|
-
return new Promise((resolve2, reject) => {
|
|
32807
|
-
let settled = false;
|
|
32808
|
-
const fail = (reason, status) => {
|
|
32809
|
-
if (settled) return;
|
|
32810
|
-
settled = true;
|
|
32811
|
-
reject(new RemoteTransportError(reason, status));
|
|
32812
|
-
};
|
|
32813
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
32814
|
-
const chunks = [];
|
|
32815
|
-
let size = 0;
|
|
32816
|
-
res.on("data", (chunk) => {
|
|
32817
|
-
size += chunk.length;
|
|
32818
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
32819
|
-
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32820
|
-
res.destroy();
|
|
32821
|
-
req.destroy();
|
|
32822
|
-
return;
|
|
32823
|
-
}
|
|
32824
|
-
chunks.push(chunk);
|
|
32825
|
-
});
|
|
32826
|
-
res.on("aborted", () => {
|
|
32827
|
-
fail("the response was aborted", res.statusCode);
|
|
32828
|
-
});
|
|
32829
|
-
res.on("end", () => {
|
|
32830
|
-
if (settled) return;
|
|
32831
|
-
settled = true;
|
|
32832
|
-
resolve2({
|
|
32833
|
-
status: res.statusCode ?? 0,
|
|
32834
|
-
headers: res.headers,
|
|
32835
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
32836
|
-
});
|
|
32837
|
-
});
|
|
32838
|
-
});
|
|
32839
|
-
const deadline = setTimeout(() => {
|
|
32840
|
-
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32841
|
-
req.destroy();
|
|
32842
|
-
}, timeoutMs);
|
|
32843
|
-
deadline.unref();
|
|
32844
|
-
req.on("upgrade", (_res, socket) => {
|
|
32845
|
-
fail("the deployment answered with a protocol upgrade");
|
|
32846
|
-
socket.destroy();
|
|
32847
|
-
});
|
|
32848
|
-
req.on("close", () => {
|
|
32849
|
-
fail("the connection closed before a response was read");
|
|
32850
|
-
clearTimeout(deadline);
|
|
32851
|
-
});
|
|
32852
|
-
req.on("error", (err) => {
|
|
32853
|
-
fail(err.message);
|
|
32854
|
-
});
|
|
32855
|
-
if (options.body !== void 0) req.write(options.body);
|
|
32856
|
-
req.end();
|
|
32857
|
-
});
|
|
32858
|
-
}
|
|
32859
|
-
|
|
32860
|
-
// ../../packages/remote/src/client.ts
|
|
32861
|
-
var ROUTES = {
|
|
32862
|
-
events: "/v1/events",
|
|
32863
|
-
auditEvents: "/v1/audit-events",
|
|
32864
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
32865
|
-
inventory: "/v1/inventory",
|
|
32866
|
-
storePosture: "/v1/store-posture",
|
|
32867
|
-
policyBundle: "/v1/policy-bundle",
|
|
32868
|
-
whoami: "/v1/plugin/whoami",
|
|
32869
|
-
shares: "/v1/shares"
|
|
32870
|
-
};
|
|
32871
|
-
function headerValue(response, name) {
|
|
32872
|
-
const raw = response.headers[name];
|
|
32873
|
-
if (raw === void 0) return void 0;
|
|
32874
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
33512
|
+
]);
|
|
33513
|
+
var SPEC_VERSION = 1;
|
|
33514
|
+
function historySyncStatePath(dataDir2) {
|
|
33515
|
+
return join23(dataDir2, HISTORY_SYNC_STATE_FILENAME);
|
|
32875
33516
|
}
|
|
32876
|
-
function
|
|
32877
|
-
|
|
32878
|
-
|
|
33517
|
+
function writeHistorySyncState(dataDir2, state) {
|
|
33518
|
+
try {
|
|
33519
|
+
ensureDataDirSync(dataDir2);
|
|
33520
|
+
const persisted = { specVersion: SPEC_VERSION, ...state };
|
|
33521
|
+
writeOwnerOnlyFileSync(historySyncStatePath(dataDir2), `${JSON.stringify(persisted)}
|
|
33522
|
+
`);
|
|
33523
|
+
} catch {
|
|
32879
33524
|
}
|
|
32880
|
-
return response.body;
|
|
32881
33525
|
}
|
|
32882
|
-
function
|
|
32883
|
-
let json2;
|
|
33526
|
+
function readHistorySyncState(dataDir2) {
|
|
32884
33527
|
try {
|
|
32885
|
-
|
|
33528
|
+
const parsed2 = JSON.parse(readFileSync15(historySyncStatePath(dataDir2), "utf8"));
|
|
33529
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
33530
|
+
const r = parsed2;
|
|
33531
|
+
if (r.specVersion !== SPEC_VERSION) return null;
|
|
33532
|
+
if (typeof r.phase !== "string" || !PHASES.has(r.phase)) return null;
|
|
33533
|
+
if (typeof r.lastOutcome !== "string" || !OUTCOMES.has(r.lastOutcome)) return null;
|
|
33534
|
+
if (!isCount(r.lastPassAtMs)) return null;
|
|
33535
|
+
if (!isCount(r.sentTotal) || !isCount(r.pendingTotal) || !isCount(r.skippedTotal)) return null;
|
|
33536
|
+
if (!isNullableCount(r.startedAtMs) || !isNullableCount(r.completedAtMs)) return null;
|
|
33537
|
+
return {
|
|
33538
|
+
specVersion: SPEC_VERSION,
|
|
33539
|
+
phase: r.phase,
|
|
33540
|
+
lastOutcome: r.lastOutcome,
|
|
33541
|
+
lastPassAtMs: r.lastPassAtMs,
|
|
33542
|
+
sentTotal: r.sentTotal,
|
|
33543
|
+
pendingTotal: r.pendingTotal,
|
|
33544
|
+
skippedTotal: r.skippedTotal,
|
|
33545
|
+
startedAtMs: r.startedAtMs,
|
|
33546
|
+
completedAtMs: r.completedAtMs
|
|
33547
|
+
};
|
|
32886
33548
|
} catch {
|
|
32887
|
-
|
|
33549
|
+
return null;
|
|
32888
33550
|
}
|
|
32889
|
-
|
|
32890
|
-
|
|
32891
|
-
|
|
33551
|
+
}
|
|
33552
|
+
function isCount(v) {
|
|
33553
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
33554
|
+
}
|
|
33555
|
+
function isNullableCount(v) {
|
|
33556
|
+
return v === null || isCount(v);
|
|
33557
|
+
}
|
|
33558
|
+
|
|
33559
|
+
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
33560
|
+
import { createHash as createHash6 } from "crypto";
|
|
33561
|
+
import { hostname as hostname5 } from "os";
|
|
33562
|
+
|
|
33563
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
33564
|
+
function rebuildCapture(row) {
|
|
33565
|
+
const kind = EventKind.safeParse(row.eventType);
|
|
33566
|
+
if (!kind.success) return void 0;
|
|
33567
|
+
if (kind.data === "code_change") return void 0;
|
|
33568
|
+
const content = row.content ?? null;
|
|
33569
|
+
const contentHash = row.contentHash ?? null;
|
|
33570
|
+
const rootSessionId = row.rootSessionId ?? null;
|
|
33571
|
+
if (content === null || contentHash === null) return void 0;
|
|
33572
|
+
const occurredAt = isoOrUndefined(row.startedAt);
|
|
33573
|
+
if (occurredAt === void 0) return void 0;
|
|
33574
|
+
const attributes = parseAttributes(row.attributes ?? null);
|
|
33575
|
+
const sourceTool = SourceTool.safeParse(attributes.source_tool);
|
|
33576
|
+
if (!sourceTool.success) return void 0;
|
|
33577
|
+
const keep = (value, schema) => value !== void 0 && schema.safeParse(value).success ? value : void 0;
|
|
33578
|
+
const rawFilePath = typeof attributes.file_path === "string" ? attributes.file_path : null;
|
|
33579
|
+
const filePath = stringOrUndefined(attributes.file_path);
|
|
33580
|
+
const metadata = {
|
|
33581
|
+
...rootSessionId === null ? {} : { sessionId: rootSessionId },
|
|
33582
|
+
...filePath === void 0 ? {} : { filePath },
|
|
33583
|
+
...pick2(attributes, { repo: "repo", toolName: "tool_name", model: "model" }),
|
|
33584
|
+
// The two constrained strings, kept only if they satisfy the wire.
|
|
33585
|
+
...withField("traceId", keep(stringOrUndefined(attributes.trace_id), TRACE_ID)),
|
|
33586
|
+
...withField(
|
|
33587
|
+
"correlationId",
|
|
33588
|
+
keep(stringOrUndefined(attributes.correlation_id), CORRELATION_ID)
|
|
33589
|
+
),
|
|
33590
|
+
...typeof attributes.gitignored === "boolean" ? { gitignored: attributes.gitignored } : {},
|
|
33591
|
+
...typeof attributes.whole_file === "boolean" ? { wholeFile: attributes.whole_file } : {},
|
|
33592
|
+
...typeof attributes.turn_index === "number" && Number.isInteger(attributes.turn_index) && attributes.turn_index >= 0 ? { turnIndex: attributes.turn_index } : {},
|
|
33593
|
+
// Carried, unlike inspectionMs below. The live forward sends it, and it is
|
|
33594
|
+
// the enforcement audit trail's link back to the grant that authorized a
|
|
33595
|
+
// bypass — dropping it would make the same capture mean different things to
|
|
33596
|
+
// the deployment depending on which route delivered it.
|
|
33597
|
+
//
|
|
33598
|
+
// Filtered ELEMENT-WISE against the wire's own element schema, for the same
|
|
33599
|
+
// reason the two constrained strings above are: a bag holding
|
|
33600
|
+
// ['legacy-grant-7'] is a plain array of strings, so a `typeof` filter passes
|
|
33601
|
+
// it, IngestEvent.safeParse then refuses the assembled event, and the whole
|
|
33602
|
+
// capture becomes a permanent skip. That is the per-row door the rest of this
|
|
33603
|
+
// function exists to avoid, and this field was the last one still using it.
|
|
33604
|
+
...withList("exceptionIds", keepAll(attributes.exception_ids, EXCEPTION_ID))
|
|
33605
|
+
// inspectionMs is DELIBERATELY not carried. It measures latency a live host
|
|
33606
|
+
// session actually waited on, and a row being drained hours later is not
|
|
33607
|
+
// that; the field's own contract says a replay leaves it absent rather than
|
|
33608
|
+
// reporting a number no session experienced.
|
|
33609
|
+
};
|
|
33610
|
+
const parsed2 = IngestEvent.safeParse({
|
|
33611
|
+
id: captureWireId(rootSessionId, contentHash, rawFilePath),
|
|
33612
|
+
sourceTool: sourceTool.data,
|
|
33613
|
+
kind: kind.data,
|
|
33614
|
+
occurredAt,
|
|
33615
|
+
contentHash,
|
|
33616
|
+
content,
|
|
33617
|
+
metadata
|
|
33618
|
+
});
|
|
33619
|
+
return parsed2.success ? parsed2.data : void 0;
|
|
33620
|
+
}
|
|
33621
|
+
function withField(name, value) {
|
|
33622
|
+
return value === void 0 ? {} : { [name]: value };
|
|
33623
|
+
}
|
|
33624
|
+
function withList(name, values) {
|
|
33625
|
+
return values.length === 0 ? {} : { [name]: values };
|
|
33626
|
+
}
|
|
33627
|
+
function keepAll(value, schema) {
|
|
33628
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string" && schema.safeParse(v).success) : [];
|
|
33629
|
+
}
|
|
33630
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
33631
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
33632
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
33633
|
+
function isoOrUndefined(epochMs) {
|
|
33634
|
+
if (!Number.isFinite(epochMs)) return void 0;
|
|
33635
|
+
const iso = new Date(epochMs);
|
|
33636
|
+
return Number.isNaN(iso.getTime()) ? void 0 : iso.toISOString();
|
|
33637
|
+
}
|
|
33638
|
+
function parseAttributes(raw) {
|
|
33639
|
+
if (raw === null) return {};
|
|
33640
|
+
try {
|
|
33641
|
+
const parsed2 = JSON.parse(raw);
|
|
33642
|
+
return typeof parsed2 === "object" && parsed2 !== null && !Array.isArray(parsed2) ? parsed2 : {};
|
|
33643
|
+
} catch {
|
|
33644
|
+
return {};
|
|
32892
33645
|
}
|
|
32893
|
-
return result.data;
|
|
32894
33646
|
}
|
|
32895
|
-
function
|
|
32896
|
-
|
|
32897
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32898
|
-
return endpoint.slice(0, end);
|
|
33647
|
+
function stringOrUndefined(value) {
|
|
33648
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
32899
33649
|
}
|
|
32900
|
-
|
|
32901
|
-
|
|
32902
|
-
const
|
|
32903
|
-
|
|
32904
|
-
|
|
32905
|
-
|
|
32906
|
-
|
|
32907
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32908
|
-
const response = await send({
|
|
32909
|
-
...common,
|
|
32910
|
-
method: "POST",
|
|
32911
|
-
url: url2(ROUTES.auditEvents),
|
|
32912
|
-
body: JSON.stringify(validated.data)
|
|
32913
|
-
});
|
|
32914
|
-
okBody(response);
|
|
32915
|
-
};
|
|
32916
|
-
return {
|
|
32917
|
-
async ingestEvents(batch) {
|
|
32918
|
-
const response = await send({
|
|
32919
|
-
...common,
|
|
32920
|
-
method: "POST",
|
|
32921
|
-
url: url2(ROUTES.events),
|
|
32922
|
-
body: JSON.stringify(batch)
|
|
32923
|
-
});
|
|
32924
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32925
|
-
},
|
|
32926
|
-
async ingestInventory(context) {
|
|
32927
|
-
const response = await send({
|
|
32928
|
-
...common,
|
|
32929
|
-
method: "POST",
|
|
32930
|
-
url: url2(ROUTES.inventory),
|
|
32931
|
-
body: JSON.stringify(context)
|
|
32932
|
-
});
|
|
32933
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32934
|
-
},
|
|
32935
|
-
async recordAuditEvent(event) {
|
|
32936
|
-
await sendOne(event);
|
|
32937
|
-
},
|
|
32938
|
-
async recordAuditEvents(events) {
|
|
32939
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
32940
|
-
if (!validated.success) {
|
|
32941
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
32942
|
-
}
|
|
32943
|
-
const response = await send({
|
|
32944
|
-
...common,
|
|
32945
|
-
method: "POST",
|
|
32946
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
32947
|
-
body: JSON.stringify(validated.data)
|
|
32948
|
-
});
|
|
32949
|
-
if (response.status === 404) {
|
|
32950
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
32951
|
-
return { accepted: validated.data.events.length };
|
|
32952
|
-
}
|
|
32953
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
32954
|
-
},
|
|
32955
|
-
async reportStorePosture(snapshot) {
|
|
32956
|
-
const response = await send({
|
|
32957
|
-
...common,
|
|
32958
|
-
method: "POST",
|
|
32959
|
-
url: url2(ROUTES.storePosture),
|
|
32960
|
-
body: JSON.stringify(snapshot)
|
|
32961
|
-
});
|
|
32962
|
-
okBody(response);
|
|
32963
|
-
},
|
|
32964
|
-
async getPolicyBundle(etag) {
|
|
32965
|
-
const response = await send({
|
|
32966
|
-
...common,
|
|
32967
|
-
method: "GET",
|
|
32968
|
-
url: url2(ROUTES.policyBundle),
|
|
32969
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32970
|
-
});
|
|
32971
|
-
if (response.status === 304) {
|
|
32972
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32973
|
-
}
|
|
32974
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32975
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32976
|
-
},
|
|
32977
|
-
async whoami() {
|
|
32978
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32979
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32980
|
-
},
|
|
32981
|
-
async recordProjectEgress(request) {
|
|
32982
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
32983
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
32984
|
-
const response = await send({
|
|
32985
|
-
...common,
|
|
32986
|
-
method: "POST",
|
|
32987
|
-
url: url2(ROUTES.shares),
|
|
32988
|
-
body: JSON.stringify(validated.data)
|
|
32989
|
-
});
|
|
32990
|
-
okBody(response);
|
|
32991
|
-
}
|
|
32992
|
-
};
|
|
33650
|
+
function pick2(attributes, mapping) {
|
|
33651
|
+
const out = {};
|
|
33652
|
+
for (const [wire, column] of Object.entries(mapping)) {
|
|
33653
|
+
const value = stringOrUndefined(attributes[column]);
|
|
33654
|
+
if (value !== void 0) out[wire] = value;
|
|
33655
|
+
}
|
|
33656
|
+
return out;
|
|
32993
33657
|
}
|
|
32994
33658
|
|
|
32995
33659
|
// ../../packages/plugin-runtime/src/attached/history-rebuild.ts
|
|
32996
33660
|
var CAPTURE_VERSION_PREFIX2 = "capture/";
|
|
32997
33661
|
function rebuildAuditEvent(row, inspections = []) {
|
|
32998
|
-
const startedAt =
|
|
33662
|
+
const startedAt = isoOrUndefined2(row.startedAt);
|
|
32999
33663
|
if (startedAt === void 0) return void 0;
|
|
33000
|
-
const endedAt =
|
|
33664
|
+
const endedAt = isoOrUndefined2(row.endedAt);
|
|
33001
33665
|
const candidate = {
|
|
33002
33666
|
id: row.id,
|
|
33003
33667
|
eventType: row.eventType,
|
|
@@ -33026,7 +33690,7 @@ function rebuildAuditEvent(row, inspections = []) {
|
|
|
33026
33690
|
return parsed2.success ? parsed2.data : void 0;
|
|
33027
33691
|
}
|
|
33028
33692
|
var MAX_EPOCH_MS = 864e13;
|
|
33029
|
-
function
|
|
33693
|
+
function isoOrUndefined2(ms) {
|
|
33030
33694
|
if (ms === null || ms === void 0 || !Number.isFinite(ms)) return void 0;
|
|
33031
33695
|
if (Math.abs(ms) > MAX_EPOCH_MS) return void 0;
|
|
33032
33696
|
return epochMillisToIso(ms);
|
|
@@ -33052,10 +33716,17 @@ var MAX_ATTEMPTS = 4;
|
|
|
33052
33716
|
var MAX_BACKOFF_MS = 6e4;
|
|
33053
33717
|
var SESSION_PAGE = 25;
|
|
33054
33718
|
var ROW_PAGE = 200;
|
|
33719
|
+
var CAPTURE_GRACE_MS = 3e4;
|
|
33720
|
+
var CAPTURE_BATCH_SIZE = INGEST_BATCH_MAX;
|
|
33721
|
+
var STRUCTURAL_BUDGET_SHARE = 0.7;
|
|
33055
33722
|
var BATCH_SIZE = AUDIT_EVENT_BATCH_MAX;
|
|
33056
33723
|
function endpointFingerprint(endpoint) {
|
|
33057
33724
|
return createHash6("sha256").update(endpoint).digest("hex");
|
|
33058
33725
|
}
|
|
33726
|
+
var didNotRun = (reason) => ({
|
|
33727
|
+
attempted: false,
|
|
33728
|
+
reason
|
|
33729
|
+
});
|
|
33059
33730
|
async function runHistorySync(deps) {
|
|
33060
33731
|
const now = deps.now ?? (() => Date.now());
|
|
33061
33732
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -33064,19 +33735,23 @@ async function runHistorySync(deps) {
|
|
|
33064
33735
|
let db;
|
|
33065
33736
|
try {
|
|
33066
33737
|
const settings = readWorkspaceSettings(deps.base);
|
|
33067
|
-
if (!isAttached(settings)) return
|
|
33738
|
+
if (!isAttached(settings)) return didNotRun("not-attached");
|
|
33068
33739
|
const connection = settings.controlPlane;
|
|
33069
|
-
if (connection === void 0) return
|
|
33070
|
-
if (!isHistorySyncConsentValid(settings.historySyncConsent, connection.endpoint))
|
|
33740
|
+
if (connection === void 0) return didNotRun("not-attached");
|
|
33741
|
+
if (!isHistorySyncConsentValid(settings.historySyncConsent, connection.endpoint)) {
|
|
33742
|
+
return didNotRun("no-consent");
|
|
33743
|
+
}
|
|
33071
33744
|
const state = readControlPlaneCredentialFile(deps.settingsDir, connection);
|
|
33072
|
-
if (!state.usable) return
|
|
33745
|
+
if (!state.usable) return didNotRun("credential-unusable");
|
|
33073
33746
|
const nowMs = now();
|
|
33074
33747
|
const openedAtMs = readForwardHealth(deps.dataDir, nowMs)?.openedAtMs ?? null;
|
|
33075
|
-
if (openedAtMs !== null && nowMs - openedAtMs < BREAKER_COOLDOWN_MS)
|
|
33748
|
+
if (openedAtMs !== null && nowMs - openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
33749
|
+
return didNotRun("breaker-open");
|
|
33750
|
+
}
|
|
33076
33751
|
db = (deps.openStore ?? openLocalDatabase)(deps.dataDir);
|
|
33077
33752
|
const ledger = db.historySync;
|
|
33078
33753
|
const attachedAtMs = Date.parse(connection.attachedAt);
|
|
33079
|
-
if (!Number.isFinite(attachedAtMs)) return
|
|
33754
|
+
if (!Number.isFinite(attachedAtMs)) return didNotRun("attachment-unreadable");
|
|
33080
33755
|
const fingerprint = endpointFingerprint(connection.endpoint);
|
|
33081
33756
|
const recorded = ledger.deployment();
|
|
33082
33757
|
let backlogBefore;
|
|
@@ -33090,24 +33765,46 @@ async function runHistorySync(deps) {
|
|
|
33090
33765
|
backlogBefore = recorded.backlogBefore;
|
|
33091
33766
|
}
|
|
33092
33767
|
const pid = process.pid;
|
|
33093
|
-
if (!ledger.claim(pid, hostname5(), now(), HISTORY_LEASE_STALE_MS))
|
|
33094
|
-
|
|
33095
|
-
|
|
33096
|
-
|
|
33097
|
-
|
|
33098
|
-
|
|
33099
|
-
|
|
33100
|
-
|
|
33101
|
-
|
|
33102
|
-
|
|
33103
|
-
|
|
33768
|
+
if (!ledger.claim(pid, hostname5(), now(), HISTORY_LEASE_STALE_MS)) {
|
|
33769
|
+
return didNotRun("already-running");
|
|
33770
|
+
}
|
|
33771
|
+
const client = deps.sendBatch !== void 0 && deps.sendCaptures !== void 0 && deps.sendOne !== void 0 ? void 0 : createRemoteClient({
|
|
33772
|
+
endpoint: connection.endpoint,
|
|
33773
|
+
apiKey: state.credential.apiKey,
|
|
33774
|
+
timeoutMs: HISTORY_REQUEST_TIMEOUT_MS
|
|
33775
|
+
});
|
|
33776
|
+
const send2 = deps.sendBatch ?? (async (events) => {
|
|
33777
|
+
if (client === void 0) throw new Error("history sync: no transport");
|
|
33778
|
+
const ack = await client.recordAuditEvents(events, { fallbackToSingleEvents: true });
|
|
33779
|
+
return { settled: ack.accepted };
|
|
33780
|
+
});
|
|
33781
|
+
const sendOne = deps.sendOne ?? (async (event) => {
|
|
33782
|
+
if (client === void 0) throw new Error("history sync: no transport");
|
|
33783
|
+
await client.recordAuditEvent(event);
|
|
33784
|
+
});
|
|
33785
|
+
const sendCaptures = deps.sendCaptures ?? (async (events) => {
|
|
33786
|
+
if (client === void 0) throw new Error("history sync: no transport");
|
|
33787
|
+
const ack = await client.ingestEvents({ events: [...events] });
|
|
33788
|
+
return { settled: ack.accepted + ack.duplicates };
|
|
33789
|
+
});
|
|
33104
33790
|
try {
|
|
33105
|
-
return await drain({
|
|
33791
|
+
return await drain({
|
|
33792
|
+
ledger,
|
|
33793
|
+
send: send2,
|
|
33794
|
+
sendOne,
|
|
33795
|
+
sendCaptures,
|
|
33796
|
+
now,
|
|
33797
|
+
sleep,
|
|
33798
|
+
random,
|
|
33799
|
+
budgetMs,
|
|
33800
|
+
pid,
|
|
33801
|
+
backlogBefore
|
|
33802
|
+
});
|
|
33106
33803
|
} finally {
|
|
33107
33804
|
ledger.release(pid);
|
|
33108
33805
|
}
|
|
33109
33806
|
} catch {
|
|
33110
|
-
return
|
|
33807
|
+
return didNotRun("failed");
|
|
33111
33808
|
} finally {
|
|
33112
33809
|
try {
|
|
33113
33810
|
db?.close();
|
|
@@ -33118,76 +33815,207 @@ async function runHistorySync(deps) {
|
|
|
33118
33815
|
async function drain(d) {
|
|
33119
33816
|
const startedAt = d.now();
|
|
33120
33817
|
const deadline = startedAt + d.budgetMs;
|
|
33818
|
+
const structuralDeadline = Math.min(deadline, startedAt + d.budgetMs * STRUCTURAL_BUDGET_SHARE);
|
|
33121
33819
|
let sent = 0;
|
|
33122
33820
|
let skipped = 0;
|
|
33123
33821
|
let lastHeartbeat = startedAt;
|
|
33124
|
-
let outcome = "ok";
|
|
33125
33822
|
const beat = () => {
|
|
33126
33823
|
const at = d.now();
|
|
33127
33824
|
if (at - lastHeartbeat < HEARTBEAT_EVERY_MS) return;
|
|
33128
33825
|
d.ledger.heartbeat(d.pid, at);
|
|
33129
33826
|
lastHeartbeat = at;
|
|
33130
33827
|
};
|
|
33131
|
-
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
|
|
33135
|
-
|
|
33136
|
-
|
|
33137
|
-
|
|
33138
|
-
|
|
33139
|
-
const
|
|
33140
|
-
|
|
33141
|
-
d.ledger.
|
|
33142
|
-
|
|
33143
|
-
|
|
33144
|
-
|
|
33145
|
-
|
|
33146
|
-
|
|
33147
|
-
|
|
33148
|
-
if (d.now() >= deadline) {
|
|
33149
|
-
outcome = "interrupted";
|
|
33150
|
-
break outer;
|
|
33828
|
+
const drainStructural = async (until) => {
|
|
33829
|
+
let stopped = "ok";
|
|
33830
|
+
outer: while (d.now() < until) {
|
|
33831
|
+
const sessions = d.ledger.pendingSessions(SESSION_PAGE, d.backlogBefore);
|
|
33832
|
+
if (sessions.length === 0) break;
|
|
33833
|
+
for (const sessionId of sessions) {
|
|
33834
|
+
const rows = d.ledger.pendingRows(sessionId, ROW_PAGE, d.backlogBefore);
|
|
33835
|
+
if (rows.length === 0) continue;
|
|
33836
|
+
const ready = [];
|
|
33837
|
+
for (const row of rows) {
|
|
33838
|
+
const event = rebuildAuditEvent(row, d.ledger.inspectionsFor(row.id));
|
|
33839
|
+
if (event === void 0) {
|
|
33840
|
+
d.ledger.markSkipped([row.id]);
|
|
33841
|
+
skipped += 1;
|
|
33842
|
+
continue;
|
|
33843
|
+
}
|
|
33844
|
+
ready.push({ id: row.id, event });
|
|
33151
33845
|
}
|
|
33152
|
-
|
|
33153
|
-
|
|
33154
|
-
|
|
33155
|
-
|
|
33156
|
-
|
|
33157
|
-
|
|
33158
|
-
|
|
33846
|
+
for (let i = 0; i < ready.length; i += BATCH_SIZE) {
|
|
33847
|
+
if (d.now() >= until) {
|
|
33848
|
+
stopped = "interrupted";
|
|
33849
|
+
break outer;
|
|
33850
|
+
}
|
|
33851
|
+
const chunk = ready.slice(i, i + BATCH_SIZE);
|
|
33852
|
+
const result = await sendChunk(d, chunk, beat);
|
|
33853
|
+
sent += result.sent;
|
|
33854
|
+
skipped += result.skipped;
|
|
33855
|
+
if (result.stopped !== void 0) {
|
|
33856
|
+
stopped = result.stopped;
|
|
33857
|
+
break outer;
|
|
33858
|
+
}
|
|
33859
|
+
beat();
|
|
33860
|
+
await d.sleep(PACE_INTERVAL_MS);
|
|
33159
33861
|
}
|
|
33160
|
-
beat();
|
|
33161
|
-
await d.sleep(PACE_INTERVAL_MS);
|
|
33162
33862
|
}
|
|
33163
33863
|
}
|
|
33864
|
+
return stopped;
|
|
33865
|
+
};
|
|
33866
|
+
let outcome = await drainStructural(structuralDeadline);
|
|
33867
|
+
if (outcome === "interrupted" && d.now() < deadline) outcome = "ok";
|
|
33868
|
+
if (outcome === "ok") {
|
|
33869
|
+
const captures = await drainCaptures(d, deadline, beat);
|
|
33870
|
+
sent += captures.sent;
|
|
33871
|
+
skipped += captures.skipped;
|
|
33872
|
+
if (captures.stopped !== void 0) outcome = captures.stopped;
|
|
33873
|
+
}
|
|
33874
|
+
if (outcome === "ok" && d.now() < deadline && d.ledger.pendingCaptureRows(1, d.now() - CAPTURE_GRACE_MS).length === 0) {
|
|
33875
|
+
outcome = await drainStructural(deadline);
|
|
33164
33876
|
}
|
|
33165
33877
|
if (outcome === "ok" && d.now() >= deadline && d.ledger.counts(d.backlogBefore).pending > 0) {
|
|
33166
33878
|
outcome = "interrupted";
|
|
33167
33879
|
}
|
|
33168
|
-
return {
|
|
33880
|
+
return {
|
|
33881
|
+
attempted: true,
|
|
33882
|
+
outcome,
|
|
33883
|
+
sent,
|
|
33884
|
+
skipped,
|
|
33885
|
+
// LIMIT 1 — this asks "is anything owed", never "how much", so it must not
|
|
33886
|
+
// pay for a count over the capture grain on every pass.
|
|
33887
|
+
capturesPending: d.ledger.pendingCaptureRows(1, d.now() - CAPTURE_GRACE_MS).length > 0,
|
|
33888
|
+
counts: d.ledger.counts(d.backlogBefore),
|
|
33889
|
+
atMs: d.now()
|
|
33890
|
+
};
|
|
33891
|
+
}
|
|
33892
|
+
async function drainCaptures(d, deadline, beat) {
|
|
33893
|
+
let sent = 0;
|
|
33894
|
+
let skipped = 0;
|
|
33895
|
+
for (; ; ) {
|
|
33896
|
+
if (d.now() >= deadline) return { sent, skipped, stopped: "interrupted" };
|
|
33897
|
+
const rows = d.ledger.pendingCaptureRows(CAPTURE_BATCH_SIZE, d.now() - CAPTURE_GRACE_MS);
|
|
33898
|
+
if (rows.length === 0) return { sent, skipped };
|
|
33899
|
+
const ready = [];
|
|
33900
|
+
const unbuildable = [];
|
|
33901
|
+
for (const row of rows) {
|
|
33902
|
+
const event = rebuildCapture(row);
|
|
33903
|
+
if (event === void 0) {
|
|
33904
|
+
unbuildable.push(row.id);
|
|
33905
|
+
continue;
|
|
33906
|
+
}
|
|
33907
|
+
ready.push({ id: row.id, event });
|
|
33908
|
+
}
|
|
33909
|
+
if (unbuildable.length > 0) {
|
|
33910
|
+
d.ledger.markSkipped(unbuildable);
|
|
33911
|
+
skipped += unbuildable.length;
|
|
33912
|
+
}
|
|
33913
|
+
if (ready.length === 0) {
|
|
33914
|
+
beat();
|
|
33915
|
+
await d.sleep(PACE_INTERVAL_MS);
|
|
33916
|
+
continue;
|
|
33917
|
+
}
|
|
33918
|
+
const result = await sendCaptureChunk(d, ready, beat);
|
|
33919
|
+
sent += result.sent;
|
|
33920
|
+
skipped += result.skipped;
|
|
33921
|
+
if (result.stopped !== void 0) return { sent, skipped, stopped: result.stopped };
|
|
33922
|
+
if (result.sent === 0 && result.skipped === 0) return { sent, skipped, stopped: "unreachable" };
|
|
33923
|
+
beat();
|
|
33924
|
+
await d.sleep(PACE_INTERVAL_MS);
|
|
33925
|
+
}
|
|
33169
33926
|
}
|
|
33170
|
-
async function
|
|
33171
|
-
const
|
|
33172
|
-
|
|
33173
|
-
|
|
33174
|
-
|
|
33175
|
-
|
|
33176
|
-
|
|
33927
|
+
async function sendCaptureChunk(d, chunk, beat) {
|
|
33928
|
+
const outcome = await sendCapturesWithRetries(d, chunk, beat);
|
|
33929
|
+
if (outcome.verdict === "refused") return { sent: 0, skipped: 0, stopped: "refused" };
|
|
33930
|
+
if (outcome.verdict === "unreachable") return { sent: 0, skipped: 0, stopped: "unreachable" };
|
|
33931
|
+
if (outcome.verdict === "sent") {
|
|
33932
|
+
const settled = outcome.settled;
|
|
33933
|
+
if (settled < chunk.length) {
|
|
33934
|
+
if (chunk.length > 1) return await isolate(d, chunk, beat);
|
|
33935
|
+
return { sent: 0, skipped: 0, stopped: "unreachable" };
|
|
33936
|
+
}
|
|
33177
33937
|
d.ledger.markSynced(
|
|
33178
33938
|
chunk.map((c) => c.id),
|
|
33179
33939
|
d.now()
|
|
33180
33940
|
);
|
|
33181
33941
|
return { sent: chunk.length, skipped: 0 };
|
|
33182
33942
|
}
|
|
33183
|
-
|
|
33184
|
-
|
|
33943
|
+
{
|
|
33944
|
+
const only = chunk.length === 1 ? chunk[0] : void 0;
|
|
33945
|
+
if (only !== void 0) {
|
|
33946
|
+
d.ledger.markSkipped([only.id]);
|
|
33947
|
+
return { sent: 0, skipped: 1 };
|
|
33948
|
+
}
|
|
33949
|
+
return await isolate(d, chunk, beat);
|
|
33950
|
+
}
|
|
33951
|
+
}
|
|
33952
|
+
async function isolate(d, chunk, beat) {
|
|
33953
|
+
let sent = 0;
|
|
33954
|
+
let skipped = 0;
|
|
33955
|
+
for (const [index, one] of chunk.entries()) {
|
|
33956
|
+
if (index > 0) await d.sleep(PACE_INTERVAL_MS);
|
|
33957
|
+
beat();
|
|
33958
|
+
const single = await sendCaptureChunk(d, [one], beat);
|
|
33959
|
+
sent += single.sent;
|
|
33960
|
+
skipped += single.skipped;
|
|
33961
|
+
if (single.stopped !== void 0) return { sent, skipped, stopped: single.stopped };
|
|
33962
|
+
}
|
|
33963
|
+
return { sent, skipped };
|
|
33964
|
+
}
|
|
33965
|
+
async function sendCapturesWithRetries(d, chunk, beat) {
|
|
33966
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
|
|
33967
|
+
try {
|
|
33968
|
+
const ack = await d.sendCaptures(chunk.map((c) => c.event));
|
|
33969
|
+
return { verdict: "sent", settled: ack.settled };
|
|
33970
|
+
} catch (err) {
|
|
33971
|
+
const kind = classify(err);
|
|
33972
|
+
if (kind === "refused") return { verdict: "refused" };
|
|
33973
|
+
if (kind === "skip") return { verdict: "skip" };
|
|
33974
|
+
if (attempt === MAX_ATTEMPTS - 1) return { verdict: "unreachable" };
|
|
33975
|
+
const ceiling = Math.min(MAX_BACKOFF_MS, 1e3 * 2 ** attempt);
|
|
33976
|
+
beat();
|
|
33977
|
+
await d.sleep(Math.floor(d.random() * ceiling));
|
|
33978
|
+
}
|
|
33185
33979
|
}
|
|
33980
|
+
return { verdict: "unreachable" };
|
|
33981
|
+
}
|
|
33982
|
+
async function sendChunk(d, chunk, beat) {
|
|
33186
33983
|
const only = chunk.length === 1 ? chunk[0] : void 0;
|
|
33187
|
-
if (only !== void 0)
|
|
33188
|
-
|
|
33189
|
-
|
|
33984
|
+
if (only !== void 0) return await sendSingleRow(d, only, beat);
|
|
33985
|
+
const outcome = await sendWithRetries(d, () => d.send(chunk.map((c) => c.event)), beat);
|
|
33986
|
+
if (outcome.verdict === "sent") {
|
|
33987
|
+
const settled = outcome.settled;
|
|
33988
|
+
if (settled < chunk.length) {
|
|
33989
|
+
return await isolateStructural(d, chunk, beat);
|
|
33990
|
+
}
|
|
33991
|
+
d.ledger.markSynced(
|
|
33992
|
+
chunk.map((c) => c.id),
|
|
33993
|
+
d.now()
|
|
33994
|
+
);
|
|
33995
|
+
return { sent: chunk.length, skipped: 0 };
|
|
33996
|
+
}
|
|
33997
|
+
if (outcome.verdict === "refused" || outcome.verdict === "unreachable") {
|
|
33998
|
+
return { sent: 0, skipped: 0, stopped: outcome.verdict };
|
|
33999
|
+
}
|
|
34000
|
+
return await isolateStructural(d, chunk, beat);
|
|
34001
|
+
}
|
|
34002
|
+
async function sendSingleRow(d, one, beat) {
|
|
34003
|
+
const outcome = await sendWithRetries(
|
|
34004
|
+
d,
|
|
34005
|
+
() => d.sendOne(one.event).then(() => ({ settled: 1 })),
|
|
34006
|
+
beat
|
|
34007
|
+
);
|
|
34008
|
+
if (outcome.verdict === "sent") {
|
|
34009
|
+
d.ledger.markSynced([one.id], d.now());
|
|
34010
|
+
return { sent: 1, skipped: 0 };
|
|
34011
|
+
}
|
|
34012
|
+
if (outcome.verdict === "refused" || outcome.verdict === "unreachable") {
|
|
34013
|
+
return { sent: 0, skipped: 0, stopped: outcome.verdict };
|
|
33190
34014
|
}
|
|
34015
|
+
d.ledger.markSkipped([one.id]);
|
|
34016
|
+
return { sent: 0, skipped: 1 };
|
|
34017
|
+
}
|
|
34018
|
+
async function isolateStructural(d, chunk, beat) {
|
|
33191
34019
|
let sent = 0;
|
|
33192
34020
|
let skipped = 0;
|
|
33193
34021
|
for (const [index, one] of chunk.entries()) {
|
|
@@ -33200,24 +34028,24 @@ async function sendChunk(d, chunk, beat) {
|
|
|
33200
34028
|
}
|
|
33201
34029
|
return { sent, skipped };
|
|
33202
34030
|
}
|
|
33203
|
-
async function sendWithRetries(d,
|
|
33204
|
-
for (let
|
|
34031
|
+
async function sendWithRetries(d, attempt, beat) {
|
|
34032
|
+
for (let i = 0; i < MAX_ATTEMPTS; i += 1) {
|
|
33205
34033
|
try {
|
|
33206
|
-
await
|
|
33207
|
-
return "sent";
|
|
34034
|
+
const { settled } = await attempt();
|
|
34035
|
+
return { verdict: "sent", settled };
|
|
33208
34036
|
} catch (err) {
|
|
33209
34037
|
const kind = classify(err);
|
|
33210
|
-
if (kind === "refused") return "refused";
|
|
33211
|
-
if (kind === "skip") return "skip";
|
|
33212
|
-
if (
|
|
33213
|
-
const ceiling = Math.min(MAX_BACKOFF_MS, 1e3 * 2 **
|
|
34038
|
+
if (kind === "refused") return { verdict: "refused" };
|
|
34039
|
+
if (kind === "skip") return { verdict: "skip" };
|
|
34040
|
+
if (i === MAX_ATTEMPTS - 1) return { verdict: "unreachable" };
|
|
34041
|
+
const ceiling = Math.min(MAX_BACKOFF_MS, 1e3 * 2 ** i);
|
|
33214
34042
|
beat();
|
|
33215
34043
|
await d.sleep(Math.floor(d.random() * ceiling));
|
|
33216
34044
|
}
|
|
33217
34045
|
}
|
|
33218
|
-
return "unreachable";
|
|
34046
|
+
return { verdict: "unreachable" };
|
|
33219
34047
|
}
|
|
33220
|
-
function
|
|
34048
|
+
function statusOf2(err) {
|
|
33221
34049
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
33222
34050
|
const { status } = err;
|
|
33223
34051
|
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
@@ -33225,7 +34053,7 @@ function statusOf(err) {
|
|
|
33225
34053
|
}
|
|
33226
34054
|
function classify(err) {
|
|
33227
34055
|
if (err.name === "RemoteRequestInvalid") return "skip";
|
|
33228
|
-
switch (
|
|
34056
|
+
switch (statusOf2(err)) {
|
|
33229
34057
|
// Terminal in a way a timeout is not: the credential may have died with an
|
|
33230
34058
|
// offboarded member, and every later row would fail the same way.
|
|
33231
34059
|
case 401:
|
|
@@ -33243,33 +34071,58 @@ function classify(err) {
|
|
|
33243
34071
|
}
|
|
33244
34072
|
|
|
33245
34073
|
// ../../packages/plugin-runtime/src/attached/history-sync-entry.ts
|
|
33246
|
-
async function runHistorySyncPass(base = defaultDataDir()) {
|
|
34074
|
+
async function runHistorySyncPass(base = defaultDataDir(), seams = {}) {
|
|
33247
34075
|
try {
|
|
33248
34076
|
const dir = dataDir(base);
|
|
33249
34077
|
const result = await runHistorySync({
|
|
33250
34078
|
base,
|
|
33251
34079
|
settingsDir: settingsDir(base),
|
|
33252
|
-
dataDir: dir
|
|
34080
|
+
dataDir: dir,
|
|
34081
|
+
...seams
|
|
33253
34082
|
});
|
|
33254
|
-
if (result
|
|
34083
|
+
if (!result.attempted) return result.reason;
|
|
33255
34084
|
const previous = readHistorySyncState(dir);
|
|
33256
|
-
const done = result.counts.pending === 0;
|
|
34085
|
+
const done = result.counts.pending === 0 && !result.capturesPending;
|
|
33257
34086
|
writeHistorySyncState(dir, {
|
|
33258
34087
|
phase: done ? "complete" : "filling",
|
|
33259
34088
|
lastOutcome: result.outcome,
|
|
33260
34089
|
lastPassAtMs: result.atMs,
|
|
33261
34090
|
sentTotal: result.counts.sent,
|
|
33262
34091
|
pendingTotal: result.counts.pending,
|
|
33263
|
-
|
|
34092
|
+
// BOTH lanes, and BOTH lifetime. `counts.skipped` filters to structural
|
|
34093
|
+
// rows, so a capture rebuildCapture refused — stamped -1 and dropped for
|
|
34094
|
+
// ever — was counted on no surface at all. Silence is the right shape for a
|
|
34095
|
+
// TRANSIENT failure everywhere else in this repo; this one is terminal, so
|
|
34096
|
+
// it owes a number.
|
|
34097
|
+
//
|
|
34098
|
+
// Both terms are ledger totals rather than this pass's tally, which is the
|
|
34099
|
+
// part that matters: a per-pass delta added to a lifetime total gives a
|
|
34100
|
+
// field whose capture half is one pass wide, so the surface beside
|
|
34101
|
+
// `sentTotal` and `pendingTotal` would announce a permanent loss once and
|
|
34102
|
+
// drop it on the next pass, while the rows stayed gone.
|
|
34103
|
+
skippedTotal: result.counts.skipped + result.counts.capturesSkipped,
|
|
33264
34104
|
// The first pass that ran is when this machine started sending, and it
|
|
33265
34105
|
// keeps that answer across every later pass.
|
|
33266
34106
|
startedAtMs: previous?.startedAtMs ?? result.atMs,
|
|
33267
|
-
// Stamped
|
|
33268
|
-
// recorded before the machine attached —
|
|
33269
|
-
//
|
|
33270
|
-
|
|
34107
|
+
// Stamped the first time BOTH lanes were empty. The structural backlog is
|
|
34108
|
+
// a fixed set — everything recorded before the machine attached — and it
|
|
34109
|
+
// genuinely finishes. The capture lane does not: its subject grows with
|
|
34110
|
+
// every live session that fails to forward, so `phase` can go back to
|
|
34111
|
+
// 'filling' after reading 'complete'. This keeps its original meaning
|
|
34112
|
+
// either way — the first moment this machine owed the deployment nothing —
|
|
34113
|
+
// which is why it is pinned rather than recomputed.
|
|
34114
|
+
// WRITTEN ONCE, on the false→true transition, and never cleared. Under v1
|
|
34115
|
+
// this was monotone because the structural lane only ever drained; the
|
|
34116
|
+
// capture lane is what makes `done` flap, and clearing on every flap would
|
|
34117
|
+
// erase the pin and re-stamp it on the next catch-up — so a consumer
|
|
34118
|
+
// reading "when this machine first caught up" would get the most recent
|
|
34119
|
+
// one instead. Carrying the previous value through the false case is what
|
|
34120
|
+
// keeps the original meaning.
|
|
34121
|
+
completedAtMs: previous?.completedAtMs ?? (done ? result.atMs : null)
|
|
33271
34122
|
});
|
|
34123
|
+
return result.outcome;
|
|
33272
34124
|
} catch {
|
|
34125
|
+
return "failed";
|
|
33273
34126
|
}
|
|
33274
34127
|
}
|
|
33275
34128
|
|
|
@@ -33279,12 +34132,12 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
33279
34132
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
33280
34133
|
|
|
33281
34134
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
33282
|
-
import { readFileSync as
|
|
34135
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
33283
34136
|
|
|
33284
34137
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
33285
34138
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
33286
34139
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
33287
|
-
import { join as
|
|
34140
|
+
import { join as join24 } from "path";
|
|
33288
34141
|
|
|
33289
34142
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
33290
34143
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -33299,11 +34152,11 @@ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
|
33299
34152
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
33300
34153
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
33301
34154
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
33302
|
-
import { join as
|
|
34155
|
+
import { join as join25 } from "path";
|
|
33303
34156
|
|
|
33304
34157
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
33305
|
-
import { readFileSync as
|
|
33306
|
-
import { join as
|
|
34158
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
34159
|
+
import { join as join26 } from "path";
|
|
33307
34160
|
|
|
33308
34161
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
33309
34162
|
var REFUSAL_LINES = {
|