@descryy/core 0.6.1 → 0.6.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/dist/governance/demotion-ledger.d.ts +76 -0
- package/dist/governance/demotion-ledger.d.ts.map +1 -0
- package/dist/governance/demotion-ledger.js +108 -0
- package/dist/governance/demotion-ledger.js.map +1 -0
- package/dist/governance/finding-funnel.d.ts +20 -1
- package/dist/governance/finding-funnel.d.ts.map +1 -1
- package/dist/governance/finding-funnel.js +28 -10
- package/dist/governance/finding-funnel.js.map +1 -1
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.d.ts.map +1 -1
- package/dist/governance/index.js +1 -0
- package/dist/governance/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The demotion ledger — `documents/plans/gate_loosening.md` §3.4.
|
|
3
|
+
*
|
|
4
|
+
* The question *"what did this run throw away, and which gate did it"* has had no answer.
|
|
5
|
+
* Each gate is individually defensible and the chain is conjunctive, so the cost of the
|
|
6
|
+
* composition is invisible from any one of them — §2 property 1, which nothing in the
|
|
7
|
+
* codebase computes or bounds.
|
|
8
|
+
*
|
|
9
|
+
* This computes it, and needs no new instrumentation to do so: every finding is already
|
|
10
|
+
* stored with the `G4Inputs` that graded it (`FindingRecord`), so the attribution is
|
|
11
|
+
* derivable over findings that already exist, retroactively.
|
|
12
|
+
*
|
|
13
|
+
* ## An instrument, not a second report surface
|
|
14
|
+
*
|
|
15
|
+
* §6 risk 3 is that this becomes a second uncapped list next to the first one. It is for
|
|
16
|
+
* tuning a threshold and should be judged on whether it changed one. Nothing here renders,
|
|
17
|
+
* and nothing here decides — `reportCategory` remains the only function that assigns a
|
|
18
|
+
* category, and this module only re-reads its inputs to say what the answer would have been
|
|
19
|
+
* without each cap.
|
|
20
|
+
*/
|
|
21
|
+
import type { EvidenceCategory, G4Inputs } from "@descryy/ir";
|
|
22
|
+
/**
|
|
23
|
+
* The two gates that can lower a category below what the evidence alone read.
|
|
24
|
+
*
|
|
25
|
+
* Not a list of every gate in the pipeline — the fabrication gates (G1, G2) reject a claim
|
|
26
|
+
* outright rather than demoting one, and the funnel's cap truncates a ranked list rather than
|
|
27
|
+
* changing any finding's category. Both are real losses and neither is a *demotion*; conflating
|
|
28
|
+
* them here would make the levels-lost totals below meaningless.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEMOTION_GATES: readonly ["evidence-precondition", "reliability-cap"];
|
|
31
|
+
export type DemotionGate = (typeof DEMOTION_GATES)[number];
|
|
32
|
+
export interface Demotion {
|
|
33
|
+
readonly gate: DemotionGate;
|
|
34
|
+
/** What the observed evidence alone read, before any cap. */
|
|
35
|
+
readonly from: EvidenceCategory;
|
|
36
|
+
/** What was actually reported. */
|
|
37
|
+
readonly to: EvidenceCategory;
|
|
38
|
+
/** Rank levels lost. 1 or 2 — a demotion of 0 is not a demotion and is reported as `null`. */
|
|
39
|
+
readonly levels: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Which gate, if any, lowered this finding's category, and by how much. `null` when nothing
|
|
43
|
+
* did. Pure and total; never throws.
|
|
44
|
+
*
|
|
45
|
+
* At most one gate is reported. When G3's precondition binds, every downstream value is already
|
|
46
|
+
* `unconfirmed` and the reliability cap has nothing left to lower — so attributing the loss to
|
|
47
|
+
* both would double-count one demotion.
|
|
48
|
+
*/
|
|
49
|
+
export declare function attributeDemotion(inputs: G4Inputs): Demotion | null;
|
|
50
|
+
/** One gate's share of the total loss. */
|
|
51
|
+
export interface GateTally {
|
|
52
|
+
/** How many findings this gate demoted. */
|
|
53
|
+
readonly count: number;
|
|
54
|
+
/** Total rank levels it removed, across those findings. */
|
|
55
|
+
readonly levels: number;
|
|
56
|
+
}
|
|
57
|
+
export interface DemotionLedger {
|
|
58
|
+
readonly total: number;
|
|
59
|
+
/** Findings demoted by some gate. */
|
|
60
|
+
readonly demoted: number;
|
|
61
|
+
readonly byGate: Readonly<Record<DemotionGate, GateTally>>;
|
|
62
|
+
/**
|
|
63
|
+
* Findings the evidence read as `confirmed` that were reported as `unconfirmed` — the full
|
|
64
|
+
* two-level fall, and the population `gate_loosening.md` §1.1(c) is about.
|
|
65
|
+
*/
|
|
66
|
+
readonly confirmedToUnconfirmed: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Aggregate the ledger over a run's findings. Order-independent and pure.
|
|
70
|
+
*
|
|
71
|
+
* Takes `G4Inputs` rather than `FindingRecord` so a caller holding either can use it — the
|
|
72
|
+
* inputs are the only part a demotion is computable from, and requiring the whole record would
|
|
73
|
+
* stop this being runnable over a stored inputs column alone.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildDemotionLedger(runs: readonly G4Inputs[]): DemotionLedger;
|
|
76
|
+
//# sourceMappingURL=demotion-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demotion-ledger.d.ts","sourceRoot":"","sources":["../../src/governance/demotion-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,uDAAwD,CAAC;AAEpF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAS3D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,kCAAkC;IAClC,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAeD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAyBnE;AAED,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;IAC3D;;;OAGG;IACH,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,GAAG,cAAc,CAoB7E"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The demotion ledger — `documents/plans/gate_loosening.md` §3.4.
|
|
3
|
+
*
|
|
4
|
+
* The question *"what did this run throw away, and which gate did it"* has had no answer.
|
|
5
|
+
* Each gate is individually defensible and the chain is conjunctive, so the cost of the
|
|
6
|
+
* composition is invisible from any one of them — §2 property 1, which nothing in the
|
|
7
|
+
* codebase computes or bounds.
|
|
8
|
+
*
|
|
9
|
+
* This computes it, and needs no new instrumentation to do so: every finding is already
|
|
10
|
+
* stored with the `G4Inputs` that graded it (`FindingRecord`), so the attribution is
|
|
11
|
+
* derivable over findings that already exist, retroactively.
|
|
12
|
+
*
|
|
13
|
+
* ## An instrument, not a second report surface
|
|
14
|
+
*
|
|
15
|
+
* §6 risk 3 is that this becomes a second uncapped list next to the first one. It is for
|
|
16
|
+
* tuning a threshold and should be judged on whether it changed one. Nothing here renders,
|
|
17
|
+
* and nothing here decides — `reportCategory` remains the only function that assigns a
|
|
18
|
+
* category, and this module only re-reads its inputs to say what the answer would have been
|
|
19
|
+
* without each cap.
|
|
20
|
+
*/
|
|
21
|
+
import { existenceCategory, reportCategory } from "@descryy/ir";
|
|
22
|
+
/**
|
|
23
|
+
* The two gates that can lower a category below what the evidence alone read.
|
|
24
|
+
*
|
|
25
|
+
* Not a list of every gate in the pipeline — the fabrication gates (G1, G2) reject a claim
|
|
26
|
+
* outright rather than demoting one, and the funnel's cap truncates a ranked list rather than
|
|
27
|
+
* changing any finding's category. Both are real losses and neither is a *demotion*; conflating
|
|
28
|
+
* them here would make the levels-lost totals below meaningless.
|
|
29
|
+
*/
|
|
30
|
+
export const DEMOTION_GATES = ["evidence-precondition", "reliability-cap"];
|
|
31
|
+
/** Strongest first, matching `@descryy/ir`'s own rank so the arithmetic agrees across packages. */
|
|
32
|
+
const RANK = {
|
|
33
|
+
confirmed: 2,
|
|
34
|
+
"strongly supported": 1,
|
|
35
|
+
unconfirmed: 0,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The evidence reading with **no** gate applied at all — not even G3's precondition.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately not {@link existenceCategory}, which enforces the precondition first. The
|
|
41
|
+
* difference is the entire point of this module: without a pre-gate baseline there is nothing
|
|
42
|
+
* for G3 to be measured against, and a record where the precondition bound would look
|
|
43
|
+
* identical to one where nothing was ever observed.
|
|
44
|
+
*/
|
|
45
|
+
function evidenceReading(inputs) {
|
|
46
|
+
if (inputs.directMechanismEvidence)
|
|
47
|
+
return "confirmed";
|
|
48
|
+
return inputs.independentSignalTypes >= 1 ? "strongly supported" : "unconfirmed";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Which gate, if any, lowered this finding's category, and by how much. `null` when nothing
|
|
52
|
+
* did. Pure and total; never throws.
|
|
53
|
+
*
|
|
54
|
+
* At most one gate is reported. When G3's precondition binds, every downstream value is already
|
|
55
|
+
* `unconfirmed` and the reliability cap has nothing left to lower — so attributing the loss to
|
|
56
|
+
* both would double-count one demotion.
|
|
57
|
+
*/
|
|
58
|
+
export function attributeDemotion(inputs) {
|
|
59
|
+
const raw = evidenceReading(inputs);
|
|
60
|
+
// G3 — binds only on a record claiming evidence it also says it does not have. In valid data
|
|
61
|
+
// this is unreachable (both M and E are computed from the same array), which is why a hit here
|
|
62
|
+
// is worth surfacing: it means a record disagrees with itself.
|
|
63
|
+
if (!inputs.hasRuntimeEvidence && RANK[raw] > RANK.unconfirmed) {
|
|
64
|
+
return {
|
|
65
|
+
gate: "evidence-precondition",
|
|
66
|
+
from: raw,
|
|
67
|
+
to: "unconfirmed",
|
|
68
|
+
levels: RANK[raw] - RANK.unconfirmed,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const existence = existenceCategory(inputs);
|
|
72
|
+
const reported = reportCategory(inputs).category;
|
|
73
|
+
if (RANK[existence] <= RANK[reported])
|
|
74
|
+
return null;
|
|
75
|
+
return {
|
|
76
|
+
gate: "reliability-cap",
|
|
77
|
+
from: existence,
|
|
78
|
+
to: reported,
|
|
79
|
+
levels: RANK[existence] - RANK[reported],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Aggregate the ledger over a run's findings. Order-independent and pure.
|
|
84
|
+
*
|
|
85
|
+
* Takes `G4Inputs` rather than `FindingRecord` so a caller holding either can use it — the
|
|
86
|
+
* inputs are the only part a demotion is computable from, and requiring the whole record would
|
|
87
|
+
* stop this being runnable over a stored inputs column alone.
|
|
88
|
+
*/
|
|
89
|
+
export function buildDemotionLedger(runs) {
|
|
90
|
+
const byGate = {
|
|
91
|
+
"evidence-precondition": { count: 0, levels: 0 },
|
|
92
|
+
"reliability-cap": { count: 0, levels: 0 },
|
|
93
|
+
};
|
|
94
|
+
let demoted = 0;
|
|
95
|
+
let confirmedToUnconfirmed = 0;
|
|
96
|
+
for (const inputs of runs) {
|
|
97
|
+
const demotion = attributeDemotion(inputs);
|
|
98
|
+
if (demotion === null)
|
|
99
|
+
continue;
|
|
100
|
+
demoted += 1;
|
|
101
|
+
const tally = byGate[demotion.gate];
|
|
102
|
+
byGate[demotion.gate] = { count: tally.count + 1, levels: tally.levels + demotion.levels };
|
|
103
|
+
if (demotion.from === "confirmed" && demotion.to === "unconfirmed")
|
|
104
|
+
confirmedToUnconfirmed += 1;
|
|
105
|
+
}
|
|
106
|
+
return { total: runs.length, demoted, byGate, confirmedToUnconfirmed };
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=demotion-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demotion-ledger.js","sourceRoot":"","sources":["../../src/governance/demotion-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGhE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,uBAAuB,EAAE,iBAAiB,CAAU,CAAC;AAIpF,mGAAmG;AACnG,MAAM,IAAI,GAA+C;IACvD,SAAS,EAAE,CAAC;IACZ,oBAAoB,EAAE,CAAC;IACvB,WAAW,EAAE,CAAC;CACf,CAAC;AAYF;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,MAAgB;IACvC,IAAI,MAAM,CAAC,uBAAuB;QAAE,OAAO,WAAW,CAAC;IACvD,OAAO,MAAM,CAAC,sBAAsB,IAAI,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,aAAa,CAAC;AACnF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAgB;IAChD,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAEpC,6FAA6F;IAC7F,+FAA+F;IAC/F,+DAA+D;IAC/D,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/D,OAAO;YACL,IAAI,EAAE,uBAAuB;YAC7B,IAAI,EAAE,GAAG;YACT,EAAE,EAAE,aAAa;YACjB,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW;SACrC,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;IACjD,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,QAAQ;QACZ,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;KACzC,CAAC;AACJ,CAAC;AAsBD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAyB;IAC3D,MAAM,MAAM,GAAoC;QAC9C,uBAAuB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;QAChD,iBAAiB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;KAC3C,CAAC;IAEF,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,sBAAsB,GAAG,CAAC,CAAC;IAE/B,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,IAAI;YAAE,SAAS;QAEhC,OAAO,IAAI,CAAC,CAAC;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC3F,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,QAAQ,CAAC,EAAE,KAAK,aAAa;YAAE,sBAAsB,IAAI,CAAC,CAAC;IAClG,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;AACzE,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** §20.1 governance funnel — five filters ending in a hard cap; F2 (feedback-store false
|
|
2
2
|
* positives) and the cap itself were unimplemented until now. Caps are starting values (pr=5,
|
|
3
3
|
* §20.1's own 3+2 example). Never silently drops: every finding lands in reported/unconfirmed/suppressed. */
|
|
4
|
-
import { type Finding } from "@descryy/ir";
|
|
4
|
+
import { type Finding, type ReportCategory } from "@descryy/ir";
|
|
5
5
|
import type { Trigger } from "../scoping/tiers.ts";
|
|
6
6
|
/** Findings one run may report, per trigger. `pr`=5 is §20.1's own arithmetic (3+2). The rest
|
|
7
7
|
* scale to the reading surface (same shape as NODE_BUDGETS) — starting values, not measurements. */
|
|
@@ -22,6 +22,18 @@ export interface FindingFunnelOptions {
|
|
|
22
22
|
readonly isDismissed?: (finding: Finding) => boolean;
|
|
23
23
|
/** Overrides the trigger's cap. For a caller that has measured its own; not a way to opt out. */
|
|
24
24
|
readonly cap?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The finding's **existence** category — how strongly the evidence says the failure is real,
|
|
27
|
+
* uncapped by the structural path (`@descryy/ir`'s `existenceCategory`).
|
|
28
|
+
*
|
|
29
|
+
* Caller-supplied for the same reason `isDismissed` is: `Finding` is §20's fourteen fields and
|
|
30
|
+
* stays fourteen, so the second axis arrives as a lookup rather than a fifteenth field. Absent
|
|
31
|
+
* means "read it off `confidence`", which reproduces the pre-existing routing exactly.
|
|
32
|
+
*
|
|
33
|
+
* Routing on this is `gate_loosening.md` §3.3: a reproduced failure whose cause was never
|
|
34
|
+
* traced is capped at `unconfirmed` on attribution and belongs in front of a developer anyway.
|
|
35
|
+
*/
|
|
36
|
+
readonly existenceOf?: (finding: Finding) => ReportCategory;
|
|
25
37
|
}
|
|
26
38
|
export interface FindingTruncation {
|
|
27
39
|
readonly total: number;
|
|
@@ -37,6 +49,13 @@ export interface FindingFunnelResult {
|
|
|
37
49
|
readonly unconfirmed: readonly Finding[];
|
|
38
50
|
/** Removed by F2, kept so the run can say what it hid and why that is not a silence. */
|
|
39
51
|
readonly suppressed: readonly Finding[];
|
|
52
|
+
/**
|
|
53
|
+
* Findings in `reported` whose own `confidence` would not have got them there — the failure
|
|
54
|
+
* is established, the cause is not. A renderer must label these as such: a confirmed symptom
|
|
55
|
+
* with an untraced cause is a different claim from a confirmed cause, and rule 7 forbids
|
|
56
|
+
* presenting the weaker one at the stronger one's weight. Empty when `existenceOf` is absent.
|
|
57
|
+
*/
|
|
58
|
+
readonly promotedByExistence: readonly Finding[];
|
|
40
59
|
readonly truncation: FindingTruncation;
|
|
41
60
|
}
|
|
42
61
|
/** §20.1's funnel. Pure and total — every input finding appears in exactly one output list.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finding-funnel.d.ts","sourceRoot":"","sources":["../../src/governance/finding-funnel.ts"],"names":[],"mappings":"AAAA;;8GAE8G;AAE9G,OAAO,EAAsB,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"finding-funnel.d.ts","sourceRoot":"","sources":["../../src/governance/finding-funnel.ts"],"names":[],"mappings":"AAAA;;8GAE8G;AAE9G,OAAO,EAAsB,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAEpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD;qGACqG;AACrG,eAAO,MAAM,YAAY;IACvB,yFAAyF;;IAEzF,uEAAuE;;IAEvE,kCAAkC;;IAElC,oFAAoF;;CAE1C,CAAC;AAc7C,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;kEAC8D;IAC9D,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;IACrD,iGAAiG;IACjG,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sGAAsG;IACtG,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,SAAS,OAAO,EAAE,CAAC;IACzC,wFAAwF;IACxF,QAAQ,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,EAAE,SAAS,OAAO,EAAE,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;CACxC;AAED;yGACyG;AACzG,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CAqDrB;AAsBD;8EAC8E;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -28,27 +28,40 @@ const NOT_CONFIDENT = new Set(["unconfirmed", "not analysable", "unsupported"]);
|
|
|
28
28
|
* Deterministic ordering (replay guarantee, §9 layer 6): ties break on fingerprint, not input order. */
|
|
29
29
|
export function runFindingFunnel(findings, options) {
|
|
30
30
|
const isDismissed = options.isDismissed ?? (() => false);
|
|
31
|
+
const existenceOf = options.existenceOf ?? ((finding) => finding.confidence);
|
|
31
32
|
const suppressed = [];
|
|
32
33
|
const confident = [];
|
|
33
34
|
const unconfirmed = [];
|
|
35
|
+
const promotedByExistence = [];
|
|
34
36
|
for (const finding of findings) {
|
|
35
37
|
if (isDismissed(finding)) {
|
|
36
38
|
suppressed.push(finding);
|
|
37
39
|
continue;
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// The stronger of the two axes decides routing: attribution says how well the cause is
|
|
42
|
+
// known, existence says whether the failure is real, and a developer needs the second one
|
|
43
|
+
// to decide whether to look at all.
|
|
44
|
+
const existence = existenceOf(finding);
|
|
45
|
+
const effective = CATEGORY_RANK[existence] > CATEGORY_RANK[finding.confidence] ? existence : finding.confidence;
|
|
46
|
+
if (NOT_CONFIDENT.has(effective))
|
|
47
|
+
unconfirmed.push({ finding, effective });
|
|
48
|
+
else {
|
|
49
|
+
confident.push({ finding, effective });
|
|
50
|
+
// Rule 7 cuts both ways: a finding that reached the reported set on evidence its
|
|
51
|
+
// attribution could not support is disclosed, never quietly upgraded.
|
|
52
|
+
if (NOT_CONFIDENT.has(finding.confidence))
|
|
53
|
+
promotedByExistence.push(finding);
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
|
-
const ranked =
|
|
56
|
+
const ranked = confident.sort(compareRanked).map((r) => r.finding);
|
|
45
57
|
const cap = Math.max(0, Math.floor(options.cap ?? FINDING_CAPS[options.trigger]));
|
|
46
58
|
const reported = ranked.slice(0, cap);
|
|
47
59
|
const dropped = ranked.length - reported.length;
|
|
48
60
|
return {
|
|
49
61
|
reported,
|
|
50
|
-
unconfirmed:
|
|
62
|
+
unconfirmed: unconfirmed.sort(compareRanked).map((r) => r.finding),
|
|
51
63
|
suppressed,
|
|
64
|
+
promotedByExistence,
|
|
52
65
|
truncation: {
|
|
53
66
|
total: ranked.length,
|
|
54
67
|
shown: reported.length,
|
|
@@ -61,16 +74,21 @@ export function runFindingFunnel(findings, options) {
|
|
|
61
74
|
},
|
|
62
75
|
};
|
|
63
76
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
77
|
+
function compareRanked(a, b) {
|
|
78
|
+
// Effective category first: is this real, however well the cause is known.
|
|
79
|
+
const byEffective = CATEGORY_RANK[b.effective] - CATEGORY_RANK[a.effective];
|
|
80
|
+
if (byEffective !== 0)
|
|
81
|
+
return byEffective;
|
|
82
|
+
// Then attribution, so a finding whose cause is also known outranks one whose cause is not.
|
|
83
|
+
const byCategory = CATEGORY_RANK[b.finding.confidence] - CATEGORY_RANK[a.finding.confidence];
|
|
66
84
|
if (byCategory !== 0)
|
|
67
85
|
return byCategory;
|
|
68
86
|
// Then by how well the code beneath it was understood — rule 3's axis.
|
|
69
|
-
const byResolution = Number(b.resolutionLevel) - Number(a.resolutionLevel);
|
|
87
|
+
const byResolution = Number(b.finding.resolutionLevel) - Number(a.finding.resolutionLevel);
|
|
70
88
|
if (byResolution !== 0)
|
|
71
89
|
return byResolution;
|
|
72
90
|
// A stable, content-derived tiebreak, so replay reproduces the order.
|
|
73
|
-
return findingFingerprint(a) < findingFingerprint(b) ? -1 : 1;
|
|
91
|
+
return findingFingerprint(a.finding) < findingFingerprint(b.finding) ? -1 : 1;
|
|
74
92
|
}
|
|
75
93
|
/** Re-exported so a caller wiring the funnel and ledger imports one module — implementation
|
|
76
94
|
* lives in @descryy/ir's identity module (DEC-NEXT-edge-id-prefix-split). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finding-funnel.js","sourceRoot":"","sources":["../../src/governance/finding-funnel.ts"],"names":[],"mappings":"AAAA;;8GAE8G;AAE9G,OAAO,EAAE,kBAAkB,EAAqC,MAAM,aAAa,CAAC;AAIpF;qGACqG;AACrG,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,yFAAyF;IACzF,WAAW,EAAE,CAAC;IACd,uEAAuE;IACvE,UAAU,EAAE,CAAC;IACb,kCAAkC;IAClC,EAAE,EAAE,CAAC;IACL,oFAAoF;IACpF,OAAO,EAAE,EAAE;CAC+B,CAAC;AAE7C,yEAAyE;AACzE,MAAM,aAAa,GAAmC;IACpD,SAAS,EAAE,CAAC;IACZ,oBAAoB,EAAE,CAAC;IACvB,WAAW,EAAE,CAAC;IACd,gBAAgB,EAAE,CAAC;IACnB,WAAW,EAAE,CAAC;CACf,CAAC;AAEF,8FAA8F;AAC9F,MAAM,aAAa,GAAgC,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"finding-funnel.js","sourceRoot":"","sources":["../../src/governance/finding-funnel.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,4 +8,6 @@ export { ESCALATION_TRIGGERS, checkEscalation } from "./escalation.ts";
|
|
|
8
8
|
export type { EscalationCheckInput, EscalationTrigger } from "./escalation.ts";
|
|
9
9
|
export { runFindingFunnel, findingFingerprint, FINDING_CAPS } from "./finding-funnel.ts";
|
|
10
10
|
export type { FindingFunnelOptions, FindingFunnelResult, FindingTruncation, } from "./finding-funnel.ts";
|
|
11
|
+
export { DEMOTION_GATES, attributeDemotion, buildDemotionLedger } from "./demotion-ledger.ts";
|
|
12
|
+
export type { Demotion, DemotionGate, DemotionLedger, GateTally } from "./demotion-ledger.ts";
|
|
11
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/governance/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAErI,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEnG,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,cAAc,EACd,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzF,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/governance/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAErI,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEnG,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,cAAc,EACd,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzF,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC9F,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/governance/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export { checkCandidateBoundary } from "./candidate-boundary.js";
|
|
|
3
3
|
export { BUDGET_CAP_NAMES, DEFAULT_INVESTIGATION_BUDGET, checkRunBudget, hypothesisRoundLimitReached, } from "./budget.js";
|
|
4
4
|
export { ESCALATION_TRIGGERS, checkEscalation } from "./escalation.js";
|
|
5
5
|
export { runFindingFunnel, findingFingerprint, FINDING_CAPS } from "./finding-funnel.js";
|
|
6
|
+
export { DEMOTION_GATES, attributeDemotion, buildDemotionLedger } from "./demotion-ledger.js";
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/governance/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,cAAc,EACd,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AAUrB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/governance/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,cAAc,EACd,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AAUrB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOzF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descryy/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Descry graph engine: storage, graph builder, query layer, traversal, diff scoping, git source, MCP server.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"build": "tsc -b"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@descryy/ir": "0.6.
|
|
36
|
+
"@descryy/ir": "0.6.2"
|
|
37
37
|
}
|
|
38
38
|
}
|