circle-ir-ai 2.48.0 → 2.55.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +249 -0
  2. package/dist/trust/index.d.ts +7 -2
  3. package/dist/trust/index.d.ts.map +1 -1
  4. package/dist/trust/index.js +9 -0
  5. package/dist/trust/index.js.map +1 -1
  6. package/dist/trust/passes/agent-inventory.d.ts.map +1 -1
  7. package/dist/trust/passes/agent-inventory.js +3 -0
  8. package/dist/trust/passes/agent-inventory.js.map +1 -1
  9. package/dist/trust/passes/ai-config-detect.d.ts +45 -0
  10. package/dist/trust/passes/ai-config-detect.d.ts.map +1 -0
  11. package/dist/trust/passes/ai-config-detect.js +116 -0
  12. package/dist/trust/passes/ai-config-detect.js.map +1 -0
  13. package/dist/trust/passes/crypto-weakness.d.ts +30 -0
  14. package/dist/trust/passes/crypto-weakness.d.ts.map +1 -0
  15. package/dist/trust/passes/crypto-weakness.js +68 -0
  16. package/dist/trust/passes/crypto-weakness.js.map +1 -0
  17. package/dist/trust/passes/dep-manifests.d.ts +25 -0
  18. package/dist/trust/passes/dep-manifests.d.ts.map +1 -0
  19. package/dist/trust/passes/dep-manifests.js +190 -0
  20. package/dist/trust/passes/dep-manifests.js.map +1 -0
  21. package/dist/trust/passes/dependency-audit.d.ts.map +1 -1
  22. package/dist/trust/passes/dependency-audit.js +4 -0
  23. package/dist/trust/passes/dependency-audit.js.map +1 -1
  24. package/dist/trust/passes/dependency-staleness.d.ts.map +1 -1
  25. package/dist/trust/passes/dependency-staleness.js +3 -0
  26. package/dist/trust/passes/dependency-staleness.js.map +1 -1
  27. package/dist/trust/passes/eu-ai-act-flags.d.ts.map +1 -1
  28. package/dist/trust/passes/eu-ai-act-flags.js +5 -0
  29. package/dist/trust/passes/eu-ai-act-flags.js.map +1 -1
  30. package/dist/trust/passes/excessive-permissions.d.ts.map +1 -1
  31. package/dist/trust/passes/excessive-permissions.js +26 -0
  32. package/dist/trust/passes/excessive-permissions.js.map +1 -1
  33. package/dist/trust/passes/license-compliance.d.ts.map +1 -1
  34. package/dist/trust/passes/license-compliance.js +4 -1
  35. package/dist/trust/passes/license-compliance.js.map +1 -1
  36. package/dist/trust/passes/malicious-install-script.d.ts.map +1 -1
  37. package/dist/trust/passes/malicious-install-script.js +3 -1
  38. package/dist/trust/passes/malicious-install-script.js.map +1 -1
  39. package/dist/trust/passes/osv-dependency-audit.d.ts +27 -0
  40. package/dist/trust/passes/osv-dependency-audit.d.ts.map +1 -0
  41. package/dist/trust/passes/osv-dependency-audit.js +156 -0
  42. package/dist/trust/passes/osv-dependency-audit.js.map +1 -0
  43. package/dist/trust/passes/prompt-injection.d.ts.map +1 -1
  44. package/dist/trust/passes/prompt-injection.js +12 -0
  45. package/dist/trust/passes/prompt-injection.js.map +1 -1
  46. package/dist/trust/passes/tool-poisoning.d.ts.map +1 -1
  47. package/dist/trust/passes/tool-poisoning.js +21 -1
  48. package/dist/trust/passes/tool-poisoning.js.map +1 -1
  49. package/dist/trust/passes/typosquat-detection.d.ts.map +1 -1
  50. package/dist/trust/passes/typosquat-detection.js +3 -0
  51. package/dist/trust/passes/typosquat-detection.js.map +1 -1
  52. package/dist/trust/reachability.d.ts +37 -0
  53. package/dist/trust/reachability.d.ts.map +1 -0
  54. package/dist/trust/reachability.js +63 -0
  55. package/dist/trust/reachability.js.map +1 -0
  56. package/dist/trust/scoring.d.ts +11 -1
  57. package/dist/trust/scoring.d.ts.map +1 -1
  58. package/dist/trust/scoring.js +99 -23
  59. package/dist/trust/scoring.js.map +1 -1
  60. package/dist/trust/security-score.d.ts +48 -0
  61. package/dist/trust/security-score.d.ts.map +1 -0
  62. package/dist/trust/security-score.js +60 -0
  63. package/dist/trust/security-score.js.map +1 -0
  64. package/dist/trust/types.d.ts +67 -0
  65. package/dist/trust/types.d.ts.map +1 -1
  66. package/dist/trust/types.js.map +1 -1
  67. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ /**
2
+ * cognium-ai#234 (L4) — reachability factor.
3
+ *
4
+ * `reachability_factor` shipped as a hardcoded `1.0` since the trust score's
5
+ * inception (T9 vuln-reachability was deferred as "complex call-graph
6
+ * analysis"); the #234 critical review flagged it as a dead leg. circle-ir now
7
+ * emits taint flows during `analyze()`, so reachability is cheap to measure:
8
+ *
9
+ * A dangerous **sink** that terminates at least one *unsanitized* taint
10
+ * **flow** from an untrusted source is *confirmed reachable* — a real,
11
+ * exploitable path, not a dormant pattern. A sink with no flow reaching it is
12
+ * not confirmed reachable. Two repos with identical sink patterns are not
13
+ * equally risky: the one where untrusted input actually reaches those sinks is
14
+ * worse, and the composite should reflect that.
15
+ *
16
+ * The factor is a bounded penalty multiplier on the composite:
17
+ *
18
+ * factor = 1 - MAX_PENALTY × (reachable dangerous sinks / all dangerous sinks)
19
+ *
20
+ * so a repo where every dangerous sink is reachable-and-unsanitized takes the
21
+ * full `MAX_PENALTY` haircut, and one where sinks exist but nothing reaches
22
+ * them stays neutral at 1.0. `measured: false` (⇒ neutral 1.0) when circle-ir
23
+ * did not run or there are no dangerous sinks — coverage-honest, never a fake
24
+ * penalty for something we couldn't see.
25
+ */
26
+ import type { CircleIR } from 'circle-ir';
27
+ import type { ReachabilityDetail } from './types.js';
28
+ /** Max fraction the composite can be reduced by reachability (15% haircut). */
29
+ export declare const REACHABILITY_MAX_PENALTY = 0.15;
30
+ export type { ReachabilityDetail };
31
+ /**
32
+ * Compute the reachability factor from circle-ir results. Neutral (1.0,
33
+ * `measured:false`) when `circleIRResults` is absent/empty or no file has a
34
+ * dangerous sink.
35
+ */
36
+ export declare function computeReachabilityFactor(circleIRResults?: CircleIR[]): ReachabilityDetail;
37
+ //# sourceMappingURL=reachability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachability.d.ts","sourceRoot":"","sources":["../../src/trust/reachability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAoBnC;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,eAAe,CAAC,EAAE,QAAQ,EAAE,GAC3B,kBAAkB,CA8CpB"}
@@ -0,0 +1,63 @@
1
+ /** Max fraction the composite can be reduced by reachability (15% haircut). */
2
+ export const REACHABILITY_MAX_PENALTY = 0.15;
3
+ const NEUTRAL = (over = {}) => ({
4
+ factor: 1.0,
5
+ measured: false,
6
+ dangerousSinks: 0,
7
+ reachableSinks: 0,
8
+ unsanitizedFlows: 0,
9
+ reachableRatio: 0,
10
+ maxPenalty: REACHABILITY_MAX_PENALTY,
11
+ ...over,
12
+ });
13
+ const sinkKey = (line, type) => `${line ?? -1}:${type ?? ''}`;
14
+ /**
15
+ * Compute the reachability factor from circle-ir results. Neutral (1.0,
16
+ * `measured:false`) when `circleIRResults` is absent/empty or no file has a
17
+ * dangerous sink.
18
+ */
19
+ export function computeReachabilityFactor(circleIRResults) {
20
+ if (!circleIRResults || circleIRResults.length === 0)
21
+ return NEUTRAL();
22
+ let dangerousSinks = 0;
23
+ let reachableSinks = 0;
24
+ let unsanitizedFlows = 0;
25
+ for (const ir of circleIRResults) {
26
+ const taint = ir.taint;
27
+ const sinks = taint?.sinks ?? [];
28
+ const flows = taint?.flows ?? [];
29
+ dangerousSinks += sinks.length;
30
+ // Keys of sinks reached by an unsanitized flow (per-file, so line numbers
31
+ // don't collide across files). `sanitized !== true` is conservative:
32
+ // an undefined/absent flag counts as unsanitized.
33
+ const reachedKeys = new Set();
34
+ for (const f of flows) {
35
+ if (f.sanitized === true)
36
+ continue;
37
+ unsanitizedFlows++;
38
+ reachedKeys.add(sinkKey(f.sink_line, f.sink_type));
39
+ }
40
+ if (reachedKeys.size === 0)
41
+ continue;
42
+ for (const s of sinks) {
43
+ if (reachedKeys.has(sinkKey(s.line, s.type)))
44
+ reachableSinks++;
45
+ }
46
+ }
47
+ // No dangerous sinks anywhere → nothing to be reachable → neutral.
48
+ if (dangerousSinks === 0) {
49
+ return NEUTRAL({ measured: false, unsanitizedFlows });
50
+ }
51
+ const reachableRatio = Math.min(1, reachableSinks / dangerousSinks);
52
+ const factor = Math.round((1 - REACHABILITY_MAX_PENALTY * reachableRatio) * 1000) / 1000;
53
+ return {
54
+ factor,
55
+ measured: true,
56
+ dangerousSinks,
57
+ reachableSinks,
58
+ unsanitizedFlows,
59
+ reachableRatio: Math.round(reachableRatio * 1000) / 1000,
60
+ maxPenalty: REACHABILITY_MAX_PENALTY,
61
+ };
62
+ }
63
+ //# sourceMappingURL=reachability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../src/trust/reachability.ts"],"names":[],"mappings":"AA4BA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAQ7C,MAAM,OAAO,GAAG,CAAC,OAAoC,EAAE,EAAsB,EAAE,CAAC,CAAC;IAC/E,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,KAAK;IACf,cAAc,EAAE,CAAC;IACjB,cAAc,EAAE,CAAC;IACjB,gBAAgB,EAAE,CAAC;IACnB,cAAc,EAAE,CAAC;IACjB,UAAU,EAAE,wBAAwB;IACpC,GAAG,IAAI;CACR,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,CAAC,IAAwB,EAAE,IAAwB,EAAU,EAAE,CAC7E,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;AAEhC;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CACvC,eAA4B;IAE5B,IAAI,CAAC,eAAe,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,EAAE,CAAC;IAEvE,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,KAAK,MAAM,EAAE,IAAI,eAAe,EAAE,CAAC;QACjC,MAAM,KAAK,GAAI,EAAkF,CAAC,KAAK,CAAC;QACxG,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC;QACjC,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC;QAE/B,0EAA0E;QAC1E,qEAAqE;QACrE,kDAAkD;QAClD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI;gBAAE,SAAS;YACnC,gBAAgB,EAAE,CAAC;YACnB,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC;YAAE,SAAS;QAErC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;gBAAE,cAAc,EAAE,CAAC;QACjE,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,GAAG,cAAc,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,wBAAwB,GAAG,cAAc,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAEzF,OAAO;QACL,MAAM;QACN,QAAQ,EAAE,IAAI;QACd,cAAc;QACd,cAAc;QACd,gBAAgB;QAChB,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI;QACxD,UAAU,EAAE,wBAAwB;KACrC,CAAC;AACJ,CAAC"}
@@ -5,7 +5,17 @@
5
5
  * Each pass maps to a category with a configurable weight, and to one
