@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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/dist/capabilities.d.ts +83 -0
  3. package/dist/capabilities.d.ts.map +1 -0
  4. package/dist/capabilities.js +91 -0
  5. package/dist/capabilities.js.map +1 -0
  6. package/dist/conformance/bind.d.ts +82 -0
  7. package/dist/conformance/bind.d.ts.map +1 -0
  8. package/dist/conformance/bind.js +157 -0
  9. package/dist/conformance/bind.js.map +1 -0
  10. package/dist/conformance/cli.d.ts +25 -0
  11. package/dist/conformance/cli.d.ts.map +1 -0
  12. package/dist/conformance/cli.js +137 -0
  13. package/dist/conformance/cli.js.map +1 -0
  14. package/dist/conformance/compare.d.ts +74 -0
  15. package/dist/conformance/compare.d.ts.map +1 -0
  16. package/dist/conformance/compare.js +140 -0
  17. package/dist/conformance/compare.js.map +1 -0
  18. package/dist/conformance/compose.d.ts +79 -0
  19. package/dist/conformance/compose.d.ts.map +1 -0
  20. package/dist/conformance/compose.js +167 -0
  21. package/dist/conformance/compose.js.map +1 -0
  22. package/dist/conformance/golden.d.ts +110 -0
  23. package/dist/conformance/golden.d.ts.map +1 -0
  24. package/dist/conformance/golden.js +252 -0
  25. package/dist/conformance/golden.js.map +1 -0
  26. package/dist/conformance/index.d.ts +26 -0
  27. package/dist/conformance/index.d.ts.map +1 -0
  28. package/dist/conformance/index.js +18 -0
  29. package/dist/conformance/index.js.map +1 -0
  30. package/dist/conformance/manifest-check.d.ts +78 -0
  31. package/dist/conformance/manifest-check.d.ts.map +1 -0
  32. package/dist/conformance/manifest-check.js +149 -0
  33. package/dist/conformance/manifest-check.js.map +1 -0
  34. package/dist/conformance/manifest.d.ts +83 -0
  35. package/dist/conformance/manifest.d.ts.map +1 -0
  36. package/dist/conformance/manifest.js +158 -0
  37. package/dist/conformance/manifest.js.map +1 -0
  38. package/dist/conformance/report.d.ts +14 -0
  39. package/dist/conformance/report.d.ts.map +1 -0
  40. package/dist/conformance/report.js +87 -0
  41. package/dist/conformance/report.js.map +1 -0
  42. package/dist/conformance/run.d.ts +172 -0
  43. package/dist/conformance/run.d.ts.map +1 -0
  44. package/dist/conformance/run.js +626 -0
  45. package/dist/conformance/run.js.map +1 -0
  46. package/dist/contracts.d.ts +198 -0
  47. package/dist/contracts.d.ts.map +1 -0
  48. package/dist/contracts.js +10 -0
  49. package/dist/contracts.js.map +1 -0
  50. package/dist/contradiction.d.ts +112 -0
  51. package/dist/contradiction.d.ts.map +1 -0
  52. package/dist/contradiction.js +97 -0
  53. package/dist/contradiction.js.map +1 -0
  54. package/dist/finding.d.ts +567 -0
  55. package/dist/finding.d.ts.map +1 -0
  56. package/dist/finding.js +491 -0
  57. package/dist/finding.js.map +1 -0
  58. package/dist/hypothesis.d.ts +130 -0
  59. package/dist/hypothesis.d.ts.map +1 -0
  60. package/dist/hypothesis.js +116 -0
  61. package/dist/hypothesis.js.map +1 -0
  62. package/dist/identity.d.ts +295 -0
  63. package/dist/identity.d.ts.map +1 -0
  64. package/dist/identity.js +404 -0
  65. package/dist/identity.js.map +1 -0
  66. package/dist/index.d.ts +34 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +21 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/ir.d.ts +360 -0
  71. package/dist/ir.d.ts.map +1 -0
  72. package/dist/ir.js +76 -0
  73. package/dist/ir.js.map +1 -0
  74. package/dist/model-tables.d.ts +111 -0
  75. package/dist/model-tables.d.ts.map +1 -0
  76. package/dist/model-tables.js +103 -0
  77. package/dist/model-tables.js.map +1 -0
  78. package/dist/normalise.d.ts +114 -0
  79. package/dist/normalise.d.ts.map +1 -0
  80. package/dist/normalise.js +603 -0
  81. package/dist/normalise.js.map +1 -0
  82. package/dist/reliability.d.ts +89 -0
  83. package/dist/reliability.d.ts.map +1 -0
  84. package/dist/reliability.js +181 -0
  85. package/dist/reliability.js.map +1 -0
  86. package/dist/tool-surface.d.ts +131 -0
  87. package/dist/tool-surface.d.ts.map +1 -0
  88. package/dist/tool-surface.js +133 -0
  89. package/dist/tool-surface.js.map +1 -0
  90. package/dist/vocabulary.d.ts +73 -0
  91. package/dist/vocabulary.d.ts.map +1 -0
  92. package/dist/vocabulary.js +150 -0
  93. package/dist/vocabulary.js.map +1 -0
  94. package/package.json +27 -0
