@akasecurity/ai-tc-claude-code 0.8.1 → 0.9.0-rc1
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 +15 -4
- package/commands/setup.md +650 -70
- package/hooks/hooks.json +1 -1
- package/package.json +7 -6
- package/scripts/apply-suppressions.js +24237 -0
- package/scripts/backfill.js +1852 -937
- package/scripts/filescan.js +1636 -900
- package/scripts/firstrun.js +1796 -929
- package/scripts/intro.js +377 -119
- package/scripts/onboard.js +6162 -106
- package/scripts/package.json +1 -0
- package/scripts/post-tool-use.js +1678 -919
- package/scripts/pre-tool-use.js +1776 -931
- package/scripts/query.js +1621 -884
- package/scripts/reconcile.js +1632 -902
- package/scripts/remediate.js +26695 -0
- package/scripts/session-start.js +1680 -941
- package/scripts/start-light.js +17542 -0
- package/scripts/statusline.js +1621 -882
- package/scripts/stop.js +301 -40
- package/scripts/triage-rubric.md +92 -0
- package/scripts/user-prompt-submit.js +1654 -917
package/scripts/session-start.js
CHANGED
|
@@ -541,6 +541,10 @@ var SQLITE_MIGRATIONS = [
|
|
|
541
541
|
{
|
|
542
542
|
tag: "0009_findings_path_expression_index",
|
|
543
543
|
sql: "-- Custom migration: partial expression index for the resolver's per-path reads.\n--\n-- openAtRestKeysForPath / resolvedAtRestKeysForPath (and the scanner's tier-3\n-- open-key probe) filter at-rest events by\n-- e.kind = 'code_change' AND json_extract(e.metadata, '$.filePath') = :path\n-- once per changed/deleted file on every scan \u2014 previously a full events scan\n-- per file. json_extract is deterministic, so SQLite allows it in an index;\n-- the WHERE kind = 'code_change' keeps the index to exactly the rows those\n-- queries can match (in-flight events are never path-addressed).\nCREATE INDEX `idx_events_code_change_path` ON `events` (json_extract(`metadata`, '$.filePath')) WHERE `kind` = 'code_change';\n"
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
tag: "0010_events_session_expression_index",
|
|
547
|
+
sql: "-- Custom migration: partial expression index for session-scoped finding reads.\n--\n-- sessionFindingsCount, the session-scoped listGroupedFindings paths, and the\n-- insert-time session dedup all filter live-capture events by\n-- json_extract(e.metadata, '$.sessionId') = :sessionId\n-- \u2014 previously a full findings-join scan with a JSON parse per row. The\n-- IS NOT NULL predicate keeps the index to session-stamped events only (an\n-- equality probe implies non-null, so SQLite still uses it).\nCREATE INDEX `idx_events_session_id` ON `events` (json_extract(`metadata`, '$.sessionId')) WHERE json_extract(`metadata`, '$.sessionId') IS NOT NULL;\n"
|
|
544
548
|
}
|
|
545
549
|
];
|
|
546
550
|
|
|
@@ -643,6 +647,12 @@ var defaultCostModel = {
|
|
|
643
647
|
}
|
|
644
648
|
};
|
|
645
649
|
|
|
650
|
+
// ../../packages/schema/src/token/format.ts
|
|
651
|
+
var COMPACT = new Intl.NumberFormat("en-US", {
|
|
652
|
+
notation: "compact",
|
|
653
|
+
maximumFractionDigits: 1
|
|
654
|
+
});
|
|
655
|
+
|
|
646
656
|
// ../../packages/schema/src/token/token-report.ts
|
|
647
657
|
var num = (value) => value ?? 0;
|
|
648
658
|
function costUsageOf(a) {
|
|
@@ -15314,6 +15324,12 @@ var FindingInstance = external_exports.object({
|
|
|
15314
15324
|
provider: FindingProvider,
|
|
15315
15325
|
repo: external_exports.string(),
|
|
15316
15326
|
file: external_exports.string(),
|
|
15327
|
+
// Host tool that produced the scanned text (event metadata's toolName).
|
|
15328
|
+
// Present whenever the capturing hook recorded one — including
|
|
15329
|
+
// file-attributed captures (views prefer `file`); its display value is
|
|
15330
|
+
// the location fallback ("via Bash") when no filePath exists. Absent for
|
|
15331
|
+
// legacy rows and non-tool captures (prompts, worktree scans).
|
|
15332
|
+
toolName: external_exports.string().optional(),
|
|
15317
15333
|
// Effective action: override.action ?? actionTaken, translated to FindingAction.
|
|
15318
15334
|
action: FindingAction,
|
|
15319
15335
|
detectedAt: external_exports.iso.datetime(),
|
|
@@ -15370,6 +15386,9 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15370
15386
|
provider: external_exports.array(FindingProvider).optional(),
|
|
15371
15387
|
action: external_exports.array(FindingAction).optional(),
|
|
15372
15388
|
q: external_exports.string().optional(),
|
|
15389
|
+
// Scope to findings whose event carries this session id (the Activity page's
|
|
15390
|
+
// session → findings drilldown). Findings without a session never match.
|
|
15391
|
+
sessionId: external_exports.string().optional(),
|
|
15373
15392
|
groupBy: external_exports.literal("type").optional(),
|
|
15374
15393
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15375
15394
|
cursor: external_exports.string().optional()
|
|
@@ -15381,7 +15400,13 @@ var ListGroupedFindingsResponse = external_exports.object({
|
|
|
15381
15400
|
}),
|
|
15382
15401
|
facets: FindingFacets,
|
|
15383
15402
|
items: external_exports.array(FindingGroup),
|
|
15384
|
-
nextCursor: external_exports.string().nullable()
|
|
15403
|
+
nextCursor: external_exports.string().nullable(),
|
|
15404
|
+
// Present only on session-scoped queries (`sessionId` set): per ruleId, how
|
|
15405
|
+
// many times that rule fired in the session's persisted transcript. Findings
|
|
15406
|
+
// here are deduplicated to unique values while the transcript tally counts
|
|
15407
|
+
// every firing, so the two numbers legitimately differ — this map lets a
|
|
15408
|
+
// session-scoped view show both.
|
|
15409
|
+
sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
|
|
15385
15410
|
}).meta({ id: "ListGroupedFindingsResponse" });
|
|
15386
15411
|
var ApplyFindingActionRequest = external_exports.object({
|
|
15387
15412
|
// 'quarantined' is system-assigned (see FindingAction) — clients may not set
|
|
@@ -15433,6 +15458,11 @@ var AuditEventType = external_exports.enum([
|
|
|
15433
15458
|
"prompt",
|
|
15434
15459
|
"response",
|
|
15435
15460
|
"code_change",
|
|
15461
|
+
// The events.kind of a scanned tool call, widened in to keep this a
|
|
15462
|
+
// superset. Narrower than 'tool_call' above and not a duplicate of it:
|
|
15463
|
+
// 'tool_call' is the reconciler's structural row for every call, while
|
|
15464
|
+
// 'tool_use' exists only where a hook enforced against the arguments.
|
|
15465
|
+
"tool_use",
|
|
15436
15466
|
// One row per config-inventory scan, hung off the session root. It is the
|
|
15437
15467
|
// fact the posture inspection findings reference (findings require an
|
|
15438
15468
|
// audit_event_id), and its started_at is the "scanned Nm ago" the read
|
|
@@ -15787,6 +15817,10 @@ var ListActivitySessionsQuery = external_exports.object({
|
|
|
15787
15817
|
from: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
|
|
15788
15818
|
/** Upper bound on startedAt; omitted defaults to now. */
|
|
15789
15819
|
to: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
|
|
15820
|
+
/** Exclude zero-activity sessions — roots whose only recorded children are
|
|
15821
|
+
* bookkeeping rows (hooks, config scans), typically background `claude`
|
|
15822
|
+
* launches. Omitted = list everything. `z.stringbool()` per the note above. */
|
|
15823
|
+
excludeEmpty: external_exports.stringbool().optional(),
|
|
15790
15824
|
/** Page size, 1–100; out-of-range values are a 400. `z.coerce` — query params arrive as strings. */
|
|
15791
15825
|
limit: external_exports.coerce.number().int().min(1).max(100).default(50),
|
|
15792
15826
|
/** Opaque pagination cursor (most-recent first). */
|
|
@@ -15795,7 +15829,11 @@ var ListActivitySessionsQuery = external_exports.object({
|
|
|
15795
15829
|
var ListActivitySessionsResponse = external_exports.object({
|
|
15796
15830
|
items: external_exports.array(ActivitySessionSummary),
|
|
15797
15831
|
/** `null` once the last page is reached. */
|
|
15798
|
-
nextCursor: external_exports.string().nullable()
|
|
15832
|
+
nextCursor: external_exports.string().nullable(),
|
|
15833
|
+
/** Zero-activity sessions matching the query's filters/range (whether or
|
|
15834
|
+
* not `excludeEmpty` dropped them from `items`) — the count a UI toggle
|
|
15835
|
+
* shows when collapsing them. */
|
|
15836
|
+
emptyCount: external_exports.number().int().nonnegative()
|
|
15799
15837
|
}).meta({ id: "ListActivitySessionsResponse" });
|
|
15800
15838
|
var ListSessionEventsQuery = external_exports.object({
|
|
15801
15839
|
/** Default 100, range 1–500. */
|
|
@@ -15824,12 +15862,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15824
15862
|
}).meta({ id: "ActivityOverviewResponse" });
|
|
15825
15863
|
|
|
15826
15864
|
// ../../packages/schema/src/zod/event.ts
|
|
15827
|
-
var EventKind = external_exports.enum(["prompt", "response", "code_change"]).meta({ id: "EventKind" });
|
|
15865
|
+
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15828
15866
|
var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
|
|
15829
15867
|
var EventMetadata = external_exports.object({
|
|
15830
15868
|
sessionId: external_exports.string().optional(),
|
|
15831
15869
|
repo: external_exports.string().optional(),
|
|
15832
15870
|
filePath: external_exports.string().optional(),
|
|
15871
|
+
// The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
|
|
15872
|
+
// set by the tool-scanning hooks. The tool NAME only — never the tool's
|
|
15873
|
+
// arguments or output, which can carry the very value a finding masked
|
|
15874
|
+
// (metadata is stored unredacted). Gives findings on non-file captures a
|
|
15875
|
+
// display location ("via Bash") when no filePath exists.
|
|
15876
|
+
toolName: external_exports.string().optional(),
|
|
15833
15877
|
// Set (true) by the worktree scanner when the file is excluded by the
|
|
15834
15878
|
// repo's .gitignore. Gitignored files ARE still scanned — local scratch and
|
|
15835
15879
|
// generated code can leak real secrets — but the provenance is recorded so
|
|
@@ -16137,7 +16181,7 @@ var DetectionException = external_exports.object({
|
|
|
16137
16181
|
justification: external_exports.string().min(1),
|
|
16138
16182
|
conditions: ExceptionConditions.nullable(),
|
|
16139
16183
|
createdBy: external_exports.string(),
|
|
16140
|
-
createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api"]),
|
|
16184
|
+
createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
|
|
16141
16185
|
createdAt: external_exports.iso.datetime(),
|
|
16142
16186
|
updatedAt: external_exports.iso.datetime(),
|
|
16143
16187
|
// Revocation is terminal and retained — consumed/expired/revoked rows are
|
|
@@ -16161,7 +16205,10 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16161
16205
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
16162
16206
|
var KeywordMatcher = external_exports.object({
|
|
16163
16207
|
type: external_exports.literal("keyword"),
|
|
16164
|
-
|
|
16208
|
+
// An empty keyword matches at every position, yielding one zero-length span
|
|
16209
|
+
// per character. Rejected here because a keyword that matches everything is
|
|
16210
|
+
// never intentional.
|
|
16211
|
+
keywords: external_exports.array(external_exports.string().min(1)).min(1),
|
|
16165
16212
|
caseSensitive: external_exports.boolean().default(false)
|
|
16166
16213
|
});
|
|
16167
16214
|
function isValidRegex(pattern, flags) {
|
|
@@ -16306,20 +16353,26 @@ var PolicyBundle = external_exports.object({
|
|
|
16306
16353
|
customKeywords: external_exports.array(external_exports.string()),
|
|
16307
16354
|
fetchedAt: external_exports.iso.datetime()
|
|
16308
16355
|
}).meta({ id: "PolicyBundle" });
|
|
16309
|
-
var DEFAULT_ACTIONS = {
|
|
16310
|
-
secret: "block",
|
|
16311
|
-
pii: "redact",
|
|
16312
|
-
financial: "redact",
|
|
16313
|
-
phi: "redact",
|
|
16314
|
-
code_context: "warn",
|
|
16315
|
-
code_flaw: "warn",
|
|
16316
|
-
custom: "warn",
|
|
16317
|
-
// Config-posture findings only observe today (they land in
|
|
16318
|
-
// inspection_findings, outside the live-capture enforcement path).
|
|
16319
|
-
config: "warn"
|
|
16320
|
-
};
|
|
16321
16356
|
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
16322
16357
|
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
16358
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
16359
|
+
secret: "critical",
|
|
16360
|
+
financial: "critical",
|
|
16361
|
+
// core-financial/credit-card
|
|
16362
|
+
code_flaw: "critical",
|
|
16363
|
+
pii: "high",
|
|
16364
|
+
phi: "high",
|
|
16365
|
+
custom: "high",
|
|
16366
|
+
// user-defined; conservative
|
|
16367
|
+
code_context: "low",
|
|
16368
|
+
config: "low"
|
|
16369
|
+
// observe-only; floors to monitor regardless
|
|
16370
|
+
};
|
|
16371
|
+
function severityFloorPolicy(category) {
|
|
16372
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
16373
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
16374
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
16375
|
+
}
|
|
16323
16376
|
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
16324
16377
|
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
|
|
16325
16378
|
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
@@ -16346,6 +16399,12 @@ var BUILTIN_POLICY_SPECS = {
|
|
|
16346
16399
|
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
16347
16400
|
}
|
|
16348
16401
|
};
|
|
16402
|
+
function builtinPolicyToAction(id) {
|
|
16403
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
16404
|
+
}
|
|
16405
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
16406
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
16407
|
+
);
|
|
16349
16408
|
var BUILTIN_POLICIES = Object.fromEntries(
|
|
16350
16409
|
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
16351
16410
|
);
|
|
@@ -16398,6 +16457,10 @@ var ListEventsResponse = external_exports.object({
|
|
|
16398
16457
|
items: external_exports.array(Event),
|
|
16399
16458
|
nextCursor: external_exports.string().nullable()
|
|
16400
16459
|
}).meta({ id: "ListEventsResponse" });
|
|
16460
|
+
var IngestResponse = external_exports.object({
|
|
16461
|
+
accepted: external_exports.number().int().nonnegative(),
|
|
16462
|
+
duplicates: external_exports.number().int().nonnegative()
|
|
16463
|
+
}).meta({ id: "IngestResponse" });
|
|
16401
16464
|
var ListFindingsQuery = external_exports.object({
|
|
16402
16465
|
cursor: external_exports.string().optional(),
|
|
16403
16466
|
limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
|
|
@@ -16866,10 +16929,8 @@ function toApiProvider(sourceTool) {
|
|
|
16866
16929
|
return TOOL_TO_HARNESS[sourceTool] ?? "api";
|
|
16867
16930
|
}
|
|
16868
16931
|
var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
|
|
16869
|
-
function
|
|
16870
|
-
const statuses = new Set(
|
|
16871
|
-
instances.map((i) => i.status).filter((s) => s !== void 0)
|
|
16872
|
-
);
|
|
16932
|
+
function foldGroupStatus(instanceStatuses) {
|
|
16933
|
+
const statuses = new Set(instanceStatuses.filter((s) => s !== void 0));
|
|
16873
16934
|
if (statuses.size === 0) return void 0;
|
|
16874
16935
|
for (const candidate of STATUS_PRECEDENCE) {
|
|
16875
16936
|
if (statuses.has(candidate)) return candidate;
|
|
@@ -16887,6 +16948,7 @@ function deriveFindingStatus(row) {
|
|
|
16887
16948
|
function buildFindingGroups(rows, opts = {}) {
|
|
16888
16949
|
const overrides = opts.overrides;
|
|
16889
16950
|
const packNames = opts.packNames;
|
|
16951
|
+
const aggregates = opts.aggregates;
|
|
16890
16952
|
const byRuleId = /* @__PURE__ */ new Map();
|
|
16891
16953
|
for (const row of rows) {
|
|
16892
16954
|
const existing = byRuleId.get(row.ruleId);
|
|
@@ -16902,23 +16964,27 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
16902
16964
|
provider: toApiProvider(r.sourceTool),
|
|
16903
16965
|
repo: r.repo,
|
|
16904
16966
|
file: r.file,
|
|
16967
|
+
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
16905
16968
|
action: toApiAction(effectiveDbAction),
|
|
16906
16969
|
detectedAt: r.occurredAt,
|
|
16907
16970
|
confidence: r.confidence,
|
|
16908
16971
|
status: r.status
|
|
16909
16972
|
};
|
|
16910
16973
|
});
|
|
16911
|
-
const
|
|
16974
|
+
const agg = aggregates?.get(ruleId);
|
|
16975
|
+
const latestDetectedAt = agg?.latestDetectedAt ?? ruleRows.reduce(
|
|
16912
16976
|
(max, r) => r.occurredAt > max ? r.occurredAt : max,
|
|
16913
16977
|
ruleRows[0]?.occurredAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
16914
16978
|
);
|
|
16915
16979
|
const seenProviders = /* @__PURE__ */ new Set();
|
|
16916
|
-
const providers = instances.map((i) => i.provider).filter((p) => {
|
|
16980
|
+
const providers = (agg ? [...new Set(agg.sourceTools.map(toApiProvider))].sort() : instances.map((i) => i.provider)).filter((p) => {
|
|
16917
16981
|
if (seenProviders.has(p)) return false;
|
|
16918
16982
|
seenProviders.add(p);
|
|
16919
16983
|
return true;
|
|
16920
16984
|
});
|
|
16921
|
-
const actionSet = new Set(
|
|
16985
|
+
const actionSet = new Set(
|
|
16986
|
+
agg ? agg.actionsTaken.map(toApiAction) : instances.map((i) => i.action)
|
|
16987
|
+
);
|
|
16922
16988
|
const aggregateAction = actionSet.size === 1 ? [...actionSet][0] ?? null : null;
|
|
16923
16989
|
const severity = ruleRows[0]?.severity ?? "low";
|
|
16924
16990
|
const detection = {
|
|
@@ -16932,8 +16998,10 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
16932
16998
|
contextPrefix: ""
|
|
16933
16999
|
// empty (pending privacy review)
|
|
16934
17000
|
};
|
|
16935
|
-
const status =
|
|
16936
|
-
|
|
17001
|
+
const status = foldGroupStatus(
|
|
17002
|
+
agg ? agg.statusInputs.map(deriveFindingStatus) : instances.map((i) => i.status)
|
|
17003
|
+
);
|
|
17004
|
+
const group = {
|
|
16937
17005
|
id: ruleId,
|
|
16938
17006
|
category: apiCategory,
|
|
16939
17007
|
subtype: ruleId,
|
|
@@ -16942,21 +17010,26 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
16942
17010
|
match,
|
|
16943
17011
|
detection,
|
|
16944
17012
|
policy,
|
|
16945
|
-
instanceCount: instances.length,
|
|
17013
|
+
instanceCount: agg?.instanceCount ?? instances.length,
|
|
16946
17014
|
providers,
|
|
16947
17015
|
aggregateAction,
|
|
16948
17016
|
latestDetectedAt,
|
|
16949
17017
|
instances,
|
|
16950
17018
|
status
|
|
16951
|
-
}
|
|
17019
|
+
};
|
|
17020
|
+
if (agg) {
|
|
17021
|
+
actionsCache.set(group, [...actionSet]);
|
|
17022
|
+
if (agg.searchText !== void 0) {
|
|
17023
|
+
haystackCache.set(group, buildHaystack(group, agg.searchText));
|
|
17024
|
+
}
|
|
17025
|
+
}
|
|
17026
|
+
groups.push(group);
|
|
16952
17027
|
}
|
|
16953
17028
|
return groups;
|
|
16954
17029
|
}
|
|
16955
17030
|
var haystackCache = /* @__PURE__ */ new WeakMap();
|
|
16956
|
-
function
|
|
16957
|
-
|
|
16958
|
-
if (cached2 !== void 0) return cached2;
|
|
16959
|
-
const haystack = [
|
|
17031
|
+
function buildHaystack(g, extra) {
|
|
17032
|
+
return [
|
|
16960
17033
|
g.subtype,
|
|
16961
17034
|
g.category,
|
|
16962
17035
|
g.match.maskedValue,
|
|
@@ -16964,11 +17037,26 @@ function groupHaystack(g) {
|
|
|
16964
17037
|
g.id,
|
|
16965
17038
|
...g.instances.map((i) => i.repo),
|
|
16966
17039
|
...g.instances.map((i) => i.file),
|
|
16967
|
-
...g.instances.map((i) => i.
|
|
17040
|
+
...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
|
|
17041
|
+
...g.instances.map((i) => i.id),
|
|
17042
|
+
...extra === void 0 ? [] : [extra]
|
|
16968
17043
|
].join(" ").toLowerCase();
|
|
17044
|
+
}
|
|
17045
|
+
function groupHaystack(g) {
|
|
17046
|
+
const cached2 = haystackCache.get(g);
|
|
17047
|
+
if (cached2 !== void 0) return cached2;
|
|
17048
|
+
const haystack = buildHaystack(g);
|
|
16969
17049
|
haystackCache.set(g, haystack);
|
|
16970
17050
|
return haystack;
|
|
16971
17051
|
}
|
|
17052
|
+
var actionsCache = /* @__PURE__ */ new WeakMap();
|
|
17053
|
+
function groupActions(g) {
|
|
17054
|
+
const cached2 = actionsCache.get(g);
|
|
17055
|
+
if (cached2 !== void 0) return cached2;
|
|
17056
|
+
const actions = [...new Set(g.instances.map((i) => i.action))];
|
|
17057
|
+
actionsCache.set(g, actions);
|
|
17058
|
+
return actions;
|
|
17059
|
+
}
|
|
16972
17060
|
function applyFindingFilters(groups, opts) {
|
|
16973
17061
|
let filtered = groups;
|
|
16974
17062
|
if (opts.severity && opts.severity.length > 0) {
|
|
@@ -16981,7 +17069,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
16981
17069
|
}
|
|
16982
17070
|
if (opts.actions && opts.actions.length > 0) {
|
|
16983
17071
|
const actionSet = new Set(opts.actions);
|
|
16984
|
-
filtered = filtered.filter((g) => g.
|
|
17072
|
+
filtered = filtered.filter((g) => groupActions(g).some((a) => actionSet.has(a)));
|
|
16985
17073
|
}
|
|
16986
17074
|
if (opts.subtype && opts.subtype.length > 0) {
|
|
16987
17075
|
const subtypeSet = new Set(opts.subtype);
|
|
@@ -17033,8 +17121,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17033
17121
|
});
|
|
17034
17122
|
const actionMap = /* @__PURE__ */ new Map();
|
|
17035
17123
|
for (const g of forAction) {
|
|
17036
|
-
const
|
|
17037
|
-
for (const a of actionSet) actionMap.set(a, (actionMap.get(a) ?? 0) + 1);
|
|
17124
|
+
for (const a of groupActions(g)) actionMap.set(a, (actionMap.get(a) ?? 0) + 1);
|
|
17038
17125
|
}
|
|
17039
17126
|
const forSubtype = applyFindingFilters(allGroups, {
|
|
17040
17127
|
providers: opts.providers,
|
|
@@ -17217,6 +17304,71 @@ var ProjectFilesScan = external_exports.object({
|
|
|
17217
17304
|
scannedAt: external_exports.string()
|
|
17218
17305
|
});
|
|
17219
17306
|
|
|
17307
|
+
// ../../packages/schema/src/zod/ranges.ts
|
|
17308
|
+
var TIME_RANGES = ["7d", "30d", "3m", "6m"];
|
|
17309
|
+
var TimeRange = external_exports.enum(TIME_RANGES).meta({ id: "TimeRange" });
|
|
17310
|
+
var DEFAULT_TIME_RANGE = "7d";
|
|
17311
|
+
var RANGE_DAYS = {
|
|
17312
|
+
"7d": 7,
|
|
17313
|
+
"30d": 30,
|
|
17314
|
+
"3m": 90,
|
|
17315
|
+
"6m": 180
|
|
17316
|
+
};
|
|
17317
|
+
var TIME_RANGE_OR_DEFAULT = TimeRange.catch(DEFAULT_TIME_RANGE);
|
|
17318
|
+
|
|
17319
|
+
// ../../packages/schema/src/zod/remediation.ts
|
|
17320
|
+
var SecretFindingState = external_exports.enum(["still-valid", "unknown", "invalid"]);
|
|
17321
|
+
var MaskedFindingLocation = external_exports.object({
|
|
17322
|
+
filePath: external_exports.string(),
|
|
17323
|
+
span: Span.optional()
|
|
17324
|
+
}).strict();
|
|
17325
|
+
var MaskedSecretFinding = external_exports.object({
|
|
17326
|
+
provider: external_exports.string(),
|
|
17327
|
+
maskedToken: external_exports.string(),
|
|
17328
|
+
where: MaskedFindingLocation,
|
|
17329
|
+
state: SecretFindingState,
|
|
17330
|
+
observedAt: external_exports.iso.datetime().optional()
|
|
17331
|
+
}).strict();
|
|
17332
|
+
var RotationChecklistEntry = external_exports.object({
|
|
17333
|
+
provider: external_exports.string(),
|
|
17334
|
+
maskedToken: external_exports.string(),
|
|
17335
|
+
consolePath: external_exports.string(),
|
|
17336
|
+
occurrenceSpread: external_exports.number().int().positive()
|
|
17337
|
+
}).strict();
|
|
17338
|
+
var RemediationOption = external_exports.enum([
|
|
17339
|
+
"redact-rotation-checklist",
|
|
17340
|
+
"redact-only",
|
|
17341
|
+
"set-secret-redact",
|
|
17342
|
+
"leave"
|
|
17343
|
+
]);
|
|
17344
|
+
var RemediationEntrySource = external_exports.enum(["first-run", "pre-push", "secret-scan"]);
|
|
17345
|
+
var RemediationEntryContext = external_exports.object({
|
|
17346
|
+
entrySource: RemediationEntrySource
|
|
17347
|
+
}).strict();
|
|
17348
|
+
var RemediationOptionChoice = external_exports.object({
|
|
17349
|
+
id: RemediationOption,
|
|
17350
|
+
label: external_exports.string()
|
|
17351
|
+
});
|
|
17352
|
+
var BatchedRemediationDecision = external_exports.object({
|
|
17353
|
+
kind: external_exports.literal("decision"),
|
|
17354
|
+
entrySource: RemediationEntrySource,
|
|
17355
|
+
secretCount: external_exports.number().int().positive(),
|
|
17356
|
+
prompt: external_exports.string(),
|
|
17357
|
+
options: external_exports.tuple([
|
|
17358
|
+
RemediationOptionChoice.extend({ id: external_exports.literal("redact-rotation-checklist") }),
|
|
17359
|
+
RemediationOptionChoice.extend({ id: external_exports.literal("redact-only") }),
|
|
17360
|
+
RemediationOptionChoice.extend({ id: external_exports.literal("set-secret-redact") }),
|
|
17361
|
+
RemediationOptionChoice.extend({ id: external_exports.literal("leave") })
|
|
17362
|
+
])
|
|
17363
|
+
});
|
|
17364
|
+
var NoRemediationDecision = external_exports.object({
|
|
17365
|
+
kind: external_exports.literal("no-decision")
|
|
17366
|
+
});
|
|
17367
|
+
var BatchedRemediation = external_exports.discriminatedUnion("kind", [
|
|
17368
|
+
BatchedRemediationDecision,
|
|
17369
|
+
NoRemediationDecision
|
|
17370
|
+
]);
|
|
17371
|
+
|
|
17220
17372
|
// ../../packages/schema/src/zod/rule-test.ts
|
|
17221
17373
|
var TestRulesRequest = external_exports.object({
|
|
17222
17374
|
rules: external_exports.array(Rule).min(1).max(100),
|
|
@@ -17275,10 +17427,8 @@ var SeveritySummaryResponse = external_exports.object({
|
|
|
17275
17427
|
// All four severity levels are always present (count may be 0).
|
|
17276
17428
|
bySeverity: external_exports.array(SeveritySummaryItem)
|
|
17277
17429
|
}).meta({ id: "SeveritySummaryResponse" });
|
|
17278
|
-
var SECURITY_RANGES = ["7d", "30d", "3m", "6m"];
|
|
17279
|
-
var SecurityRange = external_exports.enum(SECURITY_RANGES).meta({ id: "SecurityRange" });
|
|
17280
17430
|
var SecurityRangeQuery = external_exports.object({
|
|
17281
|
-
range: external_exports.enum(
|
|
17431
|
+
range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE)
|
|
17282
17432
|
});
|
|
17283
17433
|
var EnforcementActionKind = external_exports.enum(["blocked", "redacted", "warned"]).meta({ id: "EnforcementActionKind" });
|
|
17284
17434
|
var EnforcementAction = external_exports.object({
|
|
@@ -17288,7 +17438,7 @@ var EnforcementAction = external_exports.object({
|
|
|
17288
17438
|
delta: external_exports.number().int()
|
|
17289
17439
|
}).meta({ id: "EnforcementAction" });
|
|
17290
17440
|
var EnforcementActionsResponse = external_exports.object({
|
|
17291
|
-
range:
|
|
17441
|
+
range: TimeRange,
|
|
17292
17442
|
// Sum of actions[].count in the window.
|
|
17293
17443
|
total: external_exports.number().int().nonnegative(),
|
|
17294
17444
|
// One entry per kind, always all three present (count may be 0).
|
|
@@ -17303,7 +17453,7 @@ var FindingsTimeseriesPoint = external_exports.object({
|
|
|
17303
17453
|
medium: external_exports.number().int().nonnegative()
|
|
17304
17454
|
}).meta({ id: "FindingsTimeseriesPoint" });
|
|
17305
17455
|
var FindingsTimeseriesResponse = external_exports.object({
|
|
17306
|
-
range:
|
|
17456
|
+
range: TimeRange,
|
|
17307
17457
|
granularity: TimeseriesGranularity,
|
|
17308
17458
|
points: external_exports.array(FindingsTimeseriesPoint)
|
|
17309
17459
|
}).meta({ id: "FindingsTimeseriesResponse" });
|
|
@@ -17318,7 +17468,7 @@ var MttrTrendPoint = external_exports.object({
|
|
|
17318
17468
|
})
|
|
17319
17469
|
}).meta({ id: "MttrTrendPoint" });
|
|
17320
17470
|
var MttrTrendResponse = external_exports.object({
|
|
17321
|
-
range:
|
|
17471
|
+
range: TimeRange,
|
|
17322
17472
|
granularity: TimeseriesGranularity,
|
|
17323
17473
|
points: external_exports.array(MttrTrendPoint)
|
|
17324
17474
|
}).meta({ id: "MttrTrendResponse" });
|
|
@@ -17345,11 +17495,11 @@ var TopSource = external_exports.object({
|
|
|
17345
17495
|
findingsCount: external_exports.number().int().nonnegative()
|
|
17346
17496
|
}).meta({ id: "TopSource" });
|
|
17347
17497
|
var TopSourcesResponse = external_exports.object({
|
|
17348
|
-
range:
|
|
17498
|
+
range: TimeRange,
|
|
17349
17499
|
items: external_exports.array(TopSource)
|
|
17350
17500
|
}).meta({ id: "TopSourcesResponse" });
|
|
17351
17501
|
var TopSourcesQuery = external_exports.object({
|
|
17352
|
-
range: external_exports.enum(
|
|
17502
|
+
range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE),
|
|
17353
17503
|
limit: external_exports.coerce.number().int().min(1).max(50).default(5),
|
|
17354
17504
|
// Omit for both kinds.
|
|
17355
17505
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
@@ -17362,7 +17512,7 @@ var ScanCoverageProvider = external_exports.object({
|
|
|
17362
17512
|
supported: external_exports.boolean()
|
|
17363
17513
|
}).meta({ id: "ScanCoverageProvider" });
|
|
17364
17514
|
var ScanCoverageResponse = external_exports.object({
|
|
17365
|
-
range:
|
|
17515
|
+
range: TimeRange,
|
|
17366
17516
|
providers: external_exports.array(ScanCoverageProvider)
|
|
17367
17517
|
}).meta({ id: "ScanCoverageResponse" });
|
|
17368
17518
|
var SubjectType = external_exports.enum(["repo", "user", "team", "policy", "share", "rule"]).meta({
|
|
@@ -17411,6 +17561,114 @@ var ApplyRecommendedActionResponse = external_exports.object({
|
|
|
17411
17561
|
var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
|
|
17412
17562
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
17413
17563
|
|
|
17564
|
+
// ../../packages/schema/src/zod/triage.ts
|
|
17565
|
+
var TriageHit = external_exports.object({
|
|
17566
|
+
ruleId: external_exports.string(),
|
|
17567
|
+
category: DetectionCategory,
|
|
17568
|
+
severity: Severity,
|
|
17569
|
+
maskedMatch: external_exports.string(),
|
|
17570
|
+
rawMatch: external_exports.string(),
|
|
17571
|
+
context: external_exports.string(),
|
|
17572
|
+
filePath: external_exports.string().optional(),
|
|
17573
|
+
confidence: external_exports.number().min(0).max(1),
|
|
17574
|
+
id: external_exports.string().optional(),
|
|
17575
|
+
valueFingerprint: external_exports.string().optional(),
|
|
17576
|
+
keyVersion: external_exports.number().int().nonnegative().optional()
|
|
17577
|
+
});
|
|
17578
|
+
var TriagePolicy = BuiltinPolicyId;
|
|
17579
|
+
var TriageCategoryRec = external_exports.object({
|
|
17580
|
+
category: DetectionCategory,
|
|
17581
|
+
action: TriagePolicy,
|
|
17582
|
+
reasoning: external_exports.string(),
|
|
17583
|
+
genuineCount: external_exports.number().int().nonnegative(),
|
|
17584
|
+
fpCount: external_exports.number().int().nonnegative(),
|
|
17585
|
+
// TriageHit ids judged false-positive in this category. fpCount must equal
|
|
17586
|
+
// this array's length — enforced by the consumer, not this schema.
|
|
17587
|
+
fpIds: external_exports.array(external_exports.string())
|
|
17588
|
+
});
|
|
17589
|
+
var TriageRecommendation = external_exports.object({
|
|
17590
|
+
perCategory: external_exports.array(TriageCategoryRec),
|
|
17591
|
+
notes: external_exports.string()
|
|
17592
|
+
});
|
|
17593
|
+
|
|
17594
|
+
// ../../packages/schema/src/zod/setup-frame.ts
|
|
17595
|
+
var CalibrationCounts = external_exports.object({
|
|
17596
|
+
total: external_exports.number().int().nonnegative(),
|
|
17597
|
+
important: external_exports.number().int().nonnegative(),
|
|
17598
|
+
routine: external_exports.number().int().nonnegative()
|
|
17599
|
+
}).refine((c) => c.total === c.important + c.routine, {
|
|
17600
|
+
message: "total must equal important + routine",
|
|
17601
|
+
path: ["total"]
|
|
17602
|
+
});
|
|
17603
|
+
var FalsePositivePatternValue = external_exports.object({
|
|
17604
|
+
ruleId: external_exports.string(),
|
|
17605
|
+
category: DetectionCategory,
|
|
17606
|
+
valueFingerprint: external_exports.string(),
|
|
17607
|
+
keyVersion: external_exports.number().int().nonnegative()
|
|
17608
|
+
});
|
|
17609
|
+
var FalsePositivePatternGroup = external_exports.object({
|
|
17610
|
+
pattern: external_exports.string(),
|
|
17611
|
+
count: external_exports.number().int().nonnegative(),
|
|
17612
|
+
values: external_exports.array(FalsePositivePatternValue).min(1)
|
|
17613
|
+
});
|
|
17614
|
+
var CalibrationFindingKind = external_exports.object({
|
|
17615
|
+
category: DetectionCategory,
|
|
17616
|
+
count: external_exports.number().int().nonnegative(),
|
|
17617
|
+
egress: external_exports.boolean()
|
|
17618
|
+
});
|
|
17619
|
+
var CalibrationFrame = external_exports.object({
|
|
17620
|
+
counts: CalibrationCounts,
|
|
17621
|
+
routineCategories: external_exports.array(DetectionCategory),
|
|
17622
|
+
surfacedCategories: external_exports.array(DetectionCategory),
|
|
17623
|
+
findingKinds: external_exports.array(CalibrationFindingKind),
|
|
17624
|
+
posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
|
|
17625
|
+
maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
|
|
17626
|
+
falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
|
|
17627
|
+
});
|
|
17628
|
+
var CalibrationPreviewCategory = TriageCategoryRec.pick({
|
|
17629
|
+
category: true,
|
|
17630
|
+
genuineCount: true,
|
|
17631
|
+
fpCount: true
|
|
17632
|
+
}).extend({
|
|
17633
|
+
egress: external_exports.boolean()
|
|
17634
|
+
});
|
|
17635
|
+
var CalibrationPreview = external_exports.object({
|
|
17636
|
+
categories: external_exports.array(CalibrationPreviewCategory),
|
|
17637
|
+
posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
|
|
17638
|
+
});
|
|
17639
|
+
var CalibrationResult = external_exports.object({
|
|
17640
|
+
frame: CalibrationFrame,
|
|
17641
|
+
copy: external_exports.string()
|
|
17642
|
+
});
|
|
17643
|
+
var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
|
|
17644
|
+
var SetupHandoffOption = external_exports.object({
|
|
17645
|
+
id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
|
|
17646
|
+
label: external_exports.string()
|
|
17647
|
+
});
|
|
17648
|
+
var DashboardHandoffOptions = external_exports.tuple([
|
|
17649
|
+
SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
|
|
17650
|
+
SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
|
|
17651
|
+
]);
|
|
17652
|
+
var ComposedRemediationOptions = external_exports.tuple([
|
|
17653
|
+
SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
|
|
17654
|
+
SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
|
|
17655
|
+
SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
|
|
17656
|
+
]);
|
|
17657
|
+
var SetupHandoffOffer = external_exports.object({
|
|
17658
|
+
worthALook: external_exports.number().int().nonnegative(),
|
|
17659
|
+
liveKeys: external_exports.number().int().nonnegative().optional(),
|
|
17660
|
+
options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
|
|
17661
|
+
}).refine(
|
|
17662
|
+
(o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
|
|
17663
|
+
{
|
|
17664
|
+
message: "the chain-entry option is present exactly when liveKeys > 0",
|
|
17665
|
+
path: ["options"]
|
|
17666
|
+
}
|
|
17667
|
+
).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
|
|
17668
|
+
message: "liveKeys is a subset of worthALook and cannot exceed it",
|
|
17669
|
+
path: ["liveKeys"]
|
|
17670
|
+
});
|
|
17671
|
+
|
|
17414
17672
|
// ../../packages/schema/src/zod/shares.ts
|
|
17415
17673
|
var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
|
|
17416
17674
|
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
|
|
@@ -17596,6 +17854,102 @@ function reviewSeverityRank(reasons) {
|
|
|
17596
17854
|
return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
|
|
17597
17855
|
}
|
|
17598
17856
|
|
|
17857
|
+
// ../../packages/persistence/src/internal/sql-text.ts
|
|
17858
|
+
function escapeLikePattern(s) {
|
|
17859
|
+
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
17860
|
+
}
|
|
17861
|
+
function placeholders(n) {
|
|
17862
|
+
return Array.from({ length: n }, () => "?").join(", ");
|
|
17863
|
+
}
|
|
17864
|
+
function containsPattern(q) {
|
|
17865
|
+
return `%${escapeLikePattern(q)}%`;
|
|
17866
|
+
}
|
|
17867
|
+
function likeAny(exprs) {
|
|
17868
|
+
return `(${exprs.map((e) => `${e} LIKE ? ESCAPE '\\'`).join(" OR ")})`;
|
|
17869
|
+
}
|
|
17870
|
+
|
|
17871
|
+
// ../../packages/persistence/src/internal/transactions.ts
|
|
17872
|
+
var savepointSeq = 0;
|
|
17873
|
+
function withTransaction(db, fn, mode = "DEFERRED") {
|
|
17874
|
+
if (db.isTransaction) {
|
|
17875
|
+
const savepoint = `aka_sp_${String(savepointSeq += 1)}`;
|
|
17876
|
+
db.exec(`SAVEPOINT ${savepoint}`);
|
|
17877
|
+
try {
|
|
17878
|
+
fn();
|
|
17879
|
+
db.exec(`RELEASE ${savepoint}`);
|
|
17880
|
+
} catch (error51) {
|
|
17881
|
+
try {
|
|
17882
|
+
db.exec(`ROLLBACK TO ${savepoint}`);
|
|
17883
|
+
db.exec(`RELEASE ${savepoint}`);
|
|
17884
|
+
} catch {
|
|
17885
|
+
}
|
|
17886
|
+
throw error51;
|
|
17887
|
+
}
|
|
17888
|
+
return;
|
|
17889
|
+
}
|
|
17890
|
+
db.exec(mode === "IMMEDIATE" ? "BEGIN IMMEDIATE" : "BEGIN");
|
|
17891
|
+
try {
|
|
17892
|
+
fn();
|
|
17893
|
+
db.exec("COMMIT");
|
|
17894
|
+
} catch (error51) {
|
|
17895
|
+
try {
|
|
17896
|
+
db.exec("ROLLBACK");
|
|
17897
|
+
} catch {
|
|
17898
|
+
}
|
|
17899
|
+
throw error51;
|
|
17900
|
+
}
|
|
17901
|
+
}
|
|
17902
|
+
function failOpenTransaction(db, fn, mode = "DEFERRED") {
|
|
17903
|
+
const nested = db.isTransaction;
|
|
17904
|
+
try {
|
|
17905
|
+
withTransaction(db, fn, mode);
|
|
17906
|
+
return true;
|
|
17907
|
+
} catch (error51) {
|
|
17908
|
+
if (!db.isTransaction && nested) throw error51;
|
|
17909
|
+
return false;
|
|
17910
|
+
}
|
|
17911
|
+
}
|
|
17912
|
+
|
|
17913
|
+
// ../../packages/persistence/src/internal/warn.ts
|
|
17914
|
+
function akaWarn(message2) {
|
|
17915
|
+
process.stderr.write(`[aka] ${message2}
|
|
17916
|
+
`);
|
|
17917
|
+
}
|
|
17918
|
+
|
|
17919
|
+
// ../../packages/persistence/src/db/migrations/introspection.ts
|
|
17920
|
+
function evidenceObjects(sql) {
|
|
17921
|
+
const objects = [];
|
|
17922
|
+
for (const m of sql.matchAll(/CREATE TABLE (?:IF NOT EXISTS )?`([^`]+)`/g)) {
|
|
17923
|
+
if (m[1] !== void 0 && !m[1].startsWith("__new_")) {
|
|
17924
|
+
objects.push({ kind: "table", name: m[1] });
|
|
17925
|
+
}
|
|
17926
|
+
}
|
|
17927
|
+
for (const m of sql.matchAll(/ALTER TABLE `([^`]+)` ADD (?:COLUMN )?`([^`]+)`/g)) {
|
|
17928
|
+
if (m[1] !== void 0 && m[2] !== void 0) {
|
|
17929
|
+
objects.push({ kind: "column", table: m[1], name: m[2] });
|
|
17930
|
+
}
|
|
17931
|
+
}
|
|
17932
|
+
return objects;
|
|
17933
|
+
}
|
|
17934
|
+
function schemaObjectExists(db, kind, name) {
|
|
17935
|
+
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = ? AND name = ? LIMIT 1").get(kind, name);
|
|
17936
|
+
return row !== void 0;
|
|
17937
|
+
}
|
|
17938
|
+
function indexExists(db, name) {
|
|
17939
|
+
return schemaObjectExists(db, "index", name);
|
|
17940
|
+
}
|
|
17941
|
+
function columnNames(db, table, opts) {
|
|
17942
|
+
const pragma = opts?.includeGenerated ? "table_xinfo" : "table_info";
|
|
17943
|
+
const columns = db.prepare(`PRAGMA ${pragma}(${table})`).all();
|
|
17944
|
+
return columns.map((c) => c.name);
|
|
17945
|
+
}
|
|
17946
|
+
function evidenceExists(db, object2) {
|
|
17947
|
+
if (object2.kind === "column") {
|
|
17948
|
+
return columnNames(db, object2.table, { includeGenerated: true }).includes(object2.name);
|
|
17949
|
+
}
|
|
17950
|
+
return schemaObjectExists(db, "table", object2.name);
|
|
17951
|
+
}
|
|
17952
|
+
|
|
17599
17953
|
// ../../packages/persistence/src/ids.ts
|
|
17600
17954
|
import { createHash } from "crypto";
|
|
17601
17955
|
function sha256Hex(input) {
|
|
@@ -17632,28 +17986,6 @@ function inspectionFindingId(auditEventId, definitionId, spanStart, spanEnd) {
|
|
|
17632
17986
|
}
|
|
17633
17987
|
|
|
17634
17988
|
// ../../packages/persistence/src/migrations.ts
|
|
17635
|
-
function evidenceObjects(sql) {
|
|
17636
|
-
const objects = [];
|
|
17637
|
-
for (const m of sql.matchAll(/CREATE TABLE (?:IF NOT EXISTS )?`([^`]+)`/g)) {
|
|
17638
|
-
if (m[1] !== void 0 && !m[1].startsWith("__new_")) {
|
|
17639
|
-
objects.push({ kind: "table", name: m[1] });
|
|
17640
|
-
}
|
|
17641
|
-
}
|
|
17642
|
-
for (const m of sql.matchAll(/ALTER TABLE `([^`]+)` ADD (?:COLUMN )?`([^`]+)`/g)) {
|
|
17643
|
-
if (m[1] !== void 0 && m[2] !== void 0) {
|
|
17644
|
-
objects.push({ kind: "column", table: m[1], name: m[2] });
|
|
17645
|
-
}
|
|
17646
|
-
}
|
|
17647
|
-
return objects;
|
|
17648
|
-
}
|
|
17649
|
-
function evidenceExists(db, object2) {
|
|
17650
|
-
if (object2.kind === "column") {
|
|
17651
|
-
const columns = db.prepare(`PRAGMA table_xinfo(${object2.table})`).all();
|
|
17652
|
-
return columns.some((c) => c.name === object2.name);
|
|
17653
|
-
}
|
|
17654
|
-
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1").get(object2.name);
|
|
17655
|
-
return row !== void 0;
|
|
17656
|
-
}
|
|
17657
17989
|
function describeObject(object2) {
|
|
17658
17990
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
17659
17991
|
}
|
|
@@ -17664,10 +17996,6 @@ function createdIndexName(statement) {
|
|
|
17664
17996
|
const body = statement.replace(/^(?:\s*--[^\n]*\n?)+/, "").trimStart();
|
|
17665
17997
|
return /^CREATE (?:UNIQUE )?INDEX (?:IF NOT EXISTS )?`([^`]+)`/.exec(body)?.[1];
|
|
17666
17998
|
}
|
|
17667
|
-
function indexExists(db, name) {
|
|
17668
|
-
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ? LIMIT 1").get(name);
|
|
17669
|
-
return row !== void 0;
|
|
17670
|
-
}
|
|
17671
17999
|
function applyMigrations(db) {
|
|
17672
18000
|
const legacyCount = db.prepare("PRAGMA user_version").get().user_version;
|
|
17673
18001
|
db.exec(
|
|
@@ -17686,44 +18014,39 @@ function applyMigrations(db) {
|
|
|
17686
18014
|
const present = evidence.filter((o) => evidenceExists(db, o));
|
|
17687
18015
|
if (present.length > 0 && present.length < evidence.length) {
|
|
17688
18016
|
const missing = evidence.filter((o) => !present.includes(o));
|
|
17689
|
-
const message2 = `
|
|
17690
|
-
|
|
17691
|
-
`);
|
|
17692
|
-
throw new Error(message2);
|
|
18017
|
+
const message2 = `sqlite migration ${migration.tag} has no ledger row, but the store already has ${present.map(describeObject).join(", ")} while missing ${missing.map(describeObject).join(", ")} \u2014 the schema diverged from the migration history; refusing to replay or skip.`;
|
|
18018
|
+
akaWarn(message2);
|
|
18019
|
+
throw new Error(`[aka] ${message2}`);
|
|
17693
18020
|
}
|
|
17694
18021
|
const alreadyApplied = evidence.length > 0 ? present.length === evidence.length : preLedgerStore && index < legacyCount;
|
|
17695
18022
|
const wantsFkOff = /PRAGMA foreign_keys\s*=\s*OFF/i.test(migration.sql);
|
|
17696
18023
|
const statements = splitStatements(migration.sql);
|
|
17697
18024
|
if (wantsFkOff) db.exec("PRAGMA foreign_keys = OFF");
|
|
17698
18025
|
try {
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
const
|
|
17703
|
-
|
|
17704
|
-
if (
|
|
17705
|
-
|
|
17706
|
-
|
|
18026
|
+
withTransaction(
|
|
18027
|
+
db,
|
|
18028
|
+
() => {
|
|
18029
|
+
for (const statement of statements) {
|
|
18030
|
+
const indexName = createdIndexName(statement);
|
|
18031
|
+
if (indexName === void 0) {
|
|
18032
|
+
if (alreadyApplied) continue;
|
|
18033
|
+
} else if (indexExists(db, indexName)) {
|
|
18034
|
+
continue;
|
|
18035
|
+
}
|
|
18036
|
+
db.exec(statement);
|
|
17707
18037
|
}
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
);
|
|
18038
|
+
if (wantsFkOff && !alreadyApplied) {
|
|
18039
|
+
const violations = db.prepare("PRAGMA foreign_key_check").all();
|
|
18040
|
+
if (violations.length > 0) {
|
|
18041
|
+
throw new Error(
|
|
18042
|
+
`[aka] sqlite migration ${migration.tag} left ${String(violations.length)} foreign-key violation(s); rolling back.`
|
|
18043
|
+
);
|
|
18044
|
+
}
|
|
17716
18045
|
}
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
try {
|
|
17722
|
-
db.exec("ROLLBACK");
|
|
17723
|
-
} catch {
|
|
17724
|
-
}
|
|
17725
|
-
throw error51;
|
|
17726
|
-
}
|
|
18046
|
+
record2.run(migration.tag, Date.now());
|
|
18047
|
+
},
|
|
18048
|
+
"IMMEDIATE"
|
|
18049
|
+
);
|
|
17727
18050
|
} finally {
|
|
17728
18051
|
if (wantsFkOff) db.exec("PRAGMA foreign_keys = ON");
|
|
17729
18052
|
}
|
|
@@ -17766,8 +18089,7 @@ var TOKEN_USAGE_COLUMNS = [
|
|
|
17766
18089
|
}
|
|
17767
18090
|
];
|
|
17768
18091
|
function ensureTokenUsageColumns(db) {
|
|
17769
|
-
const
|
|
17770
|
-
const existing = new Set(columns.map((c) => c.name));
|
|
18092
|
+
const existing = new Set(columnNames(db, "audit_events", { includeGenerated: true }));
|
|
17771
18093
|
for (const column of TOKEN_USAGE_COLUMNS) {
|
|
17772
18094
|
if (!existing.has(column.name)) {
|
|
17773
18095
|
db.exec(column.ddl);
|
|
@@ -17805,47 +18127,39 @@ function reconcileSourceProjectIds(db) {
|
|
|
17805
18127
|
repoint: db.prepare(`UPDATE ${table} SET project_id = ? WHERE project_id = ?`)
|
|
17806
18128
|
}));
|
|
17807
18129
|
const deleteLegacy = db.prepare("DELETE FROM source_project WHERE id = ?");
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
dropCollisions
|
|
17822
|
-
|
|
18130
|
+
withTransaction(
|
|
18131
|
+
db,
|
|
18132
|
+
() => {
|
|
18133
|
+
for (const { row, canonicalId } of legacy) {
|
|
18134
|
+
foldProject.run(
|
|
18135
|
+
canonicalId,
|
|
18136
|
+
row.url,
|
|
18137
|
+
row.name,
|
|
18138
|
+
row.attributes,
|
|
18139
|
+
row.firstSeen,
|
|
18140
|
+
row.lastSeen
|
|
18141
|
+
);
|
|
18142
|
+
repointAudit.run(canonicalId, row.id);
|
|
18143
|
+
for (const { dropCollisions, repoint } of pathTables) {
|
|
18144
|
+
dropCollisions.run(row.id, canonicalId);
|
|
18145
|
+
repoint.run(canonicalId, row.id);
|
|
18146
|
+
}
|
|
18147
|
+
repointCallSite.run(canonicalId, row.id);
|
|
18148
|
+
deleteLegacy.run(row.id);
|
|
17823
18149
|
}
|
|
17824
|
-
|
|
17825
|
-
|
|
17826
|
-
|
|
17827
|
-
db.exec("COMMIT");
|
|
17828
|
-
} catch (error51) {
|
|
17829
|
-
try {
|
|
17830
|
-
db.exec("ROLLBACK");
|
|
17831
|
-
} catch {
|
|
17832
|
-
}
|
|
17833
|
-
throw error51;
|
|
17834
|
-
}
|
|
18150
|
+
},
|
|
18151
|
+
"IMMEDIATE"
|
|
18152
|
+
);
|
|
17835
18153
|
} catch (error51) {
|
|
17836
|
-
|
|
17837
|
-
`);
|
|
18154
|
+
akaWarn(`source_project id reconcile failed: ${String(error51)}`);
|
|
17838
18155
|
}
|
|
17839
18156
|
}
|
|
17840
18157
|
function isForeignSqliteLineage(db) {
|
|
17841
|
-
|
|
17842
|
-
|
|
17843
|
-
const eventsColumns = db.prepare("PRAGMA table_info(events)").all();
|
|
17844
|
-
return eventsColumns.some((c) => c.name === "tenant_id");
|
|
18158
|
+
if (schemaObjectExists(db, "table", "tenants")) return true;
|
|
18159
|
+
return columnNames(db, "events").includes("tenant_id");
|
|
17845
18160
|
}
|
|
17846
18161
|
function ensureSyncedAtColumn(db, table) {
|
|
17847
|
-
|
|
17848
|
-
if (!columns.some((c) => c.name === "synced_at")) {
|
|
18162
|
+
if (!columnNames(db, table).includes("synced_at")) {
|
|
17849
18163
|
db.exec(`ALTER TABLE ${table} ADD COLUMN synced_at integer`);
|
|
17850
18164
|
}
|
|
17851
18165
|
}
|
|
@@ -17896,8 +18210,11 @@ function ensureDataDirSync(dir) {
|
|
|
17896
18210
|
} catch {
|
|
17897
18211
|
}
|
|
17898
18212
|
}
|
|
18213
|
+
function walSidecars(file2) {
|
|
18214
|
+
return [`${file2}-wal`, `${file2}-shm`];
|
|
18215
|
+
}
|
|
17899
18216
|
function tightenPerms(file2) {
|
|
17900
|
-
for (const path of [file2,
|
|
18217
|
+
for (const path of [file2, ...walSidecars(file2)]) {
|
|
17901
18218
|
try {
|
|
17902
18219
|
chmodSync(path, DATA_FILE_MODE);
|
|
17903
18220
|
} catch {
|
|
@@ -17905,12 +18222,68 @@ function tightenPerms(file2) {
|
|
|
17905
18222
|
}
|
|
17906
18223
|
}
|
|
17907
18224
|
|
|
17908
|
-
// ../../packages/persistence/src/
|
|
17909
|
-
function
|
|
17910
|
-
|
|
18225
|
+
// ../../packages/persistence/src/internal/json.ts
|
|
18226
|
+
function safeJson(s, fallback) {
|
|
18227
|
+
if (s == null) return fallback;
|
|
18228
|
+
try {
|
|
18229
|
+
return JSON.parse(s);
|
|
18230
|
+
} catch {
|
|
18231
|
+
return fallback;
|
|
18232
|
+
}
|
|
17911
18233
|
}
|
|
17912
|
-
function
|
|
17913
|
-
|
|
18234
|
+
function parseJsonObject(s) {
|
|
18235
|
+
if (s == null) return void 0;
|
|
18236
|
+
try {
|
|
18237
|
+
const parsed = JSON.parse(s);
|
|
18238
|
+
if (typeof parsed === "object" && parsed !== null) return parsed;
|
|
18239
|
+
} catch {
|
|
18240
|
+
}
|
|
18241
|
+
return void 0;
|
|
18242
|
+
}
|
|
18243
|
+
|
|
18244
|
+
// ../../packages/persistence/src/internal/rows.ts
|
|
18245
|
+
function allRows(stmt, params) {
|
|
18246
|
+
if (params === void 0) return stmt.all();
|
|
18247
|
+
if (Array.isArray(params)) return stmt.all(...params);
|
|
18248
|
+
return stmt.all(params);
|
|
18249
|
+
}
|
|
18250
|
+
function getRow(stmt, params) {
|
|
18251
|
+
if (params === void 0) return stmt.get();
|
|
18252
|
+
if (Array.isArray(params)) return stmt.get(...params);
|
|
18253
|
+
return stmt.get(params);
|
|
18254
|
+
}
|
|
18255
|
+
function intToBool(raw) {
|
|
18256
|
+
return raw === 1 || raw === true;
|
|
18257
|
+
}
|
|
18258
|
+
function boolToInt(b) {
|
|
18259
|
+
return b ? 1 : 0;
|
|
18260
|
+
}
|
|
18261
|
+
function bindParams(row) {
|
|
18262
|
+
const out = {};
|
|
18263
|
+
for (const [key, value] of Object.entries(row)) {
|
|
18264
|
+
out[key] = value === void 0 ? null : value;
|
|
18265
|
+
}
|
|
18266
|
+
return out;
|
|
18267
|
+
}
|
|
18268
|
+
function countScalar(db, sql, params) {
|
|
18269
|
+
return getRow(db.prepare(sql), params)?.n ?? 0;
|
|
18270
|
+
}
|
|
18271
|
+
function countBy(db, sql, params) {
|
|
18272
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
18273
|
+
for (const row of allRows(db.prepare(sql), params)) {
|
|
18274
|
+
map2.set(row.k, row.n);
|
|
18275
|
+
}
|
|
18276
|
+
return map2;
|
|
18277
|
+
}
|
|
18278
|
+
function mapRowsTolerant(rows, map2) {
|
|
18279
|
+
const out = [];
|
|
18280
|
+
for (const row of rows) {
|
|
18281
|
+
try {
|
|
18282
|
+
out.push(map2(row));
|
|
18283
|
+
} catch {
|
|
18284
|
+
}
|
|
18285
|
+
}
|
|
18286
|
+
return out;
|
|
17914
18287
|
}
|
|
17915
18288
|
|
|
17916
18289
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
@@ -17962,15 +18335,11 @@ function encodeCursor(payload) {
|
|
|
17962
18335
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
17963
18336
|
}
|
|
17964
18337
|
function decodeCursor(cursor) {
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
return parsed;
|
|
17969
|
-
}
|
|
17970
|
-
return null;
|
|
17971
|
-
} catch {
|
|
17972
|
-
return null;
|
|
18338
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18339
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18340
|
+
return parsed;
|
|
17973
18341
|
}
|
|
18342
|
+
return null;
|
|
17974
18343
|
}
|
|
17975
18344
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
17976
18345
|
session: "session",
|
|
@@ -17987,15 +18356,8 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
17987
18356
|
};
|
|
17988
18357
|
function safeParseStringArray(raw) {
|
|
17989
18358
|
if (!raw) return [];
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
17993
|
-
} catch {
|
|
17994
|
-
return [];
|
|
17995
|
-
}
|
|
17996
|
-
}
|
|
17997
|
-
function toBool(raw) {
|
|
17998
|
-
return raw === 1 || raw === true;
|
|
18359
|
+
const parsed = safeJson(raw, null);
|
|
18360
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
17999
18361
|
}
|
|
18000
18362
|
function toHarness(raw) {
|
|
18001
18363
|
const parsed = Harness.safeParse(raw);
|
|
@@ -18048,8 +18410,8 @@ function buildAuditEvent(row) {
|
|
|
18048
18410
|
severity: severityParsed?.success ? severityParsed.data : null,
|
|
18049
18411
|
link: linkParsed?.success ? linkParsed.data : null,
|
|
18050
18412
|
targetId: row.target_id,
|
|
18051
|
-
internal:
|
|
18052
|
-
flagged:
|
|
18413
|
+
internal: intToBool(row.internal),
|
|
18414
|
+
flagged: intToBool(row.flagged)
|
|
18053
18415
|
};
|
|
18054
18416
|
}
|
|
18055
18417
|
var TIMELINE_COLUMNS = `
|
|
@@ -18065,6 +18427,10 @@ var TIMELINE_COLUMNS = `
|
|
|
18065
18427
|
json_extract(attributes, '$.internal') AS internal,
|
|
18066
18428
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
18067
18429
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
18430
|
+
var HAS_ACTIVITY = `EXISTS (
|
|
18431
|
+
SELECT 1 FROM audit_events c
|
|
18432
|
+
WHERE c.root_session_id = audit_events.id
|
|
18433
|
+
AND c.event_type NOT IN ('hook', 'config_scan'))`;
|
|
18068
18434
|
var SqliteActivityRepository = class {
|
|
18069
18435
|
constructor(db, now = () => Date.now()) {
|
|
18070
18436
|
this.db = db;
|
|
@@ -18075,12 +18441,16 @@ var SqliteActivityRepository = class {
|
|
|
18075
18441
|
stats(tz) {
|
|
18076
18442
|
const window = todayWindow(tz ?? defaultTimeZone(), this.now());
|
|
18077
18443
|
const { startMs, endMs } = window;
|
|
18078
|
-
const sessionsToday =
|
|
18444
|
+
const sessionsToday = countScalar(
|
|
18445
|
+
this.db,
|
|
18079
18446
|
`SELECT count(*) AS n FROM audit_events
|
|
18080
|
-
WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at <
|
|
18081
|
-
|
|
18447
|
+
WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?
|
|
18448
|
+
AND ${HAS_ACTIVITY}`,
|
|
18449
|
+
[startMs, endMs]
|
|
18450
|
+
);
|
|
18082
18451
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
18083
|
-
const liveNow =
|
|
18452
|
+
const liveNow = countScalar(
|
|
18453
|
+
this.db,
|
|
18084
18454
|
`SELECT count(*) AS n FROM audit_events s
|
|
18085
18455
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
18086
18456
|
AND max(
|
|
@@ -18089,22 +18459,29 @@ var SqliteActivityRepository = class {
|
|
|
18089
18459
|
(SELECT max(${LAST_ACTIVITY_EXPR}) FROM audit_events e WHERE e.root_session_id = s.id),
|
|
18090
18460
|
s.started_at
|
|
18091
18461
|
)
|
|
18092
|
-
) >=
|
|
18093
|
-
|
|
18094
|
-
|
|
18462
|
+
) >= ?`,
|
|
18463
|
+
[liveThreshold]
|
|
18464
|
+
);
|
|
18465
|
+
const toolCallsToday = countScalar(
|
|
18466
|
+
this.db,
|
|
18095
18467
|
`SELECT count(*) AS n FROM audit_events
|
|
18096
|
-
WHERE event_type = 'tool_call' AND started_at >= ? AND started_at <
|
|
18097
|
-
|
|
18098
|
-
|
|
18468
|
+
WHERE event_type = 'tool_call' AND started_at >= ? AND started_at < ?`,
|
|
18469
|
+
[startMs, endMs]
|
|
18470
|
+
);
|
|
18471
|
+
const findingsToday = countScalar(
|
|
18472
|
+
this.db,
|
|
18099
18473
|
`SELECT count(*) AS n FROM inspection_findings f
|
|
18100
18474
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
18101
|
-
WHERE e.started_at >= ? AND e.started_at <
|
|
18102
|
-
|
|
18103
|
-
|
|
18475
|
+
WHERE e.started_at >= ? AND e.started_at < ?`,
|
|
18476
|
+
[startMs, endMs]
|
|
18477
|
+
);
|
|
18478
|
+
const egressToday = countScalar(
|
|
18479
|
+
this.db,
|
|
18104
18480
|
`SELECT count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
18105
18481
|
FROM audit_events
|
|
18106
|
-
WHERE event_type = 'share' AND started_at >= ? AND started_at <
|
|
18107
|
-
|
|
18482
|
+
WHERE event_type = 'share' AND started_at >= ? AND started_at < ?`,
|
|
18483
|
+
[startMs, endMs]
|
|
18484
|
+
);
|
|
18108
18485
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18109
18486
|
}
|
|
18110
18487
|
listSessions(query) {
|
|
@@ -18126,7 +18503,7 @@ var SqliteActivityRepository = class {
|
|
|
18126
18503
|
conditions.push("started_at <= ?");
|
|
18127
18504
|
params.push(toMs);
|
|
18128
18505
|
if (query.q) {
|
|
18129
|
-
const pattern =
|
|
18506
|
+
const pattern = containsPattern(query.q);
|
|
18130
18507
|
conditions.push(
|
|
18131
18508
|
`(content LIKE ? ESCAPE '\\'
|
|
18132
18509
|
OR json_extract(attributes, '$.project') LIKE ? ESCAPE '\\'
|
|
@@ -18140,13 +18517,21 @@ var SqliteActivityRepository = class {
|
|
|
18140
18517
|
);
|
|
18141
18518
|
params.push(pattern, pattern, pattern, pattern, pattern, pattern);
|
|
18142
18519
|
}
|
|
18520
|
+
const emptyCount = countScalar(
|
|
18521
|
+
this.db,
|
|
18522
|
+
`SELECT count(*) AS n FROM audit_events
|
|
18523
|
+
WHERE ${[...conditions, `NOT ${HAS_ACTIVITY}`].join(" AND ")}`,
|
|
18524
|
+
params
|
|
18525
|
+
);
|
|
18526
|
+
if (query.excludeEmpty) conditions.push(HAS_ACTIVITY);
|
|
18143
18527
|
if (cursor) {
|
|
18144
18528
|
conditions.push("(started_at < ? OR (started_at = ? AND id < ?))");
|
|
18145
18529
|
params.push(cursor.startedAtMs, cursor.startedAtMs, cursor.id);
|
|
18146
18530
|
}
|
|
18147
18531
|
const limit = query.limit;
|
|
18148
|
-
const rows =
|
|
18149
|
-
|
|
18532
|
+
const rows = allRows(
|
|
18533
|
+
this.db.prepare(
|
|
18534
|
+
`SELECT id,
|
|
18150
18535
|
json_extract(attributes, '$.harness') AS harness,
|
|
18151
18536
|
content AS title,
|
|
18152
18537
|
json_extract(attributes, '$.project') AS project,
|
|
@@ -18159,7 +18544,9 @@ var SqliteActivityRepository = class {
|
|
|
18159
18544
|
WHERE ${conditions.join(" AND ")}
|
|
18160
18545
|
ORDER BY started_at DESC, id DESC
|
|
18161
18546
|
LIMIT ?`
|
|
18162
|
-
|
|
18547
|
+
),
|
|
18548
|
+
[...params, limit + 1]
|
|
18549
|
+
);
|
|
18163
18550
|
const hasMore = rows.length > limit;
|
|
18164
18551
|
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
18165
18552
|
const rollups = this.rollupsFor(page.map((r) => r.id));
|
|
@@ -18173,11 +18560,12 @@ var SqliteActivityRepository = class {
|
|
|
18173
18560
|
);
|
|
18174
18561
|
const last = page[page.length - 1];
|
|
18175
18562
|
const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
18176
|
-
return Promise.resolve({ items, nextCursor });
|
|
18563
|
+
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
18177
18564
|
}
|
|
18178
18565
|
getSession(sessionId) {
|
|
18179
|
-
const rootRow =
|
|
18180
|
-
|
|
18566
|
+
const rootRow = getRow(
|
|
18567
|
+
this.db.prepare(
|
|
18568
|
+
`SELECT id,
|
|
18181
18569
|
json_extract(attributes, '$.harness') AS harness,
|
|
18182
18570
|
content AS title,
|
|
18183
18571
|
json_extract(attributes, '$.project') AS project,
|
|
@@ -18194,47 +18582,66 @@ var SqliteActivityRepository = class {
|
|
|
18194
18582
|
FROM audit_events
|
|
18195
18583
|
WHERE id = ? AND event_type = 'session'
|
|
18196
18584
|
LIMIT 1`
|
|
18197
|
-
|
|
18585
|
+
),
|
|
18586
|
+
[sessionId]
|
|
18587
|
+
);
|
|
18198
18588
|
if (!rootRow) return Promise.resolve(null);
|
|
18199
|
-
const timelineRows =
|
|
18200
|
-
|
|
18589
|
+
const timelineRows = allRows(
|
|
18590
|
+
this.db.prepare(
|
|
18591
|
+
`SELECT ${TIMELINE_COLUMNS}
|
|
18201
18592
|
FROM audit_events
|
|
18202
18593
|
WHERE id = ? OR root_session_id = ?
|
|
18203
18594
|
ORDER BY started_at ASC, id ASC`
|
|
18204
|
-
|
|
18595
|
+
),
|
|
18596
|
+
[sessionId, sessionId]
|
|
18597
|
+
);
|
|
18205
18598
|
const events = timelineRows.map(buildAuditEvent).filter((e) => e !== null);
|
|
18206
|
-
const tokenRow =
|
|
18207
|
-
|
|
18599
|
+
const tokenRow = getRow(
|
|
18600
|
+
this.db.prepare(
|
|
18601
|
+
`SELECT
|
|
18208
18602
|
coalesce(sum(input_tokens), 0) AS input,
|
|
18209
18603
|
coalesce(sum(output_tokens), 0) AS output,
|
|
18210
18604
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
18211
18605
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
18212
18606
|
FROM audit_events
|
|
18213
18607
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
|
|
18608
|
+
),
|
|
18609
|
+
[sessionId]
|
|
18610
|
+
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
18611
|
+
const primaryModel = getRow(
|
|
18612
|
+
this.db.prepare(
|
|
18613
|
+
`SELECT model, provider FROM audit_events
|
|
18217
18614
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
18218
18615
|
ORDER BY started_at ASC, id ASC
|
|
18219
18616
|
LIMIT 1`
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
|
|
18617
|
+
),
|
|
18618
|
+
[sessionId]
|
|
18619
|
+
);
|
|
18620
|
+
const toolRows = allRows(
|
|
18621
|
+
this.db.prepare(
|
|
18622
|
+
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
18223
18623
|
count(*) AS n
|
|
18224
18624
|
FROM audit_events
|
|
18225
18625
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
18226
18626
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18627
|
+
),
|
|
18628
|
+
[sessionId]
|
|
18629
|
+
);
|
|
18630
|
+
const modelRows = allRows(
|
|
18631
|
+
this.db.prepare(
|
|
18632
|
+
`SELECT DISTINCT model FROM audit_events
|
|
18230
18633
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
18231
18634
|
ORDER BY model`
|
|
18232
|
-
|
|
18635
|
+
),
|
|
18636
|
+
[sessionId]
|
|
18637
|
+
);
|
|
18233
18638
|
const derivedModels = modelRows.map((r) => r.model);
|
|
18234
|
-
const commits =
|
|
18639
|
+
const commits = countScalar(
|
|
18640
|
+
this.db,
|
|
18235
18641
|
`SELECT count(*) AS n FROM audit_events
|
|
18236
|
-
WHERE root_session_id = ? AND event_type = 'commit'
|
|
18237
|
-
|
|
18642
|
+
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
18643
|
+
[sessionId]
|
|
18644
|
+
);
|
|
18238
18645
|
const rollup = this.rollupsFor([sessionId]).get(sessionId) ?? {
|
|
18239
18646
|
turns: 0,
|
|
18240
18647
|
findings: 0,
|
|
@@ -18301,7 +18708,10 @@ var SqliteActivityRepository = class {
|
|
|
18301
18708
|
`SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), 'claudecode') AS harness
|
|
18302
18709
|
FROM audit_events WHERE ${SESSION_ROOT}${where}`
|
|
18303
18710
|
);
|
|
18304
|
-
const rows =
|
|
18711
|
+
const rows = allRows(
|
|
18712
|
+
stmt,
|
|
18713
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
18714
|
+
);
|
|
18305
18715
|
const seen = /* @__PURE__ */ new Set();
|
|
18306
18716
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
18307
18717
|
return Promise.resolve([...seen]);
|
|
@@ -18324,23 +18734,23 @@ var SqliteActivityRepository = class {
|
|
|
18324
18734
|
conditions.push("started_at >= ?");
|
|
18325
18735
|
params.push(opts.fromMs);
|
|
18326
18736
|
}
|
|
18327
|
-
const rows =
|
|
18328
|
-
|
|
18737
|
+
const rows = allRows(
|
|
18738
|
+
this.db.prepare(
|
|
18739
|
+
`SELECT root_session_id AS sessionId, attributes
|
|
18329
18740
|
FROM audit_events
|
|
18330
18741
|
WHERE ${conditions.join(" AND ")}`
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
}
|
|
18342
|
-
|
|
18343
|
-
return leaves;
|
|
18742
|
+
),
|
|
18743
|
+
params
|
|
18744
|
+
);
|
|
18745
|
+
return mapRowsTolerant(
|
|
18746
|
+
rows.filter(
|
|
18747
|
+
(row) => row.sessionId !== null
|
|
18748
|
+
),
|
|
18749
|
+
(row) => ({
|
|
18750
|
+
sessionId: row.sessionId,
|
|
18751
|
+
attributes: JSON.parse(row.attributes)
|
|
18752
|
+
})
|
|
18753
|
+
);
|
|
18344
18754
|
}
|
|
18345
18755
|
/**
|
|
18346
18756
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
@@ -18354,57 +18764,72 @@ var SqliteActivityRepository = class {
|
|
|
18354
18764
|
);
|
|
18355
18765
|
if (sessionIds.length === 0) return result;
|
|
18356
18766
|
const inClause = placeholders(sessionIds.length);
|
|
18357
|
-
const lastActivityRows =
|
|
18358
|
-
|
|
18767
|
+
const lastActivityRows = allRows(
|
|
18768
|
+
this.db.prepare(
|
|
18769
|
+
`SELECT root_session_id AS id, max(${LAST_ACTIVITY_EXPR}) AS m FROM audit_events
|
|
18359
18770
|
WHERE root_session_id IN (${inClause})
|
|
18360
18771
|
GROUP BY root_session_id`
|
|
18361
|
-
|
|
18772
|
+
),
|
|
18773
|
+
sessionIds
|
|
18774
|
+
);
|
|
18362
18775
|
for (const row of lastActivityRows) {
|
|
18363
18776
|
if (row.id === null) continue;
|
|
18364
18777
|
const entry = result.get(row.id);
|
|
18365
18778
|
if (entry && row.m !== null) entry.lastActivityMs = row.m;
|
|
18366
18779
|
}
|
|
18367
|
-
const turnsRows =
|
|
18368
|
-
|
|
18780
|
+
const turnsRows = allRows(
|
|
18781
|
+
this.db.prepare(
|
|
18782
|
+
`SELECT root_session_id AS id, count(*) AS n FROM audit_events
|
|
18369
18783
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
18370
18784
|
GROUP BY root_session_id`
|
|
18371
|
-
|
|
18785
|
+
),
|
|
18786
|
+
sessionIds
|
|
18787
|
+
);
|
|
18372
18788
|
for (const row of turnsRows) {
|
|
18373
18789
|
if (row.id === null) continue;
|
|
18374
18790
|
const entry = result.get(row.id);
|
|
18375
18791
|
if (entry) entry.turns = row.n;
|
|
18376
18792
|
}
|
|
18377
|
-
const runKeyRows =
|
|
18378
|
-
|
|
18793
|
+
const runKeyRows = allRows(
|
|
18794
|
+
this.db.prepare(
|
|
18795
|
+
`SELECT root_session_id AS id,
|
|
18379
18796
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
18380
18797
|
FROM audit_events
|
|
18381
18798
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
18382
18799
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
18383
18800
|
GROUP BY root_session_id`
|
|
18384
|
-
|
|
18801
|
+
),
|
|
18802
|
+
sessionIds
|
|
18803
|
+
);
|
|
18385
18804
|
for (const row of runKeyRows) {
|
|
18386
18805
|
if (row.id === null) continue;
|
|
18387
18806
|
const entry = result.get(row.id);
|
|
18388
18807
|
if (entry) entry.turns = Math.max(entry.turns, row.n);
|
|
18389
18808
|
}
|
|
18390
|
-
const findingsRows =
|
|
18391
|
-
|
|
18809
|
+
const findingsRows = allRows(
|
|
18810
|
+
this.db.prepare(
|
|
18811
|
+
`SELECT e.root_session_id AS id, count(*) AS n FROM inspection_findings f
|
|
18392
18812
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
18393
18813
|
WHERE e.root_session_id IN (${inClause})
|
|
18394
18814
|
GROUP BY e.root_session_id`
|
|
18395
|
-
|
|
18815
|
+
),
|
|
18816
|
+
sessionIds
|
|
18817
|
+
);
|
|
18396
18818
|
for (const row of findingsRows) {
|
|
18397
18819
|
if (row.id === null) continue;
|
|
18398
18820
|
const entry = result.get(row.id);
|
|
18399
18821
|
if (entry) entry.findings = row.n;
|
|
18400
18822
|
}
|
|
18401
|
-
const sharesRows =
|
|
18402
|
-
|
|
18823
|
+
const sharesRows = allRows(
|
|
18824
|
+
this.db.prepare(
|
|
18825
|
+
`SELECT root_session_id AS id,
|
|
18403
18826
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
18404
18827
|
FROM audit_events
|
|
18405
18828
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
18406
18829
|
GROUP BY root_session_id`
|
|
18407
|
-
|
|
18830
|
+
),
|
|
18831
|
+
sessionIds
|
|
18832
|
+
);
|
|
18408
18833
|
for (const row of sharesRows) {
|
|
18409
18834
|
if (row.id === null) continue;
|
|
18410
18835
|
const entry = result.get(row.id);
|
|
@@ -18454,33 +18879,28 @@ var SqliteAuditEventsRepository = class {
|
|
|
18454
18879
|
// the caller fails open and drops the whole pass — recovered idempotently on the
|
|
18455
18880
|
// next pass. Nesting-safe is NOT needed: the reconciler is the sole caller.
|
|
18456
18881
|
runInTransaction(fn) {
|
|
18457
|
-
this.db
|
|
18458
|
-
try {
|
|
18459
|
-
fn();
|
|
18460
|
-
this.db.exec("COMMIT");
|
|
18461
|
-
} catch (err) {
|
|
18462
|
-
this.db.exec("ROLLBACK");
|
|
18463
|
-
throw err;
|
|
18464
|
-
}
|
|
18882
|
+
withTransaction(this.db, fn);
|
|
18465
18883
|
}
|
|
18466
18884
|
insertAuditEvent(input) {
|
|
18467
18885
|
const row = toAuditEventRow(input);
|
|
18468
|
-
this.insertStmt.run(
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
18480
|
-
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
|
|
18886
|
+
this.insertStmt.run(
|
|
18887
|
+
bindParams({
|
|
18888
|
+
id: row.id,
|
|
18889
|
+
parentId: row.parentId,
|
|
18890
|
+
rootSessionId: row.rootSessionId,
|
|
18891
|
+
eventType: row.eventType,
|
|
18892
|
+
hostId: row.hostId,
|
|
18893
|
+
harnessId: row.harnessId,
|
|
18894
|
+
sourceProjectId: row.sourceProjectId,
|
|
18895
|
+
startedAt: row.startedAt,
|
|
18896
|
+
endedAt: row.endedAt,
|
|
18897
|
+
severity: row.severity,
|
|
18898
|
+
priority: row.priority,
|
|
18899
|
+
content: row.content,
|
|
18900
|
+
contentHash: row.contentHash,
|
|
18901
|
+
attributes: row.attributes
|
|
18902
|
+
})
|
|
18903
|
+
);
|
|
18484
18904
|
}
|
|
18485
18905
|
// Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
|
|
18486
18906
|
// (which takes a caller-supplied random id), the id here is MINTED internally
|
|
@@ -18494,22 +18914,24 @@ var SqliteAuditEventsRepository = class {
|
|
|
18494
18914
|
const startedAt = isoToEpochMillis(input.startedAt);
|
|
18495
18915
|
if (!Number.isFinite(startedAt)) return;
|
|
18496
18916
|
const id = llmCallId(input.sessionId, input.messageId);
|
|
18497
|
-
this.upsertLlmCallStmt.run(
|
|
18498
|
-
|
|
18499
|
-
|
|
18500
|
-
|
|
18501
|
-
|
|
18502
|
-
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
|
|
18511
|
-
|
|
18512
|
-
|
|
18917
|
+
this.upsertLlmCallStmt.run(
|
|
18918
|
+
bindParams({
|
|
18919
|
+
id,
|
|
18920
|
+
parentId: input.parentId,
|
|
18921
|
+
rootSessionId: input.rootSessionId,
|
|
18922
|
+
eventType: "llm_call",
|
|
18923
|
+
hostId: null,
|
|
18924
|
+
harnessId: null,
|
|
18925
|
+
sourceProjectId: null,
|
|
18926
|
+
startedAt,
|
|
18927
|
+
endedAt: null,
|
|
18928
|
+
severity: null,
|
|
18929
|
+
priority: null,
|
|
18930
|
+
content: null,
|
|
18931
|
+
contentHash: null,
|
|
18932
|
+
attributes: JSON.stringify(input.attributes)
|
|
18933
|
+
})
|
|
18934
|
+
);
|
|
18513
18935
|
}
|
|
18514
18936
|
// Insert one transcript-derived `tool_call` leaf. Like `insertLlmCall` the id is
|
|
18515
18937
|
// MINTED internally from the natural key — `toolCallId(sessionId, toolUseId)` —
|
|
@@ -18533,25 +18955,29 @@ var SqliteAuditEventsRepository = class {
|
|
|
18533
18955
|
const startedAt = isoToEpochMillis(input.startedAt);
|
|
18534
18956
|
if (!Number.isFinite(startedAt)) return;
|
|
18535
18957
|
const id = toolCallId(input.sessionId, input.toolUseId);
|
|
18536
|
-
this.insertStmt.run(
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18958
|
+
this.insertStmt.run(
|
|
18959
|
+
bindParams({
|
|
18960
|
+
id,
|
|
18961
|
+
parentId: input.parentId,
|
|
18962
|
+
rootSessionId: input.rootSessionId,
|
|
18963
|
+
eventType: "tool_call",
|
|
18964
|
+
hostId: null,
|
|
18965
|
+
harnessId: null,
|
|
18966
|
+
sourceProjectId: null,
|
|
18967
|
+
startedAt,
|
|
18968
|
+
endedAt: null,
|
|
18969
|
+
severity: null,
|
|
18970
|
+
priority: null,
|
|
18971
|
+
content: null,
|
|
18972
|
+
contentHash: null,
|
|
18973
|
+
attributes: JSON.stringify(input.attributes)
|
|
18974
|
+
})
|
|
18975
|
+
);
|
|
18552
18976
|
}
|
|
18553
18977
|
findById(id) {
|
|
18554
|
-
return this.db.prepare("SELECT * FROM audit_events WHERE id = :id")
|
|
18978
|
+
return getRow(this.db.prepare("SELECT * FROM audit_events WHERE id = :id"), {
|
|
18979
|
+
id
|
|
18980
|
+
});
|
|
18555
18981
|
}
|
|
18556
18982
|
// Read the `provider` snapshotted onto a session root's attributes.
|
|
18557
18983
|
// The reconciler ensures the root, then reads provider back from it — SessionStart's
|
|
@@ -18561,14 +18987,8 @@ var SqliteAuditEventsRepository = class {
|
|
|
18561
18987
|
sessionProvider(sessionId) {
|
|
18562
18988
|
const row = this.findById(sessionId);
|
|
18563
18989
|
if (!row?.attributes) return void 0;
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
18567
|
-
const provider = parsed.provider;
|
|
18568
|
-
if (typeof provider === "string") return provider;
|
|
18569
|
-
}
|
|
18570
|
-
} catch {
|
|
18571
|
-
}
|
|
18990
|
+
const provider = parseJsonObject(row.attributes)?.provider;
|
|
18991
|
+
if (typeof provider === "string") return provider;
|
|
18572
18992
|
return void 0;
|
|
18573
18993
|
}
|
|
18574
18994
|
// Every `llm_call` leaf's session id + raw attribute bag, for the read-time token
|
|
@@ -18577,11 +18997,13 @@ var SqliteAuditEventsRepository = class {
|
|
|
18577
18997
|
// is the leaf's session (the reconciler sets parent_id = root_session_id = sessionId);
|
|
18578
18998
|
// rows whose attributes blob is NULL are skipped (nothing to roll up).
|
|
18579
18999
|
llmCallLeaves() {
|
|
18580
|
-
return
|
|
18581
|
-
|
|
19000
|
+
return allRows(
|
|
19001
|
+
this.db.prepare(
|
|
19002
|
+
`SELECT root_session_id AS sessionId, attributes
|
|
18582
19003
|
FROM audit_events
|
|
18583
19004
|
WHERE event_type = 'llm_call' AND attributes IS NOT NULL`
|
|
18584
|
-
|
|
19005
|
+
)
|
|
19006
|
+
);
|
|
18585
19007
|
}
|
|
18586
19008
|
};
|
|
18587
19009
|
|
|
@@ -18600,16 +19022,29 @@ var SqliteClassifiedDataRepository = class {
|
|
|
18600
19022
|
upsert(input) {
|
|
18601
19023
|
const id = classifiedDataId(input.class);
|
|
18602
19024
|
const row = toClassifiedDataRow(input, id);
|
|
18603
|
-
this.insertStmt.run(
|
|
18604
|
-
|
|
18605
|
-
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
|
|
19025
|
+
this.insertStmt.run(
|
|
19026
|
+
bindParams({
|
|
19027
|
+
id: row.id,
|
|
19028
|
+
class: row.class,
|
|
19029
|
+
label: row.label,
|
|
19030
|
+
attributes: row.attributes
|
|
19031
|
+
})
|
|
19032
|
+
);
|
|
18609
19033
|
return id;
|
|
18610
19034
|
}
|
|
18611
19035
|
};
|
|
18612
19036
|
|
|
19037
|
+
// ../../packages/persistence/src/repositories/config-scan.ts
|
|
19038
|
+
function latestConfigScan(db) {
|
|
19039
|
+
return getRow(
|
|
19040
|
+
db.prepare(
|
|
19041
|
+
`SELECT id, started_at, attributes FROM audit_events
|
|
19042
|
+
WHERE event_type = 'config_scan'
|
|
19043
|
+
ORDER BY started_at DESC, id DESC LIMIT 1`
|
|
19044
|
+
)
|
|
19045
|
+
);
|
|
19046
|
+
}
|
|
19047
|
+
|
|
18613
19048
|
// ../../packages/persistence/src/repositories/config-inventory.ts
|
|
18614
19049
|
var SqliteConfigInventoryRepository = class {
|
|
18615
19050
|
constructor(db) {
|
|
@@ -18617,7 +19052,7 @@ var SqliteConfigInventoryRepository = class {
|
|
|
18617
19052
|
}
|
|
18618
19053
|
db;
|
|
18619
19054
|
report() {
|
|
18620
|
-
const scan2 = this.
|
|
19055
|
+
const scan2 = latestConfigScan(this.db);
|
|
18621
19056
|
if (!scan2) {
|
|
18622
19057
|
return {
|
|
18623
19058
|
scannedAt: null,
|
|
@@ -18628,17 +19063,23 @@ var SqliteConfigInventoryRepository = class {
|
|
|
18628
19063
|
topics: []
|
|
18629
19064
|
};
|
|
18630
19065
|
}
|
|
18631
|
-
const rows =
|
|
18632
|
-
|
|
19066
|
+
const rows = allRows(
|
|
19067
|
+
this.db.prepare(
|
|
19068
|
+
`SELECT id, object_type AS objectType, title, location, attributes FROM inventory
|
|
18633
19069
|
WHERE object_type IN ('skill', 'hook', 'mcp_server', 'config_file') AND last_seen >= :startedAt
|
|
18634
19070
|
ORDER BY object_type, title`
|
|
18635
|
-
|
|
18636
|
-
|
|
18637
|
-
|
|
19071
|
+
),
|
|
19072
|
+
{ startedAt: scan2.started_at }
|
|
19073
|
+
);
|
|
19074
|
+
const findings = allRows(
|
|
19075
|
+
this.db.prepare(
|
|
19076
|
+
`SELECT f.masked_match AS maskedMatch, d.rule_id AS ruleId, d.name AS name
|
|
18638
19077
|
FROM inspection_findings f
|
|
18639
19078
|
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
18640
19079
|
WHERE f.audit_event_id = :scanId`
|
|
18641
|
-
|
|
19080
|
+
),
|
|
19081
|
+
{ scanId: scan2.id }
|
|
19082
|
+
);
|
|
18642
19083
|
const skills = [];
|
|
18643
19084
|
const hooks = [];
|
|
18644
19085
|
const mcpServers = [];
|
|
@@ -18667,7 +19108,9 @@ var SqliteConfigInventoryRepository = class {
|
|
|
18667
19108
|
// schema note); an override whose asset is gone simply never matches. A row
|
|
18668
19109
|
// with an out-of-vocabulary trust value is ignored rather than guessed at.
|
|
18669
19110
|
trustOverrides() {
|
|
18670
|
-
const rows =
|
|
19111
|
+
const rows = allRows(
|
|
19112
|
+
this.db.prepare("SELECT asset_id AS assetId, trust FROM mcp_trust_override")
|
|
19113
|
+
);
|
|
18671
19114
|
const map2 = /* @__PURE__ */ new Map();
|
|
18672
19115
|
for (const row of rows) {
|
|
18673
19116
|
if (row.trust === "known-good" || row.trust === "risky" || row.trust === "unapproved") {
|
|
@@ -18676,13 +19119,6 @@ var SqliteConfigInventoryRepository = class {
|
|
|
18676
19119
|
}
|
|
18677
19120
|
return map2;
|
|
18678
19121
|
}
|
|
18679
|
-
latestScan() {
|
|
18680
|
-
return this.db.prepare(
|
|
18681
|
-
`SELECT id, started_at, attributes FROM audit_events
|
|
18682
|
-
WHERE event_type = 'config_scan'
|
|
18683
|
-
ORDER BY started_at DESC, id DESC LIMIT 1`
|
|
18684
|
-
).get();
|
|
18685
|
-
}
|
|
18686
19122
|
};
|
|
18687
19123
|
function toSkillItem(row, bag) {
|
|
18688
19124
|
const item = {
|
|
@@ -18793,22 +19229,11 @@ function buildTopics(skills, hooks, mcpServers, configFiles, scanAttributes) {
|
|
|
18793
19229
|
return topics;
|
|
18794
19230
|
}
|
|
18795
19231
|
function countScanErrors(attributes) {
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
const parsed = JSON.parse(attributes);
|
|
18799
|
-
const errors = parsed?.errors;
|
|
18800
|
-
return typeof errors === "number" ? errors : 0;
|
|
18801
|
-
} catch {
|
|
18802
|
-
return 0;
|
|
18803
|
-
}
|
|
19232
|
+
const errors = parseJsonObject(attributes)?.errors;
|
|
19233
|
+
return typeof errors === "number" ? errors : 0;
|
|
18804
19234
|
}
|
|
18805
19235
|
function parseBag(raw) {
|
|
18806
|
-
|
|
18807
|
-
const parsed = JSON.parse(raw);
|
|
18808
|
-
if (typeof parsed === "object" && parsed !== null) return parsed;
|
|
18809
|
-
} catch {
|
|
18810
|
-
}
|
|
18811
|
-
return void 0;
|
|
19236
|
+
return parseJsonObject(raw);
|
|
18812
19237
|
}
|
|
18813
19238
|
function str(value) {
|
|
18814
19239
|
return typeof value === "string" ? value : void 0;
|
|
@@ -18817,12 +19242,7 @@ function str(value) {
|
|
|
18817
19242
|
// ../../packages/persistence/src/repositories/detections.ts
|
|
18818
19243
|
var DAY_MS2 = 864e5;
|
|
18819
19244
|
function parseRules(rulesJson) {
|
|
18820
|
-
|
|
18821
|
-
try {
|
|
18822
|
-
raw = JSON.parse(rulesJson);
|
|
18823
|
-
} catch {
|
|
18824
|
-
return [];
|
|
18825
|
-
}
|
|
19245
|
+
const raw = safeJson(rulesJson, []);
|
|
18826
19246
|
if (!Array.isArray(raw)) return [];
|
|
18827
19247
|
const rules = [];
|
|
18828
19248
|
for (const entry of raw) {
|
|
@@ -18841,11 +19261,13 @@ var SqliteDetectionsRepository = class {
|
|
|
18841
19261
|
db;
|
|
18842
19262
|
now;
|
|
18843
19263
|
listDetections(query) {
|
|
18844
|
-
const rows =
|
|
18845
|
-
|
|
19264
|
+
const rows = allRows(
|
|
19265
|
+
this.db.prepare(
|
|
19266
|
+
`SELECT namespace, pack_id AS packId, version, name, enabled, policy_id AS policyId,
|
|
18846
19267
|
rules_json AS rulesJson
|
|
18847
19268
|
FROM installed_packs`
|
|
18848
|
-
|
|
19269
|
+
)
|
|
19270
|
+
);
|
|
18849
19271
|
const available = this.availableByPack();
|
|
18850
19272
|
const summaries = rows.map((r) => {
|
|
18851
19273
|
const latest = available.get(`${r.namespace}/${r.packId}`);
|
|
@@ -18854,7 +19276,7 @@ var SqliteDetectionsRepository = class {
|
|
|
18854
19276
|
packId: r.packId,
|
|
18855
19277
|
version: r.version,
|
|
18856
19278
|
name: r.name,
|
|
18857
|
-
enabled: r.enabled
|
|
19279
|
+
enabled: intToBool(r.enabled),
|
|
18858
19280
|
// Count rules in JS via the tolerant parse rather than SQL json_array_length,
|
|
18859
19281
|
// which THROWS "malformed JSON" on a corrupt/foreign rules_json and would
|
|
18860
19282
|
// crash the whole list. This also keeps ruleCount identical to the detail
|
|
@@ -18871,19 +19293,23 @@ var SqliteDetectionsRepository = class {
|
|
|
18871
19293
|
// available_packs keyed by the "namespace/packId" slug (one read per list /
|
|
18872
19294
|
// detail call; the table is a handful of rows).
|
|
18873
19295
|
availableByPack() {
|
|
18874
|
-
const rows =
|
|
18875
|
-
|
|
19296
|
+
const rows = allRows(
|
|
19297
|
+
this.db.prepare(
|
|
19298
|
+
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson
|
|
18876
19299
|
FROM available_packs`
|
|
18877
|
-
|
|
19300
|
+
)
|
|
19301
|
+
);
|
|
18878
19302
|
return new Map(rows.map((r) => [`${r.namespace}/${r.packId}`, r]));
|
|
18879
19303
|
}
|
|
18880
19304
|
getDetectionStats() {
|
|
18881
|
-
const rows =
|
|
19305
|
+
const rows = allRows(
|
|
19306
|
+
this.db.prepare("SELECT enabled, rules_json AS rulesJson FROM installed_packs")
|
|
19307
|
+
);
|
|
18882
19308
|
let rules = 0;
|
|
18883
19309
|
let active = 0;
|
|
18884
19310
|
const ruleIds = /* @__PURE__ */ new Set();
|
|
18885
19311
|
for (const r of rows) {
|
|
18886
|
-
if (r.enabled
|
|
19312
|
+
if (intToBool(r.enabled)) active += 1;
|
|
18887
19313
|
const parsed = parseRules(r.rulesJson);
|
|
18888
19314
|
rules += parsed.length;
|
|
18889
19315
|
for (const rule of parsed) {
|
|
@@ -18901,12 +19327,15 @@ var SqliteDetectionsRepository = class {
|
|
|
18901
19327
|
const parts = splitDetectionId(id);
|
|
18902
19328
|
if (!parts) return Promise.resolve(null);
|
|
18903
19329
|
const { namespace, packId } = parts;
|
|
18904
|
-
const row =
|
|
18905
|
-
|
|
19330
|
+
const row = getRow(
|
|
19331
|
+
this.db.prepare(
|
|
19332
|
+
`SELECT namespace, pack_id AS packId, version, name, enabled, policy_id AS policyId,
|
|
18906
19333
|
rules_json AS rulesJson, updated_at AS updatedAt
|
|
18907
19334
|
FROM installed_packs
|
|
18908
19335
|
WHERE namespace = ? AND pack_id = ?`
|
|
18909
|
-
|
|
19336
|
+
),
|
|
19337
|
+
[namespace, packId]
|
|
19338
|
+
);
|
|
18910
19339
|
if (!row) return Promise.resolve(null);
|
|
18911
19340
|
const rules = parseRules(row.rulesJson);
|
|
18912
19341
|
const ruleIds = rules.map((r) => r.id).filter((id2) => typeof id2 === "string");
|
|
@@ -18924,7 +19353,7 @@ var SqliteDetectionsRepository = class {
|
|
|
18924
19353
|
packId: row.packId,
|
|
18925
19354
|
version: row.version,
|
|
18926
19355
|
name: row.name,
|
|
18927
|
-
enabled: row.enabled
|
|
19356
|
+
enabled: intToBool(row.enabled),
|
|
18928
19357
|
rules,
|
|
18929
19358
|
updatedAt: new Date(row.updatedAt),
|
|
18930
19359
|
policyId: row.policyId
|
|
@@ -18939,13 +19368,14 @@ var SqliteDetectionsRepository = class {
|
|
|
18939
19368
|
countFindingsLast30d(ruleIds) {
|
|
18940
19369
|
if (ruleIds.length === 0) return 0;
|
|
18941
19370
|
const since = this.now() - 30 * DAY_MS2;
|
|
18942
|
-
const
|
|
18943
|
-
|
|
18944
|
-
|
|
19371
|
+
const inClause = placeholders(ruleIds.length);
|
|
19372
|
+
return countScalar(
|
|
19373
|
+
this.db,
|
|
19374
|
+
`SELECT count(*) AS n
|
|
18945
19375
|
FROM findings f JOIN events e ON e.id = f.event_id
|
|
18946
|
-
WHERE e.occurred_at >= ? AND f.rule_id IN (${
|
|
18947
|
-
|
|
18948
|
-
|
|
19376
|
+
WHERE e.occurred_at >= ? AND f.rule_id IN (${inClause})`,
|
|
19377
|
+
[since, ...ruleIds]
|
|
19378
|
+
);
|
|
18949
19379
|
}
|
|
18950
19380
|
};
|
|
18951
19381
|
|
|
@@ -18962,16 +19392,17 @@ var SqliteEventsRepository = class {
|
|
|
18962
19392
|
insertStmt;
|
|
18963
19393
|
insertEvent(event) {
|
|
18964
19394
|
const row = toEventRow(event);
|
|
18965
|
-
this.insertStmt.run(
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
19395
|
+
this.insertStmt.run(
|
|
19396
|
+
bindParams({
|
|
19397
|
+
id: row.id,
|
|
19398
|
+
sourceTool: row.sourceTool,
|
|
19399
|
+
kind: row.kind,
|
|
19400
|
+
occurredAt: row.occurredAt,
|
|
19401
|
+
contentHash: row.contentHash,
|
|
19402
|
+
content: row.content,
|
|
19403
|
+
metadata: row.metadata
|
|
19404
|
+
})
|
|
19405
|
+
);
|
|
18975
19406
|
}
|
|
18976
19407
|
// Every recorded event's content hash — the historical backfill loads this once
|
|
18977
19408
|
// to skip transcript messages it has already stored, so re-running the scan
|
|
@@ -18979,13 +19410,23 @@ var SqliteEventsRepository = class {
|
|
|
18979
19410
|
// Async (Promise.resolve over synchronous node:sqlite) so it satisfies the
|
|
18980
19411
|
// async EventsReadPort contract.
|
|
18981
19412
|
contentHashes() {
|
|
18982
|
-
const rows =
|
|
19413
|
+
const rows = allRows(
|
|
19414
|
+
this.db.prepare("SELECT content_hash FROM events")
|
|
19415
|
+
);
|
|
18983
19416
|
return Promise.resolve(new Set(rows.map((r) => r.content_hash)));
|
|
18984
19417
|
}
|
|
18985
19418
|
};
|
|
18986
19419
|
|
|
18987
19420
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
18988
19421
|
import { randomUUID } from "crypto";
|
|
19422
|
+
|
|
19423
|
+
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19424
|
+
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
19425
|
+
function isUniqueConstraintError(err) {
|
|
19426
|
+
return err instanceof Error && (err.errcode === SQLITE_CONSTRAINT_UNIQUE || err.message.includes("UNIQUE constraint failed"));
|
|
19427
|
+
}
|
|
19428
|
+
|
|
19429
|
+
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
18989
19430
|
var BLOCKED_DETECTIONS_TTL_MS = 30 * 60 * 1e3;
|
|
18990
19431
|
var BLOCKED_DETECTIONS_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
18991
19432
|
var DuplicateActiveExceptionError = class extends Error {
|
|
@@ -19006,10 +19447,6 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19006
19447
|
this.name = "AmbiguousExceptionIdError";
|
|
19007
19448
|
}
|
|
19008
19449
|
};
|
|
19009
|
-
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
19010
|
-
function isUniqueConstraintError(err) {
|
|
19011
|
-
return err instanceof Error && (err.errcode === SQLITE_CONSTRAINT_UNIQUE || err.message.includes("UNIQUE constraint failed"));
|
|
19012
|
-
}
|
|
19013
19450
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19014
19451
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19015
19452
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
@@ -19065,10 +19502,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19065
19502
|
this.insertExceptionRow(id, input, now);
|
|
19066
19503
|
} catch (err) {
|
|
19067
19504
|
if (!isUniqueConstraintError(err)) throw err;
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19505
|
+
withTransaction(
|
|
19506
|
+
this.db,
|
|
19507
|
+
() => {
|
|
19508
|
+
const superseded = this.db.prepare(
|
|
19509
|
+
`UPDATE exceptions
|
|
19072
19510
|
SET revoked_at = :now, revoked_by = :revokedBy,
|
|
19073
19511
|
revoke_reason = 'superseded by a new grant for the same value',
|
|
19074
19512
|
updated_at = :now
|
|
@@ -19076,24 +19514,27 @@ var SqliteExceptionsRepository = class {
|
|
|
19076
19514
|
AND key_version = :keyVersion AND revoked_at IS NULL
|
|
19077
19515
|
AND ((expires_at IS NOT NULL AND expires_at <= :now)
|
|
19078
19516
|
OR (max_uses IS NOT NULL AND use_count >= max_uses))`
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19517
|
+
).run({
|
|
19518
|
+
now,
|
|
19519
|
+
revokedBy: input.createdBy,
|
|
19520
|
+
ruleId: input.ruleId,
|
|
19521
|
+
valueFingerprint: input.valueFingerprint,
|
|
19522
|
+
keyVersion: input.keyVersion
|
|
19523
|
+
});
|
|
19524
|
+
if (Number(superseded.changes) !== 1) {
|
|
19525
|
+
throw new DuplicateActiveExceptionError(input.ruleId);
|
|
19526
|
+
}
|
|
19527
|
+
this.insertExceptionRow(id, input, now);
|
|
19528
|
+
},
|
|
19529
|
+
"IMMEDIATE"
|
|
19530
|
+
);
|
|
19531
|
+
}
|
|
19532
|
+
const row = getRow(this.db.prepare("SELECT * FROM exceptions WHERE id = :id"), {
|
|
19533
|
+
id
|
|
19534
|
+
});
|
|
19535
|
+
if (row === void 0) {
|
|
19536
|
+
throw new Error("exception row not found immediately after insert");
|
|
19095
19537
|
}
|
|
19096
|
-
const row = this.db.prepare("SELECT * FROM exceptions WHERE id = :id").get({ id });
|
|
19097
19538
|
return parseExceptionRow(row);
|
|
19098
19539
|
}
|
|
19099
19540
|
insertExceptionRow(id, input, now) {
|
|
@@ -19131,14 +19572,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19131
19572
|
*/
|
|
19132
19573
|
list(opts) {
|
|
19133
19574
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
19134
|
-
const rows =
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
19138
|
-
|
|
19139
|
-
} catch {
|
|
19140
|
-
}
|
|
19141
|
-
}
|
|
19575
|
+
const rows = allRows(
|
|
19576
|
+
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
19577
|
+
opts?.includeTerminal ? {} : { now: Date.now() }
|
|
19578
|
+
);
|
|
19579
|
+
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
19142
19580
|
return Promise.resolve(exceptions);
|
|
19143
19581
|
}
|
|
19144
19582
|
/**
|
|
@@ -19148,7 +19586,12 @@ var SqliteExceptionsRepository = class {
|
|
|
19148
19586
|
*/
|
|
19149
19587
|
getByIdPrefix(prefix) {
|
|
19150
19588
|
if (prefix.length === 0) return Promise.resolve(void 0);
|
|
19151
|
-
const rows =
|
|
19589
|
+
const rows = allRows(
|
|
19590
|
+
this.db.prepare(
|
|
19591
|
+
String.raw`SELECT * FROM exceptions WHERE id LIKE :pattern ESCAPE '\' LIMIT 2`
|
|
19592
|
+
),
|
|
19593
|
+
{ pattern: `${escapeLikePattern(prefix)}%` }
|
|
19594
|
+
);
|
|
19152
19595
|
if (rows.length > 1) {
|
|
19153
19596
|
return Promise.reject(new AmbiguousExceptionIdError(prefix));
|
|
19154
19597
|
}
|
|
@@ -19190,30 +19633,27 @@ var SqliteExceptionsRepository = class {
|
|
|
19190
19633
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
19191
19634
|
*/
|
|
19192
19635
|
activeBundleEntries(keyVersion, now = Date.now()) {
|
|
19193
|
-
const rows =
|
|
19194
|
-
|
|
19636
|
+
const rows = allRows(
|
|
19637
|
+
this.db.prepare(
|
|
19638
|
+
`SELECT * FROM exceptions
|
|
19195
19639
|
WHERE key_version = :keyVersion AND ${ACTIVE_PREDICATE}
|
|
19196
19640
|
ORDER BY created_at DESC, rowid DESC`
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
);
|
|
19214
|
-
} catch {
|
|
19215
|
-
}
|
|
19216
|
-
}
|
|
19641
|
+
),
|
|
19642
|
+
{ keyVersion, now }
|
|
19643
|
+
);
|
|
19644
|
+
const entries = mapRowsTolerant(rows, (row) => {
|
|
19645
|
+
const conditions = row.conditions === null ? null : JSON.parse(row.conditions);
|
|
19646
|
+
return ExceptionBundleEntry.parse({
|
|
19647
|
+
id: row.id,
|
|
19648
|
+
ruleId: row.rule_id,
|
|
19649
|
+
valueFingerprint: row.value_fingerprint,
|
|
19650
|
+
keyVersion: row.key_version,
|
|
19651
|
+
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
19652
|
+
maxUses: row.max_uses,
|
|
19653
|
+
useCount: row.use_count,
|
|
19654
|
+
conditions
|
|
19655
|
+
});
|
|
19656
|
+
});
|
|
19217
19657
|
return Promise.resolve(entries);
|
|
19218
19658
|
}
|
|
19219
19659
|
/**
|
|
@@ -19240,11 +19680,14 @@ var SqliteExceptionsRepository = class {
|
|
|
19240
19680
|
}
|
|
19241
19681
|
/** Blocked detections within the window (default: the 30-minute TTL), newest-first. */
|
|
19242
19682
|
recentBlocked(windowMs = BLOCKED_DETECTIONS_TTL_MS) {
|
|
19243
|
-
const rows =
|
|
19244
|
-
|
|
19683
|
+
const rows = allRows(
|
|
19684
|
+
this.db.prepare(
|
|
19685
|
+
`SELECT * FROM blocked_detections
|
|
19245
19686
|
WHERE blocked_at > :cutoff
|
|
19246
19687
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
19247
|
-
|
|
19688
|
+
),
|
|
19689
|
+
{ cutoff: Date.now() - windowMs }
|
|
19690
|
+
);
|
|
19248
19691
|
return Promise.resolve(
|
|
19249
19692
|
rows.map((row) => ({
|
|
19250
19693
|
reference: row.reference,
|
|
@@ -19324,7 +19767,12 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
19324
19767
|
)`;
|
|
19325
19768
|
|
|
19326
19769
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
19327
|
-
var
|
|
19770
|
+
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
19771
|
+
var CONCAT_SEP = ",";
|
|
19772
|
+
var TUPLE_SEP = "|";
|
|
19773
|
+
function splitConcat(value) {
|
|
19774
|
+
return value === null || value === "" ? [] : value.split(CONCAT_SEP);
|
|
19775
|
+
}
|
|
19328
19776
|
function deriveInstanceStatus(row) {
|
|
19329
19777
|
return deriveFindingStatus({
|
|
19330
19778
|
kind: row.kind,
|
|
@@ -19392,13 +19840,16 @@ var SqliteFindingsRepository = class {
|
|
|
19392
19840
|
}
|
|
19393
19841
|
recentFindings(opts) {
|
|
19394
19842
|
const limit = opts?.limit ?? 50;
|
|
19395
|
-
const rows =
|
|
19396
|
-
|
|
19843
|
+
const rows = allRows(
|
|
19844
|
+
this.db.prepare(
|
|
19845
|
+
`SELECT f.id, f.event_id, f.rule_id, f.category, f.severity, f.masked_match,
|
|
19397
19846
|
f.action_taken, f.confidence, e.occurred_at, e.source_tool, e.kind
|
|
19398
19847
|
FROM findings f JOIN events e ON e.id = f.event_id
|
|
19399
19848
|
ORDER BY e.occurred_at DESC, f.rowid DESC
|
|
19400
19849
|
LIMIT :limit`
|
|
19401
|
-
|
|
19850
|
+
),
|
|
19851
|
+
{ limit }
|
|
19852
|
+
);
|
|
19402
19853
|
return Promise.resolve(
|
|
19403
19854
|
rows.map((r) => ({
|
|
19404
19855
|
id: r.id,
|
|
@@ -19415,26 +19866,94 @@ var SqliteFindingsRepository = class {
|
|
|
19415
19866
|
}))
|
|
19416
19867
|
);
|
|
19417
19868
|
}
|
|
19869
|
+
/** Live-enforced findings recorded for one session — a bare COUNT over the
|
|
19870
|
+
* session-stamped events (served by idx_events_session_id), so the Activity
|
|
19871
|
+
* page can label its findings link without the grouped pipeline. */
|
|
19872
|
+
sessionFindingsCount(sessionId) {
|
|
19873
|
+
if (!sessionId) return Promise.resolve(0);
|
|
19874
|
+
return Promise.resolve(
|
|
19875
|
+
countScalar(
|
|
19876
|
+
this.db,
|
|
19877
|
+
`SELECT count(*) AS n FROM findings f
|
|
19878
|
+
JOIN events e ON e.id = f.event_id
|
|
19879
|
+
WHERE json_extract(e.metadata, '$.sessionId') = :sessionId`,
|
|
19880
|
+
{ sessionId }
|
|
19881
|
+
)
|
|
19882
|
+
);
|
|
19883
|
+
}
|
|
19884
|
+
/** Per-rule transcript firing tally for one session — reads the OTHER finding
|
|
19885
|
+
* store (inspection_findings, keyed to audit_events): every detection the
|
|
19886
|
+
* transcript pass recorded, counted per firing rather than per unique value.
|
|
19887
|
+
* Rides on session-scoped grouped responses so the findings view can
|
|
19888
|
+
* reconcile the Activity page's tally with the deduped groups it lists. */
|
|
19889
|
+
sessionFirings(sessionId) {
|
|
19890
|
+
return Object.fromEntries(
|
|
19891
|
+
countBy(
|
|
19892
|
+
this.db,
|
|
19893
|
+
`SELECT d.rule_id AS k, count(*) AS n
|
|
19894
|
+
FROM inspection_findings f
|
|
19895
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
19896
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
19897
|
+
WHERE e.root_session_id = :sessionId
|
|
19898
|
+
GROUP BY d.rule_id`,
|
|
19899
|
+
{ sessionId }
|
|
19900
|
+
)
|
|
19901
|
+
);
|
|
19902
|
+
}
|
|
19418
19903
|
/**
|
|
19419
|
-
* Grouped findings for the dashboard — joins findings⋈events (repo/file
|
|
19420
|
-
* from event metadata), groups by ruleId, computes per-filter-excluded facets,
|
|
19904
|
+
* Grouped findings for the dashboard — joins findings⋈events (repo/file/
|
|
19905
|
+
* toolName from event metadata), groups by ruleId, computes per-filter-excluded facets,
|
|
19421
19906
|
* applies the requested filters, and sorts by severity then recency. Filtering
|
|
19422
19907
|
* and faceting run in JS via the shared @akasecurity/schema helpers. `totals`
|
|
19423
19908
|
* reflect the full filtered set; `items` is the requested
|
|
19424
|
-
* page (default
|
|
19909
|
+
* page (default 50); no cursor (nextCursor is always null).
|
|
19910
|
+
*
|
|
19911
|
+
* Two reads, neither of which materializes a row per finding:
|
|
19912
|
+
* 1. one aggregate row per rule_id, folding EVERY instance into the numbers
|
|
19913
|
+
* the group and the filters need (count, providers, actions, statuses,
|
|
19914
|
+
* latest, search text);
|
|
19915
|
+
* 2. each group's newest PREVIEW_INSTANCES_PER_GROUP instances, which
|
|
19916
|
+
* populate `instances` for the table's expanded rows.
|
|
19917
|
+
* The aggregates carry raw DB values and are translated by the same
|
|
19918
|
+
* @akasecurity/schema mappers the row path uses, so no enum mapping or status
|
|
19919
|
+
* rule is ever restated in SQL.
|
|
19425
19920
|
*/
|
|
19426
19921
|
listGroupedFindings(query) {
|
|
19427
|
-
const
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
|
|
19431
|
-
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19922
|
+
const sessionPredicate = query.sessionId ? `WHERE json_extract(e.metadata, '$.sessionId') = :sessionId` : "";
|
|
19923
|
+
const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
|
|
19924
|
+
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
19925
|
+
predicate: sessionPredicate,
|
|
19926
|
+
params: sessionParams
|
|
19927
|
+
});
|
|
19928
|
+
const rows = allRows(
|
|
19929
|
+
this.db.prepare(
|
|
19930
|
+
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
19931
|
+
occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
|
|
19932
|
+
FROM (
|
|
19933
|
+
SELECT f.id AS id, f.rule_id AS rule_id, f.category AS category,
|
|
19934
|
+
f.severity AS severity, f.masked_match AS masked_match,
|
|
19935
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
19936
|
+
e.occurred_at AS occurred_at, e.source_tool AS source_tool,
|
|
19937
|
+
json_extract(e.metadata, '$.repo') AS repo,
|
|
19938
|
+
json_extract(e.metadata, '$.filePath') AS file,
|
|
19939
|
+
json_extract(e.metadata, '$.toolName') AS tool_name,
|
|
19940
|
+
e.kind AS kind, f.finding_key AS finding_key,
|
|
19941
|
+
latest.status AS latest_status,
|
|
19942
|
+
ROW_NUMBER() OVER (
|
|
19943
|
+
PARTITION BY f.rule_id
|
|
19944
|
+
ORDER BY e.occurred_at DESC, f.id DESC
|
|
19945
|
+
) AS rn
|
|
19946
|
+
FROM findings f
|
|
19947
|
+
JOIN events e ON e.id = f.event_id
|
|
19948
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
19949
|
+
ON latest.finding_key = f.finding_key
|
|
19950
|
+
${sessionPredicate}
|
|
19951
|
+
)
|
|
19952
|
+
WHERE rn <= :cap
|
|
19953
|
+
ORDER BY occurred_at DESC, id DESC`
|
|
19954
|
+
),
|
|
19955
|
+
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
19956
|
+
);
|
|
19438
19957
|
const groupable = rows.map((r) => ({
|
|
19439
19958
|
id: r.id,
|
|
19440
19959
|
ruleId: r.rule_id,
|
|
@@ -19447,9 +19966,10 @@ var SqliteFindingsRepository = class {
|
|
|
19447
19966
|
sourceTool: r.source_tool,
|
|
19448
19967
|
repo: r.repo ?? "",
|
|
19449
19968
|
file: r.file ?? "",
|
|
19969
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
19450
19970
|
status: deriveInstanceStatus(r)
|
|
19451
19971
|
}));
|
|
19452
|
-
const allGroups = buildFindingGroups(groupable);
|
|
19972
|
+
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
19453
19973
|
const filterOpts = {
|
|
19454
19974
|
severity: query.severity,
|
|
19455
19975
|
providers: query.provider,
|
|
@@ -19465,44 +19985,134 @@ var SqliteFindingsRepository = class {
|
|
|
19465
19985
|
};
|
|
19466
19986
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
19467
19987
|
const items = sorted.slice(0, limit);
|
|
19468
|
-
return Promise.resolve({
|
|
19988
|
+
return Promise.resolve({
|
|
19989
|
+
totals,
|
|
19990
|
+
facets,
|
|
19991
|
+
items,
|
|
19992
|
+
nextCursor: null,
|
|
19993
|
+
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
19994
|
+
});
|
|
19995
|
+
}
|
|
19996
|
+
/**
|
|
19997
|
+
* One row per rule_id, folding EVERY instance of the group into the values
|
|
19998
|
+
* buildFindingGroups cannot recover from a preview. Bounded by the number of
|
|
19999
|
+
* distinct rule_ids (the installed packs' rules), not by the store's size.
|
|
20000
|
+
*
|
|
20001
|
+
* The per-instance sets ride back as group_concat lists of RAW DB values —
|
|
20002
|
+
* source_tool, action_taken, and the (kind, has-key, latest-status) triples
|
|
20003
|
+
* deriveFindingStatus consumes. Aggregating the status INPUTS rather than a
|
|
20004
|
+
* status keeps the classifier itself in @akasecurity/schema, where
|
|
20005
|
+
* severitySummary's SQL and this query can't drift apart on what 'resolved'
|
|
20006
|
+
* means (see resolution-sql.ts). Each of those sets is bounded by an enum, so
|
|
20007
|
+
* a group's row stays small however many findings it holds.
|
|
20008
|
+
*
|
|
20009
|
+
* `withSearchText` is the exception, and the one column here that does NOT
|
|
20010
|
+
* stay small: the group's distinct repos/filePaths, whose size tracks how many
|
|
20011
|
+
* distinct paths a rule fired across — for a rule hitting mostly-unique paths
|
|
20012
|
+
* that is a string proportional to the store (~8MB over 200k distinct paths,
|
|
20013
|
+
* and buildHaystack lowercases a second copy). It buys `q` the ability to
|
|
20014
|
+
* match an instance outside the preview, which searching the preview alone
|
|
20015
|
+
* would silently lose, so it is fetched only when the request actually
|
|
20016
|
+
* carries a `q`.
|
|
20017
|
+
*/
|
|
20018
|
+
groupAggregates(withSearchText, scope) {
|
|
20019
|
+
const searchTextColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.metadata, '$.repo')) AS repos,
|
|
20020
|
+
group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files,
|
|
20021
|
+
group_concat(DISTINCT 'via ' || json_extract(e.metadata, '$.toolName')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
20022
|
+
const rows = this.db.prepare(
|
|
20023
|
+
`SELECT f.rule_id AS rule_id,
|
|
20024
|
+
count(*) AS instance_count,
|
|
20025
|
+
max(e.occurred_at) AS latest_at,
|
|
20026
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
20027
|
+
group_concat(DISTINCT f.action_taken) AS actions_taken,
|
|
20028
|
+
group_concat(DISTINCT (
|
|
20029
|
+
e.kind || '${TUPLE_SEP}' ||
|
|
20030
|
+
(CASE WHEN f.finding_key IS NULL THEN '' ELSE 'k' END) || '${TUPLE_SEP}' ||
|
|
20031
|
+
coalesce(latest.status, '')
|
|
20032
|
+
)) AS status_inputs
|
|
20033
|
+
${searchTextColumns}
|
|
20034
|
+
FROM findings f
|
|
20035
|
+
JOIN events e ON e.id = f.event_id
|
|
20036
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
20037
|
+
ON latest.finding_key = f.finding_key
|
|
20038
|
+
${scope.predicate}
|
|
20039
|
+
GROUP BY f.rule_id`
|
|
20040
|
+
).all(scope.params);
|
|
20041
|
+
return new Map(
|
|
20042
|
+
rows.map((r) => [
|
|
20043
|
+
r.rule_id,
|
|
20044
|
+
{
|
|
20045
|
+
instanceCount: r.instance_count,
|
|
20046
|
+
sourceTools: splitConcat(r.source_tools),
|
|
20047
|
+
actionsTaken: splitConcat(r.actions_taken),
|
|
20048
|
+
statusInputs: splitConcat(r.status_inputs).map((tuple2) => {
|
|
20049
|
+
const [kind = "", keyMarker = "", latestStatus = ""] = tuple2.split(TUPLE_SEP);
|
|
20050
|
+
return {
|
|
20051
|
+
// deriveFindingStatus only distinguishes null from non-null here,
|
|
20052
|
+
// so the marker stands in for the key itself (never rendered).
|
|
20053
|
+
kind,
|
|
20054
|
+
findingKey: keyMarker === "" ? null : keyMarker,
|
|
20055
|
+
latestResolutionStatus: latestStatus === "" ? null : latestStatus
|
|
20056
|
+
};
|
|
20057
|
+
}),
|
|
20058
|
+
latestDetectedAt: epochMillisToIso(r.latest_at),
|
|
20059
|
+
// Free text only — joined and substring-matched, so group_concat's
|
|
20060
|
+
// commas need no unpicking (a repo/path containing one still matches).
|
|
20061
|
+
// Left undefined (not '') when unfetched, so buildFindingGroups can
|
|
20062
|
+
// tell "no q this request" from "a group with no repo/file at all"
|
|
20063
|
+
// and skip priming a haystack nothing will read.
|
|
20064
|
+
...withSearchText ? {
|
|
20065
|
+
searchText: [r.repos ?? "", r.files ?? "", r.tool_names ?? ""].filter((s) => s !== "").join(" ")
|
|
20066
|
+
} : {}
|
|
20067
|
+
}
|
|
20068
|
+
])
|
|
20069
|
+
);
|
|
19469
20070
|
}
|
|
19470
20071
|
healthSummary() {
|
|
19471
|
-
const total = this.db
|
|
20072
|
+
const total = countScalar(this.db, "SELECT count(*) AS n FROM findings");
|
|
19472
20073
|
const byAction = Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
|
|
19473
|
-
const grouped =
|
|
20074
|
+
const grouped = allRows(
|
|
20075
|
+
this.db.prepare("SELECT action_taken, count(*) AS c FROM findings GROUP BY action_taken")
|
|
20076
|
+
);
|
|
19474
20077
|
for (const row of grouped) {
|
|
19475
20078
|
if (row.action_taken in byAction) byAction[row.action_taken] = row.c;
|
|
19476
20079
|
}
|
|
19477
20080
|
const bySeverity = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
19478
|
-
const sevRows =
|
|
19479
|
-
|
|
20081
|
+
const sevRows = allRows(
|
|
20082
|
+
this.db.prepare(
|
|
20083
|
+
`SELECT f.severity AS severity, count(*) AS c
|
|
19480
20084
|
FROM findings f
|
|
19481
20085
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
19482
20086
|
ON latest.finding_key = f.finding_key
|
|
19483
20087
|
WHERE latest.status IS NULL OR latest.status != 'resolved'
|
|
19484
20088
|
GROUP BY f.severity`
|
|
19485
|
-
|
|
20089
|
+
)
|
|
20090
|
+
);
|
|
19486
20091
|
for (const row of sevRows) {
|
|
19487
20092
|
if (row.severity in bySeverity) bySeverity[row.severity] = row.c;
|
|
19488
20093
|
}
|
|
19489
20094
|
const categories = ENFORCEABLE_CATEGORIES;
|
|
19490
|
-
const enabledRows =
|
|
19491
|
-
|
|
20095
|
+
const enabledRows = allRows(
|
|
20096
|
+
this.db.prepare(
|
|
20097
|
+
`SELECT DISTINCT json_extract(target, '$.category') AS category
|
|
19492
20098
|
FROM policies WHERE enabled = 1 AND json_extract(target, '$.category') IS NOT NULL`
|
|
19493
|
-
|
|
20099
|
+
)
|
|
20100
|
+
);
|
|
19494
20101
|
const enabled = new Set(enabledRows.map((r) => r.category));
|
|
19495
20102
|
const coverage = categories.length === 0 ? 0 : categories.filter((c) => enabled.has(c)).length / categories.length;
|
|
19496
20103
|
return Promise.resolve({ findings: total, byAction, bySeverity, coverage });
|
|
19497
20104
|
}
|
|
19498
20105
|
activityByDay(days = 7) {
|
|
19499
20106
|
const since = startOfUtcDay(Date.now()) - (days - 1) * DAY_MS3;
|
|
19500
|
-
const rows =
|
|
19501
|
-
|
|
20107
|
+
const rows = allRows(
|
|
20108
|
+
this.db.prepare(
|
|
20109
|
+
`SELECT date(e.occurred_at / 1000, 'unixepoch') AS day, f.action_taken AS action, count(*) AS c
|
|
19502
20110
|
FROM findings f JOIN events e ON e.id = f.event_id
|
|
19503
20111
|
WHERE e.occurred_at >= :since
|
|
19504
20112
|
GROUP BY day, f.action_taken`
|
|
19505
|
-
|
|
20113
|
+
),
|
|
20114
|
+
{ since }
|
|
20115
|
+
);
|
|
19506
20116
|
const buckets = /* @__PURE__ */ new Map();
|
|
19507
20117
|
for (let i = 0; i < days; i++) {
|
|
19508
20118
|
const day = isoDay(since + i * DAY_MS3);
|
|
@@ -19574,17 +20184,19 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
19574
20184
|
insertStmt;
|
|
19575
20185
|
insertFinding(input) {
|
|
19576
20186
|
const row = toInspectionFindingRow(input);
|
|
19577
|
-
this.insertStmt.run(
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19583
|
-
|
|
19584
|
-
|
|
19585
|
-
|
|
19586
|
-
|
|
19587
|
-
|
|
20187
|
+
this.insertStmt.run(
|
|
20188
|
+
bindParams({
|
|
20189
|
+
id: row.id,
|
|
20190
|
+
auditEventId: row.auditEventId,
|
|
20191
|
+
inspectionDefinitionId: row.inspectionDefinitionId,
|
|
20192
|
+
classifiedDataId: row.classifiedDataId,
|
|
20193
|
+
spanStart: row.spanStart,
|
|
20194
|
+
spanEnd: row.spanEnd,
|
|
20195
|
+
maskedMatch: row.maskedMatch,
|
|
20196
|
+
actionTaken: row.actionTaken,
|
|
20197
|
+
confidence: row.confidence
|
|
20198
|
+
})
|
|
20199
|
+
);
|
|
19588
20200
|
}
|
|
19589
20201
|
};
|
|
19590
20202
|
|
|
@@ -19660,12 +20272,7 @@ function isMirrorDowngrade(incoming, stored) {
|
|
|
19660
20272
|
}
|
|
19661
20273
|
function ruleIdsOf(rulesJson) {
|
|
19662
20274
|
const ids = /* @__PURE__ */ new Set();
|
|
19663
|
-
|
|
19664
|
-
try {
|
|
19665
|
-
raw = JSON.parse(rulesJson);
|
|
19666
|
-
} catch {
|
|
19667
|
-
return ids;
|
|
19668
|
-
}
|
|
20275
|
+
const raw = safeJson(rulesJson, []);
|
|
19669
20276
|
if (!Array.isArray(raw)) return ids;
|
|
19670
20277
|
for (const entry of raw) {
|
|
19671
20278
|
if (entry && typeof entry === "object") {
|
|
@@ -19738,47 +20345,48 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19738
20345
|
}));
|
|
19739
20346
|
if (this.storedSignature() === inventorySignature(rows)) return;
|
|
19740
20347
|
const now = Date.now();
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
const
|
|
19747
|
-
|
|
19748
|
-
namespace: row.namespace,
|
|
19749
|
-
packId: row.packId,
|
|
19750
|
-
version: row.version,
|
|
19751
|
-
name: row.name,
|
|
19752
|
-
rulesJson: row.rulesJson,
|
|
19753
|
-
now
|
|
19754
|
-
};
|
|
19755
|
-
const stored = mirror.get(`${row.namespace}/${row.packId}`);
|
|
19756
|
-
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
19757
|
-
this.upsertAvailableStmt.run({
|
|
19758
|
-
...params,
|
|
20348
|
+
withTransaction(
|
|
20349
|
+
this.db,
|
|
20350
|
+
() => {
|
|
20351
|
+
const mirror = this.mirrorState();
|
|
20352
|
+
let behind = false;
|
|
20353
|
+
for (const row of rows) {
|
|
20354
|
+
const params = {
|
|
19759
20355
|
id: randomUUID2(),
|
|
19760
|
-
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
20356
|
+
namespace: row.namespace,
|
|
20357
|
+
packId: row.packId,
|
|
20358
|
+
version: row.version,
|
|
20359
|
+
name: row.name,
|
|
20360
|
+
rulesJson: row.rulesJson,
|
|
20361
|
+
now
|
|
20362
|
+
};
|
|
20363
|
+
const stored = mirror.get(`${row.namespace}/${row.packId}`);
|
|
20364
|
+
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20365
|
+
this.upsertAvailableStmt.run({
|
|
20366
|
+
...params,
|
|
20367
|
+
id: randomUUID2(),
|
|
20368
|
+
recordedBy: meta3?.recordedBy ?? null
|
|
20369
|
+
});
|
|
20370
|
+
} else {
|
|
20371
|
+
behind = true;
|
|
20372
|
+
}
|
|
20373
|
+
this.insertMissingStmt.run(params);
|
|
19764
20374
|
}
|
|
19765
|
-
this.
|
|
19766
|
-
}
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
} catch (err) {
|
|
19770
|
-
this.db.exec("ROLLBACK");
|
|
19771
|
-
throw err;
|
|
19772
|
-
}
|
|
20375
|
+
if (!behind) this.pruneAvailable(rows.map((r) => `${r.namespace}/${r.packId}`));
|
|
20376
|
+
},
|
|
20377
|
+
"IMMEDIATE"
|
|
20378
|
+
);
|
|
19773
20379
|
} catch {
|
|
19774
20380
|
}
|
|
19775
20381
|
}
|
|
19776
20382
|
// The mirror's current (namespace/packId → {version, ruleIds}) map — the
|
|
19777
20383
|
// input to the downgrade guard. Read INSIDE the write transaction.
|
|
19778
20384
|
mirrorState() {
|
|
19779
|
-
const rows =
|
|
19780
|
-
|
|
19781
|
-
|
|
20385
|
+
const rows = allRows(
|
|
20386
|
+
this.db.prepare(
|
|
20387
|
+
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
20388
|
+
)
|
|
20389
|
+
);
|
|
19782
20390
|
return new Map(
|
|
19783
20391
|
rows.map((r) => [
|
|
19784
20392
|
`${r.namespace}/${r.packId}`,
|
|
@@ -19790,7 +20398,9 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19790
20398
|
// (keys joined with '/', matching the detection id slug encoding — packId may
|
|
19791
20399
|
// itself contain '/', but namespace may not, so the join is unambiguous).
|
|
19792
20400
|
pruneAvailable(keep) {
|
|
19793
|
-
const rows =
|
|
20401
|
+
const rows = allRows(
|
|
20402
|
+
this.db.prepare(`SELECT namespace, pack_id AS packId FROM available_packs`)
|
|
20403
|
+
);
|
|
19794
20404
|
const keepSet = new Set(keep);
|
|
19795
20405
|
const del = this.db.prepare(`DELETE FROM available_packs WHERE namespace = ? AND pack_id = ?`);
|
|
19796
20406
|
for (const r of rows) {
|
|
@@ -19817,11 +20427,13 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19817
20427
|
if (this.db.isTransaction) {
|
|
19818
20428
|
throw new Error("applyUpdate must not be called inside an open transaction");
|
|
19819
20429
|
}
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
this.db
|
|
19823
|
-
|
|
19824
|
-
|
|
20430
|
+
let changed = false;
|
|
20431
|
+
withTransaction(
|
|
20432
|
+
this.db,
|
|
20433
|
+
() => {
|
|
20434
|
+
this.db.exec("UPDATE _pack_write_gate SET open = 1 WHERE id = 1");
|
|
20435
|
+
const res = this.db.prepare(
|
|
20436
|
+
`UPDATE installed_packs SET
|
|
19825
20437
|
version = (SELECT a.version FROM available_packs a
|
|
19826
20438
|
WHERE a.namespace = :namespace AND a.pack_id = :packId),
|
|
19827
20439
|
name = (SELECT a.name FROM available_packs a
|
|
@@ -19832,17 +20444,13 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19832
20444
|
WHERE namespace = :namespace AND pack_id = :packId
|
|
19833
20445
|
AND EXISTS (SELECT 1 FROM available_packs a
|
|
19834
20446
|
WHERE a.namespace = :namespace AND a.pack_id = :packId)`
|
|
19835
|
-
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
19840
|
-
|
|
19841
|
-
|
|
19842
|
-
} catch {
|
|
19843
|
-
}
|
|
19844
|
-
throw err;
|
|
19845
|
-
}
|
|
20447
|
+
).run({ namespace, packId, now: Date.now() });
|
|
20448
|
+
this.db.exec("UPDATE _pack_write_gate SET open = 0 WHERE id = 1");
|
|
20449
|
+
changed = Number(res.changes) > 0;
|
|
20450
|
+
},
|
|
20451
|
+
"IMMEDIATE"
|
|
20452
|
+
);
|
|
20453
|
+
return changed;
|
|
19846
20454
|
}
|
|
19847
20455
|
/**
|
|
19848
20456
|
* The scan-time ruleset: every rule under an ENABLED installed pack that
|
|
@@ -19856,9 +20464,11 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19856
20464
|
* JSON-level failure therefore counts as invalid.
|
|
19857
20465
|
*/
|
|
19858
20466
|
installedRuleset() {
|
|
19859
|
-
const rows =
|
|
19860
|
-
|
|
19861
|
-
|
|
20467
|
+
const rows = allRows(
|
|
20468
|
+
this.db.prepare(
|
|
20469
|
+
`SELECT enabled, policy_id AS policyId, rules_json AS rulesJson FROM installed_packs`
|
|
20470
|
+
)
|
|
20471
|
+
);
|
|
19862
20472
|
const out = {
|
|
19863
20473
|
installedPacks: rows.length,
|
|
19864
20474
|
enabledPacks: 0,
|
|
@@ -19867,7 +20477,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19867
20477
|
ruleActions: /* @__PURE__ */ new Map()
|
|
19868
20478
|
};
|
|
19869
20479
|
for (const row of rows) {
|
|
19870
|
-
if (row.enabled
|
|
20480
|
+
if (!intToBool(row.enabled)) continue;
|
|
19871
20481
|
out.enabledPacks += 1;
|
|
19872
20482
|
const action = policyIdToAction(row.policyId);
|
|
19873
20483
|
let raw;
|
|
@@ -19902,9 +20512,11 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19902
20512
|
* running max would mask a genuinely-newer parseable stamp.
|
|
19903
20513
|
*/
|
|
19904
20514
|
newestRecordedBinary() {
|
|
19905
|
-
const rows =
|
|
19906
|
-
|
|
19907
|
-
|
|
20515
|
+
const rows = allRows(
|
|
20516
|
+
this.db.prepare(
|
|
20517
|
+
`SELECT DISTINCT recorded_by AS recordedBy FROM available_packs WHERE recorded_by IS NOT NULL`
|
|
20518
|
+
)
|
|
20519
|
+
);
|
|
19908
20520
|
let newest = null;
|
|
19909
20521
|
for (const row of rows) {
|
|
19910
20522
|
const at = row.recordedBy.lastIndexOf("@");
|
|
@@ -19919,13 +20531,15 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19919
20531
|
return newest;
|
|
19920
20532
|
}
|
|
19921
20533
|
counts() {
|
|
19922
|
-
const row =
|
|
19923
|
-
|
|
20534
|
+
const row = getRow(
|
|
20535
|
+
this.db.prepare(
|
|
20536
|
+
`SELECT count(*) AS packs,
|
|
19924
20537
|
coalesce(sum(json_array_length(rules_json)), 0) AS rules,
|
|
19925
20538
|
coalesce(sum(enabled), 0) AS enabled
|
|
19926
20539
|
FROM installed_packs`
|
|
19927
|
-
|
|
19928
|
-
|
|
20540
|
+
)
|
|
20541
|
+
);
|
|
20542
|
+
return Promise.resolve(row ?? { packs: 0, rules: 0, enabled: 0 });
|
|
19929
20543
|
}
|
|
19930
20544
|
// ─── Policy-catalog reads ────────────────────────────────────────────────────
|
|
19931
20545
|
// Back the Policies page's built-in catalog: how many
|
|
@@ -19938,26 +20552,29 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19938
20552
|
* attributed to Monitor, matching the Detections views.
|
|
19939
20553
|
*/
|
|
19940
20554
|
countsByPolicyId() {
|
|
19941
|
-
|
|
19942
|
-
|
|
20555
|
+
return countBy(
|
|
20556
|
+
this.db,
|
|
20557
|
+
`SELECT coalesce(policy_id, '${DEFAULT_POLICY_ID}') AS k, count(*) AS n
|
|
19943
20558
|
FROM installed_packs
|
|
19944
|
-
GROUP BY
|
|
19945
|
-
)
|
|
19946
|
-
return new Map(rows.map((r) => [r.pid, r.n]));
|
|
20559
|
+
GROUP BY k`
|
|
20560
|
+
);
|
|
19947
20561
|
}
|
|
19948
20562
|
/** The detections governed by a built-in policy — one UsedByItem per pack. */
|
|
19949
20563
|
listByPolicyId(policyId) {
|
|
19950
|
-
const rows =
|
|
19951
|
-
|
|
20564
|
+
const rows = allRows(
|
|
20565
|
+
this.db.prepare(
|
|
20566
|
+
`SELECT namespace, pack_id AS packId, name, enabled, rules_json AS rulesJson
|
|
19952
20567
|
FROM installed_packs
|
|
19953
20568
|
WHERE coalesce(policy_id, '${DEFAULT_POLICY_ID}') = ?
|
|
19954
20569
|
ORDER BY name ASC`
|
|
19955
|
-
|
|
20570
|
+
),
|
|
20571
|
+
[policyId]
|
|
20572
|
+
);
|
|
19956
20573
|
return rows.map((r) => ({
|
|
19957
20574
|
id: `${r.namespace}/${r.packId}`,
|
|
19958
20575
|
name: r.name,
|
|
19959
20576
|
ruleCount: parseRules(r.rulesJson).length,
|
|
19960
|
-
enabled: r.enabled
|
|
20577
|
+
enabled: intToBool(r.enabled)
|
|
19961
20578
|
}));
|
|
19962
20579
|
}
|
|
19963
20580
|
// ─── Writes ────────────────────────────────────────────────────────────────
|
|
@@ -19986,14 +20603,14 @@ var SqliteInstalledPacksRepository = class {
|
|
|
19986
20603
|
const res = this.db.prepare(
|
|
19987
20604
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
19988
20605
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
19989
|
-
).run({ enabled: enabled
|
|
20606
|
+
).run({ enabled: boolToInt(enabled), now: Date.now(), namespace, packId });
|
|
19990
20607
|
return Number(res.changes) > 0;
|
|
19991
20608
|
}
|
|
19992
20609
|
// Fingerprint of the recorded available mirror — compared against the
|
|
19993
20610
|
// incoming inventory's signature to skip the write entirely when the running
|
|
19994
20611
|
// binary's inventory hasn't changed since the last record.
|
|
19995
20612
|
storedSignature() {
|
|
19996
|
-
const rows = this.signatureStmt
|
|
20613
|
+
const rows = allRows(this.signatureStmt);
|
|
19997
20614
|
return inventorySignature(rows);
|
|
19998
20615
|
}
|
|
19999
20616
|
};
|
|
@@ -20021,42 +20638,48 @@ var SqliteInventoryRepository = class {
|
|
|
20021
20638
|
upsert(input, now = Date.now()) {
|
|
20022
20639
|
const id = inventoryId(input.objectType, input.identityKey);
|
|
20023
20640
|
const row = toInventoryRow(input, id, now);
|
|
20024
|
-
this.upsertStmt.run(
|
|
20025
|
-
|
|
20026
|
-
|
|
20027
|
-
|
|
20028
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
20032
|
-
|
|
20033
|
-
|
|
20641
|
+
this.upsertStmt.run(
|
|
20642
|
+
bindParams({
|
|
20643
|
+
id: row.id,
|
|
20644
|
+
objectType: row.objectType,
|
|
20645
|
+
location: row.location,
|
|
20646
|
+
title: row.title,
|
|
20647
|
+
hostId: row.hostId,
|
|
20648
|
+
attributes: row.attributes,
|
|
20649
|
+
firstSeen: row.firstSeen,
|
|
20650
|
+
lastSeen: row.lastSeen
|
|
20651
|
+
})
|
|
20652
|
+
);
|
|
20034
20653
|
return id;
|
|
20035
20654
|
}
|
|
20036
20655
|
// The full row, for round-trip assertions.
|
|
20037
20656
|
findById(id) {
|
|
20038
|
-
|
|
20039
|
-
return row;
|
|
20657
|
+
return getRow(this.db.prepare("SELECT * FROM inventory WHERE id = :id"), { id });
|
|
20040
20658
|
}
|
|
20041
20659
|
// Distinct titles for an object_type — a filter facet (e.g. hostnames),
|
|
20042
20660
|
// served from the object_type index, never from audit_events.
|
|
20043
20661
|
distinctTitles(objectType) {
|
|
20044
|
-
const rows =
|
|
20045
|
-
|
|
20662
|
+
const rows = allRows(
|
|
20663
|
+
this.db.prepare(
|
|
20664
|
+
`SELECT DISTINCT title FROM inventory
|
|
20046
20665
|
WHERE object_type = :objectType AND title IS NOT NULL
|
|
20047
20666
|
ORDER BY title`
|
|
20048
|
-
|
|
20667
|
+
),
|
|
20668
|
+
{ objectType }
|
|
20669
|
+
);
|
|
20049
20670
|
return rows.map((r) => r.title);
|
|
20050
20671
|
}
|
|
20051
20672
|
// Distinct host os_version values — a facet served from an inventory index
|
|
20052
20673
|
// over the generated column, never from the audit fact (confirm via EXPLAIN
|
|
20053
20674
|
// QUERY PLAN).
|
|
20054
20675
|
osVersions() {
|
|
20055
|
-
const rows =
|
|
20056
|
-
|
|
20676
|
+
const rows = allRows(
|
|
20677
|
+
this.db.prepare(
|
|
20678
|
+
`SELECT DISTINCT os_version AS value FROM inventory
|
|
20057
20679
|
WHERE object_type = 'host' AND os_version IS NOT NULL
|
|
20058
20680
|
ORDER BY value`
|
|
20059
|
-
|
|
20681
|
+
)
|
|
20682
|
+
);
|
|
20060
20683
|
return rows.map((r) => r.value);
|
|
20061
20684
|
}
|
|
20062
20685
|
};
|
|
@@ -20076,14 +20699,6 @@ var EMPTY_PROJECT_AGG = {
|
|
|
20076
20699
|
accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
|
|
20077
20700
|
findingsCount: 0
|
|
20078
20701
|
};
|
|
20079
|
-
function safeJson(s, fallback) {
|
|
20080
|
-
if (s == null) return fallback;
|
|
20081
|
-
try {
|
|
20082
|
-
return JSON.parse(s);
|
|
20083
|
-
} catch {
|
|
20084
|
-
return fallback;
|
|
20085
|
-
}
|
|
20086
|
-
}
|
|
20087
20702
|
function resolveHarnessId(attrs, row) {
|
|
20088
20703
|
if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
|
|
20089
20704
|
return attrs.provider;
|
|
@@ -20279,30 +20894,49 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20279
20894
|
configRowsCache;
|
|
20280
20895
|
// ─── stats ─────────────────────────────────────────────────────────────────
|
|
20281
20896
|
getInventoryStats() {
|
|
20282
|
-
const
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
|
|
20286
|
-
byType
|
|
20287
|
-
|
|
20288
|
-
|
|
20289
|
-
|
|
20897
|
+
const typeCounts = countBy(
|
|
20898
|
+
this.db,
|
|
20899
|
+
"SELECT asset_type AS k, count(*) AS n FROM inventory_asset GROUP BY asset_type"
|
|
20900
|
+
);
|
|
20901
|
+
const byType = {
|
|
20902
|
+
project: 0,
|
|
20903
|
+
skill: typeCounts.get("skill") ?? 0,
|
|
20904
|
+
mcp: typeCounts.get("mcp") ?? 0,
|
|
20905
|
+
hook: typeCounts.get("hook") ?? 0,
|
|
20906
|
+
config: typeCounts.get("config") ?? 0
|
|
20907
|
+
};
|
|
20908
|
+
byType.project = countScalar(
|
|
20909
|
+
this.db,
|
|
20910
|
+
`SELECT count(*) AS n FROM source_project WHERE ${WORKTREE_CHECKOUT_FILTER}`
|
|
20911
|
+
);
|
|
20912
|
+
const mcpTrustCounts = countBy(
|
|
20913
|
+
this.db,
|
|
20914
|
+
`SELECT coalesce(o.trust, a.trust) AS k, count(*) AS n
|
|
20290
20915
|
FROM inventory_asset a
|
|
20291
20916
|
LEFT JOIN mcp_trust_override o ON o.asset_id = a.id
|
|
20292
20917
|
WHERE a.asset_type = 'mcp' AND coalesce(o.trust, a.trust) IS NOT NULL
|
|
20293
20918
|
GROUP BY coalesce(o.trust, a.trust)`
|
|
20294
|
-
)
|
|
20295
|
-
|
|
20296
|
-
|
|
20297
|
-
|
|
20919
|
+
);
|
|
20920
|
+
const mcpTrust = {
|
|
20921
|
+
"known-good": mcpTrustCounts.get("known-good") ?? 0,
|
|
20922
|
+
risky: mcpTrustCounts.get("risky") ?? 0,
|
|
20923
|
+
unapproved: mcpTrustCounts.get("unapproved") ?? 0
|
|
20924
|
+
};
|
|
20925
|
+
const harnesses = countScalar(
|
|
20926
|
+
this.db,
|
|
20298
20927
|
`SELECT count(*) AS n FROM inventory
|
|
20299
20928
|
WHERE object_type = 'harness'
|
|
20300
|
-
AND (last_seen >= :liveSince OR json_extract(attributes, '$.provenance') = 'sample')
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
const
|
|
20929
|
+
AND (last_seen >= :liveSince OR json_extract(attributes, '$.provenance') = 'sample')`,
|
|
20930
|
+
{ liveSince: Date.now() - HARNESS_LIVENESS_WINDOW_MS }
|
|
20931
|
+
);
|
|
20932
|
+
const flaggedAssets = countScalar(
|
|
20933
|
+
this.db,
|
|
20934
|
+
"SELECT count(*) AS n FROM inventory_asset WHERE flags_json <> '[]'"
|
|
20935
|
+
);
|
|
20936
|
+
const flaggedProjects = countScalar(
|
|
20937
|
+
this.db,
|
|
20304
20938
|
`SELECT count(DISTINCT project_id) AS n FROM project_file WHERE findings_count > 0`
|
|
20305
|
-
)
|
|
20939
|
+
);
|
|
20306
20940
|
const configRows = this.configAssetRows();
|
|
20307
20941
|
for (const r of configRows) {
|
|
20308
20942
|
byType[r.assetType] += 1;
|
|
@@ -20559,12 +21193,15 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20559
21193
|
}
|
|
20560
21194
|
// ─── raw fetchers ────────────────────────────────────────────────────────────
|
|
20561
21195
|
fetchHarnessRows() {
|
|
20562
|
-
return
|
|
20563
|
-
|
|
21196
|
+
return allRows(
|
|
21197
|
+
this.db.prepare(
|
|
21198
|
+
`SELECT id, title, attributes, harness_version AS harnessVersion
|
|
20564
21199
|
FROM inventory
|
|
20565
21200
|
WHERE object_type = 'harness'
|
|
20566
21201
|
AND (last_seen >= :liveSince OR json_extract(attributes, '$.provenance') = 'sample')`
|
|
20567
|
-
|
|
21202
|
+
),
|
|
21203
|
+
{ liveSince: Date.now() - HARNESS_LIVENESS_WINDOW_MS }
|
|
21204
|
+
);
|
|
20568
21205
|
}
|
|
20569
21206
|
// Every harness's assets in ONE grouped query, keyed by harness inventory id —
|
|
20570
21207
|
// replaces the per-harness-row query the listHarnesses loop used to make.
|
|
@@ -20574,12 +21211,13 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20574
21211
|
const params = [...harnessInvIds];
|
|
20575
21212
|
let where = `ha.harness_id IN (${placeholders(harnessInvIds.length)})`;
|
|
20576
21213
|
if (q) {
|
|
20577
|
-
const pat =
|
|
20578
|
-
where +=
|
|
21214
|
+
const pat = containsPattern(q);
|
|
21215
|
+
where += ` AND ${likeAny(["a.name", "a.sub"])}`;
|
|
20579
21216
|
params.push(pat, pat);
|
|
20580
21217
|
}
|
|
20581
|
-
const rows =
|
|
20582
|
-
|
|
21218
|
+
const rows = allRows(
|
|
21219
|
+
this.db.prepare(
|
|
21220
|
+
`SELECT ha.harness_id AS harnessInvId, a.id, a.asset_type AS assetType, a.name, a.sub,
|
|
20583
21221
|
a.description, a.flags_json AS flagsJson, a.meta_json AS metaJson, a.trust,
|
|
20584
21222
|
a.tools_json AS toolsJson, coalesce(o.trust, a.trust) AS effectiveTrust
|
|
20585
21223
|
FROM harness_asset ha
|
|
@@ -20587,7 +21225,9 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20587
21225
|
LEFT JOIN mcp_trust_override o ON o.asset_id = a.id
|
|
20588
21226
|
WHERE ${where}
|
|
20589
21227
|
ORDER BY a.name ASC`
|
|
20590
|
-
|
|
21228
|
+
),
|
|
21229
|
+
params
|
|
21230
|
+
);
|
|
20591
21231
|
for (const raw of rows) {
|
|
20592
21232
|
const harnessInvId = raw.harnessInvId;
|
|
20593
21233
|
const [asset] = this.mapAssetRows([raw]);
|
|
@@ -20606,21 +21246,24 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20606
21246
|
params.push(...types);
|
|
20607
21247
|
}
|
|
20608
21248
|
if (q) {
|
|
20609
|
-
const pat =
|
|
20610
|
-
conditions.push("
|
|
21249
|
+
const pat = containsPattern(q);
|
|
21250
|
+
conditions.push(likeAny(["a.name", "a.sub"]));
|
|
20611
21251
|
params.push(pat, pat);
|
|
20612
21252
|
}
|
|
20613
21253
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
20614
21254
|
const sampleRows = this.mapAssetRows(
|
|
20615
|
-
|
|
20616
|
-
|
|
21255
|
+
allRows(
|
|
21256
|
+
this.db.prepare(
|
|
21257
|
+
`SELECT a.id, a.asset_type AS assetType, a.name, a.sub, a.description,
|
|
20617
21258
|
a.flags_json AS flagsJson, a.meta_json AS metaJson, a.trust,
|
|
20618
21259
|
a.tools_json AS toolsJson, coalesce(o.trust, a.trust) AS effectiveTrust
|
|
20619
21260
|
FROM inventory_asset a
|
|
20620
21261
|
LEFT JOIN mcp_trust_override o ON o.asset_id = a.id
|
|
20621
21262
|
${where}
|
|
20622
21263
|
ORDER BY a.name ASC`
|
|
20623
|
-
|
|
21264
|
+
),
|
|
21265
|
+
params
|
|
21266
|
+
)
|
|
20624
21267
|
);
|
|
20625
21268
|
const configRows = this.configAssetRows(q).filter(
|
|
20626
21269
|
(r) => !types || types.length === 0 || types.includes(r.assetType)
|
|
@@ -20629,14 +21272,17 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20629
21272
|
}
|
|
20630
21273
|
fetchAssetById(assetId) {
|
|
20631
21274
|
const rows = this.mapAssetRows(
|
|
20632
|
-
|
|
20633
|
-
|
|
21275
|
+
allRows(
|
|
21276
|
+
this.db.prepare(
|
|
21277
|
+
`SELECT a.id, a.asset_type AS assetType, a.name, a.sub, a.description,
|
|
20634
21278
|
a.flags_json AS flagsJson, a.meta_json AS metaJson, a.trust,
|
|
20635
21279
|
a.tools_json AS toolsJson, coalesce(o.trust, a.trust) AS effectiveTrust
|
|
20636
21280
|
FROM inventory_asset a
|
|
20637
21281
|
LEFT JOIN mcp_trust_override o ON o.asset_id = a.id
|
|
20638
21282
|
WHERE a.id = ?`
|
|
20639
|
-
|
|
21283
|
+
),
|
|
21284
|
+
[assetId]
|
|
21285
|
+
)
|
|
20640
21286
|
);
|
|
20641
21287
|
return rows[0] ?? this.configAssetRows().find((r) => r.id === assetId) ?? null;
|
|
20642
21288
|
}
|
|
@@ -20692,37 +21338,39 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20692
21338
|
return rows;
|
|
20693
21339
|
}
|
|
20694
21340
|
latestConfigScanId() {
|
|
20695
|
-
|
|
20696
|
-
`SELECT id FROM audit_events WHERE event_type = 'config_scan'
|
|
20697
|
-
ORDER BY started_at DESC, id DESC LIMIT 1`
|
|
20698
|
-
).get();
|
|
20699
|
-
return row?.id ?? null;
|
|
21341
|
+
return latestConfigScan(this.db)?.id ?? null;
|
|
20700
21342
|
}
|
|
20701
21343
|
fetchProjects(q) {
|
|
20702
21344
|
let sql = `SELECT id, url, name, attributes, last_seen AS lastSeen FROM source_project
|
|
20703
21345
|
WHERE ${WORKTREE_CHECKOUT_FILTER}`;
|
|
20704
21346
|
const params = [];
|
|
20705
21347
|
if (q) {
|
|
20706
|
-
const pat =
|
|
20707
|
-
sql +=
|
|
21348
|
+
const pat = containsPattern(q);
|
|
21349
|
+
sql += ` AND ${likeAny(["name", "url"])}`;
|
|
20708
21350
|
params.push(pat, pat);
|
|
20709
21351
|
}
|
|
20710
21352
|
sql += " ORDER BY name ASC";
|
|
20711
|
-
return this.db.prepare(sql)
|
|
21353
|
+
return allRows(this.db.prepare(sql), params);
|
|
20712
21354
|
}
|
|
20713
21355
|
fetchProjectById(projectId) {
|
|
20714
|
-
return
|
|
20715
|
-
|
|
20716
|
-
|
|
21356
|
+
return getRow(
|
|
21357
|
+
this.db.prepare(
|
|
21358
|
+
"SELECT id, url, name, attributes, last_seen AS lastSeen FROM source_project WHERE id = ?"
|
|
21359
|
+
),
|
|
21360
|
+
[projectId]
|
|
21361
|
+
) ?? null;
|
|
20717
21362
|
}
|
|
20718
21363
|
// The referenced projects in ONE `id IN (…)` fetch, keyed by id.
|
|
20719
21364
|
fetchProjectsByIds(projectIds) {
|
|
20720
21365
|
const map2 = /* @__PURE__ */ new Map();
|
|
20721
21366
|
if (projectIds.length === 0) return map2;
|
|
20722
|
-
const rows =
|
|
20723
|
-
|
|
21367
|
+
const rows = allRows(
|
|
21368
|
+
this.db.prepare(
|
|
21369
|
+
`SELECT id, url, name, attributes, last_seen AS lastSeen
|
|
20724
21370
|
FROM source_project WHERE id IN (${placeholders(projectIds.length)})`
|
|
20725
|
-
|
|
21371
|
+
),
|
|
21372
|
+
projectIds
|
|
21373
|
+
);
|
|
20726
21374
|
for (const r of rows) map2.set(r.id, r);
|
|
20727
21375
|
return map2;
|
|
20728
21376
|
}
|
|
@@ -20733,8 +21381,9 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20733
21381
|
projectAggregates(projectIds) {
|
|
20734
21382
|
const map2 = /* @__PURE__ */ new Map();
|
|
20735
21383
|
if (projectIds.length === 0) return map2;
|
|
20736
|
-
const rows =
|
|
20737
|
-
|
|
21384
|
+
const rows = allRows(
|
|
21385
|
+
this.db.prepare(
|
|
21386
|
+
`SELECT f.project_id AS projectId,
|
|
20738
21387
|
coalesce(o.access, f.default_access) AS eff,
|
|
20739
21388
|
count(*) AS n,
|
|
20740
21389
|
coalesce(sum(f.findings_count), 0) AS findings
|
|
@@ -20742,7 +21391,9 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20742
21391
|
LEFT JOIN file_access_override o ON o.project_id = f.project_id AND o.path = f.path
|
|
20743
21392
|
WHERE f.project_id IN (${placeholders(projectIds.length)})
|
|
20744
21393
|
GROUP BY f.project_id, eff`
|
|
20745
|
-
|
|
21394
|
+
),
|
|
21395
|
+
projectIds
|
|
21396
|
+
);
|
|
20746
21397
|
for (const r of rows) {
|
|
20747
21398
|
let agg = map2.get(r.projectId);
|
|
20748
21399
|
if (!agg) {
|
|
@@ -20780,37 +21431,52 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
20780
21431
|
fetchProjectFilesUnder(projectId, prefix) {
|
|
20781
21432
|
if (prefix === "") {
|
|
20782
21433
|
return this.mapFileRows(
|
|
20783
|
-
|
|
21434
|
+
allRows(
|
|
21435
|
+
this.db.prepare(this.fileSelect("f.project_id = ? ORDER BY f.path ASC")),
|
|
21436
|
+
[projectId]
|
|
21437
|
+
)
|
|
20784
21438
|
);
|
|
20785
21439
|
}
|
|
20786
21440
|
return this.mapFileRows(
|
|
20787
|
-
|
|
20788
|
-
this.
|
|
20789
|
-
|
|
21441
|
+
allRows(
|
|
21442
|
+
this.db.prepare(
|
|
21443
|
+
this.fileSelect("f.project_id = ? AND f.path LIKE ? ESCAPE '\\' ORDER BY f.path ASC")
|
|
21444
|
+
),
|
|
21445
|
+
[projectId, `${escapeLikePattern(prefix)}/%`]
|
|
21446
|
+
)
|
|
20790
21447
|
);
|
|
20791
21448
|
}
|
|
20792
21449
|
fetchProjectFilesSearch(projectId, q) {
|
|
20793
|
-
const pat =
|
|
21450
|
+
const pat = containsPattern(q);
|
|
20794
21451
|
return this.mapFileRows(
|
|
20795
|
-
|
|
20796
|
-
this.
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
|
|
21452
|
+
allRows(
|
|
21453
|
+
this.db.prepare(
|
|
21454
|
+
this.fileSelect(
|
|
21455
|
+
"f.project_id = ? AND (f.path LIKE ? ESCAPE '\\' OR f.name LIKE ? ESCAPE '\\') ORDER BY f.path ASC"
|
|
21456
|
+
)
|
|
21457
|
+
),
|
|
21458
|
+
[projectId, pat, pat]
|
|
21459
|
+
)
|
|
20800
21460
|
);
|
|
20801
21461
|
}
|
|
20802
21462
|
fetchProjectFilesBlocked(projectId) {
|
|
20803
21463
|
return this.mapFileRows(
|
|
20804
|
-
|
|
20805
|
-
this.
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
21464
|
+
allRows(
|
|
21465
|
+
this.db.prepare(
|
|
21466
|
+
this.fileSelect(
|
|
21467
|
+
"f.project_id = ? AND coalesce(o.access, f.default_access) = 'blocked' AND f.blocked_at IS NOT NULL"
|
|
21468
|
+
)
|
|
21469
|
+
),
|
|
21470
|
+
[projectId]
|
|
21471
|
+
)
|
|
20809
21472
|
);
|
|
20810
21473
|
}
|
|
20811
21474
|
fetchProjectFile(projectId, path) {
|
|
20812
21475
|
const rows = this.mapFileRows(
|
|
20813
|
-
|
|
21476
|
+
allRows(
|
|
21477
|
+
this.db.prepare(this.fileSelect("f.project_id = ? AND f.path = ?")),
|
|
21478
|
+
[projectId, path]
|
|
21479
|
+
)
|
|
20814
21480
|
);
|
|
20815
21481
|
return rows[0] ?? null;
|
|
20816
21482
|
}
|
|
@@ -20824,39 +21490,32 @@ var SqlitePoliciesRepository = class {
|
|
|
20824
21490
|
}
|
|
20825
21491
|
db;
|
|
20826
21492
|
readPolicies() {
|
|
20827
|
-
const rows = this.db.prepare("SELECT * FROM policies")
|
|
20828
|
-
const policies =
|
|
20829
|
-
|
|
20830
|
-
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
20837
|
-
|
|
20838
|
-
|
|
20839
|
-
|
|
20840
|
-
customKeywords
|
|
20841
|
-
})
|
|
20842
|
-
);
|
|
20843
|
-
} catch {
|
|
20844
|
-
}
|
|
20845
|
-
}
|
|
21493
|
+
const rows = allRows(this.db.prepare("SELECT * FROM policies"));
|
|
21494
|
+
const policies = mapRowsTolerant(rows, (row) => {
|
|
21495
|
+
const target = JSON.parse(row.target);
|
|
21496
|
+
const customKeywords = row.custom_keywords ? JSON.parse(row.custom_keywords) : void 0;
|
|
21497
|
+
return Policy.parse({
|
|
21498
|
+
id: row.id,
|
|
21499
|
+
scope: row.scope,
|
|
21500
|
+
target,
|
|
21501
|
+
action: row.action,
|
|
21502
|
+
enabled: intToBool(row.enabled),
|
|
21503
|
+
customKeywords
|
|
21504
|
+
});
|
|
21505
|
+
});
|
|
20846
21506
|
return Promise.resolve(policies);
|
|
20847
21507
|
}
|
|
20848
21508
|
// Seed one policy per bundled category from DEFAULT_ACTIONS so the
|
|
20849
21509
|
// detection-type config exists from first run. Only when the table is empty,
|
|
20850
21510
|
// so a user's edits are never clobbered.
|
|
20851
21511
|
seedDefaults() {
|
|
20852
|
-
const count = this.db
|
|
21512
|
+
const count = countScalar(this.db, "SELECT count(*) AS n FROM policies");
|
|
20853
21513
|
if (count > 0) return;
|
|
20854
21514
|
const stmt = this.db.prepare(
|
|
20855
21515
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
20856
21516
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)`
|
|
20857
21517
|
);
|
|
20858
|
-
this.db
|
|
20859
|
-
try {
|
|
21518
|
+
failOpenTransaction(this.db, () => {
|
|
20860
21519
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
20861
21520
|
stmt.run({
|
|
20862
21521
|
id: randomUUID4(),
|
|
@@ -20865,10 +21524,41 @@ var SqlitePoliciesRepository = class {
|
|
|
20865
21524
|
now: Date.now()
|
|
20866
21525
|
});
|
|
20867
21526
|
}
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
21527
|
+
});
|
|
21528
|
+
}
|
|
21529
|
+
// Insert-or-update the single global per-category policy row, keyed on the
|
|
21530
|
+
// existing uq_policies_scope_target unique index (scope, target). `action`
|
|
21531
|
+
// uses the SAME vocabulary seedDefaults writes (DEFAULT_ACTIONS' ActionTaken
|
|
21532
|
+
// values), so the runtime's resolveAction reads rows written by either path
|
|
21533
|
+
// identically. On conflict, `action`, `enabled`, and `updated_at` are updated;
|
|
21534
|
+
// `id` and `created_at` are left exactly as they were.
|
|
21535
|
+
upsertCategoryAction(category, action) {
|
|
21536
|
+
const now = Date.now();
|
|
21537
|
+
this.db.prepare(
|
|
21538
|
+
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
21539
|
+
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
21540
|
+
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
21541
|
+
).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
|
|
21542
|
+
}
|
|
21543
|
+
// Caps every global per-category policy currently set to block/redact down
|
|
21544
|
+
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
21545
|
+
// Returns the number of rows changed.
|
|
21546
|
+
capCategoryActions() {
|
|
21547
|
+
const info = this.db.prepare(
|
|
21548
|
+
`UPDATE policies SET action='warn', updated_at=:now
|
|
21549
|
+
WHERE scope='global' AND action IN ('block','redact')
|
|
21550
|
+
AND json_extract(target,'$.category') IS NOT NULL`
|
|
21551
|
+
).run({ now: Date.now() });
|
|
21552
|
+
return Number(info.changes);
|
|
21553
|
+
}
|
|
21554
|
+
// Read the current action for a single global per-category policy row, mirroring
|
|
21555
|
+
// upsertCategoryAction's category-lookup predicate. Returns undefined when no
|
|
21556
|
+
// row exists yet, so callers can distinguish an unset category from a set one.
|
|
21557
|
+
getCategoryAction(category) {
|
|
21558
|
+
const row = this.db.prepare(
|
|
21559
|
+
`SELECT action FROM policies WHERE scope='global' AND json_extract(target,'$.category') = :category`
|
|
21560
|
+
).get({ category });
|
|
21561
|
+
return row?.action;
|
|
20872
21562
|
}
|
|
20873
21563
|
};
|
|
20874
21564
|
|
|
@@ -20944,7 +21634,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
20944
21634
|
maxStampStmt;
|
|
20945
21635
|
/** Replace `projectId`'s tree with the scan's files. Caller wraps in a transaction. */
|
|
20946
21636
|
replaceForProject(projectId, scan2, now) {
|
|
20947
|
-
const
|
|
21637
|
+
const maxStamp = getRow(this.maxStampStmt, { projectId })?.maxStamp ?? 0;
|
|
20948
21638
|
const stamp = Math.max(now, maxStamp + 1);
|
|
20949
21639
|
for (const file2 of scan2.files) {
|
|
20950
21640
|
this.upsertStmt.run({
|
|
@@ -21027,7 +21717,7 @@ var SqliteResolutionsRepository = class {
|
|
|
21027
21717
|
}
|
|
21028
21718
|
/** The newest disposition recorded for a finding key, or undefined if none. */
|
|
21029
21719
|
latestByKey(key) {
|
|
21030
|
-
const row = this.latestStmt
|
|
21720
|
+
const row = getRow(this.latestStmt, { findingKey: key });
|
|
21031
21721
|
if (!row) return void 0;
|
|
21032
21722
|
return {
|
|
21033
21723
|
// Safe narrows: insertResolution enum-parses both columns on every write,
|
|
@@ -21044,7 +21734,7 @@ var SqliteResolutionsRepository = class {
|
|
|
21044
21734
|
* the CLI) surfaces for that file.
|
|
21045
21735
|
*/
|
|
21046
21736
|
openAtRestKeysForPath(path) {
|
|
21047
|
-
const rows = this.openAtRestStmt
|
|
21737
|
+
const rows = allRows(this.openAtRestStmt, { path });
|
|
21048
21738
|
return rows.map((r) => r.finding_key);
|
|
21049
21739
|
}
|
|
21050
21740
|
/**
|
|
@@ -21055,7 +21745,7 @@ var SqliteResolutionsRepository = class {
|
|
|
21055
21745
|
* resolution row (see scan.ts).
|
|
21056
21746
|
*/
|
|
21057
21747
|
resolvedAtRestKeysForPath(path) {
|
|
21058
|
-
const rows = this.resolvedAtRestStmt
|
|
21748
|
+
const rows = allRows(this.resolvedAtRestStmt, { path });
|
|
21059
21749
|
return rows.map((r) => r.finding_key);
|
|
21060
21750
|
}
|
|
21061
21751
|
};
|
|
@@ -21084,38 +21774,31 @@ var SqliteScanLedgerRepository = class {
|
|
|
21084
21774
|
// Previously scanned files under THIS ruleset, keyed by path. Rows from an
|
|
21085
21775
|
// older ruleset are simply absent, which reads as "never scanned".
|
|
21086
21776
|
entriesForRuleset(rulesetHash) {
|
|
21087
|
-
const rows = this.readStmt
|
|
21777
|
+
const rows = allRows(this.readStmt, {
|
|
21778
|
+
rulesetHash
|
|
21779
|
+
});
|
|
21088
21780
|
return new Map(rows.map((r) => [r.path, { mtime: r.mtime, contentHash: r.contentHash }]));
|
|
21089
21781
|
}
|
|
21090
21782
|
upsertEntries(entries) {
|
|
21091
21783
|
if (entries.length === 0) return;
|
|
21092
21784
|
const scannedAt = Date.now();
|
|
21093
|
-
|
|
21094
|
-
|
|
21095
|
-
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
scannedAt
|
|
21103
|
-
});
|
|
21104
|
-
}
|
|
21105
|
-
this.db.exec("COMMIT");
|
|
21106
|
-
} catch (err) {
|
|
21107
|
-
this.db.exec("ROLLBACK");
|
|
21108
|
-
throw err;
|
|
21785
|
+
failOpenTransaction(this.db, () => {
|
|
21786
|
+
for (const entry of entries) {
|
|
21787
|
+
this.upsertStmt.run({
|
|
21788
|
+
path: entry.path,
|
|
21789
|
+
mtime: entry.mtime,
|
|
21790
|
+
contentHash: entry.contentHash,
|
|
21791
|
+
rulesetHash: entry.rulesetHash,
|
|
21792
|
+
scannedAt
|
|
21793
|
+
});
|
|
21109
21794
|
}
|
|
21110
|
-
}
|
|
21111
|
-
}
|
|
21795
|
+
});
|
|
21112
21796
|
}
|
|
21113
21797
|
};
|
|
21114
21798
|
|
|
21115
21799
|
// ../../packages/persistence/src/repositories/security.ts
|
|
21116
21800
|
var DAY_MS4 = 864e5;
|
|
21117
21801
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
21118
|
-
var RANGE_DAYS = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
|
|
21119
21802
|
var ACTION_TO_KIND = {
|
|
21120
21803
|
block: "blocked",
|
|
21121
21804
|
redact: "redacted",
|
|
@@ -21130,8 +21813,14 @@ var SCAN_COVERAGE = [
|
|
|
21130
21813
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
21131
21814
|
{ provider: "api", coverage: 0, supported: false }
|
|
21132
21815
|
];
|
|
21816
|
+
var GRANULARITY = {
|
|
21817
|
+
"7d": "day",
|
|
21818
|
+
"30d": "day",
|
|
21819
|
+
"3m": "week",
|
|
21820
|
+
"6m": "week"
|
|
21821
|
+
};
|
|
21133
21822
|
function granularityFor(range) {
|
|
21134
|
-
return range
|
|
21823
|
+
return GRANULARITY[range];
|
|
21135
21824
|
}
|
|
21136
21825
|
function startOfUtcDay2(ms) {
|
|
21137
21826
|
return Math.floor(ms / DAY_MS4) * DAY_MS4;
|
|
@@ -21185,8 +21874,9 @@ var SqliteSecurityRepository = class {
|
|
|
21185
21874
|
// finding — its rn = 1 filter is also what makes the LEFT JOIN safe against
|
|
21186
21875
|
// double-counting a key that accumulated several append-only rows.
|
|
21187
21876
|
severitySummary() {
|
|
21188
|
-
const rows =
|
|
21189
|
-
|
|
21877
|
+
const rows = allRows(
|
|
21878
|
+
this.db.prepare(
|
|
21879
|
+
`SELECT f.severity AS severity,
|
|
21190
21880
|
COUNT(*) AS count,
|
|
21191
21881
|
SUM(CASE
|
|
21192
21882
|
WHEN e.kind != 'code_change' THEN 1
|
|
@@ -21205,7 +21895,8 @@ var SqliteSecurityRepository = class {
|
|
|
21205
21895
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
21206
21896
|
ON latest.finding_key = f.finding_key
|
|
21207
21897
|
GROUP BY f.severity`
|
|
21208
|
-
|
|
21898
|
+
)
|
|
21899
|
+
);
|
|
21209
21900
|
const byRow = new Map(rows.map((r) => [r.severity, r]));
|
|
21210
21901
|
const bySeverity = SEVERITIES.map((severity) => ({
|
|
21211
21902
|
severity,
|
|
@@ -21289,14 +21980,15 @@ var SqliteSecurityRepository = class {
|
|
|
21289
21980
|
const numBuckets = granularity === "day" ? lenDays : Math.ceil(lenDays / 7);
|
|
21290
21981
|
const now = this.now();
|
|
21291
21982
|
const windowStart = startOfUtcDay2(now) - (lenDays - 1) * DAY_MS4;
|
|
21292
|
-
const rows =
|
|
21293
|
-
|
|
21294
|
-
|
|
21295
|
-
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21983
|
+
const rows = allRows(
|
|
21984
|
+
this.db.prepare(
|
|
21985
|
+
// first_detected_at is the PRESERVED first-detection time (set once on a
|
|
21986
|
+
// finding's INSERT, never overwritten on the re-detection upsert), so MTTR
|
|
21987
|
+
// measures from first sighting — not the latest re-scan's event, whose
|
|
21988
|
+
// occurred_at the upsert overwrites onto findings.event_id. COALESCE onto
|
|
21989
|
+
// the parent event's occurred_at defends against any legacy/edge row the
|
|
21990
|
+
// backfill left null.
|
|
21991
|
+
`SELECT COALESCE(f.first_detected_at, e.occurred_at) AS first_detected_at, f.severity AS severity,
|
|
21300
21992
|
(
|
|
21301
21993
|
SELECT fr.status FROM finding_resolution fr
|
|
21302
21994
|
WHERE fr.finding_key = f.finding_key
|
|
@@ -21322,14 +22014,16 @@ var SqliteSecurityRepository = class {
|
|
|
21322
22014
|
WHERE fr.finding_key = f.finding_key
|
|
21323
22015
|
AND fr.resolved_at >= :windowStart
|
|
21324
22016
|
)`
|
|
21325
|
-
|
|
21326
|
-
|
|
21327
|
-
|
|
21328
|
-
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
|
|
21332
|
-
|
|
22017
|
+
// The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
|
|
22018
|
+
// any resolution activity at/after the window start — a row this method
|
|
22019
|
+
// ultimately counts has its LATEST resolution inside the window, which
|
|
22020
|
+
// implies such a row exists, so nothing wanted is dropped. The exact
|
|
22021
|
+
// latest-wins + status/method + window gate stays in JS below,
|
|
22022
|
+
// dialect-agnostic. Without this, a
|
|
22023
|
+
// 7d request evaluated the store's entire trackable-findings history.
|
|
22024
|
+
),
|
|
22025
|
+
{ windowStart }
|
|
22026
|
+
);
|
|
21333
22027
|
const sums = /* @__PURE__ */ new Map();
|
|
21334
22028
|
const counts = /* @__PURE__ */ new Map();
|
|
21335
22029
|
for (const r of rows) {
|
|
@@ -21359,8 +22053,9 @@ var SqliteSecurityRepository = class {
|
|
|
21359
22053
|
if (opts.kind === "user") return Promise.resolve({ range, items: [] });
|
|
21360
22054
|
const now = this.now();
|
|
21361
22055
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
21362
|
-
const rows =
|
|
21363
|
-
|
|
22056
|
+
const rows = allRows(
|
|
22057
|
+
this.db.prepare(
|
|
22058
|
+
`SELECT json_extract(e.metadata, '$.repo') AS repo, count(*) AS c
|
|
21364
22059
|
FROM findings f JOIN events e ON e.id = f.event_id
|
|
21365
22060
|
WHERE e.occurred_at >= :from AND e.occurred_at < :to
|
|
21366
22061
|
AND json_extract(e.metadata, '$.repo') IS NOT NULL
|
|
@@ -21368,7 +22063,9 @@ var SqliteSecurityRepository = class {
|
|
|
21368
22063
|
GROUP BY repo
|
|
21369
22064
|
ORDER BY c DESC, repo
|
|
21370
22065
|
LIMIT :limit`
|
|
21371
|
-
|
|
22066
|
+
),
|
|
22067
|
+
{ from, to: now, limit }
|
|
22068
|
+
);
|
|
21372
22069
|
const items = rows.map((r) => ({
|
|
21373
22070
|
id: `repo_${r.repo}`,
|
|
21374
22071
|
name: r.repo,
|
|
@@ -21390,8 +22087,9 @@ var SqliteSecurityRepository = class {
|
|
|
21390
22087
|
// resolutions.ts's openAtRestStmt accessor. Ordered by resolved_at DESC,
|
|
21391
22088
|
// capped at `limit`.
|
|
21392
22089
|
recentlyResolved(limit = 20) {
|
|
21393
|
-
const rows =
|
|
21394
|
-
|
|
22090
|
+
const rows = allRows(
|
|
22091
|
+
this.db.prepare(
|
|
22092
|
+
`SELECT f.finding_key AS finding_key,
|
|
21395
22093
|
f.rule_id AS rule_id,
|
|
21396
22094
|
f.severity AS severity,
|
|
21397
22095
|
json_extract(e.metadata, '$.filePath') AS path,
|
|
@@ -21425,7 +22123,9 @@ var SqliteSecurityRepository = class {
|
|
|
21425
22123
|
) IS NOT NULL
|
|
21426
22124
|
ORDER BY latest_resolved_at DESC
|
|
21427
22125
|
LIMIT :limit`
|
|
21428
|
-
|
|
22126
|
+
),
|
|
22127
|
+
{ limit }
|
|
22128
|
+
);
|
|
21429
22129
|
const items = rows.map((r) => ({
|
|
21430
22130
|
findingKey: r.finding_key,
|
|
21431
22131
|
ruleId: r.rule_id,
|
|
@@ -21442,12 +22142,15 @@ var SqliteSecurityRepository = class {
|
|
|
21442
22142
|
// epoch-millis timestamp. occurred_at is an INTEGER column, so the bounds stay
|
|
21443
22143
|
// numeric and the JS aggregations bucket/split on ms directly.
|
|
21444
22144
|
findingsInRange(fromMs, toMs) {
|
|
21445
|
-
const rows =
|
|
21446
|
-
|
|
22145
|
+
const rows = allRows(
|
|
22146
|
+
this.db.prepare(
|
|
22147
|
+
`SELECT e.occurred_at AS occurred_at, f.severity AS severity, f.action_taken AS action_taken
|
|
21447
22148
|
FROM findings f JOIN events e ON e.id = f.event_id
|
|
21448
22149
|
WHERE e.occurred_at >= :from AND e.occurred_at < :to
|
|
21449
22150
|
ORDER BY e.occurred_at`
|
|
21450
|
-
|
|
22151
|
+
),
|
|
22152
|
+
{ from: fromMs, to: toMs }
|
|
22153
|
+
);
|
|
21451
22154
|
return rows.map((r) => ({
|
|
21452
22155
|
occurredAt: r.occurred_at,
|
|
21453
22156
|
severity: r.severity,
|
|
@@ -21461,12 +22164,7 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
21461
22164
|
var KIND_ORDER = ["provider", "internal", "ip"];
|
|
21462
22165
|
var CALL_SITE_EMBED_CAP = 200;
|
|
21463
22166
|
function parseNetwork(networkJson) {
|
|
21464
|
-
|
|
21465
|
-
try {
|
|
21466
|
-
return JSON.parse(networkJson);
|
|
21467
|
-
} catch {
|
|
21468
|
-
return null;
|
|
21469
|
-
}
|
|
22167
|
+
return safeJson(networkJson, null);
|
|
21470
22168
|
}
|
|
21471
22169
|
function toEndpointSummary(row) {
|
|
21472
22170
|
return {
|
|
@@ -21553,27 +22251,39 @@ var SqliteSharesRepository = class {
|
|
|
21553
22251
|
}
|
|
21554
22252
|
db;
|
|
21555
22253
|
stats() {
|
|
21556
|
-
const
|
|
21557
|
-
const
|
|
21558
|
-
const
|
|
21559
|
-
const
|
|
21560
|
-
|
|
22254
|
+
const destinations = countScalar(this.db, "SELECT count(*) AS n FROM share_destination");
|
|
22255
|
+
const endpoints = countScalar(this.db, "SELECT count(*) AS n FROM share_endpoint");
|
|
22256
|
+
const callSites = countScalar(this.db, "SELECT count(*) AS n FROM share_call_site");
|
|
22257
|
+
const insecure = countScalar(
|
|
22258
|
+
this.db,
|
|
21561
22259
|
"SELECT count(DISTINCT destination_id) AS n FROM share_endpoint WHERE transport = 'http'"
|
|
21562
22260
|
);
|
|
21563
|
-
const needsReview =
|
|
22261
|
+
const needsReview = countScalar(
|
|
22262
|
+
this.db,
|
|
21564
22263
|
`SELECT count(DISTINCT d.id) AS n
|
|
21565
22264
|
FROM share_destination d
|
|
21566
22265
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id AND e.transport = 'http'
|
|
21567
22266
|
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL`
|
|
21568
22267
|
);
|
|
21569
|
-
const
|
|
21570
|
-
|
|
21571
|
-
|
|
21572
|
-
|
|
21573
|
-
const
|
|
21574
|
-
|
|
21575
|
-
|
|
21576
|
-
|
|
22268
|
+
const kindCounts = countBy(
|
|
22269
|
+
this.db,
|
|
22270
|
+
"SELECT kind AS k, count(*) AS n FROM share_destination GROUP BY kind"
|
|
22271
|
+
);
|
|
22272
|
+
const byKind = {
|
|
22273
|
+
provider: kindCounts.get("provider") ?? 0,
|
|
22274
|
+
internal: kindCounts.get("internal") ?? 0,
|
|
22275
|
+
ip: kindCounts.get("ip") ?? 0
|
|
22276
|
+
};
|
|
22277
|
+
const trustCounts = countBy(
|
|
22278
|
+
this.db,
|
|
22279
|
+
"SELECT trust AS k, count(*) AS n FROM share_destination GROUP BY trust"
|
|
22280
|
+
);
|
|
22281
|
+
const byTrust = {
|
|
22282
|
+
recognized: trustCounts.get("recognized") ?? 0,
|
|
22283
|
+
internal: trustCounts.get("internal") ?? 0,
|
|
22284
|
+
unverified: trustCounts.get("unverified") ?? 0,
|
|
22285
|
+
ip: trustCounts.get("ip") ?? 0
|
|
22286
|
+
};
|
|
21577
22287
|
return Promise.resolve({
|
|
21578
22288
|
destinations,
|
|
21579
22289
|
endpoints,
|
|
@@ -21689,7 +22399,7 @@ var SqliteSharesRepository = class {
|
|
|
21689
22399
|
}
|
|
21690
22400
|
let sql;
|
|
21691
22401
|
if (q) {
|
|
21692
|
-
const pattern =
|
|
22402
|
+
const pattern = containsPattern(q);
|
|
21693
22403
|
conditions.push(
|
|
21694
22404
|
`(d.name LIKE ? ESCAPE '\\' OR d.category LIKE ? ESCAPE '\\' OR e.url LIKE ? ESCAPE '\\'
|
|
21695
22405
|
OR c.project LIKE ? ESCAPE '\\' OR c.file LIKE ? ESCAPE '\\')`
|
|
@@ -21709,24 +22419,31 @@ var SqliteSharesRepository = class {
|
|
|
21709
22419
|
${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
21710
22420
|
ORDER BY d.created_at ASC, d.id ASC`;
|
|
21711
22421
|
}
|
|
21712
|
-
const rows =
|
|
22422
|
+
const rows = allRows(
|
|
22423
|
+
this.db.prepare(sql),
|
|
22424
|
+
params
|
|
22425
|
+
);
|
|
21713
22426
|
return rows.map((r) => this.mapDestRow(r));
|
|
21714
22427
|
}
|
|
21715
22428
|
fetchDestinationById(destinationId) {
|
|
21716
|
-
const row =
|
|
21717
|
-
|
|
22429
|
+
const row = getRow(
|
|
22430
|
+
this.db.prepare(
|
|
22431
|
+
`SELECT d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
|
|
21718
22432
|
d.network_json AS networkJson, d.last_seen AS lastSeenMs,
|
|
21719
22433
|
o.decision AS overrideDecision
|
|
21720
22434
|
FROM share_destination d
|
|
21721
22435
|
LEFT JOIN egress_decision_override o ON o.destination_id = d.id
|
|
21722
22436
|
WHERE d.id = ?`
|
|
21723
|
-
|
|
22437
|
+
),
|
|
22438
|
+
[destinationId]
|
|
22439
|
+
);
|
|
21724
22440
|
return row ? this.mapDestRow(row) : null;
|
|
21725
22441
|
}
|
|
21726
22442
|
fetchEndpoints(destinationIds) {
|
|
21727
22443
|
if (destinationIds.length === 0) return [];
|
|
21728
|
-
const rows =
|
|
21729
|
-
|
|
22444
|
+
const rows = allRows(
|
|
22445
|
+
this.db.prepare(
|
|
22446
|
+
`SELECT e.id, e.destination_id AS destinationId, e.method, e.transport, e.url,
|
|
21730
22447
|
e.template, e.data_class AS dataClass, e.last_seen AS lastSeenMs,
|
|
21731
22448
|
count(c.id) AS callSiteCount
|
|
21732
22449
|
FROM share_endpoint e
|
|
@@ -21734,7 +22451,9 @@ var SqliteSharesRepository = class {
|
|
|
21734
22451
|
WHERE e.destination_id IN (${placeholders(destinationIds.length)})
|
|
21735
22452
|
GROUP BY e.id
|
|
21736
22453
|
ORDER BY e.created_at ASC, e.id ASC`
|
|
21737
|
-
|
|
22454
|
+
),
|
|
22455
|
+
destinationIds
|
|
22456
|
+
);
|
|
21738
22457
|
return rows.map((r) => ({
|
|
21739
22458
|
id: r.id,
|
|
21740
22459
|
destinationId: r.destinationId,
|
|
@@ -21759,13 +22478,16 @@ var SqliteSharesRepository = class {
|
|
|
21759
22478
|
}
|
|
21760
22479
|
fetchCallSites(endpointIds) {
|
|
21761
22480
|
if (endpointIds.length === 0) return [];
|
|
21762
|
-
const rows =
|
|
21763
|
-
|
|
22481
|
+
const rows = allRows(
|
|
22482
|
+
this.db.prepare(
|
|
22483
|
+
`SELECT id, endpoint_id AS endpointId, project, file, line, snippet, dynamic, vendored,
|
|
21764
22484
|
project_id AS projectId
|
|
21765
22485
|
FROM share_call_site
|
|
21766
22486
|
WHERE endpoint_id IN (${placeholders(endpointIds.length)})
|
|
21767
22487
|
ORDER BY created_at ASC, id ASC`
|
|
21768
|
-
|
|
22488
|
+
),
|
|
22489
|
+
endpointIds
|
|
22490
|
+
);
|
|
21769
22491
|
return rows.map((r) => ({
|
|
21770
22492
|
id: r.id,
|
|
21771
22493
|
endpointId: r.endpointId,
|
|
@@ -21801,27 +22523,36 @@ var SqliteSourceProjectRepository = class {
|
|
|
21801
22523
|
upsert(input, now = Date.now()) {
|
|
21802
22524
|
const id = sourceProjectId(input.url);
|
|
21803
22525
|
const row = toSourceProjectRow(input, id, now);
|
|
21804
|
-
this.upsertStmt.run(
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
22526
|
+
this.upsertStmt.run(
|
|
22527
|
+
bindParams({
|
|
22528
|
+
id: row.id,
|
|
22529
|
+
url: row.url,
|
|
22530
|
+
name: row.name,
|
|
22531
|
+
attributes: row.attributes,
|
|
22532
|
+
firstSeen: row.firstSeen,
|
|
22533
|
+
lastSeen: row.lastSeen
|
|
22534
|
+
})
|
|
22535
|
+
);
|
|
21812
22536
|
return id;
|
|
21813
22537
|
}
|
|
21814
22538
|
findById(id) {
|
|
21815
|
-
return
|
|
22539
|
+
return getRow(
|
|
22540
|
+
this.db.prepare("SELECT * FROM source_project WHERE id = :id"),
|
|
22541
|
+
{
|
|
22542
|
+
id
|
|
22543
|
+
}
|
|
22544
|
+
);
|
|
21816
22545
|
}
|
|
21817
22546
|
// Distinct project names — a filter facet, served from the source_project
|
|
21818
22547
|
// table, never from the audit fact table.
|
|
21819
22548
|
distinctNames() {
|
|
21820
|
-
const rows =
|
|
21821
|
-
|
|
22549
|
+
const rows = allRows(
|
|
22550
|
+
this.db.prepare(
|
|
22551
|
+
`SELECT DISTINCT name FROM source_project
|
|
21822
22552
|
WHERE name IS NOT NULL
|
|
21823
22553
|
ORDER BY name`
|
|
21824
|
-
|
|
22554
|
+
)
|
|
22555
|
+
);
|
|
21825
22556
|
return rows.map((r) => r.name);
|
|
21826
22557
|
}
|
|
21827
22558
|
};
|
|
@@ -21840,8 +22571,7 @@ function hasLegacySampleRows(db) {
|
|
|
21840
22571
|
function purgeSampleData(db) {
|
|
21841
22572
|
try {
|
|
21842
22573
|
if (!hasLegacySampleRows(db)) return;
|
|
21843
|
-
db
|
|
21844
|
-
try {
|
|
22574
|
+
withTransaction(db, () => {
|
|
21845
22575
|
db.exec(
|
|
21846
22576
|
`DELETE FROM share_call_site WHERE endpoint_id IN (
|
|
21847
22577
|
SELECT e.id FROM share_endpoint e
|
|
@@ -21886,11 +22616,7 @@ function purgeSampleData(db) {
|
|
|
21886
22616
|
value TEXT NOT NULL
|
|
21887
22617
|
)`);
|
|
21888
22618
|
db.exec("DELETE FROM app_meta WHERE key LIKE 'sample_seeded:%'");
|
|
21889
|
-
|
|
21890
|
-
} catch (err) {
|
|
21891
|
-
db.exec("ROLLBACK");
|
|
21892
|
-
throw err;
|
|
21893
|
-
}
|
|
22619
|
+
});
|
|
21894
22620
|
} catch {
|
|
21895
22621
|
}
|
|
21896
22622
|
}
|
|
@@ -21909,7 +22635,7 @@ function openWithPragmas(file2) {
|
|
|
21909
22635
|
function backupLegacyStore(file2) {
|
|
21910
22636
|
const backup = `${file2}.legacy.${String(Date.now())}.bak`;
|
|
21911
22637
|
renameSync(file2, backup);
|
|
21912
|
-
for (const sidecar of
|
|
22638
|
+
for (const sidecar of walSidecars(file2)) {
|
|
21913
22639
|
if (existsSync(sidecar)) rmSync(sidecar);
|
|
21914
22640
|
}
|
|
21915
22641
|
return backup;
|
|
@@ -21922,9 +22648,8 @@ function openLocalDatabase(dir) {
|
|
|
21922
22648
|
db.close();
|
|
21923
22649
|
const backup = backupLegacyStore(file2);
|
|
21924
22650
|
db = openWithPragmas(file2);
|
|
21925
|
-
|
|
21926
|
-
`
|
|
21927
|
-
`
|
|
22651
|
+
akaWarn(
|
|
22652
|
+
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
21928
22653
|
);
|
|
21929
22654
|
}
|
|
21930
22655
|
applyMigrations(db);
|
|
@@ -21952,96 +22677,77 @@ function openLocalDatabase(dir) {
|
|
|
21952
22677
|
const configInventory = new SqliteConfigInventoryRepository(db);
|
|
21953
22678
|
policies.seedDefaults();
|
|
21954
22679
|
function recordCapture(event, detected) {
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
findings.insertFindings(detected, sessionId ? { sessionId } : {});
|
|
21961
|
-
db.exec("COMMIT");
|
|
21962
|
-
} catch (err) {
|
|
21963
|
-
db.exec("ROLLBACK");
|
|
21964
|
-
throw err;
|
|
21965
|
-
}
|
|
21966
|
-
} catch {
|
|
21967
|
-
}
|
|
22680
|
+
failOpenTransaction(db, () => {
|
|
22681
|
+
events.insertEvent(event);
|
|
22682
|
+
const sessionId = event.metadata?.sessionId;
|
|
22683
|
+
findings.insertFindings(detected, sessionId ? { sessionId } : {});
|
|
22684
|
+
});
|
|
21968
22685
|
}
|
|
21969
22686
|
function ensureInventory(ctx) {
|
|
21970
22687
|
const resolved = {};
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
if (ctx.harness) {
|
|
21977
|
-
resolved.harnessId = inventory.upsert(linkHost(ctx.harness, resolved.hostId), now);
|
|
21978
|
-
}
|
|
21979
|
-
resolved.accountId = inventory.upsert(
|
|
21980
|
-
linkHost(
|
|
21981
|
-
{
|
|
21982
|
-
objectType: "user",
|
|
21983
|
-
identityKey: "local",
|
|
21984
|
-
attributes: { source: "local" }
|
|
21985
|
-
},
|
|
21986
|
-
resolved.hostId
|
|
21987
|
-
),
|
|
21988
|
-
now
|
|
21989
|
-
);
|
|
21990
|
-
if (ctx.project) resolved.sourceProjectId = sourceProject.upsert(ctx.project, now);
|
|
21991
|
-
db.exec("COMMIT");
|
|
21992
|
-
} catch (err) {
|
|
21993
|
-
db.exec("ROLLBACK");
|
|
21994
|
-
throw err;
|
|
22688
|
+
const committed = failOpenTransaction(db, () => {
|
|
22689
|
+
const now = Date.now();
|
|
22690
|
+
if (ctx.host) resolved.hostId = inventory.upsert(ctx.host, now);
|
|
22691
|
+
if (ctx.harness) {
|
|
22692
|
+
resolved.harnessId = inventory.upsert(linkHost(ctx.harness, resolved.hostId), now);
|
|
21995
22693
|
}
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
21999
|
-
|
|
22694
|
+
resolved.accountId = inventory.upsert(
|
|
22695
|
+
linkHost(
|
|
22696
|
+
{
|
|
22697
|
+
objectType: "user",
|
|
22698
|
+
identityKey: "local",
|
|
22699
|
+
attributes: { source: "local" }
|
|
22700
|
+
},
|
|
22701
|
+
resolved.hostId
|
|
22702
|
+
),
|
|
22703
|
+
now
|
|
22704
|
+
);
|
|
22705
|
+
if (ctx.project) resolved.sourceProjectId = sourceProject.upsert(ctx.project, now);
|
|
22706
|
+
});
|
|
22707
|
+
return committed ? resolved : {};
|
|
22000
22708
|
}
|
|
22001
22709
|
function recordConfigScan(record2) {
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
}
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
confidence: finding2.confidence
|
|
22023
|
-
});
|
|
22024
|
-
}
|
|
22025
|
-
db.exec("COMMIT");
|
|
22026
|
-
} catch (err) {
|
|
22027
|
-
db.exec("ROLLBACK");
|
|
22028
|
-
throw err;
|
|
22710
|
+
failOpenTransaction(db, () => {
|
|
22711
|
+
const now = isoToEpochMillis(record2.scanEvent.startedAt);
|
|
22712
|
+
for (const item of record2.items) inventory.upsert(item, now);
|
|
22713
|
+
auditEvents.insertAuditEvent(record2.scanEvent);
|
|
22714
|
+
const definitionIds = /* @__PURE__ */ new Map();
|
|
22715
|
+
for (const def of record2.definitions ?? []) {
|
|
22716
|
+
definitionIds.set(`${def.ruleId}@${def.version}`, inspectionDefinitions.upsert(def));
|
|
22717
|
+
}
|
|
22718
|
+
for (const finding2 of record2.findings ?? []) {
|
|
22719
|
+
const definitionId = definitionIds.get(`${finding2.ruleId}@${finding2.version}`);
|
|
22720
|
+
if (!definitionId) continue;
|
|
22721
|
+
inspectionFindings.insertFinding({
|
|
22722
|
+
id: randomUUID8(),
|
|
22723
|
+
auditEventId: record2.scanEvent.id,
|
|
22724
|
+
inspectionDefinitionId: definitionId,
|
|
22725
|
+
span: finding2.span,
|
|
22726
|
+
maskedMatch: finding2.maskedMatch,
|
|
22727
|
+
actionTaken: finding2.actionTaken,
|
|
22728
|
+
confidence: finding2.confidence
|
|
22729
|
+
});
|
|
22029
22730
|
}
|
|
22030
|
-
}
|
|
22031
|
-
}
|
|
22731
|
+
});
|
|
22032
22732
|
}
|
|
22033
22733
|
function recordProjectFiles(projectId, scan2) {
|
|
22034
22734
|
if (scan2.files.length === 0) return;
|
|
22735
|
+
failOpenTransaction(db, () => {
|
|
22736
|
+
projectFiles.replaceForProject(projectId, scan2, Date.now());
|
|
22737
|
+
});
|
|
22738
|
+
}
|
|
22739
|
+
async function transaction(fn) {
|
|
22740
|
+
db.exec("BEGIN");
|
|
22035
22741
|
try {
|
|
22036
|
-
|
|
22742
|
+
const result = await fn();
|
|
22743
|
+
db.exec("COMMIT");
|
|
22744
|
+
return result;
|
|
22745
|
+
} catch (err) {
|
|
22037
22746
|
try {
|
|
22038
|
-
projectFiles.replaceForProject(projectId, scan2, Date.now());
|
|
22039
|
-
db.exec("COMMIT");
|
|
22040
|
-
} catch (err) {
|
|
22041
22747
|
db.exec("ROLLBACK");
|
|
22042
|
-
|
|
22748
|
+
} catch {
|
|
22043
22749
|
}
|
|
22044
|
-
|
|
22750
|
+
throw err;
|
|
22045
22751
|
}
|
|
22046
22752
|
}
|
|
22047
22753
|
function reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
@@ -22060,8 +22766,7 @@ function openLocalDatabase(dir) {
|
|
|
22060
22766
|
patternWin: `${escapeLikePattern(headPosix.split("/").join("\\"))}\\\\.claude\\\\worktrees\\\\%`
|
|
22061
22767
|
});
|
|
22062
22768
|
if (stale.length === 0) return;
|
|
22063
|
-
db
|
|
22064
|
-
try {
|
|
22769
|
+
withTransaction(db, () => {
|
|
22065
22770
|
for (const { id } of stale) {
|
|
22066
22771
|
db.prepare(
|
|
22067
22772
|
"UPDATE audit_events SET source_project_id = :canonicalId WHERE source_project_id = :id"
|
|
@@ -22073,11 +22778,7 @@ function openLocalDatabase(dir) {
|
|
|
22073
22778
|
db.prepare("DELETE FROM project_file WHERE project_id = :id").run({ id });
|
|
22074
22779
|
db.prepare("DELETE FROM source_project WHERE id = :id").run({ id });
|
|
22075
22780
|
}
|
|
22076
|
-
|
|
22077
|
-
} catch (err) {
|
|
22078
|
-
db.exec("ROLLBACK");
|
|
22079
|
-
throw err;
|
|
22080
|
-
}
|
|
22781
|
+
});
|
|
22081
22782
|
} catch {
|
|
22082
22783
|
}
|
|
22083
22784
|
}
|
|
@@ -22119,6 +22820,7 @@ function openLocalDatabase(dir) {
|
|
|
22119
22820
|
purgeSampleData: () => {
|
|
22120
22821
|
purgeSampleData(db);
|
|
22121
22822
|
},
|
|
22823
|
+
transaction,
|
|
22122
22824
|
close: () => {
|
|
22123
22825
|
db.close();
|
|
22124
22826
|
}
|
|
@@ -22211,12 +22913,27 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
22211
22913
|
}
|
|
22212
22914
|
}
|
|
22213
22915
|
function readJson(file2) {
|
|
22916
|
+
let text;
|
|
22214
22917
|
try {
|
|
22215
|
-
|
|
22216
|
-
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
22918
|
+
text = readFileSync2(file2, "utf8");
|
|
22217
22919
|
} catch {
|
|
22218
22920
|
return null;
|
|
22219
22921
|
}
|
|
22922
|
+
return parseJsonObject(text) ?? null;
|
|
22923
|
+
}
|
|
22924
|
+
|
|
22925
|
+
// ../../packages/persistence/src/warn-era-cap.ts
|
|
22926
|
+
import { existsSync as existsSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
22927
|
+
import { join as join5 } from "path";
|
|
22928
|
+
var MARKER = "warn-era-capped";
|
|
22929
|
+
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
22930
|
+
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
22931
|
+
const marker = join5(dataDir2, MARKER);
|
|
22932
|
+
if (existsSync2(marker)) return { capped: 0, skipped: "already-run" };
|
|
22933
|
+
const capped = db.policies.capCategoryActions();
|
|
22934
|
+
writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
|
|
22935
|
+
`, { mode: DATA_FILE_MODE });
|
|
22936
|
+
return { capped };
|
|
22220
22937
|
}
|
|
22221
22938
|
|
|
22222
22939
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
@@ -22291,23 +23008,30 @@ function resolveProviderSafe() {
|
|
|
22291
23008
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
22292
23009
|
import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "fs";
|
|
22293
23010
|
import { homedir as homedir2 } from "os";
|
|
22294
|
-
import { basename as basename3, join as
|
|
23011
|
+
import { basename as basename3, join as join7 } from "path";
|
|
23012
|
+
|
|
23013
|
+
// ../../packages/detections/src/escape-regexp.ts
|
|
23014
|
+
function escapeRegExp(value) {
|
|
23015
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23016
|
+
}
|
|
23017
|
+
|
|
23018
|
+
// ../../packages/detections/src/matchers/limits.ts
|
|
23019
|
+
var MAX_MATCHES_PER_RULE = 1e4;
|
|
22295
23020
|
|
|
22296
23021
|
// ../../packages/detections/src/matchers/keyword.ts
|
|
22297
23022
|
var KeywordMatcher2 = class {
|
|
22298
23023
|
match(text, rule) {
|
|
22299
23024
|
if (rule.matcher.type !== "keyword") return [];
|
|
22300
23025
|
const { keywords, caseSensitive } = rule.matcher;
|
|
22301
|
-
const haystack = caseSensitive ? text : text.toLowerCase();
|
|
22302
23026
|
const spans = [];
|
|
22303
23027
|
for (const kw of keywords) {
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
spans.push({ start:
|
|
22310
|
-
|
|
23028
|
+
if (kw.length === 0) continue;
|
|
23029
|
+
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
23030
|
+
const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
|
|
23031
|
+
let m;
|
|
23032
|
+
while ((m = re.exec(text)) !== null) {
|
|
23033
|
+
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
23034
|
+
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
22311
23035
|
}
|
|
22312
23036
|
}
|
|
22313
23037
|
return spans;
|
|
@@ -22315,7 +23039,6 @@ var KeywordMatcher2 = class {
|
|
|
22315
23039
|
};
|
|
22316
23040
|
|
|
22317
23041
|
// ../../packages/detections/src/matchers/regex.ts
|
|
22318
|
-
var MAX_MATCHES_PER_RULE = 1e4;
|
|
22319
23042
|
var RegexMatcher2 = class {
|
|
22320
23043
|
match(text, rule) {
|
|
22321
23044
|
if (rule.matcher.type !== "regex") return [];
|
|
@@ -22401,9 +23124,6 @@ function registerPack(pack) {
|
|
|
22401
23124
|
function getLoadedRules() {
|
|
22402
23125
|
return [...packs.values()].flatMap((p) => p.rules);
|
|
22403
23126
|
}
|
|
22404
|
-
function escapeRegExp(value) {
|
|
22405
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22406
|
-
}
|
|
22407
23127
|
function isCorroborated(candidate, candidates, text) {
|
|
22408
23128
|
const req = candidate.rule.requiresNearby;
|
|
22409
23129
|
if (!req) return true;
|
|
@@ -23267,7 +23987,6 @@ var db_table_name_default = {
|
|
|
23267
23987
|
"SELECT * FROM ",
|
|
23268
23988
|
"SELECT COUNT(*) FROM ",
|
|
23269
23989
|
"INSERT INTO ",
|
|
23270
|
-
"UPDATE ",
|
|
23271
23990
|
"DELETE FROM ",
|
|
23272
23991
|
"CREATE TABLE ",
|
|
23273
23992
|
"ALTER TABLE ",
|
|
@@ -24755,8 +25474,8 @@ function maskText(text) {
|
|
|
24755
25474
|
}
|
|
24756
25475
|
|
|
24757
25476
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
24758
|
-
import { existsSync as
|
|
24759
|
-
import { basename as basename2, dirname, isAbsolute, join as
|
|
25477
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
|
|
25478
|
+
import { basename as basename2, dirname, isAbsolute, join as join6, sep as sep2 } from "path";
|
|
24760
25479
|
function resolveRepoIdentity(cwd) {
|
|
24761
25480
|
try {
|
|
24762
25481
|
const root = findGitRoot(cwd);
|
|
@@ -24806,7 +25525,7 @@ function resolveGitBranch(cwd) {
|
|
|
24806
25525
|
try {
|
|
24807
25526
|
const root = findGitRoot(cwd);
|
|
24808
25527
|
if (!root) return void 0;
|
|
24809
|
-
const dotGit =
|
|
25528
|
+
const dotGit = join6(root, ".git");
|
|
24810
25529
|
let gitdir;
|
|
24811
25530
|
try {
|
|
24812
25531
|
gitdir = statSync(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
|
|
@@ -24814,7 +25533,7 @@ function resolveGitBranch(cwd) {
|
|
|
24814
25533
|
return void 0;
|
|
24815
25534
|
}
|
|
24816
25535
|
if (gitdir === void 0) return void 0;
|
|
24817
|
-
const head = safeRead(
|
|
25536
|
+
const head = safeRead(join6(gitdir, "HEAD"));
|
|
24818
25537
|
if (!head) return void 0;
|
|
24819
25538
|
return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
|
|
24820
25539
|
} catch {
|
|
@@ -24824,37 +25543,37 @@ function resolveGitBranch(cwd) {
|
|
|
24824
25543
|
function resolveWorktreeGitdir(root, dotGitFile) {
|
|
24825
25544
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
|
|
24826
25545
|
if (!target) return void 0;
|
|
24827
|
-
return isAbsolute(target) ? target :
|
|
25546
|
+
return isAbsolute(target) ? target : join6(root, target);
|
|
24828
25547
|
}
|
|
24829
25548
|
function findGitRoot(start) {
|
|
24830
25549
|
let dir = start;
|
|
24831
25550
|
for (; ; ) {
|
|
24832
|
-
if (
|
|
25551
|
+
if (existsSync3(join6(dir, ".git"))) return dir;
|
|
24833
25552
|
const parent = dirname(dir);
|
|
24834
25553
|
if (parent === dir) return void 0;
|
|
24835
25554
|
dir = parent;
|
|
24836
25555
|
}
|
|
24837
25556
|
}
|
|
24838
25557
|
function resolveGitContext(root) {
|
|
24839
|
-
const dotGit =
|
|
25558
|
+
const dotGit = join6(root, ".git");
|
|
24840
25559
|
try {
|
|
24841
25560
|
if (statSync(dotGit).isDirectory()) {
|
|
24842
|
-
return { configPath:
|
|
25561
|
+
return { configPath: join6(dotGit, "config"), headRoot: root };
|
|
24843
25562
|
}
|
|
24844
25563
|
} catch {
|
|
24845
25564
|
return void 0;
|
|
24846
25565
|
}
|
|
24847
25566
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
24848
25567
|
if (!target) return void 0;
|
|
24849
|
-
const gitdir = isAbsolute(target) ? target :
|
|
24850
|
-
if (
|
|
24851
|
-
return { configPath:
|
|
25568
|
+
const gitdir = isAbsolute(target) ? target : join6(root, target);
|
|
25569
|
+
if (existsSync3(join6(gitdir, "config"))) {
|
|
25570
|
+
return { configPath: join6(gitdir, "config"), headRoot: root };
|
|
24852
25571
|
}
|
|
24853
|
-
const commonRaw = safeRead(
|
|
25572
|
+
const commonRaw = safeRead(join6(gitdir, "commondir"))?.trim();
|
|
24854
25573
|
if (!commonRaw) return void 0;
|
|
24855
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
25574
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join6(gitdir, commonRaw);
|
|
24856
25575
|
const headRoot = basename2(commonGitDir) === ".git" ? dirname(commonGitDir) : root;
|
|
24857
|
-
return { configPath:
|
|
25576
|
+
return { configPath: join6(commonGitDir, "config"), headRoot };
|
|
24858
25577
|
}
|
|
24859
25578
|
function safeRead(path) {
|
|
24860
25579
|
try {
|
|
@@ -24920,31 +25639,31 @@ function resolveConfigInventory(input) {
|
|
|
24920
25639
|
};
|
|
24921
25640
|
try {
|
|
24922
25641
|
const home = input.homeDir ?? homedir2();
|
|
24923
|
-
const claudeDir =
|
|
25642
|
+
const claudeDir = join7(home, ".claude");
|
|
24924
25643
|
const repo = resolveRepoIdentity(input.cwd);
|
|
24925
25644
|
const repoIdentity = repo?.url ?? input.cwd;
|
|
24926
25645
|
const projectSource = `project:${repoIdentity}`;
|
|
24927
|
-
collectSettingsHooks(scan2,
|
|
24928
|
-
collectSettingsHooks(scan2,
|
|
24929
|
-
collectSettingsHooks(scan2,
|
|
25646
|
+
collectSettingsHooks(scan2, join7(claudeDir, "settings.json"), "user");
|
|
25647
|
+
collectSettingsHooks(scan2, join7(input.cwd, ".claude", "settings.json"), "project");
|
|
25648
|
+
collectSettingsHooks(scan2, join7(input.cwd, ".claude", "settings.local.json"), "local");
|
|
24930
25649
|
const projectOrigin = { scope: "project", project: repoIdentity };
|
|
24931
|
-
collectMcpFile(scan2,
|
|
24932
|
-
collectUserClaudeJson(scan2,
|
|
24933
|
-
collectMcpFile(scan2,
|
|
24934
|
-
collectMcpFile(scan2,
|
|
24935
|
-
collectMcpFile(scan2,
|
|
25650
|
+
collectMcpFile(scan2, join7(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
|
|
25651
|
+
collectUserClaudeJson(scan2, join7(home, ".claude.json"), input.cwd, repoIdentity);
|
|
25652
|
+
collectMcpFile(scan2, join7(claudeDir, "settings.json"), { scope: "user" });
|
|
25653
|
+
collectMcpFile(scan2, join7(input.cwd, ".claude", "settings.json"), projectOrigin);
|
|
25654
|
+
collectMcpFile(scan2, join7(input.cwd, ".claude", "settings.local.json"), {
|
|
24936
25655
|
scope: "local",
|
|
24937
25656
|
project: repoIdentity
|
|
24938
25657
|
});
|
|
24939
25658
|
collectConfigFiles(scan2, claudeDir, input.cwd);
|
|
24940
|
-
collectSkillsDir(scan2,
|
|
24941
|
-
collectSkillsDir(scan2,
|
|
25659
|
+
collectSkillsDir(scan2, join7(claudeDir, "skills"), { source: "local", scope: "user" });
|
|
25660
|
+
collectSkillsDir(scan2, join7(input.cwd, ".claude", "skills"), {
|
|
24942
25661
|
source: projectSource,
|
|
24943
25662
|
scope: "project"
|
|
24944
25663
|
});
|
|
24945
25664
|
collectInstalledPlugins(scan2, claudeDir);
|
|
24946
25665
|
collectMarketplaceSkills(scan2, claudeDir);
|
|
24947
|
-
collectSkillsDir(scan2,
|
|
25666
|
+
collectSkillsDir(scan2, join7(input.cwd, "skills"), { source: projectSource, scope: "project" });
|
|
24948
25667
|
scan2.skills = dedupeSkills(scan2.skills);
|
|
24949
25668
|
scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
|
|
24950
25669
|
} catch (err) {
|
|
@@ -25073,7 +25792,7 @@ function projectEntryFor(projects, cwd) {
|
|
|
25073
25792
|
return void 0;
|
|
25074
25793
|
}
|
|
25075
25794
|
function collectPluginManifestMcp(scan2, installPath, origin) {
|
|
25076
|
-
const manifestPath =
|
|
25795
|
+
const manifestPath = join7(installPath, ".claude-plugin", "plugin.json");
|
|
25077
25796
|
const raw = readOptional(manifestPath);
|
|
25078
25797
|
if (raw === void 0) return;
|
|
25079
25798
|
try {
|
|
@@ -25081,7 +25800,7 @@ function collectPluginManifestMcp(scan2, installPath, origin) {
|
|
|
25081
25800
|
if (typeof parsed !== "object" || parsed === null) return;
|
|
25082
25801
|
const declared = parsed.mcpServers;
|
|
25083
25802
|
if (typeof declared === "string" && declared.length > 0) {
|
|
25084
|
-
collectMcpFile(scan2,
|
|
25803
|
+
collectMcpFile(scan2, join7(installPath, declared), origin, { recordErrors: true });
|
|
25085
25804
|
} else {
|
|
25086
25805
|
collectMcpObject(scan2, declared, manifestPath, origin);
|
|
25087
25806
|
}
|
|
@@ -25098,14 +25817,14 @@ var SETTINGS_KEY_LABELS = [
|
|
|
25098
25817
|
["statusLine", "status line"]
|
|
25099
25818
|
];
|
|
25100
25819
|
function collectConfigFiles(scan2, claudeDir, cwd) {
|
|
25101
|
-
settingsConfigFile(scan2,
|
|
25102
|
-
settingsConfigFile(scan2,
|
|
25103
|
-
settingsConfigFile(scan2,
|
|
25104
|
-
memoryConfigFile(scan2,
|
|
25105
|
-
memoryConfigFile(scan2,
|
|
25106
|
-
mcpJsonConfigFile(scan2,
|
|
25107
|
-
dirConfigFile(scan2,
|
|
25108
|
-
dirConfigFile(scan2,
|
|
25820
|
+
settingsConfigFile(scan2, join7(claudeDir, "settings.json"), "user", "User settings");
|
|
25821
|
+
settingsConfigFile(scan2, join7(cwd, ".claude", "settings.json"), "project", "Project settings");
|
|
25822
|
+
settingsConfigFile(scan2, join7(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
|
|
25823
|
+
memoryConfigFile(scan2, join7(claudeDir, "CLAUDE.md"), "user", "User memory");
|
|
25824
|
+
memoryConfigFile(scan2, join7(cwd, "CLAUDE.md"), "project", "Project memory");
|
|
25825
|
+
mcpJsonConfigFile(scan2, join7(cwd, ".mcp.json"));
|
|
25826
|
+
dirConfigFile(scan2, join7(cwd, ".claude", "commands"), "Slash commands", "command");
|
|
25827
|
+
dirConfigFile(scan2, join7(cwd, ".claude", "agents"), "Subagents", "subagent");
|
|
25109
25828
|
}
|
|
25110
25829
|
function configFileEntry(path, scope, kind) {
|
|
25111
25830
|
try {
|
|
@@ -25180,7 +25899,7 @@ function countMarkdownFiles(dir, depth) {
|
|
|
25180
25899
|
let count = 0;
|
|
25181
25900
|
for (const dirent of readdirSync(dir, { withFileTypes: true })) {
|
|
25182
25901
|
if (dirent.name.startsWith(".")) continue;
|
|
25183
|
-
if (dirent.isDirectory()) count += countMarkdownFiles(
|
|
25902
|
+
if (dirent.isDirectory()) count += countMarkdownFiles(join7(dir, dirent.name), depth + 1);
|
|
25184
25903
|
else if (dirent.name.endsWith(".md")) count += 1;
|
|
25185
25904
|
}
|
|
25186
25905
|
return count;
|
|
@@ -25193,7 +25912,7 @@ function collectSkillsDir(scan2, dir, origin) {
|
|
|
25193
25912
|
return;
|
|
25194
25913
|
}
|
|
25195
25914
|
for (const name of names) {
|
|
25196
|
-
const skillFile =
|
|
25915
|
+
const skillFile = join7(dir, name, "SKILL.md");
|
|
25197
25916
|
try {
|
|
25198
25917
|
const raw = readOptional(skillFile);
|
|
25199
25918
|
if (raw === void 0) continue;
|
|
@@ -25202,7 +25921,7 @@ function collectSkillsDir(scan2, dir, origin) {
|
|
|
25202
25921
|
name: front.name ?? name,
|
|
25203
25922
|
source: origin.source,
|
|
25204
25923
|
scope: origin.scope,
|
|
25205
|
-
location:
|
|
25924
|
+
location: join7(dir, name),
|
|
25206
25925
|
updatedAt: statSync2(skillFile).mtime.toISOString()
|
|
25207
25926
|
};
|
|
25208
25927
|
const version2 = front.version ?? origin.defaultVersion;
|
|
@@ -25233,7 +25952,7 @@ function parseFrontmatter(raw) {
|
|
|
25233
25952
|
return out;
|
|
25234
25953
|
}
|
|
25235
25954
|
function collectInstalledPlugins(scan2, claudeDir) {
|
|
25236
|
-
const manifestPath =
|
|
25955
|
+
const manifestPath = join7(claudeDir, "plugins", "installed_plugins.json");
|
|
25237
25956
|
const raw = readOptional(manifestPath);
|
|
25238
25957
|
if (raw === void 0) return;
|
|
25239
25958
|
let plugins;
|
|
@@ -25258,7 +25977,7 @@ function collectInstalledPlugins(scan2, claudeDir) {
|
|
|
25258
25977
|
if (typeof installPath !== "string" || seen.has(installPath)) continue;
|
|
25259
25978
|
seen.add(installPath);
|
|
25260
25979
|
const version2 = install.version;
|
|
25261
|
-
const hooksPath =
|
|
25980
|
+
const hooksPath = join7(installPath, "hooks", "hooks.json");
|
|
25262
25981
|
const hooksRaw = readOptional(hooksPath);
|
|
25263
25982
|
if (hooksRaw !== void 0) {
|
|
25264
25983
|
try {
|
|
@@ -25278,22 +25997,22 @@ function collectInstalledPlugins(scan2, claudeDir) {
|
|
|
25278
25997
|
}
|
|
25279
25998
|
const origin = { source: marketplace, scope: "plugin", pluginName };
|
|
25280
25999
|
if (typeof version2 === "string") origin.defaultVersion = version2;
|
|
25281
|
-
collectSkillsDir(scan2,
|
|
26000
|
+
collectSkillsDir(scan2, join7(installPath, "skills"), origin);
|
|
25282
26001
|
const mcpOrigin = { scope: "plugin", pluginName, marketplace };
|
|
25283
|
-
collectMcpFile(scan2,
|
|
26002
|
+
collectMcpFile(scan2, join7(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
|
|
25284
26003
|
collectPluginManifestMcp(scan2, installPath, mcpOrigin);
|
|
25285
26004
|
}
|
|
25286
26005
|
}
|
|
25287
26006
|
}
|
|
25288
26007
|
function collectMarketplaceSkills(scan2, claudeDir) {
|
|
25289
|
-
for (const mp of readMarketplaces(
|
|
26008
|
+
for (const mp of readMarketplaces(join7(claudeDir, "plugins", "known_marketplaces.json"))) {
|
|
25290
26009
|
if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
|
|
25291
|
-
collectSkillsDir(scan2,
|
|
26010
|
+
collectSkillsDir(scan2, join7(mp.installLocation, "skills"), {
|
|
25292
26011
|
source: mp.name,
|
|
25293
26012
|
scope: "plugin"
|
|
25294
26013
|
});
|
|
25295
|
-
collectPluginSkillDirs(scan2,
|
|
25296
|
-
collectPluginSkillDirs(scan2,
|
|
26014
|
+
collectPluginSkillDirs(scan2, join7(mp.installLocation, "plugins"), mp.name);
|
|
26015
|
+
collectPluginSkillDirs(scan2, join7(mp.installLocation, "external_plugins"), mp.name);
|
|
25297
26016
|
}
|
|
25298
26017
|
}
|
|
25299
26018
|
function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
|
|
@@ -25304,7 +26023,7 @@ function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
|
|
|
25304
26023
|
return;
|
|
25305
26024
|
}
|
|
25306
26025
|
for (const plugin of plugins) {
|
|
25307
|
-
collectSkillsDir(scan2,
|
|
26026
|
+
collectSkillsDir(scan2, join7(pluginsDir, plugin, "skills"), {
|
|
25308
26027
|
source: marketplace,
|
|
25309
26028
|
scope: "plugin",
|
|
25310
26029
|
pluginName: plugin
|
|
@@ -25413,22 +26132,22 @@ function resolveInventoryContext(input) {
|
|
|
25413
26132
|
}
|
|
25414
26133
|
|
|
25415
26134
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
25416
|
-
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as
|
|
25417
|
-
import { join as
|
|
26135
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
26136
|
+
import { join as join8 } from "path";
|
|
25418
26137
|
var SESSION_START_MARKER = "session-start-last";
|
|
25419
26138
|
function claimSessionStart(dataDir2, sessionId) {
|
|
25420
26139
|
return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
|
|
25421
26140
|
}
|
|
25422
26141
|
function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
25423
26142
|
if (!sessionId) return true;
|
|
25424
|
-
const path =
|
|
26143
|
+
const path = join8(dataDir2, marker);
|
|
25425
26144
|
try {
|
|
25426
26145
|
if (readFileSync5(path, "utf8") === sessionId) return false;
|
|
25427
26146
|
} catch {
|
|
25428
26147
|
}
|
|
25429
26148
|
try {
|
|
25430
26149
|
mkdirSync3(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
25431
|
-
|
|
26150
|
+
writeFileSync4(path, sessionId, { mode: DATA_FILE_MODE });
|
|
25432
26151
|
} catch {
|
|
25433
26152
|
}
|
|
25434
26153
|
return true;
|
|
@@ -25436,8 +26155,8 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
|
25436
26155
|
|
|
25437
26156
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
25438
26157
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
25439
|
-
import { existsSync as
|
|
25440
|
-
import { basename as basename4, join as
|
|
26158
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
26159
|
+
import { basename as basename4, join as join9, relative, sep as sep3 } from "path";
|
|
25441
26160
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
25442
26161
|
".git",
|
|
25443
26162
|
"node_modules",
|
|
@@ -25457,7 +26176,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
25457
26176
|
var MAX_FILES = 2e4;
|
|
25458
26177
|
function readIgnoreLayer(dir) {
|
|
25459
26178
|
try {
|
|
25460
|
-
const content = readFileSync6(
|
|
26179
|
+
const content = readFileSync6(join9(dir, ".gitignore"), "utf8");
|
|
25461
26180
|
return { base: dir, matcher: (0, import_ignore.default)().add(content) };
|
|
25462
26181
|
} catch {
|
|
25463
26182
|
return void 0;
|
|
@@ -25535,10 +26254,10 @@ function resolveProjectFiles(cwd) {
|
|
|
25535
26254
|
const layer = readIgnoreLayer(dir);
|
|
25536
26255
|
const dirLayers = layer ? [...layers, layer] : layers;
|
|
25537
26256
|
for (const entry of dirents) {
|
|
25538
|
-
const fullPath =
|
|
26257
|
+
const fullPath = join9(dir, entry.name);
|
|
25539
26258
|
if (entry.isDirectory()) {
|
|
25540
26259
|
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, fullPath, true)) continue;
|
|
25541
|
-
if (
|
|
26260
|
+
if (existsSync4(join9(fullPath, ".git"))) continue;
|
|
25542
26261
|
if (visit2(fullPath, dirLayers)) return true;
|
|
25543
26262
|
continue;
|
|
25544
26263
|
}
|
|
@@ -25575,18 +26294,21 @@ function resolveProjectFiles(cwd) {
|
|
|
25575
26294
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
25576
26295
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
25577
26296
|
|
|
26297
|
+
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
26298
|
+
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
26299
|
+
|
|
25578
26300
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
25579
|
-
import { mkdirSync as mkdirSync4, statSync as statSync3, writeFileSync as
|
|
25580
|
-
import { join as
|
|
26301
|
+
import { mkdirSync as mkdirSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
26302
|
+
import { join as join10 } from "path";
|
|
25581
26303
|
function throttled(dataDir2, markerName, windowMs) {
|
|
25582
|
-
const marker =
|
|
26304
|
+
const marker = join10(dataDir2, markerName);
|
|
25583
26305
|
try {
|
|
25584
26306
|
if (Date.now() - statSync3(marker).mtimeMs < windowMs) return true;
|
|
25585
26307
|
} catch {
|
|
25586
26308
|
}
|
|
25587
26309
|
try {
|
|
25588
26310
|
mkdirSync4(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
25589
|
-
|
|
26311
|
+
writeFileSync5(marker, String(Date.now()), { mode: DATA_FILE_MODE });
|
|
25590
26312
|
} catch {
|
|
25591
26313
|
}
|
|
25592
26314
|
return false;
|
|
@@ -25786,6 +26508,14 @@ var StandaloneDataGateway = class {
|
|
|
25786
26508
|
sweepTerminalExceptions(retentionMs) {
|
|
25787
26509
|
return this.db.exceptions.sweepTerminal(retentionMs);
|
|
25788
26510
|
}
|
|
26511
|
+
// The warn-era enforcement cap, standalone-only store maintenance invoked
|
|
26512
|
+
// from SessionStart, not part of the DataGateway port. Returns the number
|
|
26513
|
+
// of block/redact rows capped to warn (0 for a redact-policy store or an
|
|
26514
|
+
// already-capped one).
|
|
26515
|
+
capWarnEraEnforcement(policyMode) {
|
|
26516
|
+
const { capped } = capWarnEraEnforcementOnce(this.db, policyMode, this.dataDir);
|
|
26517
|
+
return { capped };
|
|
26518
|
+
}
|
|
25789
26519
|
// One project-file scan → the local project_file tree (one transaction inside
|
|
25790
26520
|
// the LocalDatabase, fail-open there). Like the sweep above, this is
|
|
25791
26521
|
// NOT part of the DataGateway port: the file tree is a local-store read model.
|
|
@@ -25908,6 +26638,15 @@ async function handleSessionStart(input, config2 = loadConfig()) {
|
|
|
25908
26638
|
await gateway.sweepTerminalExceptions(EXCEPTION_RETENTION_MS);
|
|
25909
26639
|
} catch {
|
|
25910
26640
|
}
|
|
26641
|
+
try {
|
|
26642
|
+
const { capped } = gateway.capWarnEraEnforcement(config2.settings.policy);
|
|
26643
|
+
if (capped > 0) {
|
|
26644
|
+
process.stderr.write(
|
|
26645
|
+
'AKA: the global "warn only" handling was retired; your existing block/redact categories were kept at warn. Re-run /aka:setup to adopt per-category enforcement.\n'
|
|
26646
|
+
);
|
|
26647
|
+
}
|
|
26648
|
+
} catch {
|
|
26649
|
+
}
|
|
25911
26650
|
try {
|
|
25912
26651
|
if (resolved.sourceProjectId) {
|
|
25913
26652
|
const filesScan = resolveProjectFiles(input.cwd);
|
|
@@ -26001,7 +26740,7 @@ function buildSessionRoot(sessionId, input, ctx, resolved, provider, branch) {
|
|
|
26001
26740
|
|
|
26002
26741
|
// src/history/reconcile-trigger.ts
|
|
26003
26742
|
import { spawn } from "child_process";
|
|
26004
|
-
import { dirname as dirname2, join as
|
|
26743
|
+
import { dirname as dirname2, join as join12 } from "path";
|
|
26005
26744
|
import { fileURLToPath } from "url";
|
|
26006
26745
|
|
|
26007
26746
|
// src/history/tail.ts
|
|
@@ -26013,9 +26752,9 @@ import {
|
|
|
26013
26752
|
openSync,
|
|
26014
26753
|
readFileSync as readFileSync7,
|
|
26015
26754
|
readSync,
|
|
26016
|
-
writeFileSync as
|
|
26755
|
+
writeFileSync as writeFileSync6
|
|
26017
26756
|
} from "fs";
|
|
26018
|
-
import { join as
|
|
26757
|
+
import { join as join11 } from "path";
|
|
26019
26758
|
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
|
|
26020
26759
|
function safeSessionId(sessionId) {
|
|
26021
26760
|
if (SAFE_SESSION_ID.test(sessionId) && sessionId !== "." && sessionId !== "..") {
|
|
@@ -26032,7 +26771,7 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
|
|
|
26032
26771
|
const marker = `${RECONCILE_MARKER_PREFIX}-${safeSessionId(sessionId)}`;
|
|
26033
26772
|
if (throttled(dataDir2, marker, RECONCILE_THROTTLE_MS)) return;
|
|
26034
26773
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
26035
|
-
const child = spawn(process.execPath, [
|
|
26774
|
+
const child = spawn(process.execPath, [join12(here, "reconcile.js"), sessionId, transcriptPath], {
|
|
26036
26775
|
detached: true,
|
|
26037
26776
|
stdio: "ignore"
|
|
26038
26777
|
});
|