@descryy/ir 0.1.0
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/LICENSE +21 -0
- package/dist/capabilities.d.ts +83 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +91 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/conformance/bind.d.ts +82 -0
- package/dist/conformance/bind.d.ts.map +1 -0
- package/dist/conformance/bind.js +157 -0
- package/dist/conformance/bind.js.map +1 -0
- package/dist/conformance/cli.d.ts +25 -0
- package/dist/conformance/cli.d.ts.map +1 -0
- package/dist/conformance/cli.js +137 -0
- package/dist/conformance/cli.js.map +1 -0
- package/dist/conformance/compare.d.ts +74 -0
- package/dist/conformance/compare.d.ts.map +1 -0
- package/dist/conformance/compare.js +140 -0
- package/dist/conformance/compare.js.map +1 -0
- package/dist/conformance/compose.d.ts +79 -0
- package/dist/conformance/compose.d.ts.map +1 -0
- package/dist/conformance/compose.js +167 -0
- package/dist/conformance/compose.js.map +1 -0
- package/dist/conformance/golden.d.ts +110 -0
- package/dist/conformance/golden.d.ts.map +1 -0
- package/dist/conformance/golden.js +252 -0
- package/dist/conformance/golden.js.map +1 -0
- package/dist/conformance/index.d.ts +26 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +18 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/manifest-check.d.ts +78 -0
- package/dist/conformance/manifest-check.d.ts.map +1 -0
- package/dist/conformance/manifest-check.js +149 -0
- package/dist/conformance/manifest-check.js.map +1 -0
- package/dist/conformance/manifest.d.ts +83 -0
- package/dist/conformance/manifest.d.ts.map +1 -0
- package/dist/conformance/manifest.js +158 -0
- package/dist/conformance/manifest.js.map +1 -0
- package/dist/conformance/report.d.ts +14 -0
- package/dist/conformance/report.d.ts.map +1 -0
- package/dist/conformance/report.js +87 -0
- package/dist/conformance/report.js.map +1 -0
- package/dist/conformance/run.d.ts +172 -0
- package/dist/conformance/run.d.ts.map +1 -0
- package/dist/conformance/run.js +626 -0
- package/dist/conformance/run.js.map +1 -0
- package/dist/contracts.d.ts +198 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +10 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contradiction.d.ts +112 -0
- package/dist/contradiction.d.ts.map +1 -0
- package/dist/contradiction.js +97 -0
- package/dist/contradiction.js.map +1 -0
- package/dist/finding.d.ts +567 -0
- package/dist/finding.d.ts.map +1 -0
- package/dist/finding.js +491 -0
- package/dist/finding.js.map +1 -0
- package/dist/hypothesis.d.ts +130 -0
- package/dist/hypothesis.d.ts.map +1 -0
- package/dist/hypothesis.js +116 -0
- package/dist/hypothesis.js.map +1 -0
- package/dist/identity.d.ts +295 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +404 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/ir.d.ts +360 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +76 -0
- package/dist/ir.js.map +1 -0
- package/dist/model-tables.d.ts +111 -0
- package/dist/model-tables.d.ts.map +1 -0
- package/dist/model-tables.js +103 -0
- package/dist/model-tables.js.map +1 -0
- package/dist/normalise.d.ts +114 -0
- package/dist/normalise.d.ts.map +1 -0
- package/dist/normalise.js +603 -0
- package/dist/normalise.js.map +1 -0
- package/dist/reliability.d.ts +89 -0
- package/dist/reliability.d.ts.map +1 -0
- package/dist/reliability.js +181 -0
- package/dist/reliability.js.map +1 -0
- package/dist/tool-surface.d.ts +131 -0
- package/dist/tool-surface.d.ts.map +1 -0
- package/dist/tool-surface.js +133 -0
- package/dist/tool-surface.js.map +1 -0
- package/dist/vocabulary.d.ts +73 -0
- package/dist/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary.js +150 -0
- package/dist/vocabulary.js.map +1 -0
- package/package.json +27 -0
package/dist/finding.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared finding record, and G3/G4 — the gates that decide what may be
|
|
3
|
+
* called what.
|
|
4
|
+
*
|
|
5
|
+
* `documents/architecture/descry-ai-layer.md` §20 is canonical for the type:
|
|
6
|
+
* **fourteen fields, no more and no fewer.** §42 carries a fifteenth,
|
|
7
|
+
* `Hypotheses considered`, which belongs to the output a developer reads and
|
|
8
|
+
* not to the stored record; it is deliberately absent here. DEC-276's own prose
|
|
9
|
+
* says *"eleven fields are extended to thirteen"* — eleven plus three is
|
|
10
|
+
* fourteen, and §20 corrects the arithmetic while keeping the field list the
|
|
11
|
+
* decision actually produced.
|
|
12
|
+
*
|
|
13
|
+
* It lives in `@descryy/ir` because two repositories construct and assert
|
|
14
|
+
* against it — the pipeline that produces findings and the golden-report test
|
|
15
|
+
* that checks all fourteen render — and both plans put it here specifically so
|
|
16
|
+
* that a divergence is a compile error rather than a field quietly missing from
|
|
17
|
+
* a page.
|
|
18
|
+
*
|
|
19
|
+
* ## Five axes, and they are not interchangeable
|
|
20
|
+
*
|
|
21
|
+
* A finding says five separate things, and collapsing any pair of them loses a
|
|
22
|
+
* distinction the product exists to make:
|
|
23
|
+
*
|
|
24
|
+
* - **Confidence** — how sure we are (§19's five categories).
|
|
25
|
+
* - **Fault layer** — whose problem it is (DEC-273). A finding can be
|
|
26
|
+
* `confirmed` and `environment` at once: fully proven, and not the
|
|
27
|
+
* application's fault.
|
|
28
|
+
* - **Reliability class** — how certain the *category* of claim is (§15.2).
|
|
29
|
+
* - **Resolution level** — how well the *code was understood* (§11A.6).
|
|
30
|
+
* - **Environment level** — how faithful the environment that produced it was
|
|
31
|
+
* (architecture §16.3).
|
|
32
|
+
*
|
|
33
|
+
* **Coverage is a sixth thing and not a confidence value.** §19's adopted note
|
|
34
|
+
* moved untested scope out of the category list and into `Verification status`
|
|
35
|
+
* deliberately: *"we found nothing"* and *"we did not look"* are different
|
|
36
|
+
* statements, and the precision-over-recall rule is only defensible while the
|
|
37
|
+
* second one has an honest home.
|
|
38
|
+
*/
|
|
39
|
+
import { reliabilityCap } from "./reliability.js";
|
|
40
|
+
import { RESOLUTION_LEVELS } from "./ir.js";
|
|
41
|
+
/**
|
|
42
|
+
* The one cast in this module, kept in one place so the brand has exactly one
|
|
43
|
+
* runtime origin. Not exported — {@link rederive} is the whole legitimate
|
|
44
|
+
* surface for producing one on the read side of a storage boundary; every
|
|
45
|
+
* other producer is one of the derivations below, computed fresh.
|
|
46
|
+
*/
|
|
47
|
+
function derive(value) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* §19's five, adopted as superseding the architecture's previous four.
|
|
52
|
+
*
|
|
53
|
+
* `not analysable` and `unsupported` are refusals rather than readings of
|
|
54
|
+
* evidence — *"we cannot establish this here"* and *"we do not do this at
|
|
55
|
+
* all"* — and G4 below does not produce them; they are inherited from the
|
|
56
|
+
* static case unchanged.
|
|
57
|
+
*/
|
|
58
|
+
export const REPORT_CATEGORIES = [
|
|
59
|
+
"confirmed",
|
|
60
|
+
"strongly supported",
|
|
61
|
+
"unconfirmed",
|
|
62
|
+
"not analysable",
|
|
63
|
+
"unsupported",
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* The three of the five that G4's step 2 can return, ordered strongest first.
|
|
67
|
+
*
|
|
68
|
+
* A narrower return type than {@link ReportCategory} on purpose: the gate reads
|
|
69
|
+
* evidence, and no amount of evidence turns into *"we cannot analyse this"*.
|
|
70
|
+
*/
|
|
71
|
+
export const EVIDENCE_CATEGORIES = ["confirmed", "strongly supported", "unconfirmed"];
|
|
72
|
+
/**
|
|
73
|
+
* DEC-273 — a second axis beside confidence, never a sixth category.
|
|
74
|
+
*
|
|
75
|
+
* Confidence asks *how sure are we*; fault layer asks *whose problem is this*.
|
|
76
|
+
* They are independent, and a finding is not more or less true for being an
|
|
77
|
+
* environment fault — it is attributable to a different owner.
|
|
78
|
+
*
|
|
79
|
+
* **Classification heuristics remain deliberately unspecified for `product`,
|
|
80
|
+
* and for the no-comparison-available fallback on `environment`/`dependency`.**
|
|
81
|
+
* DEC-273 rests on three real classifications, and three is not enough to
|
|
82
|
+
* generalize a pattern-match rule from — `descry-ai-layer.md` §20's own
|
|
83
|
+
* caution, unchanged by the ruling below. **`automation`, `dependency` and
|
|
84
|
+
* `environment` are the three exceptions** — see {@link classifyFaultLayer}.
|
|
85
|
+
* None of the three is a heuristic in the sense being deferred: each is a
|
|
86
|
+
* structural comparison a tool either reports or does not (a harness
|
|
87
|
+
* self-failure flag, a declared-vs-resolved dependency version, a
|
|
88
|
+
* declared-vs-observed runner version), not a pattern inferred from few
|
|
89
|
+
* examples. `automation` was unblocked 2026-08-24 by `descry-runtime`'s
|
|
90
|
+
* RT-219 self-observability channel; `dependency`/`environment`'s structural
|
|
91
|
+
* comparison is ruled in
|
|
92
|
+
* `DEC-NEXT-fault-layer-empirical-confirmation.md`, which also requires every
|
|
93
|
+
* classification to carry its own confidence marker — see
|
|
94
|
+
* {@link faultLayerConfidence}.
|
|
95
|
+
*/
|
|
96
|
+
export const FAULT_LAYERS = [
|
|
97
|
+
/** The application's own code. */
|
|
98
|
+
"product",
|
|
99
|
+
/** Descry's own test or harness logic. See {@link classifyFaultLayer}. */
|
|
100
|
+
"automation",
|
|
101
|
+
/** Runner, interpreter version, CI configuration — a mismatch, not a bug. */
|
|
102
|
+
"environment",
|
|
103
|
+
/** A pinned or transitive dependency. */
|
|
104
|
+
"dependency",
|
|
105
|
+
/** Not yet classified. An honest value, not a placeholder for a missing one. */
|
|
106
|
+
"unknown",
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* `DEC-NEXT-fault-layer-empirical-confirmation.md` — whether a fault-layer
|
|
110
|
+
* value rests on a structural comparison/rerun or a pattern-match with no
|
|
111
|
+
* comparison available. See {@link faultLayerConfidence}.
|
|
112
|
+
*/
|
|
113
|
+
export const FAULT_LAYER_CONFIDENCES = ["structural", "heuristic"];
|
|
114
|
+
/**
|
|
115
|
+
* Architecture §16.3's fidelity levels — how real the environment that produced
|
|
116
|
+
* the finding was. Emitted on every finding as `Validated Against: Level N`.
|
|
117
|
+
*
|
|
118
|
+
* 1 stub · 2 real code + disposable seeded database (the preferred default) ·
|
|
119
|
+
* 3 real code + redacted production-shaped recordings · 4 real staging.
|
|
120
|
+
* Selection prefers the highest available, 4 → 1.
|
|
121
|
+
*/
|
|
122
|
+
export const ENVIRONMENT_LEVELS = [1, 2, 3, 4];
|
|
123
|
+
/**
|
|
124
|
+
* The six independent observation channels, closed list —
|
|
125
|
+
* `ai-governance-spec.md` §5.2, ruled 2026-08-24 in
|
|
126
|
+
* `DEC-NEXT-independent-signal-type-inventory.md`, superseding DEC-276's
|
|
127
|
+
* original four.
|
|
128
|
+
*
|
|
129
|
+
* *"Two log lines are one signal type."* `E` counts **channels**, not
|
|
130
|
+
* observations, which is why {@link independentSignalTypes} derives it rather
|
|
131
|
+
* than trusting a caller's count.
|
|
132
|
+
*
|
|
133
|
+
* **Not a raw count over `descry-runtime`'s twenty-two runtime event types.**
|
|
134
|
+
* Each channel folds in the event types that belong to it (the full mapping
|
|
135
|
+
* is the spec's own table). `TEST_FAILED` — one of the four previously-open
|
|
136
|
+
* types — is deliberately **not** a seventh channel: its evidentiary weight
|
|
137
|
+
* belongs to `M` alone (see {@link directMechanismEvidence}), and counting it
|
|
138
|
+
* as one more corroborating channel would route a reproduced failure through
|
|
139
|
+
* the strictly weaker `E >= 1` path when it already qualifies for `M`'s
|
|
140
|
+
* direct path. `COLLECTOR_ERROR` and every harness-action type
|
|
141
|
+
* (`PROCESS_STARTED`, `NAVIGATION`, `CLICK`, ...) are excluded by name — they
|
|
142
|
+
* record that the observer failed, or what Descry did, never what the system
|
|
143
|
+
* under test did.
|
|
144
|
+
*
|
|
145
|
+
* **This list is deliberately narrow, and widening it must stay a compile
|
|
146
|
+
* error.** A new collector arriving quietly and re-tuning every confidence
|
|
147
|
+
* boundary with nothing failing is the exact defect the result-state enum next
|
|
148
|
+
* door exists to prevent, one level up. Typing `E` as a bare `number` would
|
|
149
|
+
* "fix" the friction by removing the alarm. An unrecognised event type must
|
|
150
|
+
* fail closed — {@link RuntimeEvidence.signal} of `null` — until this table is
|
|
151
|
+
* extended by the same process that built it.
|
|
152
|
+
*/
|
|
153
|
+
export const RUNTIME_SIGNAL_TYPES = [
|
|
154
|
+
"browser-visual",
|
|
155
|
+
"browser-console",
|
|
156
|
+
"network",
|
|
157
|
+
"backend-log",
|
|
158
|
+
"database",
|
|
159
|
+
"external-service",
|
|
160
|
+
];
|
|
161
|
+
/**
|
|
162
|
+
* Legal values of `E`. Bounded by the six channels above, hence 0–6.
|
|
163
|
+
*/
|
|
164
|
+
export const SIGNAL_TYPE_COUNTS = [0, 1, 2, 3, 4, 5, 6];
|
|
165
|
+
/**
|
|
166
|
+
* §20's fourteen fields, in §20's order.
|
|
167
|
+
*
|
|
168
|
+
* Exported so the golden-report test can iterate the canonical list rather than
|
|
169
|
+
* restate it — the desktop plan's test previously asserted eleven fields and
|
|
170
|
+
* would have passed green while two axes vanished from the product. A list
|
|
171
|
+
* asserted against `Object.keys` of a real finding is what makes that a
|
|
172
|
+
* failure instead of a silence.
|
|
173
|
+
*/
|
|
174
|
+
export const FINDING_FIELDS = [
|
|
175
|
+
"finding",
|
|
176
|
+
"scope",
|
|
177
|
+
"affectedComponents",
|
|
178
|
+
"evidence",
|
|
179
|
+
"graphPath",
|
|
180
|
+
"runtimeEvidence",
|
|
181
|
+
"relevantChange",
|
|
182
|
+
"confidence",
|
|
183
|
+
"faultLayer",
|
|
184
|
+
"reliabilityClass",
|
|
185
|
+
"resolutionLevel",
|
|
186
|
+
"environmentLevel",
|
|
187
|
+
"recommendedAction",
|
|
188
|
+
"verificationStatus",
|
|
189
|
+
];
|
|
190
|
+
/**
|
|
191
|
+
* `E` — how many of the six channels named this finding, counting
|
|
192
|
+
* **channels** rather than observations, and ignoring any record whose
|
|
193
|
+
* `signal` is `null` (excluded by design — see {@link RuntimeEvidence}).
|
|
194
|
+
*/
|
|
195
|
+
export function independentSignalTypes(evidence) {
|
|
196
|
+
const distinct = new Set(evidence.map((item) => item.signal).filter((signal) => signal !== null));
|
|
197
|
+
// Indexed rather than cast: the set cannot exceed the declared channels, and
|
|
198
|
+
// the clamp keeps that provable to the compiler instead of asserted. The
|
|
199
|
+
// bound is read off the array so widening the vocabulary does not need this
|
|
200
|
+
// line changed too.
|
|
201
|
+
return SIGNAL_TYPE_COUNTS[Math.min(distinct.size, SIGNAL_TYPE_COUNTS.length - 1)] ?? 0;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* `M` — direct mechanism evidence, `ai-governance-spec.md` §5.3. `true` when
|
|
205
|
+
* at least one runtime evidence record's resolved graph node id equals
|
|
206
|
+
* `failurePoint` — the hypothesis's named failure site — and that record is
|
|
207
|
+
* itself marked {@link RuntimeEvidence.directMechanism}, not merely
|
|
208
|
+
* co-occurring with the failure in time.
|
|
209
|
+
*
|
|
210
|
+
* A plain node-id match, the same identity mechanism §10 already requires for
|
|
211
|
+
* graph/runtime contradiction detection — deterministic, never a model
|
|
212
|
+
* judgement, which is what makes it a gate input rather than a slot output.
|
|
213
|
+
*/
|
|
214
|
+
export function directMechanismEvidence(evidence, failurePoint) {
|
|
215
|
+
return evidence.some((item) => item.directMechanism === true && item.nodeId === failurePoint);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* `G3` — the evidence precondition, `ai-governance-spec.md` §4. `true` iff at
|
|
219
|
+
* least one runtime evidence record exists at all for this hypothesis,
|
|
220
|
+
* however it classifies under `E`/`M`. Restated from `root-cause-score.ts`'s
|
|
221
|
+
* own module doc: `RuntimeEvidence > 0` is a precondition on promotion above
|
|
222
|
+
* `unconfirmed`, never an addend folded into {@link reportCategory}'s step 2
|
|
223
|
+
* arithmetic.
|
|
224
|
+
*/
|
|
225
|
+
export function evidencePrecondition(evidence) {
|
|
226
|
+
return evidence.length > 0;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* P11 — `ai-layer-plan.md` §11, `ai-governance-spec.md` §7. DEC-273's fault
|
|
230
|
+
* layer, computed rather than authored, for the one value that is currently
|
|
231
|
+
* groundable: {@link RuntimeEvidence.harnessFailure} is a structural fact
|
|
232
|
+
* (`descry-runtime` RT-219's self-observability channel either reported the
|
|
233
|
+
* harness dying or it did not) rather than a pattern inferred from a small
|
|
234
|
+
* number of examples, so classifying `"automation"` from it is not the kind
|
|
235
|
+
* of heuristic {@link FAULT_LAYERS}'s doc defers.
|
|
236
|
+
*
|
|
237
|
+
* **`product` / `environment` / `dependency` are deliberately not
|
|
238
|
+
* classified here.** No mechanism this codebase has built yet can positively
|
|
239
|
+
* distinguish them from evidence alone, and DEC-273's own three real
|
|
240
|
+
* classifications are explicitly too few to generalize a rule from. Every
|
|
241
|
+
* finding that is not an `automation` case is `"unknown"` — the honest
|
|
242
|
+
* default, not a placeholder for a missing case.
|
|
243
|
+
*
|
|
244
|
+
* A harness failure classifies `"automation"` even when domain evidence is
|
|
245
|
+
* also present: a collector that died mid-run can leave partial application
|
|
246
|
+
* evidence behind, and that evidence does not make the underlying failure
|
|
247
|
+
* any less Descry's own.
|
|
248
|
+
*/
|
|
249
|
+
export function classifyFaultLayer(evidence) {
|
|
250
|
+
// Priority order is not specified by the ruling for the three structural
|
|
251
|
+
// checks; automation goes first because a harness self-failure explains why
|
|
252
|
+
// nothing else on the run can be trusted, and dependency before environment
|
|
253
|
+
// because a pinned-dependency mismatch is the narrower, more specific fact
|
|
254
|
+
// of the two.
|
|
255
|
+
if (evidence.some((item) => item.harnessFailure === true))
|
|
256
|
+
return "automation";
|
|
257
|
+
if (evidence.some((item) => item.dependencyVersionMismatch === true))
|
|
258
|
+
return "dependency";
|
|
259
|
+
if (evidence.some((item) => item.environmentVersionMismatch === true))
|
|
260
|
+
return "environment";
|
|
261
|
+
return "unknown";
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* DEC-NEXT-fault-layer-empirical-confirmation.md — every fault-layer value
|
|
265
|
+
* carries its own confidence marker, independent of the finding's
|
|
266
|
+
* `Confidence`: `structural` (a comparison or rerun confirmed it) vs.
|
|
267
|
+
* `heuristic` (pattern-matched, no comparison run). *"A bare `environment` or
|
|
268
|
+
* `dependency` label must never be presented at the same visual weight as a
|
|
269
|
+
* `confirmed` finding unless it earned `structural`."*
|
|
270
|
+
*
|
|
271
|
+
* **Every path {@link classifyFaultLayer} can produce today is `structural`.**
|
|
272
|
+
* `product`'s heuristic and the no-comparison-available fallback for
|
|
273
|
+
* `dependency`/`environment` are the ones the ruling explicitly defers
|
|
274
|
+
* (`descry-ai-layer.md` §20's caution against generalizing from three
|
|
275
|
+
* classifications) and are not built — `heuristic` is reserved for whichever
|
|
276
|
+
* future function writes that fallback, not returned by anything here.
|
|
277
|
+
*/
|
|
278
|
+
export function faultLayerConfidence(_layer) {
|
|
279
|
+
return "structural";
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* `R` — the **minimum** resolution across the path.
|
|
283
|
+
*
|
|
284
|
+
* Minimum rather than average, first or last, and that single choice is what
|
|
285
|
+
* makes NOTE-4's control #6 (*a five-hop path inherits its worst hop's cap*)
|
|
286
|
+
* true by construction rather than by a second rule that could disagree.
|
|
287
|
+
*
|
|
288
|
+
* **An empty path derives R0**, the conservative floor: no hop resolved
|
|
289
|
+
* anything, so nothing above R0 has been established, and rule 2 says omit
|
|
290
|
+
* rather than overstate. Whether a purely runtime-observed finding should
|
|
291
|
+
* instead carry a synthetic R4 hop — so that a symptom confirmed on two
|
|
292
|
+
* channels is not capped to `unconfirmed` for having no traced cause — is a
|
|
293
|
+
* real question and is **not** decided here; filed as
|
|
294
|
+
* `documents/decisions-inbox/DEC-NEXT-empty-graph-path-resolution-floor.md`.
|
|
295
|
+
*/
|
|
296
|
+
export function minimumResolution(path) {
|
|
297
|
+
let lowest = 0;
|
|
298
|
+
let seen = false;
|
|
299
|
+
for (const hop of path) {
|
|
300
|
+
if (!seen || hop.resolution < lowest)
|
|
301
|
+
lowest = hop.resolution;
|
|
302
|
+
seen = true;
|
|
303
|
+
}
|
|
304
|
+
return derive(lowest);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* `H` — testimony anywhere under the finding.
|
|
308
|
+
*
|
|
309
|
+
* Any load-bearing hop is enough: DEC-223's cap is not diluted by the number of
|
|
310
|
+
* honest hops beside it, because one unaudited assertion is one unaudited
|
|
311
|
+
* assertion however much resolved structure surrounds it.
|
|
312
|
+
*/
|
|
313
|
+
export function humanAssertedOnPath(path) {
|
|
314
|
+
return path.some((hop) => hop.humanAsserted === true);
|
|
315
|
+
}
|
|
316
|
+
/** Strongest first, so a cap is a `Math.min` over the rank. */
|
|
317
|
+
const CATEGORY_RANK = {
|
|
318
|
+
confirmed: 2,
|
|
319
|
+
"strongly supported": 1,
|
|
320
|
+
unconfirmed: 0,
|
|
321
|
+
};
|
|
322
|
+
const BY_RANK = ["unconfirmed", "strongly supported", "confirmed"];
|
|
323
|
+
/** Step 3's table — the highest category each class permits. */
|
|
324
|
+
const CLASS_CEILING = {
|
|
325
|
+
A: "confirmed",
|
|
326
|
+
B: "strongly supported",
|
|
327
|
+
C: "unconfirmed",
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* **G3 + G4 — the evidence precondition and the category gate.**
|
|
331
|
+
* `ai-governance-spec.md` §4–§5, stage 14 of the V1 loop. G3 fires first; G4
|
|
332
|
+
* is three steps.
|
|
333
|
+
*
|
|
334
|
+
* ```
|
|
335
|
+
* G3 no runtime evidence at all -> unconfirmed, steps 1-3 below never run
|
|
336
|
+
* step 1 class = reliabilityCap(R, N, H) -> "A" | "B" | "C"
|
|
337
|
+
* step 2 M -> confirmed | !M && E >= 1 -> strongly supported | E == 0 -> unconfirmed
|
|
338
|
+
* step 3 A -> stands | B -> at most strongly supported | C -> at most unconfirmed
|
|
339
|
+
* ```
|
|
340
|
+
*
|
|
341
|
+
* **Resolution is an input to step 1 and appears nowhere in step 2.** An
|
|
342
|
+
* earlier form of this gate granted `confirmed` on *"the structural path
|
|
343
|
+
* resolves R2+ throughout"*, which reads resolution directly as a category
|
|
344
|
+
* threshold — re-implementing rule 3 in a second place, at a different
|
|
345
|
+
* threshold, with nothing tying the two together. `reliabilityCap()` is the one
|
|
346
|
+
* function that turns a resolution into a class, so step 1 calls it and never
|
|
347
|
+
* restates its table.
|
|
348
|
+
*
|
|
349
|
+
* The step 3 mapping is corroborated rather than invented: DEC-223 reached the
|
|
350
|
+
* class-B row independently, in its own words — *"a finding resting on
|
|
351
|
+
* testimony is at most strongly supported, never confirmed."*
|
|
352
|
+
*
|
|
353
|
+
* **`M` dominates step 2, not channel count** — `ai-governance-spec.md` §5.1.
|
|
354
|
+
* One piece of direct mechanism evidence — a reproduced test failure, an
|
|
355
|
+
* exception whose stack frame resolves to the hypothesis's node, a network
|
|
356
|
+
* response matching the predicted defect on the exact route — reaches
|
|
357
|
+
* `confirmed` regardless of how many channels agree. Two channels that merely
|
|
358
|
+
* co-occur without naming the node (§5.3's own example: a console error and a
|
|
359
|
+
* failed network call, neither correlated to the hypothesis) are `E = 2,
|
|
360
|
+
* M = false`: `strongly supported`, never `confirmed`. This is what the
|
|
361
|
+
* earlier `E >= 2 -> confirmed` form left open — §41 rule 12 (correlation must
|
|
362
|
+
* not become causation) re-entering through the gate built to enforce it.
|
|
363
|
+
*
|
|
364
|
+
* Two consequences worth stating because they are the point: `confirmed` at
|
|
365
|
+
* class B is unreachable, so the two labels can never disagree about one
|
|
366
|
+
* finding; and G3's precondition survives even inside step 2 alone, because
|
|
367
|
+
* zero runtime evidence forces both `M = false` and `E = 0` by construction
|
|
368
|
+
* (both are computed from the same evidence array) — the explicit
|
|
369
|
+
* short-circuit below is a second, independent enforcement of that same
|
|
370
|
+
* invariant, not a case step 2 would otherwise get wrong.
|
|
371
|
+
*
|
|
372
|
+
* **Ruled 2026-08-24 — `ai-governance-spec.md` §5, closing
|
|
373
|
+
* `documents/decisions-inbox/DEC-NEXT-hypothesis-states-vs-g4.md` and
|
|
374
|
+
* `documents/decisions-inbox/DEC-NEXT-independent-signal-type-inventory.md`.**
|
|
375
|
+
* `documents/architecture/descry-ai-layer.md` §12's hypothesis-state
|
|
376
|
+
* thresholds and this gate now agree on identical evidence — there is only
|
|
377
|
+
* one function; §12's states are machine states, never a label directly
|
|
378
|
+
* (`ai-layer-plan.md` §6 rule 2).
|
|
379
|
+
*
|
|
380
|
+
* Takes an object rather than positionals because several of the fields are
|
|
381
|
+
* adjacent booleans, and a swapped pair is a silent wrong answer rather than a
|
|
382
|
+
* compile error.
|
|
383
|
+
*/
|
|
384
|
+
export function reportCategory(inputs) {
|
|
385
|
+
// Step 1 — the structural ceiling. Computed unconditionally: it is a pure
|
|
386
|
+
// function of the graph path alone (R, N, H), so it stays well-defined —
|
|
387
|
+
// and useful, e.g. for display — even when G3 below short-circuits the
|
|
388
|
+
// category.
|
|
389
|
+
const reliability = reliabilityCap(inputs.resolution, inputs.nameLevel, inputs.humanAsserted);
|
|
390
|
+
// G3 — the evidence precondition, evaluated before step 2's arithmetic ever
|
|
391
|
+
// runs. Zero runtime evidence records at all means steps 2/3 are not
|
|
392
|
+
// consulted: a purely static finding cannot buy its way to `confirmed` by
|
|
393
|
+
// scoring well on the structural terms alone.
|
|
394
|
+
if (!inputs.hasRuntimeEvidence) {
|
|
395
|
+
return { category: "unconfirmed", reliabilityClass: derive(reliability) };
|
|
396
|
+
}
|
|
397
|
+
// Step 2 — evidence category. Resolution plays no part here. `M` dominates:
|
|
398
|
+
// one piece of direct mechanism evidence reaches `confirmed` regardless of
|
|
399
|
+
// channel count; channel count only decides among the cases `M` does not
|
|
400
|
+
// settle.
|
|
401
|
+
const observed = inputs.directMechanismEvidence
|
|
402
|
+
? "confirmed"
|
|
403
|
+
: inputs.independentSignalTypes >= 1
|
|
404
|
+
? "strongly supported"
|
|
405
|
+
: "unconfirmed";
|
|
406
|
+
// Step 3 — rule 3 applied one layer up. A cap, so it can only ever lower.
|
|
407
|
+
const ceiling = CLASS_CEILING[reliability];
|
|
408
|
+
const rank = Math.min(CATEGORY_RANK[observed], CATEGORY_RANK[ceiling]);
|
|
409
|
+
return {
|
|
410
|
+
category: BY_RANK[rank] ?? "unconfirmed",
|
|
411
|
+
reliabilityClass: derive(reliability),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Thrown by {@link rederive} when a record cannot be trusted to re-derive
|
|
416
|
+
* from — a missing, mistyped or out-of-range G4 input, or a finding whose
|
|
417
|
+
* `confidence` was never G4-gated in the first place.
|
|
418
|
+
*
|
|
419
|
+
* A thrown error rather than a best-effort fallback, because the alternative
|
|
420
|
+
* is a *silently* wrong reliability class — the one field the whole cap in
|
|
421
|
+
* rule 3 rests on — which is worse than none at all.
|
|
422
|
+
*/
|
|
423
|
+
export class RederivationError extends Error {
|
|
424
|
+
name = "RederivationError";
|
|
425
|
+
}
|
|
426
|
+
function fail(detail) {
|
|
427
|
+
throw new RederivationError(detail);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Runtime-validate one G4 input, regardless of what its static type claims.
|
|
431
|
+
*
|
|
432
|
+
* `record.inputs` is typed `G4Inputs` on {@link FindingRecord}, which is true
|
|
433
|
+
* for a caller holding a value this module produced — but the entire reason
|
|
434
|
+
* this validation exists is the storage boundary in between: a `JSON.parse`
|
|
435
|
+
* result is `any`, and nothing stops a writer from dropping a field, and
|
|
436
|
+
* nothing about TypeScript's guarantee survives that round trip. This is the
|
|
437
|
+
* one place that gap is closed.
|
|
438
|
+
*/
|
|
439
|
+
function checked(value, field, isValid) {
|
|
440
|
+
if (!isValid(value))
|
|
441
|
+
fail(`"${field}" is missing or invalid — got ${JSON.stringify(value)}`);
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
const isSignalTypeCount = (v) => typeof v === "number" && SIGNAL_TYPE_COUNTS.includes(v);
|
|
445
|
+
const isResolutionLevel = (v) => typeof v === "number" && RESOLUTION_LEVELS.includes(v);
|
|
446
|
+
const isBoolean = (v) => typeof v === "boolean";
|
|
447
|
+
function validatedG4Inputs(inputs) {
|
|
448
|
+
return {
|
|
449
|
+
hasRuntimeEvidence: checked(inputs.hasRuntimeEvidence, "hasRuntimeEvidence", isBoolean),
|
|
450
|
+
independentSignalTypes: checked(inputs.independentSignalTypes, "independentSignalTypes", isSignalTypeCount),
|
|
451
|
+
directMechanismEvidence: checked(inputs.directMechanismEvidence, "directMechanismEvidence", isBoolean),
|
|
452
|
+
resolution: checked(inputs.resolution, "resolution", isResolutionLevel),
|
|
453
|
+
nameLevel: checked(inputs.nameLevel, "nameLevel", isBoolean),
|
|
454
|
+
humanAsserted: checked(inputs.humanAsserted, "humanAsserted", isBoolean),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Read a {@link FindingRecord} back as a `Finding` — the legitimate producer
|
|
459
|
+
* of `Derived<T>` on the read side of a storage boundary. The provisional
|
|
460
|
+
* escape hatch this module used to carry for the same purpose is gone; this
|
|
461
|
+
* is what made it deletable rather than permanent.
|
|
462
|
+
*
|
|
463
|
+
* Recomputes `reliabilityClass`, `resolutionLevel` and `confidence` from
|
|
464
|
+
* `record.inputs` by calling {@link reportCategory} — the one function that
|
|
465
|
+
* turns `E`/`R`/`N`/`H` into a class, never restated here. The recomputed
|
|
466
|
+
* values are authoritative; `record.finding`'s own copies of these three
|
|
467
|
+
* fields are not read at all, only overwritten, because the entire point is
|
|
468
|
+
* that this is a *re-derivation*, not a restoration of whatever the record
|
|
469
|
+
* happened to say.
|
|
470
|
+
*
|
|
471
|
+
* Throws {@link RederivationError} — rather than returning a plausible but
|
|
472
|
+
* wrong value — when `record.inputs` cannot be trusted (a missing, mistyped
|
|
473
|
+
* or out-of-range field) or when `record.finding.confidence` is not one of
|
|
474
|
+
* {@link EVIDENCE_CATEGORIES} (the finding was never G4-gated, so it has no
|
|
475
|
+
* G4 inputs to rederive from regardless of what `record.inputs` claims).
|
|
476
|
+
*/
|
|
477
|
+
export function rederive(record) {
|
|
478
|
+
if (!EVIDENCE_CATEGORIES.includes(record.finding.confidence)) {
|
|
479
|
+
fail(`cannot rederive confidence "${record.finding.confidence}": only the three G4-gated ` +
|
|
480
|
+
`categories (${EVIDENCE_CATEGORIES.join(", ")}) have recorded G4 inputs to rederive from.`);
|
|
481
|
+
}
|
|
482
|
+
const inputs = validatedG4Inputs(record.inputs);
|
|
483
|
+
const { category, reliabilityClass } = reportCategory(inputs);
|
|
484
|
+
return {
|
|
485
|
+
...record.finding,
|
|
486
|
+
confidence: category,
|
|
487
|
+
reliabilityClass,
|
|
488
|
+
resolutionLevel: derive(inputs.resolution),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
//# sourceMappingURL=finding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finding.js","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hypothesis state machine — `descry-ai-layer.md` §12, P19 of
|
|
3
|
+
* `ai-layer-plan.md` §11 (Phase 4).
|
|
4
|
+
*
|
|
5
|
+
* Six states, four transitions, all evaluated outside the model: *"Every
|
|
6
|
+
* transition is a deterministic rule evaluated outside the model. The model
|
|
7
|
+
* proposes; a rule disposes."* (`ai-layer-plan.md` §6). Same posture as
|
|
8
|
+
* G1–G4 (`../governance/*.ts`, `./finding.ts`) — a pure function over
|
|
9
|
+
* caller-supplied facts, never a prompt instruction.
|
|
10
|
+
*
|
|
11
|
+
* ## This is not a report category
|
|
12
|
+
*
|
|
13
|
+
* §12's own ruling, 2026-08-24 (Option B, `ai-governance-spec.md` §9): these
|
|
14
|
+
* six are *machine states*. `supported`/`confirmed` are not §19's
|
|
15
|
+
* `strongly supported`/`confirmed` report categories — only G4
|
|
16
|
+
* ({@link "./finding.ts".reportCategory}) assigns those, and it reads
|
|
17
|
+
* different inputs (resolution, name-level-ness, humanAsserted) that this
|
|
18
|
+
* module does not see at all. Do not compare a `HypothesisState` against a
|
|
19
|
+
* `ReportCategory` anywhere upstream of G4.
|
|
20
|
+
*
|
|
21
|
+
* ## What this module does not compute
|
|
22
|
+
*
|
|
23
|
+
* Three of the five inputs below are facts only the caller (the investigation
|
|
24
|
+
* loop, stage 12, P20) can supply, and are deliberately *not* derived here:
|
|
25
|
+
*
|
|
26
|
+
* - **`contradicted`** — whether evidence directly contradicts the
|
|
27
|
+
* hypothesis. Detecting a contradiction is §10's own machinery (graph vs.
|
|
28
|
+
* runtime disagreement, P21) — a larger, separate mechanism this module
|
|
29
|
+
* does not reimplement.
|
|
30
|
+
* - **`roundLimitReached`** — whether this hypothesis has exhausted its
|
|
31
|
+
* per-hypothesis tool-call budget. That accounting is `../governance/budget.ts`
|
|
32
|
+
* (P18)'s job; this module only reacts to the boolean it produces.
|
|
33
|
+
* - **`toolCallsIssued`** — the raw count of tool calls issued so far.
|
|
34
|
+
* `candidate → investigating` fires the instant the first call is issued,
|
|
35
|
+
* before any result — even an empty or refused one — comes back, so this
|
|
36
|
+
* cannot be derived from the evidence array's shape.
|
|
37
|
+
*/
|
|
38
|
+
import type { RuntimeEvidence } from "./finding.ts";
|
|
39
|
+
/** §12's six states, in the order the architecture lists them. */
|
|
40
|
+
export declare const HYPOTHESIS_STATES: readonly ["candidate", "investigating", "supported", "confirmed", "rejected", "unconfirmed"];
|
|
41
|
+
export type HypothesisState = (typeof HYPOTHESIS_STATES)[number];
|
|
42
|
+
/** States §12 marks terminal outright — the loop stops touching this hypothesis. */
|
|
43
|
+
export declare const TERMINAL_HYPOTHESIS_STATES: readonly ["confirmed", "rejected", "unconfirmed"];
|
|
44
|
+
export declare function isTerminalHypothesisState(state: HypothesisState): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Why an `unconfirmed` result was reached. `ai-layer-plan.md` §6 point 4 names
|
|
47
|
+
* three: `round-limit` is the one transition §12 actually defines
|
|
48
|
+
* (`any state → unconfirmed`, the round limit reached before `confirmed` or
|
|
49
|
+
* `rejected`); `superseded` and `unreachable` are named by the plan as
|
|
50
|
+
* additional reasons but have no defined trigger here — **disclosed, not
|
|
51
|
+
* modeled**, same as every other gap this project leaves rather than guesses
|
|
52
|
+
* a rule for.
|
|
53
|
+
*/
|
|
54
|
+
export declare const UNCONFIRMED_REASONS: readonly ["round-limit"];
|
|
55
|
+
export type UnconfirmedReason = (typeof UNCONFIRMED_REASONS)[number];
|
|
56
|
+
/**
|
|
57
|
+
* Everything {@link deriveHypothesisState} reads. Computed fresh each round
|
|
58
|
+
* from the hypothesis's accumulated evidence — this is a derivation, not an
|
|
59
|
+
* incremental transition-application function, matching G3/G4's own shape
|
|
60
|
+
* (`../governance/*.ts` calls this the same way `reportCategory` is called:
|
|
61
|
+
* full inputs in, one output out, every time).
|
|
62
|
+
*/
|
|
63
|
+
export interface HypothesisStateInput {
|
|
64
|
+
/** How many tool calls have been issued investigating this hypothesis. `0` means still `candidate`. */
|
|
65
|
+
readonly toolCallsIssued: number;
|
|
66
|
+
/**
|
|
67
|
+
* The failure-site node id this hypothesis names — what
|
|
68
|
+
* {@link directMechanismEvidence} matches evidence against. `hash(repo,
|
|
69
|
+
* kind, qualified symbol path)`, never a file path.
|
|
70
|
+
*/
|
|
71
|
+
readonly failurePoint: string;
|
|
72
|
+
/** Every runtime evidence record gathered for this hypothesis so far. */
|
|
73
|
+
readonly runtimeEvidence: readonly RuntimeEvidence[];
|
|
74
|
+
/**
|
|
75
|
+
* Whether a graph-structural fact — independent of any runtime
|
|
76
|
+
* observation — supports this hypothesis. One of §12's two named
|
|
77
|
+
* `supported` sources (*"a graph fact plus a runtime observation"*); the
|
|
78
|
+
* other is two distinct runtime channels, already counted via
|
|
79
|
+
* `runtimeEvidence`.
|
|
80
|
+
*/
|
|
81
|
+
readonly hasGraphFact: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Caller-supplied: at least one gathered evidence record directly
|
|
84
|
+
* contradicts the hypothesis. Not computed here — see the module doc.
|
|
85
|
+
*/
|
|
86
|
+
readonly contradicted: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Caller-supplied: this hypothesis has reached its round limit (§41 rule
|
|
89
|
+
* 17) without reaching `confirmed` or `rejected`. Not computed here — see
|
|
90
|
+
* the module doc.
|
|
91
|
+
*/
|
|
92
|
+
readonly roundLimitReached: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface HypothesisStateResult {
|
|
95
|
+
readonly state: HypothesisState;
|
|
96
|
+
/** Present only when `state === "unconfirmed"`. */
|
|
97
|
+
readonly unconfirmedReason?: UnconfirmedReason;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* How many independent evidence sources name this hypothesis — a graph fact
|
|
101
|
+
* counts as one, and each of the six {@link RuntimeSignalType} channels
|
|
102
|
+
* counts once however many records carry it (the same dedup-by-channel
|
|
103
|
+
* discipline `independentSignalTypes` uses for G4's `E`, applied to a wider
|
|
104
|
+
* source set).
|
|
105
|
+
*
|
|
106
|
+
* **Not `E`.** G4's `E` is a closed count over exactly the six runtime
|
|
107
|
+
* channels (`ai-governance-spec.md` §5.2) and a graph fact is not one of
|
|
108
|
+
* them by design — conflating the two is exactly the "machine state leaking
|
|
109
|
+
* into report vocabulary" failure §12's own ruling exists to prevent. This
|
|
110
|
+
* function answers a different question — *how many independent sources
|
|
111
|
+
* back this hypothesis, for the state machine's own `supported` transition*
|
|
112
|
+
* — and must never be passed anywhere G4 reads.
|
|
113
|
+
*/
|
|
114
|
+
export declare function independentEvidenceSources(runtimeEvidence: readonly RuntimeEvidence[], hasGraphFact: boolean): number;
|
|
115
|
+
/**
|
|
116
|
+
* Derive this hypothesis's current state from its accumulated evidence.
|
|
117
|
+
* Never throws — this is a check, not an execution of the thing it is
|
|
118
|
+
* checking.
|
|
119
|
+
*
|
|
120
|
+
* Precedence, most decisive first: a caller-flagged contradiction always
|
|
121
|
+
* rejects, ahead of mechanism evidence — a sane caller never sets both, but
|
|
122
|
+
* rejection winning is the conservative reading. Direct mechanism evidence
|
|
123
|
+
* confirms next, regardless of round limit — §12's `confirmed` transition
|
|
124
|
+
* names no round bound. Only after both are checked does the round limit
|
|
125
|
+
* force `unconfirmed`. Below that, two or more independent sources reach
|
|
126
|
+
* `supported`; otherwise the hypothesis is still `investigating` once at
|
|
127
|
+
* least one call has been issued, or `candidate` before the first.
|
|
128
|
+
*/
|
|
129
|
+
export declare function deriveHypothesisState(input: HypothesisStateInput): HypothesisStateResult;
|
|
130
|
+
//# sourceMappingURL=hypothesis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hypothesis.d.ts","sourceRoot":"","sources":["../src/hypothesis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAqB,MAAM,cAAc,CAAC;AAGvE,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,8FAOpB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,oFAAoF;AACpF,eAAO,MAAM,0BAA0B,mDAAoD,CAAC;AAE5F,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAEzE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,0BAA2B,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,uGAAuG;IACvG,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,eAAe,EAAE,SAAS,eAAe,EAAE,CAAC;IACrD;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,mDAAmD;IACnD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CAChD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,SAAS,eAAe,EAAE,EAC3C,YAAY,EAAE,OAAO,GACpB,MAAM,CAOR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CAsBxF"}
|