6
6
  * of the 13 TrustScoreResult component fields.
7
7
  */
8
+ import type { CircleIR } from 'circle-ir';
8
9
  import { type TrustPassResult, type TrustScoreResult } from './types.js';
10
+ /**
11
+ * cognium-ai#234 (L4) — optional analysis context for the scoring engine.
12
+ * Carries circle-ir results so the reachability factor can be measured. Absent
13
+ * ⇒ neutral factor 1.0 (byte-identical to pre-L4 scoring for every caller that
14
+ * doesn't pass it).
15
+ */
16
+ export interface TrustScoringContext {
17
+ circleIRResults?: CircleIR[];
18
+ }
9
19
  export type TrustCategory = 'supply_chain' | 'code_security' | 'ai_safety' | 'compliance' | 'artifacts';
10
20
  export interface TrustScoringConfig {
11
21
  weights: Record<TrustCategory, number>;
@@ -40,6 +50,6 @@ export declare function validateScoringConfig(config: TrustScoringConfig): void;
40
50
  * 4. Populate 13 component scores via {@link PASS_COMPONENT_MAP}.
41
51
  * 5. Derive tier via {@link getTierForScore}; check for critical findings.
42
52
  */
43
- export declare function computeTrustScore(passResults: TrustPassResult[], config?: TrustScoringConfig): TrustScoreResult;
53
+ export declare function computeTrustScore(passResults: TrustPassResult[], config?: TrustScoringConfig, context?: TrustScoringContext): TrustScoreResult;
44
54
  export {};
45
55
  //# sourceMappingURL=scoring.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/trust/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAItB,MAAM,YAAY,CAAC;AAMpB,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,eAAe,GACf,WAAW,GACX,YAAY,GACZ,WAAW,CAAC;AAMhB,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,sBAAsB,EAAE,kBAQpC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAE7D,CAAC;AAUF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CA0D3D,CAAC;AAMF,iEAAiE;AACjE,KAAK,cAAc,GACf,qBAAqB,GACrB,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,cAAc,GACd,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,CAAC;AAExB,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAmC7D,CAAC;AAMF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAUtE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,eAAe,EAAE,EAC9B,MAAM,GAAE,kBAA2C,GAClD,gBAAgB,CAwIlB"}
1
+ {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/trust/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAKtB,MAAM,YAAY,CAAC;AAGpB;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC9B;AAMD,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,eAAe,GACf,WAAW,GACX,YAAY,GACZ,WAAW,CAAC;AAMhB,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,sBAAsB,EAAE,kBAQpC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAE7D,CAAC;AAUF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CA0E3D,CAAC;AAMF,iEAAiE;AACjE,KAAK,cAAc,GACf,qBAAqB,GACrB,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,cAAc,GACd,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,CAAC;AAExB,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAsC7D,CAAC;AAMF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAUtE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,eAAe,EAAE,EAC9B,MAAM,GAAE,kBAA2C,EACnD,OAAO,GAAE,mBAAwB,GAChC,gBAAgB,CAgMlB"}
@@ -6,6 +6,7 @@
6
6
  * of the 13 TrustScoreResult component fields.
7
7
  */
8
8
  import { getTierForScore, } from './types.js';
9
+ import { computeReachabilityFactor } from './reachability.js';
9
10
  export const DEFAULT_SCORING_CONFIG = {
10
11
  weights: {
11
12
  supply_chain: 0.3,
@@ -40,6 +41,9 @@ export const PASS_CATEGORY_MAP = {
40
41
  'typosquat-detection': 'supply_chain',
41
42
  'dependency-staleness': 'supply_chain',
42
43
  'dependency-audit': 'supply_chain',
44
+ // cognium-ai#234 (L2) — ecosystem-agnostic CVE audit (Maven/PyPI/Go/Cargo)
45
+ // via OSV.dev; makes supply_chain a real signal on non-npm repos.
46
+ 'osv-dependency-audit': 'supply_chain',
43
47
  // code_security
44
48
  'data-exfiltration': 'code_security',
45
49
  'input-validation': 'code_security',
@@ -50,13 +54,25 @@ export const PASS_CATEGORY_MAP = {
50
54
  // skipped it entirely and the aggregate ignored LLM-discovered vulns.
51
55
  // Mapped to code_security + weighted (see PASS_INTRA_CATEGORY_WEIGHT).
52
56
  'llm-security-scan': 'code_security',
53
- // ai_safety
57
+ // cognium-ai#234 (L3) — cryptographic-weakness pass (weak hash/cipher/PRNG/
58
+ // key), consuming circle-ir's crypto SAST findings.
59
+ 'crypto-weakness': 'code_security',
60
+ // ai_safety — AI-agent/skill THREAT passes (all AI-surface-gated, so on a
61
+ // non-AI repo they're all applicable:false and L1 drops the category +
62
+ // re-normalizes its 20% away — cognium-ai#234 L6).
54
63
  'prompt-injection': 'ai_safety',
55
64
  'excessive-permissions': 'ai_safety',
56
65
  'agent-inventory': 'ai_safety',
57
66
  'eu-ai-act-flags': 'ai_safety',
58
67
  'tool-poisoning': 'ai_safety',
59
- 'ai-generated-fingerprint': 'ai_safety',
68
+ // cognium-ai#234 (L6) — ai-generated-fingerprint measures code *provenance*
69
+ // (was this AI-written), not an AI-safety threat. It runs on any source and
70
+ // is legitimately clean-100 on human-written code, so leaving it in ai_safety
71
+ // kept that 20% category permanently "validated" on non-AI repos — exactly
72
+ // the constant the #234 review flagged. Moved to artifacts (informational,
73
+ // weight 0): still reported (provenance_score + findings), no longer anchors
74
+ // a security category. It CANNOT be gated on findings — "no AI patterns" is a
75
+ // real clean result, not "couldn't look".
60
76
  // compliance
61
77
  'license-compliance': 'compliance',
62
78
  'malicious-install': 'compliance',
@@ -79,16 +95,18 @@ export const PASS_CATEGORY_MAP = {
79
95
  'naming-convention': 'code_security',
80
96
  // code_security (Sprint 10 — Semantic Understanding)
81
97
  'spec-gap-report': 'code_security',
82
- // artifacts
98
+ // artifacts (informational, weight 0)
83
99
  'sbom-generation': 'artifacts',
84
100
  'vex-generation': 'artifacts',
85
101
  'audit-trail': 'artifacts',
102
+ 'ai-generated-fingerprint': 'artifacts', // #234 L6 — provenance, not a threat
86
103
  };
87
104
  export const PASS_COMPONENT_MAP = {
88
105
  'input-validation': 'vulnerability_score',
89
106
  'typosquat-detection': 'supply_chain_score',
90
107
  'dependency-staleness': 'supply_chain_score',
91
108
  'dependency-audit': 'supply_chain_score',
109
+ 'osv-dependency-audit': 'supply_chain_score',
92
110
  'data-exfiltration': 'exfiltration_score',
93
111
  'prompt-injection': 'instruction_score',
94
112
  'excessive-permissions': 'permission_score',
@@ -118,7 +136,9 @@ export const PASS_COMPONENT_MAP = {
118
136
  'god-class': 'hygiene_score',
119
137
  'naming-convention': 'hygiene_score',
120
138
  'spec-gap-report': 'hygiene_score',
121
- // crypto_score has no pass yet — defaults to 100
139
+ // cognium-ai#234 (L3) — crypto_score is now fed by the crypto-weakness pass
140
+ // (was a permanent 100 with no pass mapped here).
141
+ 'crypto-weakness': 'crypto_score',
122
142
  };
123
143
  // ---------------------------------------------------------------------------
124
144
  // Validation
@@ -147,19 +167,31 @@ export function validateScoringConfig(config) {
147
167
  * 4. Populate 13 component scores via {@link PASS_COMPONENT_MAP}.
148
168
  * 5. Derive tier via {@link getTierForScore}; check for critical findings.
149
169
  */
150
- export function computeTrustScore(passResults, config = DEFAULT_SCORING_CONFIG) {
170
+ export function computeTrustScore(passResults, config = DEFAULT_SCORING_CONFIG, context = {}) {
151
171
  validateScoringConfig(config);
152
- // -- Step 1 & 2: Category averages ----------------------------------------
172
+ // -- Step 1 & 2: Category averages (cognium-ai#234 L1 — coverage-aware) ----
153
173
  // cognium-ai#119 — weighted accumulation per category (weight from
154
- // PASS_INTRA_CATEGORY_WEIGHT, default 1). With all weights 1 this is
155
- // byte-identical to the previous simple average.
174
+ // PASS_INTRA_CATEGORY_WEIGHT, default 1).
175
+ // cognium-ai#234 (L1) — a pass with `applicable === false` couldn't look
176
+ // (wrong ecosystem, no relevant files), so its 100 is NOT evidence of
177
+ // cleanliness — exclude it from its category average, and remember it as
178
+ // inapplicable for the coverage report.
156
179
  const categoryScores = new Map();
180
+ const inapplicableByCategory = new Map();
157
181
  for (const pr of passResults) {
158
182
  const category = PASS_CATEGORY_MAP[pr.pass];
159
183
  if (category === undefined) {
160
184
  // Unknown pass — skip silently so future passes don't break scoring
161
185
  continue;
162
186
  }
187
+ if (pr.applicable === false) {
188
+ const list = inapplicableByCategory.get(category);
189
+ if (list)
190
+ list.push(pr.pass);
191
+ else
192
+ inapplicableByCategory.set(category, [pr.pass]);
193
+ continue;
194
+ }
163
195
  const w = passWeight(pr.pass);
164
196
  const bucket = categoryScores.get(category);
165
197
  if (bucket) {
@@ -170,32 +202,71 @@ export function computeTrustScore(passResults, config = DEFAULT_SCORING_CONFIG)
170
202
  categoryScores.set(category, { weightedSum: pr.score * w, weightSum: w });
171
203
  }
172
204
  }
205
+ // A category is VALIDATED only if ≥1 applicable pass contributed.
173
206
  const categoryAverages = new Map();
174
207
  for (const [cat, { weightedSum, weightSum }] of categoryScores) {
175
- categoryAverages.set(cat, weightSum > 0 ? weightedSum / weightSum : 100);
208
+ if (weightSum > 0)
209
+ categoryAverages.set(cat, weightedSum / weightSum);
176
210
  }
177
- // -- Step 3: Weighted composite score -------------------------------------
178
- let compositeScore = 0;
179
- const categories = [
211
+ // -- Step 3: Coverage-aware weighted composite ----------------------------
212
+ //
213
+ // cognium-ai#234 (L1) — the composite is the weighted mean of ONLY the
214
+ // validated categories, with weights RE-NORMALIZED across them. An
215
+ // unvalidated category is excluded rather than treated as a clean 100, so a
216
+ // score can no longer be inflated by legs that never looked (npm-only
217
+ // dependency audit on a Maven repo, AI-safety on a non-agent repo, …). The
218
+ // `coverageWeight` (sum of validated default weights) tells consumers how
219
+ // much of the score rests on real signal.
220
+ const scoredCategories = [
180
221
  'supply_chain',
181
222
  'code_security',
182
223
  'ai_safety',
183
224
  'compliance',
184
- 'artifacts',
185
- ];
186
- for (const cat of categories) {
225
+ ]; // artifacts is weight 0 / informational — never in the composite
226
+ let weightedSum = 0;
227
+ let validatedWeight = 0;
228
+ for (const cat of scoredCategories) {
187
229
  const weight = config.weights[cat];
188
230
  if (weight === 0)
189
231
  continue;
190
- // If no passes ran for this category, treat it as 100 (clean)
191
- const avg = categoryAverages.get(cat) ?? 100;
192
- compositeScore += avg * weight;
232
+ const avg = categoryAverages.get(cat);
233
+ if (avg === undefined)
234
+ continue; // unvalidated — excluded + re-normalized
235
+ weightedSum += avg * weight;
236
+ validatedWeight += weight;
193
237
  }
194
- // Clamp to 0-100
238
+ // Re-normalize over validated weight. If NOTHING validated (pathological —
239
+ // no pass applied at all), fall back to 100 but flag coverageWeight 0.
240
+ let compositeScore = validatedWeight > 0 ? weightedSum / validatedWeight : 100;
195
241
  compositeScore = Math.max(0, Math.min(100, Math.round(compositeScore * 100) / 100));
242
+ // cognium-ai#234 (L4) — apply the reachability factor. A dangerous sink that
243
+ // an unsanitized taint flow actually reaches is a confirmed exploitable path;
244
+ // the composite is discounted in proportion to how much of the sink surface
245
+ // is reachable. Neutral 1.0 (no change) when circle-ir didn't run or there
246
+ // are no dangerous sinks — so every caller that omits `context` is
247
+ // byte-identical to pre-L4 scoring.
248
+ const reachability = computeReachabilityFactor(context.circleIRResults);
249
+ compositeScore = Math.max(0, Math.min(100, Math.round(compositeScore * reachability.factor * 100) / 100));
250
+ // Coverage report (additive output).
251
+ const categoryCoverage = scoredCategories.map((cat) => {
252
+ const avg = categoryAverages.get(cat);
253
+ return {
254
+ category: cat,
255
+ validated: avg !== undefined,
256
+ score: avg !== undefined ? Math.round(avg * 100) / 100 : null,
257
+ inapplicablePasses: inapplicableByCategory.get(cat) ?? [],
258
+ weight: config.weights[cat],
259
+ };
260
+ });
261
+ const coverageWeight = Math.round(validatedWeight * 1000) / 1000;
196
262
  // -- Step 4: Component scores ---------------------------------------------
197
263
  const componentBuckets = new Map();
198
264
  for (const pr of passResults) {
265
+ // cognium-ai#234 (L1) — an inapplicable pass never looked; excluding it
266
+ // here keeps the display component honest too (a bucket shared by an
267
+ // applicable low score and an inapplicable 100 must not be inflated).
268
+ if (pr.applicable === false)
269
+ continue;
199
270
  const component = PASS_COMPONENT_MAP[pr.pass];
200
271
  if (component === undefined)
201
272
  continue;
@@ -210,7 +281,7 @@ export function computeTrustScore(passResults, config = DEFAULT_SCORING_CONFIG)
210
281
  const componentAvg = (key) => {
211
282
  const scores = componentBuckets.get(key);
212
283
  if (!scores || scores.length === 0)
213
- return 100; // default clean
284
+ return 100; // default clean / unvalidated
214
285
  return scores.reduce((s, v) => s + v, 0) / scores.length;
215
286
  };
216
287
  // -- Step 5: Critical findings → BLOCKED ----------------------------------
@@ -246,6 +317,9 @@ export function computeTrustScore(passResults, config = DEFAULT_SCORING_CONFIG)
246
317
  tierReason,
247
318
  baseTier,
248
319
  blockingFindings,
320
+ // cognium-ai#234 (L1) — coverage transparency
321
+ categoryCoverage,
322
+ coverageWeight,
249
323
  // 12 component scores
250
324
  vulnerability_score: componentAvg('vulnerability_score'),
251
325
  supply_chain_score: componentAvg('supply_chain_score'),
@@ -253,17 +327,19 @@ export function computeTrustScore(passResults, config = DEFAULT_SCORING_CONFIG)
253
327
  instruction_score: componentAvg('instruction_score'),
254
328
  permission_score: componentAvg('permission_score'),
255
329
  secrets_score: componentAvg('secrets_score'),
256
- crypto_score: componentAvg('crypto_score'), // no pass yet → 100
330
+ crypto_score: componentAvg('crypto_score'), // #234 L3 → crypto-weakness pass
257
331
  license_score: componentAvg('license_score'),
258
332
  compliance_score: componentAvg('compliance_score'),
259
333
  hygiene_score: componentAvg('hygiene_score'),
260
334
  poisoning_score: componentAvg('poisoning_score'),
261
335
  provenance_score: componentAvg('provenance_score'),
262
336
  performance_score: componentAvg('performance_score'),
263
- // 3 multiplicative factors — neutral for now
264
- reachability_factor: 1.0,
337
+ // 3 multiplicative factors. reachability is LIVE (#234 L4); drift +
338
+ // freshness remain neutral pending L-series follow-ups.
339
+ reachability_factor: reachability.factor,
265
340
  drift_factor: 1.0,
266
341
  freshness_factor: 1.0,
342
+ reachabilityDetail: reachability,
267
343
  passResults,
268
344
  timestamp: new Date().toISOString(),
269
345
  };
@@ -1 +1 @@
1
- {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/trust/scoring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/trust/scoring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * cognium-ai#234 (L5) — de-saturate code_security.
3
+ *
4
+ * The synthetic `llm-security-scan` pass (weight 10 in code_security) scored
5
+ * findings with a linear penalty clamped to [0,100]:
6
+ *
7
+ * score = 100 - (crit×25 + high×10 + med×3 + low×1)
8
+ *
9
+ * That pins to 0 the moment the load crosses 100 — i.e. ~4 criticals. On a
10
+ * large repo (the #234 review saw 28,300 raw findings) 3 vulns and 3,000 both
11
+ * score ≈0: the pass carries a heavy weight but conveys no signal beyond
12
+ * "something fired". It saturates instead of discriminating.
13
+ *
14
+ * This replaces the linear clamp with a **rational (reciprocal) decay** on the
15
+ * same severity-weighted load:
16
+ *
17
+ * score = 100 × SCALE / (SCALE + load)
18
+ *
19
+ * Properties: 100 at load 0; 50 at load = SCALE; monotonic decreasing;
20
+ * asymptotic to 0 but never pinned there, so it keeps ranking across the whole
21
+ * range (3 findings ≠ 3,000). The severity weights are unchanged, so the
22
+ * ordering of "which repo is worse" is preserved — only the saturation is
23
+ * removed. Numeric leniency at moderate counts is intentional and safe: the
24
+ * BLOCKED tier override still fires on ANY critical finding regardless of this
25
+ * number, so a repo with real criticals cannot pass on score alone.
26
+ */
27
+ /** Severity → penalty weight (unchanged from the legacy linear formula). */
28
+ export declare const SECURITY_SEVERITY_PENALTY: Record<'critical' | 'high' | 'medium' | 'low', number>;
29
+ /**
30
+ * Half-life load: the severity-weighted load at which the score is 50.
31
+ * 90 ≈ 3–4 criticals, ~9 highs, or ~30 mediums. Tunable.
32
+ */
33
+ export declare const SECURITY_SCORE_SCALE = 90;
34
+ export interface SecurityFindingCounts {
35
+ critical?: number;
36
+ high?: number;
37
+ medium?: number;
38
+ low?: number;
39
+ }
40
+ /** Severity-weighted load (higher = worse). */
41
+ export declare function securityLoad(counts: SecurityFindingCounts): number;
42
+ /**
43
+ * Discriminating 0–100 security score via rational decay. Never pins to 0, so
44
+ * a handful of findings and thousands remain distinguishable. `scale` is the
45
+ * load at which the score is 50 (defaults to {@link SECURITY_SCORE_SCALE}).
46
+ */
47
+ export declare function discriminatingSecurityScore(counts: SecurityFindingCounts, scale?: number): number;
48
+ //# sourceMappingURL=security-score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-score.d.ts","sourceRoot":"","sources":["../../src/trust/security-score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,EAAE,MAAM,CAK5F,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,+CAA+C;AAC/C,wBAAgB,YAAY,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAOlE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,qBAAqB,EAC7B,KAAK,GAAE,MAA6B,GACnC,MAAM,CAOR"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * cognium-ai#234 (L5) — de-saturate code_security.
3
+ *
4
+ * The synthetic `llm-security-scan` pass (weight 10 in code_security) scored
5
+ * findings with a linear penalty clamped to [0,100]:
6
+ *
7
+ * score = 100 - (crit×25 + high×10 + med×3 + low×1)
8
+ *
9
+ * That pins to 0 the moment the load crosses 100 — i.e. ~4 criticals. On a
10
+ * large repo (the #234 review saw 28,300 raw findings) 3 vulns and 3,000 both
11
+ * score ≈0: the pass carries a heavy weight but conveys no signal beyond
12
+ * "something fired". It saturates instead of discriminating.
13
+ *
14
+ * This replaces the linear clamp with a **rational (reciprocal) decay** on the
15
+ * same severity-weighted load:
16
+ *
17
+ * score = 100 × SCALE / (SCALE + load)
18
+ *
19
+ * Properties: 100 at load 0; 50 at load = SCALE; monotonic decreasing;
20
+ * asymptotic to 0 but never pinned there, so it keeps ranking across the whole
21
+ * range (3 findings ≠ 3,000). The severity weights are unchanged, so the
22
+ * ordering of "which repo is worse" is preserved — only the saturation is
23
+ * removed. Numeric leniency at moderate counts is intentional and safe: the
24
+ * BLOCKED tier override still fires on ANY critical finding regardless of this
25
+ * number, so a repo with real criticals cannot pass on score alone.
26
+ */
27
+ /** Severity → penalty weight (unchanged from the legacy linear formula). */
28
+ export const SECURITY_SEVERITY_PENALTY = {
29
+ critical: 25,
30
+ high: 10,
31
+ medium: 3,
32
+ low: 1,
33
+ };
34
+ /**
35
+ * Half-life load: the severity-weighted load at which the score is 50.
36
+ * 90 ≈ 3–4 criticals, ~9 highs, or ~30 mediums. Tunable.
37
+ */
38
+ export const SECURITY_SCORE_SCALE = 90;
39
+ /** Severity-weighted load (higher = worse). */
40
+ export function securityLoad(counts) {
41
+ return ((counts.critical ?? 0) * SECURITY_SEVERITY_PENALTY.critical +
42
+ (counts.high ?? 0) * SECURITY_SEVERITY_PENALTY.high +
43
+ (counts.medium ?? 0) * SECURITY_SEVERITY_PENALTY.medium +
44
+ (counts.low ?? 0) * SECURITY_SEVERITY_PENALTY.low);
45
+ }
46
+ /**
47
+ * Discriminating 0–100 security score via rational decay. Never pins to 0, so
48
+ * a handful of findings and thousands remain distinguishable. `scale` is the
49
+ * load at which the score is 50 (defaults to {@link SECURITY_SCORE_SCALE}).
50
+ */
51
+ export function discriminatingSecurityScore(counts, scale = SECURITY_SCORE_SCALE) {
52
+ const load = securityLoad(counts);
53
+ if (load <= 0)
54
+ return 100;
55
+ const s = scale > 0 ? scale : SECURITY_SCORE_SCALE;
56
+ const raw = (100 * s) / (s + load);
57
+ // 2 decimals so the high end still ranks (0.12 vs 0.06) instead of both → 0.
58
+ return Math.round(raw * 100) / 100;
59
+ }
60
+ //# sourceMappingURL=security-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-score.js","sourceRoot":"","sources":["../../src/trust/security-score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,yBAAyB,GAA2D;IAC/F,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;CACP,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AASvC,+CAA+C;AAC/C,MAAM,UAAU,YAAY,CAAC,MAA6B;IACxD,OAAO,CACL,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,yBAAyB,CAAC,QAAQ;QAC3D,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,yBAAyB,CAAC,IAAI;QACnD,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,yBAAyB,CAAC,MAAM;QACvD,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,yBAAyB,CAAC,GAAG,CAClD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAA6B,EAC7B,QAAgB,oBAAoB;IAEpC,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAC1B,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAoB,CAAC;IACnD,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACnC,6EAA6E;IAC7E,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACrC,CAAC"}
@@ -59,6 +59,50 @@ export interface TrustPassResult {
59
59
  durationMs: number;
60
60
  /** Optional generated artifacts (SBOM, VEX, audit log) */
61
61
  artifacts?: Record<string, unknown>;
62
+ /**
63
+ * cognium-ai#234 (L1) — did this pass have anything to analyze?
64
+ *
65
+ * `true` (default) — the pass ran against applicable input and its `score`
66
+ * is a real signal (clean or not).
67
+ * `false` — the pass could NOT apply: no relevant files, wrong ecosystem,
68
+ * or a hard skip (e.g. `dependency-audit` with no `package-lock.json`,
69
+ * an AI-safety pass on a repo with no agent manifests). Its `score` of 100
70
+ * is *not* evidence of cleanliness and must be excluded from scoring so a
71
+ * category isn't inflated to 100 by passes that never looked.
72
+ *
73
+ * Absent ⇒ treated as `true` (byte-identical to pre-L1 scoring).
74
+ */
75
+ applicable?: boolean;
76
+ }
77
+ /** cognium-ai#234 (L4) — reachability breakdown behind `reachability_factor`. */
78
+ export interface ReachabilityDetail {
79
+ /** The multiplier applied to the composite (0.85–1.0). */
80
+ factor: number;
81
+ /** True only when circle-ir ran AND there was ≥1 dangerous sink to assess. */
82
+ measured: boolean;
83
+ /** Total dangerous taint sinks across all analyzed files. */
84
+ dangerousSinks: number;
85
+ /** Sinks that terminate ≥1 unsanitized flow (confirmed reachable). */
86
+ reachableSinks: number;
87
+ /** Count of unsanitized source→sink flows. */
88
+ unsanitizedFlows: number;
89
+ /** reachableSinks / dangerousSinks, clamped to [0,1]. */
90
+ reachableRatio: number;
91
+ /** MAX_PENALTY in effect (for transparency / reproducibility). */
92
+ maxPenalty: number;
93
+ }
94
+ /** cognium-ai#234 (L1) — per-category coverage in the composite. */
95
+ export interface CategoryCoverage {
96
+ /** Category name (supply_chain | code_security | ai_safety | compliance). */
97
+ category: string;
98
+ /** True if ≥1 applicable pass contributed a real signal. */
99
+ validated: boolean;
100
+ /** Category score (0-100) when validated; null when unvalidated. */
101
+ score: number | null;
102
+ /** Passes that ran but couldn't apply (excluded), for transparency. */
103
+ inapplicablePasses: string[];
104
+ /** Default weight of this category (before re-normalization). */
105
+ weight: number;
62
106
  }
63
107
  export interface TrustScoreResult {
64
108
  /** Weighted composite score 0-100 */
@@ -71,6 +115,17 @@ export interface TrustScoreResult {
71
115
  baseTier?: TrustTier;
72
116
  /** Critical findings that forced BLOCKED; empty when tierReason==='score'. */
73
117
  blockingFindings?: BlockingFinding[];
118
+ /**
119
+ * Per-category coverage: which categories actually had an applicable pass
120
+ * vs. were `unvalidated` (no pass could look — excluded from the composite
121
+ * and its weight re-normalized across validated categories). Lets consumers
122
+ * see "supply_chain: unvalidated" instead of a fake 100, and understand
123
+ * which fraction of the score is real signal.
124
+ */
125
+ categoryCoverage?: CategoryCoverage[];
126
+ /** Sum of the DEFAULT weights of categories that were validated (0..1).
127
+ * A low value means most of the score rests on few real legs. */
128
+ coverageWeight?: number;
74
129
  vulnerability_score: number;
75
130
  supply_chain_score: number;
76
131
  exfiltration_score: number;
@@ -84,9 +139,21 @@ export interface TrustScoreResult {
84
139
  poisoning_score: number;
85
140
  provenance_score: number;
86
141
  performance_score: number;
142
+ /**
143
+ * cognium-ai#234 (L4) — LIVE. A bounded penalty multiplier applied to the
144
+ * composite: `1 - MAX_PENALTY × (reachable dangerous sinks / all dangerous
145
+ * sinks)`, from circle-ir's taint flows. 1.0 = neutral (no dangerous sinks,
146
+ * nothing reachable, or circle-ir did not run). See {@link reachabilityDetail}.
147
+ */
87
148
  reachability_factor: number;
88
149
  drift_factor: number;
89
150
  freshness_factor: number;
151
+ /**
152
+ * cognium-ai#234 (L4) — reachability breakdown behind `reachability_factor`
153
+ * (additive/optional). `measured: false` means the factor is a neutral 1.0
154
+ * because there was nothing to assess, not because everything is safe.
155
+ */
156
+ reachabilityDetail?: ReachabilityDetail;
90
157
  /** Individual pass results */
91
158
  passResults: TrustPassResult[];
92
159
  /** ISO timestamp */