crumbtrail-core 0.2.0 → 0.2.2
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/README.md +84 -5
- package/dist/index.cjs +120 -2
- package/dist/index.d.cts +81 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.js +120 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,91 @@
|
|
|
1
1
|
# crumbtrail-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The capture engine behind [Crumbtrail](https://crumbtrail.dev). It records what
|
|
4
|
+
actually happened around a bug — the console, the network calls, the DOM
|
|
5
|
+
interactions, the state — and hands it over as evidence a coding agent can act on,
|
|
6
|
+
instead of a screenshot and a vague repro.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
Framework-agnostic, with **no dependencies**.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
7
11
|
|
|
8
12
|
```bash
|
|
9
|
-
npm
|
|
13
|
+
npm install crumbtrail-core
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or let the wizard install and wire it for you:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx crumbtrail
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
Call `Crumbtrail.init()` once, at your app's entry point:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Crumbtrail, PRESET_PASSIVE } from "crumbtrail-core";
|
|
28
|
+
|
|
29
|
+
Crumbtrail.init({
|
|
30
|
+
...PRESET_PASSIVE,
|
|
31
|
+
httpEndpoint: "https://api.crumbtrail.dev",
|
|
32
|
+
httpAuthToken: process.env.CRUMBTRAIL_KEY,
|
|
33
|
+
});
|
|
10
34
|
```
|
|
11
35
|
|
|
12
|
-
|
|
36
|
+
That's the whole integration — capture runs in the background from there.
|
|
37
|
+
|
|
38
|
+
### Presets
|
|
39
|
+
|
|
40
|
+
| Preset | Behaviour |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `PRESET_PASSIVE` | Capture continuously and auto-flag on errors and signals. The default. |
|
|
43
|
+
| `PRESET_LIGHT` | Leaner capture, less overhead. |
|
|
44
|
+
| `PRESET_FULL` | Everything, for a heavy debugging session. |
|
|
45
|
+
|
|
46
|
+
### Flagging a bug yourself
|
|
47
|
+
|
|
48
|
+
`init()` returns the instance, so you can mark moments explicitly:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const crumbtrail = Crumbtrail.init({ ...PRESET_PASSIVE, httpEndpoint });
|
|
52
|
+
|
|
53
|
+
crumbtrail.mark("checkout: submitted");
|
|
54
|
+
|
|
55
|
+
await crumbtrail.flagBug({
|
|
56
|
+
note: "Payment failed with no error toast",
|
|
57
|
+
tags: ["checkout"],
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Also on the instance: `addEvent`, `registerStateProvider`, `setEnv`,
|
|
62
|
+
`createRequestHeaders`, `pause`, `resume`, `stop`, `getSessionId`.
|
|
63
|
+
|
|
64
|
+
## Redaction is on by default
|
|
65
|
+
|
|
66
|
+
Crumbtrail is meant to be pointed at real traffic, so scrubbing isn't opt-in.
|
|
67
|
+
Tokens, cookies, storage values and sensitive input values are redacted before an
|
|
68
|
+
event ever leaves the browser. See `BROWSER_REDACTION_POLICY` and the `redact*`
|
|
69
|
+
helpers if you want to inspect or tighten the policy.
|
|
70
|
+
|
|
71
|
+
## Related packages
|
|
72
|
+
|
|
73
|
+
| Package | Use it for |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| [`crumbtrail`](https://www.npmjs.com/package/crumbtrail) | The `npx crumbtrail` setup wizard |
|
|
76
|
+
| [`crumbtrail-node`](https://www.npmjs.com/package/crumbtrail-node) | Self-hosted server, Express middleware, MCP evidence tools |
|
|
77
|
+
| [`crumbtrail-react`](https://www.npmjs.com/package/crumbtrail-react) | React error boundary and state-capture hook |
|
|
78
|
+
| [`crumbtrail-react-native`](https://www.npmjs.com/package/crumbtrail-react-native) | React Native bindings |
|
|
79
|
+
| [`crumbtrail-tauri`](https://www.npmjs.com/package/crumbtrail-tauri) | Tauri desktop bindings |
|
|
80
|
+
|
|
81
|
+
## Links
|
|
82
|
+
|
|
83
|
+
- **Website** — https://crumbtrail.dev
|
|
84
|
+
- **Docs** — https://crumbtrail.dev/docs
|
|
85
|
+
- **How it works** — https://crumbtrail.dev/how-it-works
|
|
86
|
+
- **Source** — https://github.com/crumbtrail-dev/crumbtrail-js
|
|
87
|
+
- **Issues** — https://github.com/crumbtrail-dev/crumbtrail-js/issues
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -4634,21 +4634,139 @@ function inferIntent(evidence, commits) {
|
|
|
4634
4634
|
var FUSION_SCHEMA_VERSION = "fusion.v1";
|
|
4635
4635
|
function assembleBundle(input) {
|
|
4636
4636
|
const evidence = rankEvidence(input.symptom, input.evidence);
|
|
4637
|
-
const
|
|
4637
|
+
const classified = classifyHypotheses(
|
|
4638
4638
|
input.symptom,
|
|
4639
4639
|
input.evidence,
|
|
4640
4640
|
input.intent
|
|
4641
4641
|
);
|
|
4642
4642
|
const gaps = input.gaps ?? [];
|
|
4643
|
+
const located = input.located;
|
|
4644
|
+
const hypotheses = classified.map((hypothesis) => {
|
|
4645
|
+
const verification = deriveVerification(hypothesis, evidence);
|
|
4646
|
+
return verification.length > 0 ? { ...hypothesis, verification } : hypothesis;
|
|
4647
|
+
});
|
|
4648
|
+
const contextCompleteness = deriveContextCompleteness(
|
|
4649
|
+
evidence,
|
|
4650
|
+
gaps,
|
|
4651
|
+
hypotheses,
|
|
4652
|
+
located
|
|
4653
|
+
);
|
|
4654
|
+
const escalation = deriveEscalation(contextCompleteness, hypotheses);
|
|
4643
4655
|
return {
|
|
4644
4656
|
schemaVersion: FUSION_SCHEMA_VERSION,
|
|
4645
4657
|
symptom: input.symptom,
|
|
4646
4658
|
evidence,
|
|
4647
4659
|
opinion: { stance: "advisory", hypotheses },
|
|
4648
4660
|
gaps,
|
|
4649
|
-
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps)
|
|
4661
|
+
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps),
|
|
4662
|
+
contextCompleteness,
|
|
4663
|
+
escalation,
|
|
4664
|
+
...located ? { located } : {}
|
|
4650
4665
|
};
|
|
4651
4666
|
}
|
|
4667
|
+
function clamp01(value) {
|
|
4668
|
+
if (Number.isNaN(value)) return 0;
|
|
4669
|
+
return Math.max(0, Math.min(1, value));
|
|
4670
|
+
}
|
|
4671
|
+
var COMPLETENESS_LANES = [
|
|
4672
|
+
"network",
|
|
4673
|
+
"db",
|
|
4674
|
+
"flow",
|
|
4675
|
+
"browser",
|
|
4676
|
+
"env"
|
|
4677
|
+
];
|
|
4678
|
+
function deriveContextCompleteness(evidence, gaps, hypotheses, located) {
|
|
4679
|
+
const lanesPresent = new Set(evidence.map((item) => item.lane));
|
|
4680
|
+
const informativePresent = COMPLETENESS_LANES.filter(
|
|
4681
|
+
(lane) => lanesPresent.has(lane)
|
|
4682
|
+
);
|
|
4683
|
+
const breadth = Math.min(1, informativePresent.length / 3);
|
|
4684
|
+
const volume = Math.min(1, evidence.length / 5);
|
|
4685
|
+
const top = hypotheses[0];
|
|
4686
|
+
const hypothesisStrength = !top || top.kind === "inconclusive" ? 0 : top.confidence;
|
|
4687
|
+
let score = 0.4 * breadth + 0.25 * volume + 0.35 * hypothesisStrength;
|
|
4688
|
+
if (located) {
|
|
4689
|
+
score = located.outcome === "matched" ? 0.85 * score + 0.15 * clamp01(located.confidence) : score * 0.6;
|
|
4690
|
+
}
|
|
4691
|
+
const hardGaps = gaps.filter((gap) => gap.kind === "source-unavailable").length;
|
|
4692
|
+
const softGaps = gaps.length - hardGaps;
|
|
4693
|
+
const gapPenalty = Math.min(0.5, softGaps * 0.1 + hardGaps * 0.3);
|
|
4694
|
+
score = clamp01(score - gapPenalty);
|
|
4695
|
+
const level = score < 0.34 ? "low" : score < 0.67 ? "medium" : "high";
|
|
4696
|
+
const reasons = [];
|
|
4697
|
+
const missingLanes = COMPLETENESS_LANES.filter(
|
|
4698
|
+
(lane) => !lanesPresent.has(lane)
|
|
4699
|
+
);
|
|
4700
|
+
if (informativePresent.length === 0) {
|
|
4701
|
+
reasons.push("no network/db/flow/browser/env evidence captured");
|
|
4702
|
+
} else if (missingLanes.length > 0) {
|
|
4703
|
+
reasons.push(`missing evidence lanes: ${missingLanes.join(", ")}`);
|
|
4704
|
+
}
|
|
4705
|
+
if (evidence.length > 0 && evidence.length < 3) {
|
|
4706
|
+
reasons.push(`thin evidence (${evidence.length} item(s))`);
|
|
4707
|
+
}
|
|
4708
|
+
if (hardGaps > 0) reasons.push(`source unavailable for ${hardGaps} lane(s)`);
|
|
4709
|
+
if (softGaps > 0) reasons.push(`${softGaps} evidence gap(s)`);
|
|
4710
|
+
if (located?.outcome === "inconclusive") {
|
|
4711
|
+
reasons.push("incident location inconclusive");
|
|
4712
|
+
}
|
|
4713
|
+
if (!top || top.kind === "inconclusive") {
|
|
4714
|
+
reasons.push("no distinguishing hypothesis");
|
|
4715
|
+
}
|
|
4716
|
+
return { score, level, reasons };
|
|
4717
|
+
}
|
|
4718
|
+
function pkString(pk) {
|
|
4719
|
+
return Object.keys(pk).sort().map((key) => `${key}=${String(pk[key])}`).join(", ");
|
|
4720
|
+
}
|
|
4721
|
+
function deriveVerification(hypothesis, evidence) {
|
|
4722
|
+
if (hypothesis.kind === "inconclusive") return [];
|
|
4723
|
+
const cited = new Set(hypothesis.evidenceIds);
|
|
4724
|
+
const out = [];
|
|
4725
|
+
for (const item of evidence) {
|
|
4726
|
+
if (!cited.has(item.id)) continue;
|
|
4727
|
+
const ref = item.ref;
|
|
4728
|
+
if (item.lane === "db" && ref.table && ref.pk && Object.keys(ref.pk).length > 0) {
|
|
4729
|
+
out.push({
|
|
4730
|
+
observation: `row in ${ref.table} (${pkString(ref.pk)}) matches the intended post-fix state`,
|
|
4731
|
+
evidenceIds: [item.id],
|
|
4732
|
+
how: "db"
|
|
4733
|
+
});
|
|
4734
|
+
} else if (ref.requestId) {
|
|
4735
|
+
const sigPart = ref.sig ? ` for signature ${ref.sig}` : "";
|
|
4736
|
+
out.push({
|
|
4737
|
+
observation: `request ${ref.requestId} succeeds${sigPart} on a fresh run (no error response)`,
|
|
4738
|
+
evidenceIds: [item.id],
|
|
4739
|
+
how: "request"
|
|
4740
|
+
});
|
|
4741
|
+
} else if (ref.sig) {
|
|
4742
|
+
out.push({
|
|
4743
|
+
observation: `error signature ${ref.sig} no longer appears in a fresh session over the same route`,
|
|
4744
|
+
evidenceIds: [item.id],
|
|
4745
|
+
how: "session"
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
return out;
|
|
4750
|
+
}
|
|
4751
|
+
function deriveEscalation(completeness, hypotheses) {
|
|
4752
|
+
const allInconclusive = hypotheses.length > 0 && hypotheses.every((hypothesis) => hypothesis.kind === "inconclusive");
|
|
4753
|
+
const recommended = completeness.level === "low" || allInconclusive;
|
|
4754
|
+
if (!recommended) return { recommended: false, when: [] };
|
|
4755
|
+
const when = [
|
|
4756
|
+
"if you cannot reproduce the symptom via the anchored request or session, stop and request human triage \u2014 do not widen the search"
|
|
4757
|
+
];
|
|
4758
|
+
if (completeness.level === "low") {
|
|
4759
|
+
when.push(
|
|
4760
|
+
"context is thin: confirm the missing evidence lanes before acting on the top hypothesis"
|
|
4761
|
+
);
|
|
4762
|
+
}
|
|
4763
|
+
if (allInconclusive) {
|
|
4764
|
+
when.push(
|
|
4765
|
+
"no hypothesis is distinguished; treat the listed causes as equally unproven"
|
|
4766
|
+
);
|
|
4767
|
+
}
|
|
4768
|
+
return { recommended, when };
|
|
4769
|
+
}
|
|
4652
4770
|
var LANE_PRIOR = {
|
|
4653
4771
|
db: 0.2,
|
|
4654
4772
|
network: 0.2,
|
package/dist/index.d.cts
CHANGED
|
@@ -831,6 +831,76 @@ interface Hypothesis {
|
|
|
831
831
|
rationale: string;
|
|
832
832
|
/** EvidenceItem.id values backing this hypothesis. */
|
|
833
833
|
evidenceIds: string[];
|
|
834
|
+
/**
|
|
835
|
+
* Advisory, additive: concrete observations that would confirm a fix for
|
|
836
|
+
* THIS hypothesis worked. Emitted only for non-inconclusive hypotheses whose
|
|
837
|
+
* cited evidence carries a concrete anchor (signature / requestId /
|
|
838
|
+
* table+pk); absent otherwise. Sparse-and-concrete by design — never vacuous
|
|
839
|
+
* prose like "verify the bug is gone". See {@link Verification}.
|
|
840
|
+
*/
|
|
841
|
+
verification?: Verification[];
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* A concrete, post-fix observation that would confirm a hypothesis's fix
|
|
845
|
+
* worked. `observation` always names a concrete signal (an error signature, a
|
|
846
|
+
* request id, or a table + primary key) — the derivation emits nothing when it
|
|
847
|
+
* cannot anchor to one, so an emitted verification is never a vacuous
|
|
848
|
+
* restatement.
|
|
849
|
+
*/
|
|
850
|
+
interface Verification {
|
|
851
|
+
/** Names a concrete signal — signature, request id, or table/pk. */
|
|
852
|
+
observation: string;
|
|
853
|
+
/** EvidenceItem.id values this observation is anchored to. */
|
|
854
|
+
evidenceIds: string[];
|
|
855
|
+
how: "session" | "request" | "db";
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Where the incident was located, when a locate ran. Advisory and optional
|
|
859
|
+
* (absent for explicit baseline/current comparison bundles that never locate).
|
|
860
|
+
* Shape shared with the node locate engine's `LocateMatch` and War-game 02's
|
|
861
|
+
* deterministic token join — `method` distinguishes the two so 02 can populate
|
|
862
|
+
* "token" without a schema change (War-game 02 Fork C: one definition, reused).
|
|
863
|
+
*/
|
|
864
|
+
interface Located {
|
|
865
|
+
outcome: "matched" | "inconclusive";
|
|
866
|
+
/** 0..1 locate confidence. */
|
|
867
|
+
confidence: number;
|
|
868
|
+
/**
|
|
869
|
+
* How the incident was located. "fuzzy" = the scored locate engine;
|
|
870
|
+
* "token" = a deterministic token join (War-game 02). Absent when a caller
|
|
871
|
+
* supplies neither.
|
|
872
|
+
*/
|
|
873
|
+
method?: "fuzzy" | "token";
|
|
874
|
+
/** The matched session, present ONLY when outcome === "matched". Never fabricated. */
|
|
875
|
+
sessionId?: string;
|
|
876
|
+
reasons?: string[];
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Bundle-level answer to "how much context do we actually have here?" — the
|
|
880
|
+
* LOW_CONTEXT signal. Derived purely from the assembled bundle (evidence
|
|
881
|
+
* lanes, gaps, hypothesis strength, locate confidence). Advisory: it NEVER
|
|
882
|
+
* gates or blocks bundle emission. `reasons` is the load-bearing part; `score`
|
|
883
|
+
* and `level` summarize it.
|
|
884
|
+
*/
|
|
885
|
+
interface ContextCompleteness {
|
|
886
|
+
/** 0..1; higher = richer, more actionable context. */
|
|
887
|
+
score: number;
|
|
888
|
+
level: "high" | "medium" | "low";
|
|
889
|
+
/** Human-legible drivers: missing lanes, thin evidence, inconclusive locate. */
|
|
890
|
+
reasons: string[];
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Consumer-side advisory: what the CONSUMING agent should do when context is
|
|
894
|
+
* thin. Distinct from {@link EvidenceGap} (capture-side: what evidence is
|
|
895
|
+
* missing) — escalation is what to do about it. Always present; `recommended`
|
|
896
|
+
* is false with an empty `when` when context is adequate. Never gates the
|
|
897
|
+
* bundle (VISION: advisory, never a boolean verdict on the bug itself).
|
|
898
|
+
*/
|
|
899
|
+
interface Escalation {
|
|
900
|
+
recommended: boolean;
|
|
901
|
+
/** Conditions phrased for the consuming agent, e.g. "if you cannot reproduce
|
|
902
|
+
* via the anchored request, stop and request human triage". */
|
|
903
|
+
when: string[];
|
|
834
904
|
}
|
|
835
905
|
interface EvidenceGap {
|
|
836
906
|
lane: EvidenceLane;
|
|
@@ -871,12 +941,22 @@ interface RankedBundle {
|
|
|
871
941
|
gaps: EvidenceGap[];
|
|
872
942
|
/** Advisory-only: suggested capture escalations when evidence is thin. */
|
|
873
943
|
directives: CaptureDirective[];
|
|
944
|
+
/** How much actionable context this bundle carries. Advisory, never gates. */
|
|
945
|
+
contextCompleteness: ContextCompleteness;
|
|
946
|
+
/** What the consuming agent should do when context is thin. Advisory. */
|
|
947
|
+
escalation: Escalation;
|
|
948
|
+
/** Where the incident was located, when a locate ran. Absent otherwise. */
|
|
949
|
+
located?: Located;
|
|
874
950
|
}
|
|
875
951
|
interface AssembleBundleInput {
|
|
876
952
|
symptom: Symptom;
|
|
877
953
|
evidence: EvidenceItem[];
|
|
878
954
|
intent: IntentSignal[];
|
|
879
955
|
gaps?: EvidenceGap[];
|
|
956
|
+
/** The locate decision, when one ran (auto-locate / token join). Threaded
|
|
957
|
+
* onto the bundle as {@link RankedBundle.located} and folded into
|
|
958
|
+
* completeness. Omit for explicit baseline/current comparison bundles. */
|
|
959
|
+
located?: Located;
|
|
880
960
|
}
|
|
881
961
|
/**
|
|
882
962
|
* Compose the RankedBundle: rank the complete evidence set (nothing dropped),
|
|
@@ -971,4 +1051,4 @@ type Stack = "nextjs" | "react" | "vue" | "svelte" | "vite" | "express" | "hono"
|
|
|
971
1051
|
/** Stable ordered id list, handy for iterating or building pickers. */
|
|
972
1052
|
declare const STACK_IDS: readonly Stack[];
|
|
973
1053
|
|
|
974
|
-
export { type AbandonedFlowOptions, type AddBugEventOptions, type AssembleBundleInput, type AutoFlagController, type AutoFlagOptions, BROWSER_REDACTION_POLICY, type BodyRedactionOptions, type BodyRedactionResult, type BugEvent, type BugReport, CRUMBTRAIL_EVENT_KINDS, CRUMBTRAIL_REQUEST_HEADER, CRUMBTRAIL_REQUEST_HEADER_LOWER, CRUMBTRAIL_REQUEST_ID_MAX_LENGTH, CRUMBTRAIL_SCHEMA_VERSION, CRUMBTRAIL_SESSION_HEADER, CRUMBTRAIL_SESSION_HEADER_LOWER, type CaptureDirective, type CollectorCleanup, type CollectorContext, type CommitInfo, Crumbtrail, type CrumbtrailCapabilities, type CrumbtrailConfig, type CrumbtrailPlatform, type CrumbtrailPreset, type CrumbtrailSdkDescriptor, type CrumbtrailTransport, DB_DIFF_BULK_EVENT_KIND, DB_DIFF_EVENT_KIND, DB_READ_BULK_EVENT_KIND, DB_READ_EVENT_KIND, DEFAULT_CONFIG, DEFAULT_SESSION_STORAGE_KEY, type DbDiffBulkEventData, type DbDiffEventData, type DbDiffOp, type DbEngine, type DbReadBulkEventData, type DbReadEventData, EVIDENCE_SCHEMA_VERSION, EVIDENCE_SOURCE_SCHEMA_VERSION, type ElementSignature, type EnvDeclaration, type EnvSnapshot, EventBus, type EvidenceGap, type EvidenceItem, type EvidenceJoinKey, type EvidenceLane, type EvidenceQuery, type EvidenceRef, type EvidenceSourceDescriptor, type EvidenceSourceResult, FUSION_SCHEMA_VERSION, type FlagBugOptions, type GitHostClient, type GitHostRef, HttpTransport, type Hypothesis, type HypothesisKind, type InputValueRedactionOptions, type IntentSignal, type InteractionElementDescriptor, type InteractionElementDescriptorFactory, type OutboundCorrelation, PRESET_FULL, PRESET_LIGHT, PRESET_PASSIVE, type PayloadSummary, type PersistedSession, REDACTED_STORAGE_KEY, REDACTED_VALUE, type RageClickOptions, type RankedBundle, type RedactionAction, type RedactionField, type RedactionMetadata, type RedactionResult, type RetryStormOptions, RingBuffer, STACK_IDS, type SessionStore, type Signal, type SignalDetector, type SlowResponseOptions, type Stack, type StoredValueRedactionOptions, type Symptom, type TargetDescriptor, type TargetDescriptorResolver, type W3CTraceContext, W3C_TRACEPARENT_HEADER, WebTargetDescriptorResolver, abandonedFlowDetector, assembleBundle, attachRedactionMetadata, buildEnvDelta, buildEnvSnapshot, canInjectCorrelationHeaders, computeElementPath, computeElementSignature, createAutoFlagController, createCrumbtrailRequestHeaders, createWebSessionStore, environmentCollector, errorDetector, errorSignature, formatTraceparent, generateRequestId, generateSpanId, generateTraceContext, generateTraceId, hashString, inferIntent, mergeRedactionMetadata, parseTraceparent, rageClickDetector, redactCookieMap, redactCookieName, redactCookieValue, redactHeaders, redactInputValue, redactNetworkTextBody, redactStorageKey, redactStoredValue, redactTokenLikeString, redactUrl, redactUrlsInText, redactValue, resolveOutboundCorrelation, retryStormDetector, slowResponseDetector, summarizeBinaryPayload, summarizeOmittedPayload, webTargetDescriptorResolver };
|
|
1054
|
+
export { type AbandonedFlowOptions, type AddBugEventOptions, type AssembleBundleInput, type AutoFlagController, type AutoFlagOptions, BROWSER_REDACTION_POLICY, type BodyRedactionOptions, type BodyRedactionResult, type BugEvent, type BugReport, CRUMBTRAIL_EVENT_KINDS, CRUMBTRAIL_REQUEST_HEADER, CRUMBTRAIL_REQUEST_HEADER_LOWER, CRUMBTRAIL_REQUEST_ID_MAX_LENGTH, CRUMBTRAIL_SCHEMA_VERSION, CRUMBTRAIL_SESSION_HEADER, CRUMBTRAIL_SESSION_HEADER_LOWER, type CaptureDirective, type CollectorCleanup, type CollectorContext, type CommitInfo, type ContextCompleteness, Crumbtrail, type CrumbtrailCapabilities, type CrumbtrailConfig, type CrumbtrailPlatform, type CrumbtrailPreset, type CrumbtrailSdkDescriptor, type CrumbtrailTransport, DB_DIFF_BULK_EVENT_KIND, DB_DIFF_EVENT_KIND, DB_READ_BULK_EVENT_KIND, DB_READ_EVENT_KIND, DEFAULT_CONFIG, DEFAULT_SESSION_STORAGE_KEY, type DbDiffBulkEventData, type DbDiffEventData, type DbDiffOp, type DbEngine, type DbReadBulkEventData, type DbReadEventData, EVIDENCE_SCHEMA_VERSION, EVIDENCE_SOURCE_SCHEMA_VERSION, type ElementSignature, type EnvDeclaration, type EnvSnapshot, type Escalation, EventBus, type EvidenceGap, type EvidenceItem, type EvidenceJoinKey, type EvidenceLane, type EvidenceQuery, type EvidenceRef, type EvidenceSourceDescriptor, type EvidenceSourceResult, FUSION_SCHEMA_VERSION, type FlagBugOptions, type GitHostClient, type GitHostRef, HttpTransport, type Hypothesis, type HypothesisKind, type InputValueRedactionOptions, type IntentSignal, type InteractionElementDescriptor, type InteractionElementDescriptorFactory, type Located, type OutboundCorrelation, PRESET_FULL, PRESET_LIGHT, PRESET_PASSIVE, type PayloadSummary, type PersistedSession, REDACTED_STORAGE_KEY, REDACTED_VALUE, type RageClickOptions, type RankedBundle, type RedactionAction, type RedactionField, type RedactionMetadata, type RedactionResult, type RetryStormOptions, RingBuffer, STACK_IDS, type SessionStore, type Signal, type SignalDetector, type SlowResponseOptions, type Stack, type StoredValueRedactionOptions, type Symptom, type TargetDescriptor, type TargetDescriptorResolver, type Verification, type W3CTraceContext, W3C_TRACEPARENT_HEADER, WebTargetDescriptorResolver, abandonedFlowDetector, assembleBundle, attachRedactionMetadata, buildEnvDelta, buildEnvSnapshot, canInjectCorrelationHeaders, computeElementPath, computeElementSignature, createAutoFlagController, createCrumbtrailRequestHeaders, createWebSessionStore, environmentCollector, errorDetector, errorSignature, formatTraceparent, generateRequestId, generateSpanId, generateTraceContext, generateTraceId, hashString, inferIntent, mergeRedactionMetadata, parseTraceparent, rageClickDetector, redactCookieMap, redactCookieName, redactCookieValue, redactHeaders, redactInputValue, redactNetworkTextBody, redactStorageKey, redactStoredValue, redactTokenLikeString, redactUrl, redactUrlsInText, redactValue, resolveOutboundCorrelation, retryStormDetector, slowResponseDetector, summarizeBinaryPayload, summarizeOmittedPayload, webTargetDescriptorResolver };
|
package/dist/index.d.ts
CHANGED
|
@@ -831,6 +831,76 @@ interface Hypothesis {
|
|
|
831
831
|
rationale: string;
|
|
832
832
|
/** EvidenceItem.id values backing this hypothesis. */
|
|
833
833
|
evidenceIds: string[];
|
|
834
|
+
/**
|
|
835
|
+
* Advisory, additive: concrete observations that would confirm a fix for
|
|
836
|
+
* THIS hypothesis worked. Emitted only for non-inconclusive hypotheses whose
|
|
837
|
+
* cited evidence carries a concrete anchor (signature / requestId /
|
|
838
|
+
* table+pk); absent otherwise. Sparse-and-concrete by design — never vacuous
|
|
839
|
+
* prose like "verify the bug is gone". See {@link Verification}.
|
|
840
|
+
*/
|
|
841
|
+
verification?: Verification[];
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* A concrete, post-fix observation that would confirm a hypothesis's fix
|
|
845
|
+
* worked. `observation` always names a concrete signal (an error signature, a
|
|
846
|
+
* request id, or a table + primary key) — the derivation emits nothing when it
|
|
847
|
+
* cannot anchor to one, so an emitted verification is never a vacuous
|
|
848
|
+
* restatement.
|
|
849
|
+
*/
|
|
850
|
+
interface Verification {
|
|
851
|
+
/** Names a concrete signal — signature, request id, or table/pk. */
|
|
852
|
+
observation: string;
|
|
853
|
+
/** EvidenceItem.id values this observation is anchored to. */
|
|
854
|
+
evidenceIds: string[];
|
|
855
|
+
how: "session" | "request" | "db";
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Where the incident was located, when a locate ran. Advisory and optional
|
|
859
|
+
* (absent for explicit baseline/current comparison bundles that never locate).
|
|
860
|
+
* Shape shared with the node locate engine's `LocateMatch` and War-game 02's
|
|
861
|
+
* deterministic token join — `method` distinguishes the two so 02 can populate
|
|
862
|
+
* "token" without a schema change (War-game 02 Fork C: one definition, reused).
|
|
863
|
+
*/
|
|
864
|
+
interface Located {
|
|
865
|
+
outcome: "matched" | "inconclusive";
|
|
866
|
+
/** 0..1 locate confidence. */
|
|
867
|
+
confidence: number;
|
|
868
|
+
/**
|
|
869
|
+
* How the incident was located. "fuzzy" = the scored locate engine;
|
|
870
|
+
* "token" = a deterministic token join (War-game 02). Absent when a caller
|
|
871
|
+
* supplies neither.
|
|
872
|
+
*/
|
|
873
|
+
method?: "fuzzy" | "token";
|
|
874
|
+
/** The matched session, present ONLY when outcome === "matched". Never fabricated. */
|
|
875
|
+
sessionId?: string;
|
|
876
|
+
reasons?: string[];
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Bundle-level answer to "how much context do we actually have here?" — the
|
|
880
|
+
* LOW_CONTEXT signal. Derived purely from the assembled bundle (evidence
|
|
881
|
+
* lanes, gaps, hypothesis strength, locate confidence). Advisory: it NEVER
|
|
882
|
+
* gates or blocks bundle emission. `reasons` is the load-bearing part; `score`
|
|
883
|
+
* and `level` summarize it.
|
|
884
|
+
*/
|
|
885
|
+
interface ContextCompleteness {
|
|
886
|
+
/** 0..1; higher = richer, more actionable context. */
|
|
887
|
+
score: number;
|
|
888
|
+
level: "high" | "medium" | "low";
|
|
889
|
+
/** Human-legible drivers: missing lanes, thin evidence, inconclusive locate. */
|
|
890
|
+
reasons: string[];
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Consumer-side advisory: what the CONSUMING agent should do when context is
|
|
894
|
+
* thin. Distinct from {@link EvidenceGap} (capture-side: what evidence is
|
|
895
|
+
* missing) — escalation is what to do about it. Always present; `recommended`
|
|
896
|
+
* is false with an empty `when` when context is adequate. Never gates the
|
|
897
|
+
* bundle (VISION: advisory, never a boolean verdict on the bug itself).
|
|
898
|
+
*/
|
|
899
|
+
interface Escalation {
|
|
900
|
+
recommended: boolean;
|
|
901
|
+
/** Conditions phrased for the consuming agent, e.g. "if you cannot reproduce
|
|
902
|
+
* via the anchored request, stop and request human triage". */
|
|
903
|
+
when: string[];
|
|
834
904
|
}
|
|
835
905
|
interface EvidenceGap {
|
|
836
906
|
lane: EvidenceLane;
|
|
@@ -871,12 +941,22 @@ interface RankedBundle {
|
|
|
871
941
|
gaps: EvidenceGap[];
|
|
872
942
|
/** Advisory-only: suggested capture escalations when evidence is thin. */
|
|
873
943
|
directives: CaptureDirective[];
|
|
944
|
+
/** How much actionable context this bundle carries. Advisory, never gates. */
|
|
945
|
+
contextCompleteness: ContextCompleteness;
|
|
946
|
+
/** What the consuming agent should do when context is thin. Advisory. */
|
|
947
|
+
escalation: Escalation;
|
|
948
|
+
/** Where the incident was located, when a locate ran. Absent otherwise. */
|
|
949
|
+
located?: Located;
|
|
874
950
|
}
|
|
875
951
|
interface AssembleBundleInput {
|
|
876
952
|
symptom: Symptom;
|
|
877
953
|
evidence: EvidenceItem[];
|
|
878
954
|
intent: IntentSignal[];
|
|
879
955
|
gaps?: EvidenceGap[];
|
|
956
|
+
/** The locate decision, when one ran (auto-locate / token join). Threaded
|
|
957
|
+
* onto the bundle as {@link RankedBundle.located} and folded into
|
|
958
|
+
* completeness. Omit for explicit baseline/current comparison bundles. */
|
|
959
|
+
located?: Located;
|
|
880
960
|
}
|
|
881
961
|
/**
|
|
882
962
|
* Compose the RankedBundle: rank the complete evidence set (nothing dropped),
|
|
@@ -971,4 +1051,4 @@ type Stack = "nextjs" | "react" | "vue" | "svelte" | "vite" | "express" | "hono"
|
|
|
971
1051
|
/** Stable ordered id list, handy for iterating or building pickers. */
|
|
972
1052
|
declare const STACK_IDS: readonly Stack[];
|
|
973
1053
|
|
|
974
|
-
export { type AbandonedFlowOptions, type AddBugEventOptions, type AssembleBundleInput, type AutoFlagController, type AutoFlagOptions, BROWSER_REDACTION_POLICY, type BodyRedactionOptions, type BodyRedactionResult, type BugEvent, type BugReport, CRUMBTRAIL_EVENT_KINDS, CRUMBTRAIL_REQUEST_HEADER, CRUMBTRAIL_REQUEST_HEADER_LOWER, CRUMBTRAIL_REQUEST_ID_MAX_LENGTH, CRUMBTRAIL_SCHEMA_VERSION, CRUMBTRAIL_SESSION_HEADER, CRUMBTRAIL_SESSION_HEADER_LOWER, type CaptureDirective, type CollectorCleanup, type CollectorContext, type CommitInfo, Crumbtrail, type CrumbtrailCapabilities, type CrumbtrailConfig, type CrumbtrailPlatform, type CrumbtrailPreset, type CrumbtrailSdkDescriptor, type CrumbtrailTransport, DB_DIFF_BULK_EVENT_KIND, DB_DIFF_EVENT_KIND, DB_READ_BULK_EVENT_KIND, DB_READ_EVENT_KIND, DEFAULT_CONFIG, DEFAULT_SESSION_STORAGE_KEY, type DbDiffBulkEventData, type DbDiffEventData, type DbDiffOp, type DbEngine, type DbReadBulkEventData, type DbReadEventData, EVIDENCE_SCHEMA_VERSION, EVIDENCE_SOURCE_SCHEMA_VERSION, type ElementSignature, type EnvDeclaration, type EnvSnapshot, EventBus, type EvidenceGap, type EvidenceItem, type EvidenceJoinKey, type EvidenceLane, type EvidenceQuery, type EvidenceRef, type EvidenceSourceDescriptor, type EvidenceSourceResult, FUSION_SCHEMA_VERSION, type FlagBugOptions, type GitHostClient, type GitHostRef, HttpTransport, type Hypothesis, type HypothesisKind, type InputValueRedactionOptions, type IntentSignal, type InteractionElementDescriptor, type InteractionElementDescriptorFactory, type OutboundCorrelation, PRESET_FULL, PRESET_LIGHT, PRESET_PASSIVE, type PayloadSummary, type PersistedSession, REDACTED_STORAGE_KEY, REDACTED_VALUE, type RageClickOptions, type RankedBundle, type RedactionAction, type RedactionField, type RedactionMetadata, type RedactionResult, type RetryStormOptions, RingBuffer, STACK_IDS, type SessionStore, type Signal, type SignalDetector, type SlowResponseOptions, type Stack, type StoredValueRedactionOptions, type Symptom, type TargetDescriptor, type TargetDescriptorResolver, type W3CTraceContext, W3C_TRACEPARENT_HEADER, WebTargetDescriptorResolver, abandonedFlowDetector, assembleBundle, attachRedactionMetadata, buildEnvDelta, buildEnvSnapshot, canInjectCorrelationHeaders, computeElementPath, computeElementSignature, createAutoFlagController, createCrumbtrailRequestHeaders, createWebSessionStore, environmentCollector, errorDetector, errorSignature, formatTraceparent, generateRequestId, generateSpanId, generateTraceContext, generateTraceId, hashString, inferIntent, mergeRedactionMetadata, parseTraceparent, rageClickDetector, redactCookieMap, redactCookieName, redactCookieValue, redactHeaders, redactInputValue, redactNetworkTextBody, redactStorageKey, redactStoredValue, redactTokenLikeString, redactUrl, redactUrlsInText, redactValue, resolveOutboundCorrelation, retryStormDetector, slowResponseDetector, summarizeBinaryPayload, summarizeOmittedPayload, webTargetDescriptorResolver };
|
|
1054
|
+
export { type AbandonedFlowOptions, type AddBugEventOptions, type AssembleBundleInput, type AutoFlagController, type AutoFlagOptions, BROWSER_REDACTION_POLICY, type BodyRedactionOptions, type BodyRedactionResult, type BugEvent, type BugReport, CRUMBTRAIL_EVENT_KINDS, CRUMBTRAIL_REQUEST_HEADER, CRUMBTRAIL_REQUEST_HEADER_LOWER, CRUMBTRAIL_REQUEST_ID_MAX_LENGTH, CRUMBTRAIL_SCHEMA_VERSION, CRUMBTRAIL_SESSION_HEADER, CRUMBTRAIL_SESSION_HEADER_LOWER, type CaptureDirective, type CollectorCleanup, type CollectorContext, type CommitInfo, type ContextCompleteness, Crumbtrail, type CrumbtrailCapabilities, type CrumbtrailConfig, type CrumbtrailPlatform, type CrumbtrailPreset, type CrumbtrailSdkDescriptor, type CrumbtrailTransport, DB_DIFF_BULK_EVENT_KIND, DB_DIFF_EVENT_KIND, DB_READ_BULK_EVENT_KIND, DB_READ_EVENT_KIND, DEFAULT_CONFIG, DEFAULT_SESSION_STORAGE_KEY, type DbDiffBulkEventData, type DbDiffEventData, type DbDiffOp, type DbEngine, type DbReadBulkEventData, type DbReadEventData, EVIDENCE_SCHEMA_VERSION, EVIDENCE_SOURCE_SCHEMA_VERSION, type ElementSignature, type EnvDeclaration, type EnvSnapshot, type Escalation, EventBus, type EvidenceGap, type EvidenceItem, type EvidenceJoinKey, type EvidenceLane, type EvidenceQuery, type EvidenceRef, type EvidenceSourceDescriptor, type EvidenceSourceResult, FUSION_SCHEMA_VERSION, type FlagBugOptions, type GitHostClient, type GitHostRef, HttpTransport, type Hypothesis, type HypothesisKind, type InputValueRedactionOptions, type IntentSignal, type InteractionElementDescriptor, type InteractionElementDescriptorFactory, type Located, type OutboundCorrelation, PRESET_FULL, PRESET_LIGHT, PRESET_PASSIVE, type PayloadSummary, type PersistedSession, REDACTED_STORAGE_KEY, REDACTED_VALUE, type RageClickOptions, type RankedBundle, type RedactionAction, type RedactionField, type RedactionMetadata, type RedactionResult, type RetryStormOptions, RingBuffer, STACK_IDS, type SessionStore, type Signal, type SignalDetector, type SlowResponseOptions, type Stack, type StoredValueRedactionOptions, type Symptom, type TargetDescriptor, type TargetDescriptorResolver, type Verification, type W3CTraceContext, W3C_TRACEPARENT_HEADER, WebTargetDescriptorResolver, abandonedFlowDetector, assembleBundle, attachRedactionMetadata, buildEnvDelta, buildEnvSnapshot, canInjectCorrelationHeaders, computeElementPath, computeElementSignature, createAutoFlagController, createCrumbtrailRequestHeaders, createWebSessionStore, environmentCollector, errorDetector, errorSignature, formatTraceparent, generateRequestId, generateSpanId, generateTraceContext, generateTraceId, hashString, inferIntent, mergeRedactionMetadata, parseTraceparent, rageClickDetector, redactCookieMap, redactCookieName, redactCookieValue, redactHeaders, redactInputValue, redactNetworkTextBody, redactStorageKey, redactStoredValue, redactTokenLikeString, redactUrl, redactUrlsInText, redactValue, resolveOutboundCorrelation, retryStormDetector, slowResponseDetector, summarizeBinaryPayload, summarizeOmittedPayload, webTargetDescriptorResolver };
|
package/dist/index.js
CHANGED
|
@@ -4319,21 +4319,139 @@ function inferIntent(evidence, commits) {
|
|
|
4319
4319
|
var FUSION_SCHEMA_VERSION = "fusion.v1";
|
|
4320
4320
|
function assembleBundle(input) {
|
|
4321
4321
|
const evidence = rankEvidence(input.symptom, input.evidence);
|
|
4322
|
-
const
|
|
4322
|
+
const classified = classifyHypotheses(
|
|
4323
4323
|
input.symptom,
|
|
4324
4324
|
input.evidence,
|
|
4325
4325
|
input.intent
|
|
4326
4326
|
);
|
|
4327
4327
|
const gaps = input.gaps ?? [];
|
|
4328
|
+
const located = input.located;
|
|
4329
|
+
const hypotheses = classified.map((hypothesis) => {
|
|
4330
|
+
const verification = deriveVerification(hypothesis, evidence);
|
|
4331
|
+
return verification.length > 0 ? { ...hypothesis, verification } : hypothesis;
|
|
4332
|
+
});
|
|
4333
|
+
const contextCompleteness = deriveContextCompleteness(
|
|
4334
|
+
evidence,
|
|
4335
|
+
gaps,
|
|
4336
|
+
hypotheses,
|
|
4337
|
+
located
|
|
4338
|
+
);
|
|
4339
|
+
const escalation = deriveEscalation(contextCompleteness, hypotheses);
|
|
4328
4340
|
return {
|
|
4329
4341
|
schemaVersion: FUSION_SCHEMA_VERSION,
|
|
4330
4342
|
symptom: input.symptom,
|
|
4331
4343
|
evidence,
|
|
4332
4344
|
opinion: { stance: "advisory", hypotheses },
|
|
4333
4345
|
gaps,
|
|
4334
|
-
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps)
|
|
4346
|
+
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps),
|
|
4347
|
+
contextCompleteness,
|
|
4348
|
+
escalation,
|
|
4349
|
+
...located ? { located } : {}
|
|
4335
4350
|
};
|
|
4336
4351
|
}
|
|
4352
|
+
function clamp01(value) {
|
|
4353
|
+
if (Number.isNaN(value)) return 0;
|
|
4354
|
+
return Math.max(0, Math.min(1, value));
|
|
4355
|
+
}
|
|
4356
|
+
var COMPLETENESS_LANES = [
|
|
4357
|
+
"network",
|
|
4358
|
+
"db",
|
|
4359
|
+
"flow",
|
|
4360
|
+
"browser",
|
|
4361
|
+
"env"
|
|
4362
|
+
];
|
|
4363
|
+
function deriveContextCompleteness(evidence, gaps, hypotheses, located) {
|
|
4364
|
+
const lanesPresent = new Set(evidence.map((item) => item.lane));
|
|
4365
|
+
const informativePresent = COMPLETENESS_LANES.filter(
|
|
4366
|
+
(lane) => lanesPresent.has(lane)
|
|
4367
|
+
);
|
|
4368
|
+
const breadth = Math.min(1, informativePresent.length / 3);
|
|
4369
|
+
const volume = Math.min(1, evidence.length / 5);
|
|
4370
|
+
const top = hypotheses[0];
|
|
4371
|
+
const hypothesisStrength = !top || top.kind === "inconclusive" ? 0 : top.confidence;
|
|
4372
|
+
let score = 0.4 * breadth + 0.25 * volume + 0.35 * hypothesisStrength;
|
|
4373
|
+
if (located) {
|
|
4374
|
+
score = located.outcome === "matched" ? 0.85 * score + 0.15 * clamp01(located.confidence) : score * 0.6;
|
|
4375
|
+
}
|
|
4376
|
+
const hardGaps = gaps.filter((gap) => gap.kind === "source-unavailable").length;
|
|
4377
|
+
const softGaps = gaps.length - hardGaps;
|
|
4378
|
+
const gapPenalty = Math.min(0.5, softGaps * 0.1 + hardGaps * 0.3);
|
|
4379
|
+
score = clamp01(score - gapPenalty);
|
|
4380
|
+
const level = score < 0.34 ? "low" : score < 0.67 ? "medium" : "high";
|
|
4381
|
+
const reasons = [];
|
|
4382
|
+
const missingLanes = COMPLETENESS_LANES.filter(
|
|
4383
|
+
(lane) => !lanesPresent.has(lane)
|
|
4384
|
+
);
|
|
4385
|
+
if (informativePresent.length === 0) {
|
|
4386
|
+
reasons.push("no network/db/flow/browser/env evidence captured");
|
|
4387
|
+
} else if (missingLanes.length > 0) {
|
|
4388
|
+
reasons.push(`missing evidence lanes: ${missingLanes.join(", ")}`);
|
|
4389
|
+
}
|
|
4390
|
+
if (evidence.length > 0 && evidence.length < 3) {
|
|
4391
|
+
reasons.push(`thin evidence (${evidence.length} item(s))`);
|
|
4392
|
+
}
|
|
4393
|
+
if (hardGaps > 0) reasons.push(`source unavailable for ${hardGaps} lane(s)`);
|
|
4394
|
+
if (softGaps > 0) reasons.push(`${softGaps} evidence gap(s)`);
|
|
4395
|
+
if (located?.outcome === "inconclusive") {
|
|
4396
|
+
reasons.push("incident location inconclusive");
|
|
4397
|
+
}
|
|
4398
|
+
if (!top || top.kind === "inconclusive") {
|
|
4399
|
+
reasons.push("no distinguishing hypothesis");
|
|
4400
|
+
}
|
|
4401
|
+
return { score, level, reasons };
|
|
4402
|
+
}
|
|
4403
|
+
function pkString(pk) {
|
|
4404
|
+
return Object.keys(pk).sort().map((key) => `${key}=${String(pk[key])}`).join(", ");
|
|
4405
|
+
}
|
|
4406
|
+
function deriveVerification(hypothesis, evidence) {
|
|
4407
|
+
if (hypothesis.kind === "inconclusive") return [];
|
|
4408
|
+
const cited = new Set(hypothesis.evidenceIds);
|
|
4409
|
+
const out = [];
|
|
4410
|
+
for (const item of evidence) {
|
|
4411
|
+
if (!cited.has(item.id)) continue;
|
|
4412
|
+
const ref = item.ref;
|
|
4413
|
+
if (item.lane === "db" && ref.table && ref.pk && Object.keys(ref.pk).length > 0) {
|
|
4414
|
+
out.push({
|
|
4415
|
+
observation: `row in ${ref.table} (${pkString(ref.pk)}) matches the intended post-fix state`,
|
|
4416
|
+
evidenceIds: [item.id],
|
|
4417
|
+
how: "db"
|
|
4418
|
+
});
|
|
4419
|
+
} else if (ref.requestId) {
|
|
4420
|
+
const sigPart = ref.sig ? ` for signature ${ref.sig}` : "";
|
|
4421
|
+
out.push({
|
|
4422
|
+
observation: `request ${ref.requestId} succeeds${sigPart} on a fresh run (no error response)`,
|
|
4423
|
+
evidenceIds: [item.id],
|
|
4424
|
+
how: "request"
|
|
4425
|
+
});
|
|
4426
|
+
} else if (ref.sig) {
|
|
4427
|
+
out.push({
|
|
4428
|
+
observation: `error signature ${ref.sig} no longer appears in a fresh session over the same route`,
|
|
4429
|
+
evidenceIds: [item.id],
|
|
4430
|
+
how: "session"
|
|
4431
|
+
});
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
return out;
|
|
4435
|
+
}
|
|
4436
|
+
function deriveEscalation(completeness, hypotheses) {
|
|
4437
|
+
const allInconclusive = hypotheses.length > 0 && hypotheses.every((hypothesis) => hypothesis.kind === "inconclusive");
|
|
4438
|
+
const recommended = completeness.level === "low" || allInconclusive;
|
|
4439
|
+
if (!recommended) return { recommended: false, when: [] };
|
|
4440
|
+
const when = [
|
|
4441
|
+
"if you cannot reproduce the symptom via the anchored request or session, stop and request human triage \u2014 do not widen the search"
|
|
4442
|
+
];
|
|
4443
|
+
if (completeness.level === "low") {
|
|
4444
|
+
when.push(
|
|
4445
|
+
"context is thin: confirm the missing evidence lanes before acting on the top hypothesis"
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
if (allInconclusive) {
|
|
4449
|
+
when.push(
|
|
4450
|
+
"no hypothesis is distinguished; treat the listed causes as equally unproven"
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
return { recommended, when };
|
|
4454
|
+
}
|
|
4337
4455
|
var LANE_PRIOR = {
|
|
4338
4456
|
db: 0.2,
|
|
4339
4457
|
network: 0.2,
|