@akasecurity/ai-tc-claude-code 0.9.12 → 0.9.13

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.
@@ -20482,7 +20482,17 @@ var UNPRICEABLE_PROVIDERS = Object.freeze([
20482
20482
  "gateway",
20483
20483
  "unknown",
20484
20484
  "cli",
20485
- "api"
20485
+ "api",
20486
+ // The browser extension's native host records these as `llm_call.provider`
20487
+ // for a web-chat turn — the web tool id, deliberately never the vendor id
20488
+ // (`openai`/`anthropic`) the session root carries. Subscription traffic
20489
+ // burns rate-limit budget, not dollar credits, and listing them here is
20490
+ // what keeps that true structurally: a later maintainer who wants to price
20491
+ // web-chat traffic at API rates has to delete this entry first, and meet
20492
+ // the reason on the way, rather than quietly adding one to
20493
+ // PROVIDER_PLATFORM.
20494
+ "chatgpt",
20495
+ "claude-ai"
20486
20496
  ]);
20487
20497
  function platformForProvider(provider) {
20488
20498
  return PROVIDER_PLATFORM.get(provider.trim().toLowerCase()) ?? null;
@@ -20625,7 +20635,12 @@ var HARNESS = {
20625
20635
  ClaudeDesktop: "claudedesktop",
20626
20636
  ChatGpt: "chatgpt",
20627
20637
  ClaudeAi: "claudeai",
20628
- Api: "api"
20638
+ Api: "api",
20639
+ // Not a coding assistant a person drives — an in-process SDK embedded in an
20640
+ // application, so it has no IDE/CLI/desktop/web surface of its own. Carries
20641
+ // the same id as its SOURCE_TOOL counterpart, unlike every capture-side tool
20642
+ // whose wire spelling differs from its display spelling.
20643
+ AiTcSdk: "ai-tc-sdk"
20629
20644
  };
20630
20645
  var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
20631
20646
  var SOURCE_TOOL = {
@@ -20641,9 +20656,15 @@ var SOURCE_TOOL = {
20641
20656
  // whose tool could not be identified both render through the read side's
20642
20657
  // miss path rather than as a harness of their own.
20643
20658
  Cli: "cli",
20644
- Unknown: "unknown"
20659
+ Unknown: "unknown",
20660
+ // The wire id an in-process, request-path SDK stamps on its own structural
20661
+ // rows (`request_decision`) — never a capture of prompt/response/tool text,
20662
+ // since the SDK sits in front of a model call rather than inside a coding
20663
+ // assistant's own hook contract.
20664
+ AiTcSdk: "ai-tc-sdk"
20645
20665
  };
20646
20666
  var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
20667
+ var WebSourceTool = SourceTool.extract(["ChatGpt", "ClaudeAi"]);
20647
20668
  var TOOL_TO_HARNESS = {
20648
20669
  [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
20649
20670
  [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
@@ -20652,7 +20673,12 @@ var TOOL_TO_HARNESS = {
20652
20673
  [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
20653
20674
  [SOURCE_TOOL.Codex]: HARNESS.Codex,
20654
20675
  [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
20655
- [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
20676
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi,
20677
+ // Wire and display id are the same string here, but the row still belongs:
20678
+ // both vocabularies carry the `AiTcSdk` member, and the join is exactly
20679
+ // their intersection — leaving a shared member out would read as an
20680
+ // uninstrumented tool on both surfaces, which this one is not.
20681
+ [SOURCE_TOOL.AiTcSdk]: HARNESS.AiTcSdk
20656
20682
  };
20657
20683
  function harnessFromTool(tool) {
20658
20684
  return (Object.hasOwn(TOOL_TO_HARNESS, tool) ? TOOL_TO_HARNESS[tool] : void 0) ?? tool;
@@ -20689,7 +20715,8 @@ var FindingProvider = Harness.extract([
20689
20715
  "ClaudeAi",
20690
20716
  "Codex",
20691
20717
  "Antigravity",
20692
- "Api"
20718
+ "Api",
20719
+ "AiTcSdk"
20693
20720
  ]).meta({ id: "FindingProvider" });
20694
20721
  var FindingCategory = external_exports.enum([
20695
20722
  "secret",
@@ -21066,18 +21093,41 @@ var AuditEventType = external_exports.enum([
21066
21093
  // 'tool_call' is the reconciler's structural row for every call, while
21067
21094
  // 'tool_use' exists only where a hook enforced against the arguments.
21068
21095
  "tool_use",
21069
- // One row per model REFUSAL: a switch onto a prohibited model that was
21070
- // denied, or a turn refused because the session was already running on one.
21071
- // A structural row like the ones above rather than a capture — it carries
21072
- // the model that was refused and nothing the user typed, because what is
21073
- // worth recording about a governance decision is the decision, and prompt
21074
- // text is the thing this product exists to keep from travelling.
21096
+ // One row per model REFUSAL, across all four seams a prohibited model can be
21097
+ // stopped at: a switch onto it, a turn already running on it, a subagent
21098
+ // spawn asking for it, or a request-path refusal an embedded request-path
21099
+ // SDK makes in-process before the call leaves the application. Which seam
21100
+ // rides `attributes.refusal_seam`, never this member name. A structural row
21101
+ // like the ones above rather than a capture — it carries the model that was
21102
+ // refused and nothing the user typed, because what is worth recording about
21103
+ // a governance decision is the decision, and prompt text is the thing this
21104
+ // product exists to keep from travelling.
21075
21105
  "model_refusal",
21106
+ // One row per request-path DECISION: a policy check an embedded request-path
21107
+ // SDK performs in-process before a model call leaves the application, or
21108
+ // against that call's non-streamed response. A structural row like
21109
+ // 'model_refusal' rather than a capture — content-free in the same way:
21110
+ // which side, which seam, what action and which field are decided rides
21111
+ // `attributes`, never this member name, and the matched text itself never
21112
+ // travels.
21113
+ //
21114
+ // A prohibited-model refusal on the request path is deliberately NOT this
21115
+ // member: it stays 'model_refusal' with `refusal_seam: 'request'`, so it
21116
+ // shares one bucket with the plugin's switch/turn/spawn refusals rather
21117
+ // than splitting one governance concept across two event types. This
21118
+ // member carries every OTHER request-path decision.
21119
+ "request_decision",
21076
21120
  // One row per config-inventory scan, hung off the session root. It is the
21077
21121
  // fact the posture inspection findings reference (findings require an
21078
21122
  // audit_event_id), and its started_at is the "scanned Nm ago" the read
21079
21123
  // surface renders.
21080
- "config_scan"
21124
+ "config_scan",
21125
+ // One row per reported browser-extension capture status, hung off the web
21126
+ // session root. The durable home of what one tab's network interception
21127
+ // is doing — a write-through of the native host's in-memory tracker, so a
21128
+ // second process (aka extension status) and a restarted host both have
21129
+ // somewhere to read it back from.
21130
+ "capture_status"
21081
21131
  ]).meta({ id: "AuditEventType" });
21082
21132
  var AttributeBag = external_exports.record(external_exports.string(), external_exports.unknown());
21083
21133
  var HostAttributes = external_exports.object({
@@ -21237,6 +21287,20 @@ var CaptureAttributes = external_exports.object({
21237
21287
  // repeated rather than referenced because a store reader opens this file.
21238
21288
  redact_degraded_to: ActionTaken.optional()
21239
21289
  }).catchall(external_exports.unknown());
21290
+ var CaptureStatusAttributes = external_exports.object({
21291
+ source_tool: external_exports.string().optional(),
21292
+ patched: external_exports.boolean().optional(),
21293
+ live: external_exports.boolean().optional(),
21294
+ blind: external_exports.boolean().optional(),
21295
+ sends_seen_dom: external_exports.number().int().nonnegative().optional(),
21296
+ exchanges_seen_net: external_exports.number().int().nonnegative().optional(),
21297
+ parse_failures: external_exports.number().int().nonnegative().optional(),
21298
+ unparsed_bodies: external_exports.number().int().nonnegative().optional(),
21299
+ shape_misses: external_exports.array(external_exports.string()).optional(),
21300
+ conversation_endpoints: external_exports.number().int().nonnegative().optional(),
21301
+ closed: external_exports.boolean().optional(),
21302
+ enforcement: external_exports.string().optional()
21303
+ }).catchall(external_exports.unknown());
21240
21304
  var ToolCallInspection = external_exports.object({
21241
21305
  ruleId: external_exports.string().min(1),
21242
21306
  ruleName: external_exports.string(),
@@ -22096,6 +22160,30 @@ var AttachedCredential = external_exports.object({
22096
22160
  keyPrefix: external_exports.string().min(1).max(16).optional(),
22097
22161
  mintedAt: external_exports.iso.datetime().optional()
22098
22162
  });
22163
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
22164
+ function unsafeEndpointReason(endpoint) {
22165
+ let parsed2;
22166
+ try {
22167
+ parsed2 = new URL(endpoint);
22168
+ } catch {
22169
+ return "unparseable";
22170
+ }
22171
+ if (parsed2.username !== "" || parsed2.password !== "") return "userinfo";
22172
+ if (parsed2.search !== "" || parsed2.hash !== "") return "query-or-fragment";
22173
+ if (parsed2.protocol === "https:") return null;
22174
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname) ? null : "insecure";
22175
+ }
22176
+ function isSafeEndpoint(endpoint) {
22177
+ return unsafeEndpointReason(endpoint) === null;
22178
+ }
22179
+ function originOnly(endpoint) {
22180
+ try {
22181
+ const parsed2 = new URL(endpoint);
22182
+ return `${parsed2.protocol}//${parsed2.host}`;
22183
+ } catch {
22184
+ return "(unparseable endpoint)";
22185
+ }
22186
+ }
22099
22187
  var MAX_DATE_MS = 253402300799999;
22100
22188
  var MAX_INT4 = 2147483647;
22101
22189
  var StorePosturePack = external_exports.object({
@@ -22250,6 +22338,11 @@ var RemoteFailureKind = external_exports.enum([
22250
22338
  "rejected",
22251
22339
  "unreachable"
22252
22340
  ]);
22341
+ var ControlPlaneFailure = RemoteFailureKind.extract([
22342
+ "unauthorized",
22343
+ "forbidden",
22344
+ "unreachable"
22345
+ ]);
22253
22346
  var AttachDeviceRequest = external_exports.object({
22254
22347
  // This machine's own continuity id, so re-attaching ROTATES the credential
22255
22348
  // on one machine record instead of producing a second one. Client-minted
@@ -22789,7 +22882,12 @@ var EventMetadata = external_exports.object({
22789
22882
  // in — set by the browser extension's network capture so a stored `response`
22790
22883
  // row can be joined to the `llm_call` leaf describing the same turn. Absent
22791
22884
  // on every other capture path, which has no such id.
22792
- messageId: external_exports.string().optional(),
22885
+ //
22886
+ // Non-empty for the reason WebExchange.messageId is: it is the join key, and
22887
+ // a blank one matches no `llm_call` leaf. That refusal reaches only the
22888
+ // places an event is PARSED; the local write path types the event and parses
22889
+ // nothing, which is why `toCaptureAttributes` omits a blank one separately.
22890
+ messageId: external_exports.string().min(1).optional(),
22793
22891
  conversationId: external_exports.string().optional(),
22794
22892
  // How long THIS capture's inspection blocked its caller, in whole
22795
22893
  // milliseconds — the plugin's own added latency, NOT the LLM call it sat in
@@ -23631,6 +23729,85 @@ function policyIdIsReversible(policyId) {
23631
23729
  var DEFAULT_ACTIONS = Object.fromEntries(
23632
23730
  DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
23633
23731
  );
23732
+ function ruleCategoryMap(wireRules, localRules, compiledRules) {
23733
+ const map2 = /* @__PURE__ */ new Map();
23734
+ for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
23735
+ for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
23736
+ for (const rule of compiledRules) map2.set(rule.id, rule.category);
23737
+ return map2;
23738
+ }
23739
+ function policyKey(policy) {
23740
+ return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
23741
+ }
23742
+ function floorFor(policy, categoryByRuleId) {
23743
+ const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
23744
+ return category === void 0 ? null : DEFAULT_ACTIONS[category];
23745
+ }
23746
+ function strongerOf(a, b) {
23747
+ if (a === null) return b;
23748
+ if (b === null) return a;
23749
+ return strongerAction(a, b);
23750
+ }
23751
+ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
23752
+ const merged = /* @__PURE__ */ new Map();
23753
+ const disabled = [];
23754
+ const remoteCategoryAction = /* @__PURE__ */ new Map();
23755
+ for (const policy of remotePolicies) {
23756
+ if (!policy.enabled) continue;
23757
+ if (!("category" in policy.target)) continue;
23758
+ if (remoteCategoryAction.has(policy.target.category)) continue;
23759
+ const floor = floorFor(policy, categoryByRuleId);
23760
+ remoteCategoryAction.set(
23761
+ policy.target.category,
23762
+ floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
23763
+ );
23764
+ }
23765
+ for (const policy of localPolicies) {
23766
+ if (!policy.enabled) {
23767
+ disabled.push(policy);
23768
+ continue;
23769
+ }
23770
+ const key = policyKey(policy);
23771
+ if (merged.has(key)) continue;
23772
+ let remoteFloor = null;
23773
+ if ("ruleId" in policy.target) {
23774
+ const category = categoryByRuleId.get(policy.target.ruleId);
23775
+ if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
23776
+ }
23777
+ merged.set(
23778
+ key,
23779
+ remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
23780
+ );
23781
+ }
23782
+ const localCategoryAction = /* @__PURE__ */ new Map();
23783
+ for (const policy of merged.values()) {
23784
+ if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
23785
+ }
23786
+ for (const policy of remotePolicies) {
23787
+ if (!policy.enabled) {
23788
+ disabled.push(policy);
23789
+ continue;
23790
+ }
23791
+ const key = policyKey(policy);
23792
+ const floor = floorFor(policy, categoryByRuleId);
23793
+ let localFloor = null;
23794
+ if ("ruleId" in policy.target) {
23795
+ const category = categoryByRuleId.get(policy.target.ruleId);
23796
+ if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
23797
+ }
23798
+ const effectiveFloor = strongerOf(floor, localFloor);
23799
+ const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
23800
+ const existing = merged.get(key);
23801
+ if (existing === void 0) {
23802
+ merged.set(key, clamped);
23803
+ continue;
23804
+ }
23805
+ if (actionRank(clamped.action) > actionRank(existing.action)) {
23806
+ merged.set(key, clamped);
23807
+ }
23808
+ }
23809
+ return [...merged.values(), ...disabled];
23810
+ }
23634
23811
  var BUILTIN_POLICIES = Object.fromEntries(
23635
23812
  KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
23636
23813
  );
@@ -23863,6 +24040,18 @@ var HistorySyncConsent = external_exports.object({
23863
24040
  payloadVersion: external_exports.number().int().positive(),
23864
24041
  endpoint: external_exports.string()
23865
24042
  });
24043
+ var WebChatCaptureConsent = external_exports.object({
24044
+ acknowledgedAt: external_exports.iso.datetime(),
24045
+ version: external_exports.number().int().positive()
24046
+ });
24047
+ var WebChatResponseCapture = external_exports.enum(["with-findings", "always", "never"]);
24048
+ var WebChatCapture = external_exports.object({
24049
+ responses: WebChatResponseCapture.default("with-findings"),
24050
+ account: external_exports.boolean().default(false),
24051
+ // Absent until granted. Presence alone does not authorize anything — see
24052
+ // isWebChatCaptureConsentValid.
24053
+ consent: WebChatCaptureConsent.optional()
24054
+ });
23866
24055
  var BODY_RETENTION_DEFAULT_DAYS = 30;
23867
24056
  var BodyRetention = external_exports.object({
23868
24057
  enabled: external_exports.boolean().default(false),
@@ -23921,6 +24110,16 @@ var WorkspaceSettings = external_exports.object({
23921
24110
  // both widenings. Absent until granted, and a grant for a different endpoint
23922
24111
  // or an older payload no longer counts.
23923
24112
  historySyncConsent: HistorySyncConsent.optional(),
24113
+ // What the browser extension may record from a web chat, and the grant that
24114
+ // authorizes it. Absent until the user answers: recording something that was
24115
+ // never recorded before is never an assumed grant on upgrade, so the whole
24116
+ // block is optional rather than defaulted in. What an absent block means is
24117
+ // webChatCaptureOf's answer, in one place.
24118
+ //
24119
+ // Enforcement is NOT gated on this. A machine that has never answered still
24120
+ // blocks, redacts and warns on what a user sends; the grant covers what is
24121
+ // written down.
24122
+ webChatCapture: WebChatCapture.optional(),
23924
24123
  // Local body expiry (see BodyRetention). Off until switched on; expiring a
23925
24124
  // body never removes the row or its findings.
23926
24125
  bodyRetention: BodyRetention.default({
@@ -24028,7 +24227,10 @@ function toCaptureAttributes(event) {
24028
24227
  // `.catchall(z.unknown())` carries the long tail.
24029
24228
  ...metadata?.model !== void 0 ? { model: metadata.model } : {},
24030
24229
  ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {},
24031
- ...metadata?.messageId !== void 0 ? { message_id: metadata.messageId } : {},
24230
+ // A blank id is omitted rather than stored: it is a join key and `''` joins
24231
+ // nothing. This runs on the local write path, which types the event but
24232
+ // never parses it, so EventMetadata's own `.min(1)` does not reach here.
24233
+ ...metadata?.messageId !== void 0 && metadata.messageId !== "" ? { message_id: metadata.messageId } : {},
24032
24234
  ...metadata?.conversationId !== void 0 ? { conversation_id: metadata.conversationId } : {}
24033
24235
  };
24034
24236
  }
@@ -24402,12 +24604,14 @@ var RecommendedActionIdParam = external_exports.object({ id: external_exports.st
24402
24604
  // ../../packages/schema/src/zod/settings-action.ts
24403
24605
  var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
24404
24606
  var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
24607
+ var WebChatCaptureConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "WebChatCaptureConsentChoice" });
24405
24608
  var SaveSettingsInput = external_exports.object({
24406
24609
  historicalAccess: external_exports.string(),
24407
24610
  modelJudgeConsent: ModelJudgeConsentChoice,
24408
24611
  historySyncConsent: HistorySyncConsentChoice,
24409
24612
  vaultConsent: external_exports.string(),
24410
24613
  vaultInlineReveal: external_exports.string(),
24614
+ webChatCaptureConsent: WebChatCaptureConsentChoice,
24411
24615
  // Widened to `string` like its neighbours rather than typed as
24412
24616
  // `RedactFallback`, on this module's own layering rule: shape here, VALUE at
24413
24617
  // the call site, so the domain check receives the type it was written for.
@@ -24622,11 +24826,24 @@ var WebExchange = external_exports.object({
24622
24826
  turnIndex: external_exports.number().int().nonnegative().optional(),
24623
24827
  toolCalls: external_exports.array(WebToolCall).default([]),
24624
24828
  // Absent when the adapter recovered no text. Capped by the caller at
24625
- // RESPONSE_TEXT_MAX_BYTES; `truncated` records that the cap was reached, so a
24626
- // short capture is never mistaken for a short reply.
24829
+ // RESPONSE_TEXT_MAX_BYTES, so a short capture is never mistaken for a short
24830
+ // reply.
24627
24831
  responseText: external_exports.string().optional(),
24832
+ // The stored text is short of the reply. It does NOT say which of the two
24833
+ // ceilings on this path cut it: the caller applies its own cap on the raw
24834
+ // bytes it reads off the wire, which can be reached by a stream whose
24835
+ // recovered text stays well under RESPONSE_TEXT_MAX_BYTES, and applies that
24836
+ // one to the text. A reader cannot tell them apart, and nothing downstream
24837
+ // should branch as though it could.
24628
24838
  truncated: external_exports.boolean().default(false)
24629
24839
  });
24840
+ var WebEnforcementState = external_exports.enum([
24841
+ "watching",
24842
+ "composer-only",
24843
+ "button-only",
24844
+ "unattached",
24845
+ "unknown"
24846
+ ]);
24630
24847
  var RESPONSE_TEXT_MAX_BYTES = 2 * 1024 * 1024;
24631
24848
  var WebCaptureStatus = external_exports.object({
24632
24849
  patched: external_exports.boolean(),
@@ -24638,8 +24855,66 @@ var WebCaptureStatus = external_exports.object({
24638
24855
  unparsedBodies: external_exports.number().int().nonnegative(),
24639
24856
  // The adapter-declared JSON key paths that were absent from a real payload —
24640
24857
  // the earliest signal that a site's contract moved.
24641
- shapeMisses: external_exports.array(external_exports.string()).default([])
24642
- });
24858
+ shapeMisses: external_exports.array(external_exports.string()).default([]),
24859
+ // How many `kind: 'conversation'` endpoints the reporting tab's adapter
24860
+ // compiled. Zero means this build declares none for the site, so observing
24861
+ // nothing is the design rather than a fault — the one fact that separates a
24862
+ // site nobody has surveyed yet from one whose contract moved. Defaulted so a
24863
+ // build predating the field is read as declaring nothing rather than refused.
24864
+ conversationEndpoints: external_exports.number().int().nonnegative().default(0),
24865
+ // The document that sent this report is going away. The bridge sets it on
24866
+ // its `pagehide` report and nowhere else.
24867
+ //
24868
+ // A property of the REPORT rather than of capture health, which is why
24869
+ // nothing in `deriveWebCaptureState` reads it and why it stays out of the
24870
+ // bridge's own report signature — a closing tab's last word must not be
24871
+ // suppressed for carrying the same health as the report before it. What
24872
+ // reads it is the per-site fold: a document that said it was unloading stops
24873
+ // voting on the site's state, so the reload the `blind` remediation asks for
24874
+ // can actually clear the verdict it was shown. A document that dies without
24875
+ // sending one is covered by CAPTURE_STATUS_DOCUMENT_QUIET_MS instead.
24876
+ //
24877
+ // Defaulted so a build predating the field reads as a document that never
24878
+ // said it was closing — which keeps it voting, the same as every report that
24879
+ // is not a final one.
24880
+ closed: external_exports.boolean().default(false),
24881
+ // What the DOM enforcement path is doing, which none of the counters above
24882
+ // can say: `sendsSeenDom` rises only once a send has COMPLETED, so a tab
24883
+ // whose watcher never bound reports zero exactly like a tab nobody typed in.
24884
+ // Defaulted to 'unknown' rather than 'watching' so a status from a build
24885
+ // predating the field is not read as reporting a healthy one.
24886
+ enforcement: WebEnforcementState.default("unknown")
24887
+ });
24888
+ function webCaptureStatusObservedTurnPath(status) {
24889
+ if (!status.patched) return true;
24890
+ if (status.conversationEndpoints === 0) return true;
24891
+ return status.blind || status.shapeMisses.length > 0 || status.parseFailures > 0 || status.unparsedBodies > 0 || status.exchangesSeenNet > 0;
24892
+ }
24893
+ function pickReportedCaptureStatus(candidates) {
24894
+ return candidates.find((c) => webCaptureStatusObservedTurnPath(c.status)) ?? candidates[0];
24895
+ }
24896
+ var CAPTURE_STATUS_RECENCY_MS = 30 * 24 * 60 * 60 * 1e3;
24897
+ var CAPTURE_STATUS_RECENCY_DAYS = CAPTURE_STATUS_RECENCY_MS / (24 * 60 * 60 * 1e3);
24898
+ var CAPTURE_STATUS_DOCUMENT_QUIET_MS = 12 * 60 * 60 * 1e3;
24899
+ function fromCaptureStatusAttributes(bag) {
24900
+ const parsedBag = CaptureStatusAttributes.safeParse(bag);
24901
+ if (!parsedBag.success) return null;
24902
+ const b = parsedBag.data;
24903
+ const parsedStatus = WebCaptureStatus.safeParse({
24904
+ patched: b.patched,
24905
+ live: b.live,
24906
+ blind: b.blind,
24907
+ sendsSeenDom: b.sends_seen_dom,
24908
+ exchangesSeenNet: b.exchanges_seen_net,
24909
+ parseFailures: b.parse_failures,
24910
+ unparsedBodies: b.unparsed_bodies,
24911
+ shapeMisses: b.shape_misses,
24912
+ conversationEndpoints: b.conversation_endpoints,
24913
+ closed: b.closed,
24914
+ enforcement: b.enforcement
24915
+ });
24916
+ return parsedStatus.success ? parsedStatus.data : null;
24917
+ }
24643
24918
 
24644
24919
  // ../../packages/persistence/src/paths.ts
24645
24920
  import {
@@ -24770,17 +25045,6 @@ function publishByLink(tmp, file2, data) {
24770
25045
  function controlPlaneCredentialPath(settingsDir2) {
24771
25046
  return join2(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
24772
25047
  }
24773
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
24774
- function isSafeEndpoint(endpoint) {
24775
- let parsed2;
24776
- try {
24777
- parsed2 = new URL(endpoint);
24778
- } catch {
24779
- return false;
24780
- }
24781
- if (parsed2.protocol === "https:") return true;
24782
- return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
24783
- }
24784
25048
  function repairOrRefuseMode(file2) {
24785
25049
  const link = lstatSync2(file2, { throwIfNoEntry: false });
24786
25050
  if (link === void 0) return "absent";
@@ -26563,7 +26827,7 @@ var SESSION_ROOT = `event_type = 'session'`;
26563
26827
  var HAS_ACTIVITY = `EXISTS (
26564
26828
  SELECT 1 FROM audit_events c
26565
26829
  WHERE c.root_session_id = audit_events.id
26566
- AND c.event_type NOT IN ('hook', 'config_scan'))`;
26830
+ AND c.event_type NOT IN ('hook', 'config_scan', 'capture_status'))`;
26567
26831
  var SqliteActivityRepository = class {
26568
26832
  constructor(db, now = () => Date.now()) {
26569
26833
  this.db = db;
@@ -26590,10 +26854,10 @@ var SqliteActivityRepository = class {
26590
26854
  SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
26591
26855
  UNION
26592
26856
  SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
26593
- WHERE started_at >= ?
26857
+ WHERE started_at >= ? AND event_type <> 'capture_status'
26594
26858
  UNION
26595
26859
  SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
26596
- WHERE ended_at >= ?)`,
26860
+ WHERE ended_at >= ? AND event_type <> 'capture_status')`,
26597
26861
  [liveThreshold, liveThreshold, liveThreshold]
26598
26862
  );
26599
26863
  const toolCallsToday = countScalar(
@@ -27000,7 +27264,10 @@ var SqliteAuditEventsRepository = class {
27000
27264
  attributes = excluded.attributes,
27001
27265
  ended_at = excluded.ended_at
27002
27266
  WHERE COALESCE(json_extract(excluded.attributes, '$.output_tokens'), 0)
27003
- > COALESCE(json_extract(audit_events.attributes, '$.output_tokens'), 0)`
27267
+ > COALESCE(json_extract(audit_events.attributes, '$.output_tokens'), 0)
27268
+ OR (json_extract(excluded.attributes, '$.usage_source') IS NOT NULL
27269
+ AND json_extract(excluded.attributes, '$.output_tokens') IS NULL
27270
+ AND excluded.attributes <> audit_events.attributes)`
27004
27271
  );
27005
27272
  this.upsertSessionRootStmt = db.prepare(
27006
27273
  `INSERT OR IGNORE INTO audit_events
@@ -27268,6 +27535,87 @@ var SqliteBodyRetentionRepository = class {
27268
27535
  }
27269
27536
  };
27270
27537
 
27538
+ // ../../packages/persistence/src/repositories/capture-status.ts
27539
+ var STATUS_LOOKBACK_ROWS = 128;
27540
+ var SqliteCaptureStatusRepository = class {
27541
+ constructor(db) {
27542
+ this.db = db;
27543
+ this.recentStmt = db.prepare(
27544
+ `SELECT a.started_at AS startedAt,
27545
+ a.attributes AS attributes,
27546
+ a.root_session_id AS rootSessionId
27547
+ FROM audit_events a
27548
+ WHERE a.event_type = 'capture_status'
27549
+ AND a.source_tool = ?
27550
+ AND a.started_at >= ?
27551
+ ORDER BY a.started_at DESC, a.id DESC
27552
+ LIMIT ?`
27553
+ );
27554
+ }
27555
+ db;
27556
+ recentStmt;
27557
+ /**
27558
+ * Every document that reported for a site, in registry order by site, from
27559
+ * the last `CAPTURE_STATUS_RECENCY_MS`.
27560
+ *
27561
+ * SEVERAL per site, not one: a browser is many documents and each reports
27562
+ * for itself, so one row per site is a choice about which of them a user
27563
+ * sees — and the newest is the wrong one, since a healthy tab writing a
27564
+ * fresh report would hide a drifting tab's verdict, which is the whole
27565
+ * reason these rows exist. The pick WITHIN a document is made here (the
27566
+ * unchanged `pickReportedCaptureStatus`, over that document's own rows);
27567
+ * choosing between documents belongs where the state semantics live, and
27568
+ * that is `reportedCaptureDocumentForSite` in `@akasecurity/detections` —
27569
+ * this package may not import it.
27570
+ *
27571
+ * `now` is a required argument rather than a `Date.now()` read, so a caller
27572
+ * that already holds a render instant passes THAT one and a test can drive
27573
+ * the window without moving the wall clock.
27574
+ *
27575
+ * A site whose reports have all aged out contributes nothing, so it derives
27576
+ * to `unreported`. That is the point: nothing but the browser extension ever
27577
+ * writes these rows, so an uninstalled extension's last verdict would
27578
+ * otherwise stand as a live claim for ever with no later report able to
27579
+ * clear it.
27580
+ */
27581
+ latest(now) {
27582
+ const since = now - CAPTURE_STATUS_RECENCY_MS;
27583
+ const documents = [];
27584
+ for (const tool of WebSourceTool.options) {
27585
+ const rows = /* @__PURE__ */ new Map();
27586
+ const lastWord = /* @__PURE__ */ new Map();
27587
+ for (const row of allRows(this.recentStmt, [
27588
+ tool,
27589
+ since,
27590
+ STATUS_LOOKBACK_ROWS
27591
+ ])) {
27592
+ const status = fromCaptureStatusAttributes(parseJsonObject(row.attributes));
27593
+ if (status === null) continue;
27594
+ const record2 = { tool, observedAt: epochMillisToIso(row.startedAt), status };
27595
+ const group = rows.get(row.rootSessionId);
27596
+ if (group === void 0) {
27597
+ rows.set(row.rootSessionId, [record2]);
27598
+ lastWord.set(row.rootSessionId, { at: record2.observedAt, closed: status.closed });
27599
+ } else {
27600
+ group.push(record2);
27601
+ }
27602
+ }
27603
+ for (const [root, candidates] of rows) {
27604
+ const picked = pickReportedCaptureStatus(candidates);
27605
+ const last = lastWord.get(root);
27606
+ if (picked === void 0 || last === void 0) continue;
27607
+ documents.push({
27608
+ ...picked,
27609
+ ...root === null ? {} : { rootSessionId: root },
27610
+ lastReportAt: last.at,
27611
+ closed: last.closed
27612
+ });
27613
+ }
27614
+ }
27615
+ return documents;
27616
+ }
27617
+ };
27618
+
27271
27619
  // ../../packages/persistence/src/repositories/classified-data.ts
27272
27620
  var SqliteClassifiedDataRepository = class {
27273
27621
  constructor(db) {
@@ -32848,6 +33196,7 @@ function openAndInitialize(file2, base, skipTags) {
32848
33196
  activity: new SqliteActivityRepository(db),
32849
33197
  sourceProject: new SqliteSourceProjectRepository(db),
32850
33198
  auditEvents: new SqliteAuditEventsRepository(db),
33199
+ captureStatus: new SqliteCaptureStatusRepository(db),
32851
33200
  classifiedData: new SqliteClassifiedDataRepository(db),
32852
33201
  inspectionDefinitions: new SqliteInspectionDefinitionsRepository(db),
32853
33202
  inspectionFindings: new SqliteInspectionFindingsRepository(db),
@@ -32888,6 +33237,7 @@ function openLocalDatabase(dir, options = {}) {
32888
33237
  activity,
32889
33238
  sourceProject,
32890
33239
  auditEvents,
33240
+ captureStatus,
32891
33241
  classifiedData,
32892
33242
  inspectionDefinitions,
32893
33243
  inspectionFindings,
@@ -33105,6 +33455,7 @@ function openLocalDatabase(dir, options = {}) {
33105
33455
  activity,
33106
33456
  sourceProject,
33107
33457
  auditEvents,
33458
+ captureStatus,
33108
33459
  classifiedData,
33109
33460
  inspectionDefinitions,
33110
33461
  inspectionFindings,
@@ -33343,11 +33694,6 @@ function fingerprintValue(key, raw) {
33343
33694
  // ../../packages/persistence/src/forward-health.ts
33344
33695
  import { readFileSync as readFileSync7 } from "fs";
33345
33696
  import { join as join9 } from "path";
33346
- var FAILURES = /* @__PURE__ */ new Set([
33347
- "unauthorized",
33348
- "forbidden",
33349
- "unreachable"
33350
- ]);
33351
33697
  var BREAKER_COOLDOWN_MS = 3e4;
33352
33698
  function parseForwardHealth(raw, nowMs) {
33353
33699
  try {
@@ -33356,7 +33702,8 @@ function parseForwardHealth(raw, nowMs) {
33356
33702
  const record2 = parsed2;
33357
33703
  const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
33358
33704
  const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
33359
- const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
33705
+ const parsedFailure = ControlPlaneFailure.safeParse(record2.lastFailure);
33706
+ const lastFailure = parsedFailure.success ? parsedFailure.data : null;
33360
33707
  return { consecutiveFailures: failures, openedAtMs, lastFailure };
33361
33708
  } catch {
33362
33709
  return null;
@@ -35360,6 +35707,56 @@ var CONFIG_POSTURE_RULES = [
35360
35707
  }
35361
35708
  ];
35362
35709
 
35710
+ // ../../packages/detections/src/posture/web-capture-posture.ts
35711
+ var RULE_VERSION2 = "1";
35712
+ var DRIFT_MIN_PARSE_FAILURES = 2;
35713
+ var WEB_CAPTURE_DRIFT_STATES = /* @__PURE__ */ new Set([
35714
+ "blind",
35715
+ "degraded"
35716
+ ]);
35717
+ var WEB_CAPTURE_DRIFT_RULE = {
35718
+ ruleId: "web-capture-drift",
35719
+ version: RULE_VERSION2,
35720
+ name: "Web chat capture is not reading the site",
35721
+ category: "config",
35722
+ severity: "medium",
35723
+ definition: JSON.stringify({
35724
+ kind: "web-capture-drift",
35725
+ states: [...WEB_CAPTURE_DRIFT_STATES],
35726
+ minParseFailures: DRIFT_MIN_PARSE_FAILURES
35727
+ })
35728
+ };
35729
+ var STATIC_COPY = {
35730
+ active: { headline: "turns are being observed on this site" },
35731
+ unreported: {
35732
+ // Says "recently" rather than "yet": the store read is bounded to
35733
+ // CAPTURE_STATUS_RECENCY_MS, so this state covers a site nothing has ever
35734
+ // reported for AND one whose last report has aged out. The two are the
35735
+ // same fact to a reader — nobody has confirmed anything lately — and the
35736
+ // copy may not claim the stronger of them.
35737
+ headline: `no report in the last ${String(CAPTURE_STATUS_RECENCY_DAYS)} days \u2014 open the site in Chrome with the extension loaded`
35738
+ },
35739
+ standby: {
35740
+ headline: "this build declares no endpoints for the site, so nothing is observed yet"
35741
+ },
35742
+ unpatched: {
35743
+ // Says what the flags say and no more. `patched` is false both for a tap
35744
+ // that installed and hooked neither transport and for one that never ran
35745
+ // at all — a page reports the same status either way, so the copy may not
35746
+ // assert one of them.
35747
+ headline: "the page tap captured neither fetch nor XHR \u2014 it may not have installed; reload the extension at chrome://extensions"
35748
+ },
35749
+ idle: { headline: "watching; no turn has been observed yet" },
35750
+ blind: {
35751
+ headline: "messages were sent in the page that the network capture never saw",
35752
+ remediation: "reload the tab. If it persists after `aka update` and reloading the extension at chrome://extensions, the site's send path has changed and needs a new extension build."
35753
+ },
35754
+ degraded: {
35755
+ headline: "the site's payloads no longer carry the fields the extension reads",
35756
+ remediation: "run `aka update`, then reload the extension at chrome://extensions. If it stays degraded after an update, the site's contract has changed and needs a new extension build."
35757
+ }
35758
+ };
35759
+
35363
35760
  // ../../packages/detections/src/security/redos-probe.ts
35364
35761
  var BUDGET_MS = 100;
35365
35762
  var EXPONENTIAL_UNITS = [
@@ -38078,6 +38475,12 @@ var RemoteRequestInvalid = class extends Error {
38078
38475
  }
38079
38476
  cause;
38080
38477
  };
38478
+ var RemoteEndpointRefused = class extends Error {
38479
+ constructor(endpoint) {
38480
+ super(`refusing to talk to an unsafe control-plane endpoint: ${originOnly(endpoint)}`);
38481
+ this.name = "RemoteEndpointRefused";
38482
+ }
38483
+ };
38081
38484
  var RemoteResponseInvalid = class extends Error {
38082
38485
  constructor(route, detail) {
38083
38486
  super(`control plane answered ${route} with ${detail}`);
@@ -38230,14 +38633,15 @@ function parsed(schema, body, route) {
38230
38633
  }
38231
38634
  return result.data;
38232
38635
  }
38233
- function withoutTrailingSlashes(endpoint) {
38636
+ function resolveBaseUrl(endpoint) {
38637
+ if (!isSafeEndpoint(endpoint)) throw new RemoteEndpointRefused(endpoint);
38234
38638
  let end = endpoint.length;
38235
38639
  while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH2) end -= 1;
38236
38640
  return endpoint.slice(0, end);
38237
38641
  }
38238
38642
  var SLASH2 = "/".charCodeAt(0);
38239
38643
  function createRemoteClient(options) {
38240
- const base = withoutTrailingSlashes(options.endpoint);
38644
+ const base = resolveBaseUrl(options.endpoint);
38241
38645
  const url2 = (route) => `${base}${route}`;
38242
38646
  const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
38243
38647
  const sendOne = async (event) => {
@@ -38367,6 +38771,7 @@ function classifyRemoteFailure(err) {
38367
38771
  case "RemoteRouteAbsent":
38368
38772
  return "route-absent";
38369
38773
  case "RemoteRequestInvalid":
38774
+ case "RemoteEndpointRefused":
38370
38775
  return "invalid-request";
38371
38776
  case "RemoteResponseInvalid":
38372
38777
  return "rejected";
@@ -38570,86 +38975,10 @@ function createForwardPolicy(deps) {
38570
38975
  }
38571
38976
 
38572
38977
  // ../../packages/plugin-runtime/src/attached/gateway.ts
38573
- function strongerOf(a, b) {
38574
- if (a === null) return b;
38575
- if (b === null) return a;
38576
- return strongerAction(a, b);
38577
- }
38578
- function ruleCategoryMap(wireRules, localRules) {
38579
- const map2 = /* @__PURE__ */ new Map();
38580
- for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
38581
- for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
38582
- for (const pack of bundledDetections()) {
38583
- for (const rule of pack.rules) map2.set(rule.id, rule.category);
38584
- }
38585
- return map2;
38586
- }
38587
- function policyKey(policy) {
38588
- return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
38589
- }
38590
- function floorFor(policy, categoryByRuleId) {
38591
- const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
38592
- return category === void 0 ? null : DEFAULT_ACTIONS[category];
38593
- }
38594
- function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
38595
- const merged = /* @__PURE__ */ new Map();
38596
- const disabled = [];
38597
- const remoteCategoryAction = /* @__PURE__ */ new Map();
38598
- for (const policy of remotePolicies) {
38599
- if (!policy.enabled) continue;
38600
- if (!("category" in policy.target)) continue;
38601
- if (remoteCategoryAction.has(policy.target.category)) continue;
38602
- const floor = floorFor(policy, categoryByRuleId);
38603
- remoteCategoryAction.set(
38604
- policy.target.category,
38605
- floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
38606
- );
38607
- }
38608
- for (const policy of localPolicies) {
38609
- if (!policy.enabled) {
38610
- disabled.push(policy);
38611
- continue;
38612
- }
38613
- const key = policyKey(policy);
38614
- if (merged.has(key)) continue;
38615
- let remoteFloor = null;
38616
- if ("ruleId" in policy.target) {
38617
- const category = categoryByRuleId.get(policy.target.ruleId);
38618
- if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
38619
- }
38620
- merged.set(
38621
- key,
38622
- remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
38623
- );
38624
- }
38625
- const localCategoryAction = /* @__PURE__ */ new Map();
38626
- for (const policy of merged.values()) {
38627
- if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
38628
- }
38629
- for (const policy of remotePolicies) {
38630
- if (!policy.enabled) {
38631
- disabled.push(policy);
38632
- continue;
38633
- }
38634
- const key = policyKey(policy);
38635
- const floor = floorFor(policy, categoryByRuleId);
38636
- let localFloor = null;
38637
- if ("ruleId" in policy.target) {
38638
- const category = categoryByRuleId.get(policy.target.ruleId);
38639
- if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
38640
- }
38641
- const effectiveFloor = strongerOf(floor, localFloor);
38642
- const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
38643
- const existing = merged.get(key);
38644
- if (existing === void 0) {
38645
- merged.set(key, clamped);
38646
- continue;
38647
- }
38648
- if (actionRank(clamped.action) > actionRank(existing.action)) {
38649
- merged.set(key, clamped);
38650
- }
38651
- }
38652
- return [...merged.values(), ...disabled];
38978
+ var bundledRulesFlatCache;
38979
+ function bundledRulesFlat() {
38980
+ bundledRulesFlatCache ??= bundledDetections().flatMap((pack) => pack.rules);
38981
+ return bundledRulesFlatCache;
38653
38982
  }
38654
38983
  var AttachedDataGateway = class {
38655
38984
  constructor(deps) {
@@ -38969,6 +39298,9 @@ var AttachedDataGateway = class {
38969
39298
  async readSessionProvider(sessionId) {
38970
39299
  return this.deps.local.readSessionProvider(sessionId);
38971
39300
  }
39301
+ async readCaptureStatuses() {
39302
+ return this.deps.local.readCaptureStatuses();
39303
+ }
38972
39304
  async facets() {
38973
39305
  return this.deps.local.facets();
38974
39306
  }
@@ -39051,7 +39383,7 @@ var AttachedDataGateway = class {
39051
39383
  policies: mergeRaiseOnly(
39052
39384
  local.policies,
39053
39385
  cached2.policies,
39054
- ruleCategoryMap(cached2.rules, local.rules)
39386
+ ruleCategoryMap(cached2.rules, local.rules, bundledRulesFlat())
39055
39387
  ),
39056
39388
  customKeywords: [...local.customKeywords, ...cached2.customKeywords],
39057
39389
  // TAKEN FROM THE CACHE, unlike the two fields below — and the asymmetry
@@ -39267,10 +39599,15 @@ import { join as join27 } from "path";
39267
39599
  // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
39268
39600
  import { rename as rename2 } from "fs/promises";
39269
39601
  var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
39270
- var ATTEMPTS = 5;
39602
+ var IMMEDIATE_RETRIES = 8;
39603
+ var TIMED_RETRIES = 4;
39604
+ var ATTEMPTS = 1 + IMMEDIATE_RETRIES + TIMED_RETRIES;
39271
39605
  var delay = (ms) => new Promise((resolve3) => {
39272
39606
  setTimeout(resolve3, ms);
39273
39607
  });
39608
+ var yieldToLoop = () => new Promise((resolve3) => {
39609
+ setImmediate(resolve3);
39610
+ });
39274
39611
  async function publishByRename(tmp, file2, move = rename2) {
39275
39612
  for (let attempt = 1; ; attempt += 1) {
39276
39613
  try {
@@ -39279,7 +39616,7 @@ async function publishByRename(tmp, file2, move = rename2) {
39279
39616
  } catch (err) {
39280
39617
  const code = err.code;
39281
39618
  if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
39282
- await delay(attempt * 10);
39619
+ await (attempt <= IMMEDIATE_RETRIES ? yieldToLoop() : delay((attempt - IMMEDIATE_RETRIES) * 10));
39283
39620
  }
39284
39621
  }
39285
39622
  }
@@ -39743,6 +40080,9 @@ var StandaloneDataGateway = class {
39743
40080
  readSessionProvider(sessionId) {
39744
40081
  return Promise.resolve(this.db.auditEvents.sessionProvider(sessionId));
39745
40082
  }
40083
+ readCaptureStatuses() {
40084
+ return Promise.resolve(this.db.captureStatus.latest(Date.now()));
40085
+ }
39746
40086
  facets() {
39747
40087
  return Promise.resolve(this.db.facets());
39748
40088
  }