@@ -0,0 +1,116 @@
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 { directMechanismEvidence } from "./finding.js";
39
+ /** §12's six states, in the order the architecture lists them. */
40
+ export const HYPOTHESIS_STATES = [
41
+ "candidate",
42
+ "investigating",
43
+ "supported",
44
+ "confirmed",
45
+ "rejected",
46
+ "unconfirmed",
47
+ ];
48
+ /** States §12 marks terminal outright — the loop stops touching this hypothesis. */
49
+ export const TERMINAL_HYPOTHESIS_STATES = ["confirmed", "rejected", "unconfirmed"];
50
+ export function isTerminalHypothesisState(state) {
51
+ return TERMINAL_HYPOTHESIS_STATES.includes(state);
52
+ }
53
+ /**
54
+ * Why an `unconfirmed` result was reached. `ai-layer-plan.md` §6 point 4 names
55
+ * three: `round-limit` is the one transition §12 actually defines
56
+ * (`any state → unconfirmed`, the round limit reached before `confirmed` or
57
+ * `rejected`); `superseded` and `unreachable` are named by the plan as
58
+ * additional reasons but have no defined trigger here — **disclosed, not
59
+ * modeled**, same as every other gap this project leaves rather than guesses
60
+ * a rule for.
61
+ */
62
+ export const UNCONFIRMED_REASONS = ["round-limit"];
63
+ /**
64
+ * How many independent evidence sources name this hypothesis — a graph fact
65
+ * counts as one, and each of the six {@link RuntimeSignalType} channels
66
+ * counts once however many records carry it (the same dedup-by-channel
67
+ * discipline `independentSignalTypes` uses for G4's `E`, applied to a wider
68
+ * source set).
69
+ *
70
+ * **Not `E`.** G4's `E` is a closed count over exactly the six runtime
71
+ * channels (`ai-governance-spec.md` §5.2) and a graph fact is not one of
72
+ * them by design — conflating the two is exactly the "machine state leaking
73
+ * into report vocabulary" failure §12's own ruling exists to prevent. This
74
+ * function answers a different question — *how many independent sources
75
+ * back this hypothesis, for the state machine's own `supported` transition*
76
+ * — and must never be passed anywhere G4 reads.
77
+ */
78
+ export function independentEvidenceSources(runtimeEvidence, hasGraphFact) {
79
+ const channels = new Set(runtimeEvidence
80
+ .map((item) => item.signal)
81
+ .filter((signal) => signal !== null));
82
+ return channels.size + (hasGraphFact ? 1 : 0);
83
+ }
84
+ /**
85
+ * Derive this hypothesis's current state from its accumulated evidence.
86
+ * Never throws — this is a check, not an execution of the thing it is
87
+ * checking.
88
+ *
89
+ * Precedence, most decisive first: a caller-flagged contradiction always
90
+ * rejects, ahead of mechanism evidence — a sane caller never sets both, but
91
+ * rejection winning is the conservative reading. Direct mechanism evidence
92
+ * confirms next, regardless of round limit — §12's `confirmed` transition
93
+ * names no round bound. Only after both are checked does the round limit
94
+ * force `unconfirmed`. Below that, two or more independent sources reach
95
+ * `supported`; otherwise the hypothesis is still `investigating` once at
96
+ * least one call has been issued, or `candidate` before the first.
97
+ */
98
+ export function deriveHypothesisState(input) {
99
+ if (input.toolCallsIssued === 0) {
100
+ return { state: "candidate" };
101
+ }
102
+ if (input.contradicted) {
103
+ return { state: "rejected" };
104
+ }
105
+ if (directMechanismEvidence(input.runtimeEvidence, input.failurePoint)) {
106
+ return { state: "confirmed" };
107
+ }
108
+ if (input.roundLimitReached) {
109
+ return { state: "unconfirmed", unconfirmedReason: "round-limit" };
110
+ }
111
+ if (independentEvidenceSources(input.runtimeEvidence, input.hasGraphFact) >= 2) {
112
+ return { state: "supported" };
113
+ }
114
+ return { state: "investigating" };
115
+ }
116
+ //# sourceMappingURL=hypothesis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hypothesis.js","sourceRoot":"","sources":["../src/hypothesis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,kEAAkE;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,WAAW;IACX,eAAe;IACf,WAAW;IACX,WAAW;IACX,UAAU;IACV,aAAa;CACL,CAAC;AAIX,oFAAoF;AACpF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,WAAW,EAAE,UAAU,EAAE,aAAa,CAAU,CAAC;AAE5F,MAAM,UAAU,yBAAyB,CAAC,KAAsB;IAC9D,OAAQ,0BAAyD,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,aAAa,CAAU,CAAC;AAgD5D;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,0BAA0B,CACxC,eAA2C,EAC3C,YAAqB;IAErB,MAAM,QAAQ,GAAG,IAAI,GAAG,CACtB,eAAe;SACZ,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;SAC1B,MAAM,CAAC,CAAC,MAAM,EAA+B,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC,CACpE,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAA2B;IAC/D,IAAI,KAAK,CAAC,eAAe,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAC/B,CAAC;IAED,IAAI,uBAAuB,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACvE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,CAAC;IACpE,CAAC;IAED,IAAI,0BAA0B,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/E,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Node and edge identity.
3
+ *
4
+ * The single most consequential property here is that a node ID survives a file
5
+ * move or rename. Incident links, `CHANGES_WITH` weights and every compounding
6
+ * asset in the product are keyed to node identity — path-derived IDs churn on
7
+ * rename and reset all of it silently, with nothing in the output to reveal that
8
+ * it happened.
9
+ *
10
+ * A node ID is `hash(scopeOf(kind), language, kind, qualifiedSymbolPath)`.
11
+ *
12
+ * Neither of the first two fields is decoration. **Scope** is per node type, so a
13
+ * caller in one repository and a route in another land on one `API_ENDPOINT`
14
+ * instead of two that never meet. **Language** keeps `models.Order` in two
15
+ * languages apart, which module-qualified paths alone do not — that is the
16
+ * ordinary layout of a polyglot monorepo, and a collision there merges two
17
+ * unrelated symbols and pools their edges without a word in the output.
18
+ *
19
+ * See DEC-054 (scope and language), DEC-004 (exclude file path) and DEC-011 (the
20
+ * per-type qualified symbol path table, and the schema tag).
21
+ */
22
+ import { type NodeType } from "./vocabulary.ts";
23
+ /**
24
+ * Schema tag on every node ID. Costs three bytes and gives IR schema migration
25
+ * (§32 item 14 / DEC-017) a handle: a stored graph whose IDs carry a different
26
+ * tag is recognisably from a different identity scheme rather than silently
27
+ * mismatching.
28
+ *
29
+ * `n1:`/`e1:` were `hash(repo, kind, qsp)`. DEC-054 moved them to `n2:`/`e2:`,
30
+ * and that bump is the first real exercise of the mechanism — performed while
31
+ * there are zero stored graphs, because the alternative is discovering its bugs
32
+ * against half a million nodes and earned data that cannot be rebuilt.
33
+ */
34
+ export declare const NODE_ID_PREFIX = "n2:";
35
+ export declare const EDGE_ID_PREFIX = "e2:";
36
+ /**
37
+ * Where a node's identity lives.
38
+ *
39
+ * `workspace` is the set of repositories that talk to each other — the contract
40
+ * boundary. It is **not** a GitHub organisation: an organisation is an ownership
41
+ * boundary, and the two overlap by accident and diverge by design. A workspace is
42
+ * always a deliberate configuration.
43
+ *
44
+ * It defaults to `repo`, so a single-repo user never learns the concept exists
45
+ * and local mode needs no configuration at all.
46
+ */
47
+ export interface IdentityScope {
48
+ readonly repo: string;
49
+ readonly workspace?: string | undefined;
50
+ }
51
+ /**
52
+ * The DEC-054 scope table.
53
+ *
54
+ * The rule it encodes, which should be derivable from the table without reading
55
+ * the decision: **a source artefact belongs to a repository, a contract belongs
56
+ * to a system, and earned knowledge belongs to whoever earned it.**
57
+ *
58
+ * `API_ROUTE` is repo-scoped and `API_ENDPOINT` is workspace-scoped, and that
59
+ * asymmetry is the entire contract mechanism:
60
+ *
61
+ * API_ROUTE ──SERVES_API──▶ API_ENDPOINT ◀──USES_API── caller
62
+ * (repo) (workspace)
63
+ *
64
+ * Widening `API_ROUTE` to match would collapse two services' handlers for one
65
+ * path into a single node and destroy the only question worth asking — *which
66
+ * service serves it* — while leaving the join count unchanged, so nothing in the
67
+ * output would reveal the loss.
68
+ *
69
+ * `MODEL` and `DTO` stay repo-scoped deliberately. Widening them would join a
70
+ * `Company` in one language to a `Company` in another on the strength of a
71
+ * shared word; §13.2's mechanism is shape comparison at R3, not identifiers. An
72
+ * endpoint template is safe to key on because both sides literally transmit it.
73
+ *
74
+ * `INCIDENT` and `FIX_PATTERN` are workspace *provisionally*. Global is the
75
+ * attractive answer and may yet be right, but they are the two types DEC-017
76
+ * says must survive a rebuild — so they are the only two where widening later is
77
+ * lossy, because the earned facts on a merged node cannot be re-derived apart.
78
+ * Widening is the safe direction to defer; un-merging is not a direction.
79
+ */
80
+ export declare const NODE_SCOPE: {
81
+ readonly FILE: "repo";
82
+ readonly FUNCTION: "repo";
83
+ readonly CLASS: "repo";
84
+ readonly MODULE: "repo";
85
+ readonly API_ROUTE: "repo";
86
+ readonly MODEL: "repo";
87
+ readonly DTO: "repo";
88
+ readonly COMPONENT: "repo";
89
+ readonly PAGE: "repo";
90
+ readonly TEST_CASE: "repo";
91
+ readonly API_ENDPOINT: "workspace";
92
+ readonly DATABASE_TABLE: "workspace";
93
+ readonly DATABASE_COLUMN: "workspace";
94
+ readonly INCIDENT: "workspace";
95
+ readonly FIX_PATTERN: "workspace";
96
+ };
97
+ export type ScopeKind = (typeof NODE_SCOPE)[NodeType];
98
+ /** Node types whose identity crosses a repository boundary. */
99
+ export declare const WORKSPACE_SCOPED_NODE_TYPES: readonly NodeType[];
100
+ /**
101
+ * The scope string that actually enters the hash, per DEC-054's table.
102
+ *
103
+ * The value must be **byte-identical** across every repository in a workspace.
104
+ * `billing-platform` in one config and `billing_platform` in another yields a
105
+ * graph that is structurally perfect, internally consistent, and joins nothing —
106
+ * zero joins, zero errors, zero warnings. That is why the workspace id is
107
+ * declared once and validated at the boundary rather than restated per repo.
108
+ */
109
+ export declare function scopeOf(kind: NodeType, scope: IdentityScope): string;
110
+ /**
111
+ * Deterministic JSON: object keys sorted, arrays order-preserving.
112
+ *
113
+ * Two structurally equal values must serialise byte-identically regardless of
114
+ * key insertion order, or identity depends on the order an adapter happened to
115
+ * build its objects in.
116
+ */
117
+ export declare function canonicalJson(value: unknown): string;
118
+ /**
119
+ * `n2:` + hash(scopeOf(kind), language, kind, qualified symbol path).
120
+ *
121
+ * Note what is absent: file path, line number, and `attrs`. A function that moves
122
+ * between files is the same function; a node whose attributes change is the same
123
+ * node.
124
+ *
125
+ * `language` is required rather than defaulted. Defaulting it to `null` would let
126
+ * a forgotten argument produce an id that quietly fails to match the one the
127
+ * adapter computes — a missing join with nothing to see. An identity change is
128
+ * exactly the moment every call site should have to be looked at.
129
+ *
130
+ * **`language` is now part of identity, which makes it a vocabulary rather than a
131
+ * free string.** Two producers describing the same artefact as `typescript` and
132
+ * `tsx` emit different nodes for the same symbol and never merge. Before DEC-054
133
+ * the field was descriptive; it is load-bearing now.
134
+ */
135
+ export declare function nodeId(scope: IdentityScope, kind: NodeType, qualifiedSymbolPath: string, language: string | null): string;
136
+ /**
137
+ * `e2:` + hash(from, to, type). See DEC-012.
138
+ *
139
+ * Deliberately excludes `resolution`, `confidence` and `producedBy`: two adapters
140
+ * finding the same relationship produce the same edge, which is what makes the
141
+ * merge policy possible. In prior-art graphs 30% of edges were exact duplicates,
142
+ * so this is load-bearing rather than tidy.
143
+ */
144
+ export declare function edgeId(from: string, to: string, type: string): string;
145
+ /**
146
+ * A stable identifier for one model-slot prompt's exact rendered text.
147
+ *
148
+ * `recording/writer.ts`'s `StartRunOptions.promptVersion` exists to carry
149
+ * this — `ai-layer-plan.md` §9 layer 4/layer 7's "invalidated by a prompt
150
+ * change" and "a golden that silently triples in cost after a prompt change
151
+ * is a regression" both need a real value here — and until now nothing
152
+ * computed one; that field's own doc reads *"No model layer exists yet, so
153
+ * always null today."* P25 (§11 Phase 5).
154
+ *
155
+ * Same hashing convention as {@link nodeId}/{@link edgeId} — first 128 bits
156
+ * of SHA-256, prefixed so a stored value is recognisably a prompt version
157
+ * rather than a node or edge id that happened to land in the same column.
158
+ * Hashes the **whole rendered prompt text**, not a caller-chosen label: a
159
+ * label is something a person remembers to update, a hash cannot be
160
+ * forgotten to change, and any difference — including whitespace — produces
161
+ * a different version, which is exactly the precision a looser comparison
162
+ * (trimmed, or blind to template placeholders) would miss.
163
+ *
164
+ * **Disclosed gap, same shape as every gate function before it in this
165
+ * plan:** `descry-desktop/packages/pipeline`'s model slots
166
+ * (`root-cause-analysis.ts`'s `NAME_LEVEL_SYSTEM_PROMPT` and its siblings)
167
+ * are this function's intended caller. None calls it yet — wiring it in, and
168
+ * republishing `@descryy/ir` so that repository can consume it, is
169
+ * follow-up work, not built here.
170
+ */
171
+ export declare function promptVersion(promptText: string): string;
172
+ /**
173
+ * A stable identity for one *shape* of finding — the key a dismissal ledger
174
+ * stores, so dismissing a finding suppresses the same claim on a later run
175
+ * without suppressing a different one that merely reads similarly.
176
+ *
177
+ * Architecture §20.1's funnel has always had an `F2` filter — *"known false
178
+ * positives from the feedback store"* — and §21.3 calls that store *"the real
179
+ * learning loop, not a cosmetic feature."* Neither had an implementation, and
180
+ * neither could until a finding had an identity that survives a re-run. This
181
+ * is that identity.
182
+ *
183
+ * **What it is over, and why each part earns its place:**
184
+ *
185
+ * - `finding`, `scope` — the claim, and what was being checked.
186
+ * - `affectedComponents` — node ids, which are `hash(repo, kind, qualified
187
+ * symbol path)` and carry no file path, so a dismissal survives a rename.
188
+ * The identity rule doing exactly the work it exists for.
189
+ * - the graph path's edges — the structural route the claim rests on. Same
190
+ * words about the same component via a different path is a different claim.
191
+ * - **`resolutionLevel`, per §21.3 explicitly**: corrections are stored *"with
192
+ * the resolution level at which the finding was produced… A false positive
193
+ * generated at R1 and one generated at R3 have different causes and should
194
+ * not suppress each other blindly."*
195
+ *
196
+ * **Deliberately excluded:** `recommendedAction`, `evidence`,
197
+ * `verificationStatus` — prose and per-run detail. A model rewording its own
198
+ * advice must not resurrect a finding the developer already dismissed.
199
+ *
200
+ * Lives here rather than in the funnel that consumes it for the reason
201
+ * `DEC-NEXT-edge-id-prefix-split` was filed: an identity gets one
202
+ * implementation, in the module named for identity, or it drifts.
203
+ */
204
+ export declare function findingFingerprint(finding: {
205
+ readonly finding: string;
206
+ readonly scope: string;
207
+ readonly affectedComponents: readonly string[];
208
+ readonly graphPath: readonly {
209
+ readonly from: string;
210
+ readonly to: string;
211
+ readonly edgeType: string;
212
+ }[];
213
+ readonly resolutionLevel: unknown;
214
+ }): string;
215
+ /**
216
+ * Erase every parameter segment to the literal token `{param}`.
217
+ *
218
+ * `/orders/:id`, `/orders/{order_id}`, `/orders/<int:id>` and `/orders/[id]` all
219
+ * normalise to `/orders/{param}`. This is what makes one `API_ENDPOINT` node out
220
+ * of two adapters that never share a line of code — the cross-language join.
221
+ *
222
+ * Position is preserved by path structure, so distinct routes cannot collide:
223
+ * `/orders/{param}/items/{param}` stays distinct from `/orders/{param}`.
224
+ *
225
+ * See DEC-013. Base paths, router mounts and gateway rewrites remain open
226
+ * (architecture §32 item 16) — this handles the segment shape only.
227
+ *
228
+ * ## The query string is not part of the identity, and leaving it in broke joins
229
+ *
230
+ * **Found on real code, by a lane whose own gates could not see it.** A caller
231
+ * writes `GET /api/invoices?company_id={param}`; the route producer writes
232
+ * `GET /api/invoices`, because no routing framework declares a query in its
233
+ * path. With the query left in, the two hash to different endpoints and the
234
+ * join returns **nothing** — no error, no rejection, no ledger row. Five such
235
+ * calls ship today across two repositories.
236
+ *
237
+ * The defect survived a 100/100 precision draw with a 0/100 control on both
238
+ * sides, because the adjudicator compares an edge against the source text and
239
+ * the source really does write `?company_id=`. Every instrument was asking *did
240
+ * you read this call correctly* and none was asking *can this identity ever
241
+ * join* — the same shape as DEC-112, one layer down: the reader and its grader
242
+ * shared an assumption, so no amount of grading escaped it.
243
+ *
244
+ * **A query string cannot distinguish two endpoints here**, because only
245
+ * consumers write one. Two callers differing only by query are reaching the
246
+ * same route, so collapsing them is the correct merge rather than a lossy one.
247
+ * The residual limit is real and disclosed rather than modelled: an API that
248
+ * *routes* on a query parameter — `?action=create` dispatching separately from
249
+ * `?action=delete` — collapses to one endpoint here. No producer in this
250
+ * project emits such a route, and inventing identity for one nobody declares
251
+ * would be the guess this rule exists to prevent.
252
+ */
253
+ export declare function normaliseEndpointPath(path: string): string;
254
+ /**
255
+ * Qualified symbol path for an `API_ENDPOINT`: `METHOD /normalised/path`.
256
+ * This value IS the endpoint's identity, which is why normalisation must be
257
+ * applied before hashing rather than after.
258
+ */
259
+ export declare function endpointQsp(method: string, path: string): string;
260
+ /** Qualified symbol path for a `DATABASE_TABLE` or `DATABASE_COLUMN`. */
261
+ export declare function tableQsp(schema: string, table: string, column?: string): string;
262
+ /**
263
+ * Qualified symbol path for a `TEST_CASE`: `pkg::suite::case`.
264
+ *
265
+ * **The package root is part of it, exactly as it is for a code symbol.**
266
+ * `symbolQsp` has carried it since DEC-047, and this did not — so a test's
267
+ * qualifier was its module path *relative to its own package anchor*, with the
268
+ * anchor thrown away. Five Strapi packages each ship
269
+ * `admin/src/translations/tests/plural.test.ts` with the same titles, and all
270
+ * five hashed to one node: 42 cross-file collisions dropping 86 test cases on
271
+ * one repository, and 0 on either repository this was built against.
272
+ *
273
+ * `occurrence` distinguishes two cases a runner would report identically. Two
274
+ * `it("…")` calls with the same title in one describe block are legal and
275
+ * common — 57 pairs on Rocket.Chat, 30 on Strapi — and they are two different
276
+ * tests. Merging them loses one; numbering them keeps both. Only the second and
277
+ * later carry a suffix, so the common case is unchanged.
278
+ */
279
+ export declare function testCaseQsp(pkg: string, suite: readonly string[], caseName: string, occurrence?: number): string;
280
+ /**
281
+ * Qualified symbol path for a code symbol: `pkg::Namespace.Symbol`.
282
+ * `pkg` is the module/package root, not a file path.
283
+ */
284
+ export declare function symbolQsp(pkg: string, symbolPath: readonly string[]): string;
285
+ /**
286
+ * Qualified symbol path for a `FILE`: the repo-relative path.
287
+ *
288
+ * This is the documented path-fallback in DEC-004. It is unavoidable — a file's
289
+ * only stable name IS its path — and it has a real consequence: `CHANGES_WITH`
290
+ * weights are file-to-file, so they churn on rename unless the rename-detection
291
+ * pass rewrites them. DEC-004's guarantee is total for symbols and partial for
292
+ * files, and that asymmetry is deliberate rather than overlooked.
293
+ */
294
+ export declare function fileQsp(repoRelativePath: string): string;
295
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}