@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
package/dist/ir.d.ts ADDED
@@ -0,0 +1,360 @@
1
+ /**
2
+ * The Canonical IR — the boundary every language reaches the graph through.
3
+ *
4
+ * Nothing above this boundary may name a language (architecture principle 7).
5
+ * `language` is carried as provenance so findings can be reported honestly; it is
6
+ * never branched on. The boundary lint enforces that mechanically.
7
+ */
8
+ import type { EdgeType, NodeType } from "./vocabulary.ts";
9
+ /**
10
+ * How well the code behind a node or edge was actually understood.
11
+ *
12
+ * R0 tree-sitter only · R1 + module resolution · R2 + LSP references/definitions
13
+ * · R3 + type checker (shapes) · R4 observed firing at runtime.
14
+ *
15
+ * This is the third confidence axis, alongside reliability class and environment
16
+ * level. It caps the reliability class of any finding resting on it, which is
17
+ * what stops multi-language support meaning multi-language guessing.
18
+ *
19
+ * Includes 4. See DEC-005 — R4 was defined in the architecture's prose but the
20
+ * IR type still read `0 | 1 | 2 | 3`, leaving the stated core moat nowhere to be
21
+ * stored.
22
+ */
23
+ export type ResolutionLevel = 0 | 1 | 2 | 3 | 4;
24
+ export declare const RESOLUTION_LEVELS: readonly [0, 1, 2, 3, 4];
25
+ /** Inclusive line range within a file. */
26
+ export interface Range {
27
+ readonly startLine: number;
28
+ readonly endLine: number;
29
+ }
30
+ export interface IRNode {
31
+ /**
32
+ * Stable across runs AND across file moves: `n2:` + hash(scope, language, kind,
33
+ * qualified symbol path). Deliberately excludes file path — see DEC-004,
34
+ * DEC-011 and DEC-054.
35
+ */
36
+ readonly id: string;
37
+ readonly type: NodeType;
38
+ readonly name: string;
39
+ /** Repo-relative. Null for fileless node types — see DEC-014. */
40
+ readonly file: string | null;
41
+ /** Null wherever `file` is null. */
42
+ readonly range: Range | null;
43
+ /**
44
+ * Provenance only. NEVER branched on above the IR boundary.
45
+ * Null on `API_ENDPOINT`, which belongs to no single language — the Normaliser
46
+ * sets this and rejects any adapter that claims otherwise.
47
+ */
48
+ readonly language: string | null;
49
+ /** `adapterId@version`. Drives graph invalidation on adapter upgrade. */
50
+ readonly producedBy: string;
51
+ readonly resolution: ResolutionLevel;
52
+ /**
53
+ * Language- and framework-specific detail, opaque above the boundary. This is
54
+ * the pressure valve that keeps the type list at 15: a Django model and a
55
+ * Prisma model are both `MODEL`, and what differs lives here.
56
+ *
57
+ * Never part of node identity.
58
+ */
59
+ readonly attrs: Readonly<Record<string, unknown>>;
60
+ }
61
+ export interface IREdge {
62
+ readonly from: string;
63
+ readonly to: string;
64
+ readonly type: EdgeType;
65
+ readonly resolution: ResolutionLevel;
66
+ /** 0–1. Feeds the impact score. Clamped down by resolution, never up. */
67
+ readonly confidence: number;
68
+ readonly producedBy: string;
69
+ /**
70
+ * The run that witnessed this edge actually firing. Required when
71
+ * `resolution === 4`; meaningless otherwise. Drives R4 expiry.
72
+ */
73
+ readonly observedByRun?: string;
74
+ readonly attrs?: Readonly<Record<string, unknown>>;
75
+ }
76
+ /**
77
+ * A reference an adapter saw but could not resolve to a real node.
78
+ *
79
+ * These are NOT written as edges. A name-matched guess is a false claim, and one
80
+ * wrong edge corrupts every layer above the graph — so it is dropped. But dropped
81
+ * silently there is no denominator for recall and nothing to report under "not
82
+ * analysable", so it is recorded here instead.
83
+ *
84
+ * Measured on real prior-art graphs, this is 52–60% of everything an adapter
85
+ * emits. See DEC-016.
86
+ */
87
+ export interface UnresolvedRef {
88
+ readonly fromNodeId: string;
89
+ readonly edgeType: EdgeType;
90
+ /** The raw text the adapter could not resolve, e.g. `append`. */
91
+ readonly rawTarget: string;
92
+ readonly file: string | null;
93
+ readonly line: number | null;
94
+ readonly producedBy: string;
95
+ readonly reason: string;
96
+ /**
97
+ * Producer-supplied detail, opaque above this boundary — the same contract
98
+ * as `IRNode.attrs`/`IREdge.attrs`. Two keys are named here:
99
+ *
100
+ * `blockedBy` — the literal source text of the specific expression that
101
+ * stopped resolution, e.g. `env.BACKEND_API_BASE_URL`,
102
+ * `import.meta.env.VITE_BACKEND_API_URL`. NOT the callee (`rawTarget`
103
+ * already carries that — `fetch`, `api.get`) and NOT a restatement of
104
+ * `reason` (prose for a person; `blockedBy` is the exact string a later
105
+ * human-confirmed answer is matched against, byte for byte, scoped to one
106
+ * repo — DEC-223 §3). `null` or absent means the row's cause is not "one
107
+ * unresolved expression" — a genuinely dynamic call has nothing singular
108
+ * to name.
109
+ *
110
+ * `refusalClass` — one of `UNRESOLVED_REF_CLASSES`, the producer's own
111
+ * answer to "can a human sentence ever resolve this row?" A `blockedBy`
112
+ * string alone cannot say that: naming the blocking expression does not
113
+ * mean a person can supply its value — see `UnresolvedRefClass`'s own doc
114
+ * for why this is a second, independent field rather than folded into the
115
+ * first. Never inferred from `reason`'s prose by a consumer (rule 3 — a
116
+ * text/name heuristic is not evidence); a producer sets it only when it
117
+ * knows structurally which bucket a row is in.
118
+ *
119
+ * **Assigned after config resolution, not before.** A producer must first
120
+ * check whatever local config sources it already reads (`.env` files,
121
+ * `wrangler.toml`'s `[vars]`, framework config) for the blocked
122
+ * expression's value — CLAUDE.md's own rule, "if the framework will tell
123
+ * you, never infer it." Only a value genuinely absent from every config
124
+ * source this reader can see is `"value-unknown"`; a value already sitting
125
+ * in a config file the repo contains must never reach this field as
126
+ * unresolved, and must never be classified in a way that could route it to
127
+ * a human. This ordering is a producer-side contract this field cannot
128
+ * enforce structurally — stated here because a shape that assumed
129
+ * classification runs first would license exactly that mistake.
130
+ *
131
+ * Both keys are optional and unenforced: most producers do not set them
132
+ * yet, and absence is a disclosed gap, never a rejection — see the
133
+ * reconciliation test in `normalise.test.ts` and DEC-223. Absent
134
+ * `refusalClass` means **unclassified**, and an unclassified row must never
135
+ * be defaulted to askable by a consumer — see the type's own doc.
136
+ */
137
+ readonly attrs?: Readonly<Record<string, unknown>>;
138
+ }
139
+ /**
140
+ * The closed set `attrs.refusalClass` may hold on an `UnresolvedRef`.
141
+ *
142
+ * Producer-declared, never inferred from `reason`'s prose — a text/name
143
+ * heuristic over a free-text field is exactly the guess rule 3 bans, and
144
+ * `reason` is reworded by producers without the underlying question changing
145
+ * (`packages/core/src/query/unresolved.ts`'s own header is the measured
146
+ * account of that failure). Assigned only after the producer's own
147
+ * config-resolution pass has had a chance to answer the row from a source
148
+ * the repo already contains — see `UnresolvedRef.attrs`'s doc.
149
+ *
150
+ * - `"value-unknown"` — the row names something whose *value* the reader does
151
+ * not statically know AND does not find in any config source it reads (an
152
+ * env var with no matching `.env`/deploy-config entry). One human sentence
153
+ * can resolve it, and — because matching is exact-string on
154
+ * `(repo, blockedBy)` — the same sentence resolves every row in that repo
155
+ * sharing the identical `attrs.blockedBy` (DEC-223 §3). This is the class
156
+ * `blockedBy` was built for, and the only one a question queue may surface.
157
+ * - `"capability-gap"` — the reader cannot parse or resolve this shape at
158
+ * all: an enum-dispatched verb, a path pattern it does not trace, a
159
+ * language construct with no extractor yet. No sentence answers this —
160
+ * only code does — and it must never reach a question queue. Measured: 92
161
+ * of 95 censused C#/Java refusal rows are this class, not `value-unknown`.
162
+ * - `"out-of-scope"` — the reference resolved correctly to something outside
163
+ * this workspace (a third-party host, another team's service) and was
164
+ * filed as a refusal anyway. Correct information sitting in the wrong
165
+ * ledger, not a gap and not askable either.
166
+ * - `"varies-per-call"` — the blocked value is not fixed anywhere to ask
167
+ * about: a caller-supplied prop, a function parameter, a request-scoped
168
+ * value that is, by design, different on every call (`FileViewerModal`'s
169
+ * `url` prop; an upload helper's destination argument). Distinct from
170
+ * `"value-unknown"` on purpose — that class means *a* fixed value exists
171
+ * and nobody has stated it yet; this class means no single value exists to
172
+ * state. Asking a human "what is `url`?" here is a category error, not an
173
+ * unanswered question, and folding this into `"value-unknown"` behind a
174
+ * flag would let a consumer that forgets to check the flag put an
175
+ * unanswerable row in front of someone anyway — the same failure a missing
176
+ * class was already found to cause once (see the measured C#/Java case
177
+ * below). A dedicated class makes the omission a type error instead.
178
+ *
179
+ * **Absent means unclassified, not `"value-unknown"`.** A consumer building a
180
+ * question queue from this ledger must treat an unset `refusalClass` as
181
+ * not-askable — defaulting unset to askable is exactly how an unaudited row
182
+ * would reach a user as a question nobody vetted.
183
+ *
184
+ * This split is proposed from measured censuses across two cycles, not
185
+ * settled — whether `"out-of-scope"` belongs in this ledger at all, rather
186
+ * than being a class within it, is an open question for whoever reviews this
187
+ * contract next, not decided here.
188
+ */
189
+ export declare const UNRESOLVED_REF_CLASSES: readonly ["value-unknown", "capability-gap", "out-of-scope", "varies-per-call"];
190
+ export type UnresolvedRefClass = (typeof UNRESOLVED_REF_CLASSES)[number];
191
+ /**
192
+ * DEC-223's answer half — a person's confirmed value for a `"value-unknown"`
193
+ * refusal-ledger row.
194
+ *
195
+ * Keyed on **exact string equality on `(repo, blockedBy)`, never fuzzy and
196
+ * never inferred across repositories** (rule 3 — "an answer is a declaration
197
+ * about the case it answers, never an inference about a second one"). `env.
198
+ * BACKEND_URL` and `env.BACKEND_API_BASE_URL` are different questions even
199
+ * when they plausibly resolve the same way; the same repository's identically
200
+ * spelled variable in a second application is a different case entirely.
201
+ * `blockedBy` here matches `UnresolvedRef.attrs.blockedBy` byte for byte —
202
+ * the field is named identically on both sides deliberately, so a reader (and
203
+ * a linter) can see the two are the same value without tracing a rename. A
204
+ * row with no `blockedBy` has nothing to key an answer on and cannot be
205
+ * answered by this mechanism at all, only by a code change.
206
+ *
207
+ * One fact legitimately answers every ledger row in `repo` sharing the
208
+ * identical `blockedBy` value, because they are, by construction, blocked by
209
+ * the same runtime binding — DEC-223's own measured case is exactly this: one
210
+ * `BACKEND_API_BASE_URL` value would join 18 rows in `sherpa-cf-worker`, one
211
+ * `VITE_BACKEND_API_URL` value already joins 50 in `sherpa-frontend`.
212
+ *
213
+ * This type says nothing about what an edge built from it may be classified
214
+ * as — that is `reliabilityCap`'s `humanAsserted` parameter, a second and
215
+ * independent guard, deliberately not encoded here so that a consumer cannot
216
+ * satisfy the obligation by constructing a `ConfirmedFact` correctly and then
217
+ * forgetting to pass the flag three functions later.
218
+ */
219
+ export interface ConfirmedFact {
220
+ /** Scope — matches `RepoRoot.repo`. Never inferred across repositories. */
221
+ readonly repo: string;
222
+ /** Must equal some row's `attrs.blockedBy`, exact string match. */
223
+ readonly blockedBy: string;
224
+ /** The human-supplied literal. */
225
+ readonly value: string;
226
+ /**
227
+ * Optional, and unscoped is a legal, common answer — not a placeholder for
228
+ * one not yet given. A real answer arrived for `VITE_BACKEND_API_URL` in
229
+ * `sherpa-frontend` naming no environment at all, and forcing one would
230
+ * have meant guessing rather than recording what was actually said.
231
+ *
232
+ * Two facts for the same `(repo, blockedBy)` are the same answer only when
233
+ * their `environment` also matches (`undefined` is its own value, not a
234
+ * wildcard) — recording `"staging"` after recording an unscoped answer adds
235
+ * a second fact rather than overwriting the first, because a repository can
236
+ * genuinely need three different values for one expression: one per
237
+ * environment, or none at all when the repository does not distinguish
238
+ * them. `applyConfirmedFacts` surfaces every matching fact rather than
239
+ * picking one, so a caller sees the split rather than an arbitrarily
240
+ * chosen value.
241
+ *
242
+ * **Human-supplied only, never inferred.** Nothing above this type may
243
+ * read `"staging"` out of a hostname or a file path and assign it here —
244
+ * the same discipline `blockedBy` matching already applies to `expression`
245
+ * text, extended to this field: a guess dressed as a declaration is worse
246
+ * than an honest `undefined`.
247
+ */
248
+ readonly environment?: string;
249
+ /** Free text — who or what asserted this. Not validated; recorded as given. */
250
+ readonly confirmedBy: string;
251
+ /** ISO timestamp. */
252
+ readonly confirmedAt: string;
253
+ /**
254
+ * The repo's `commitSha` (see `IRBatch.commitSha`) at the moment this fact
255
+ * was confirmed — "against what", to `confirmedAt`'s "when". Optional: a
256
+ * fact confirmed before this field existed carries none, and that absence
257
+ * is itself meaningful (staleness cannot be checked, not "assumed fresh") —
258
+ * never backfilled with a guess.
259
+ *
260
+ * This is the disclosure floor, not a re-verification mechanism: nothing
261
+ * reads this field to re-check a fact against a live source (there is no
262
+ * live source yet — `descry-runtime` does not exist). It exists so a
263
+ * consumer minting an edge from this fact *today* can say whether today's
264
+ * commit matches the one the human answer was given against, and disclose
265
+ * the mismatch rather than presenting an old answer as current.
266
+ */
267
+ readonly confirmedAtCommit?: string;
268
+ }
269
+ /**
270
+ * A node this batch offers as a target for a **declared cross-language
271
+ * namespace peer** — see `CapabilityMatrix.crossLanguageNamespace`, DEC-161.
272
+ *
273
+ * `qualifiedName` is what the REFERENCING side's own source would spell to
274
+ * name this node — a Java `import`'s fully-qualified name, for a Kotlin
275
+ * declaration reached from Java. It is opaque above this point: the resolver
276
+ * that consumes it does string equality only, never parses it, and nothing
277
+ * above the IR may know what syntax produced it. This is the field
278
+ * `IRNode.id`'s hash cannot serve in its place — an id is stable and
279
+ * unreadable by design, and a cross-language match needs a string a *different
280
+ * adapter* could independently compute.
281
+ *
282
+ * Not identity. `nodeId` is the real, existing node id; this is an additional,
283
+ * adapter-declared fact about how to *reach* it from outside its own language.
284
+ */
285
+ export interface CrossLanguageDeclaration {
286
+ readonly qualifiedName: string;
287
+ readonly nodeId: string;
288
+ }
289
+ /**
290
+ * Why a file this batch's adapter attempted contributed no nodes or edges.
291
+ *
292
+ * Distinct from `UnresolvedRef` — that ledger is a reference the adapter *saw*
293
+ * and could not resolve to a node; this is a file the adapter could not read
294
+ * or parse at all, so it never got the chance to see anything in it. Mirrors
295
+ * `ParseFailure` (`@descryhq-wq/adapter-treesitter`) rather than inventing new
296
+ * vocabulary, since that shape is already what most producers compute — this
297
+ * field just gives it somewhere to go instead of dead-ending on `session`.
298
+ */
299
+ export declare const SKIP_REASONS: readonly ["too-large", "unreadable", "grammar-error", "other"];
300
+ export type SkipReason = (typeof SKIP_REASONS)[number];
301
+ export interface SkippedFile {
302
+ readonly file: string;
303
+ readonly reason: SkipReason;
304
+ /** Producer-supplied, human-readable — the underlying error message, not a restatement of `reason`. */
305
+ readonly detail: string;
306
+ }
307
+ /**
308
+ * The unit of ingest, and the unit of invalidation.
309
+ *
310
+ * `IREdge` carries no file, so stale edges from a changed file cannot be deleted
311
+ * from the edge alone — and derived edges (`PROPAGATES_TO`, `CHANGES_WITH`) have
312
+ * no owning file at all. Rather than patch `IREdge`, which the architecture pins,
313
+ * invalidation lives here: the writer deletes everything previously written by
314
+ * `producedBy` for `sourceFiles`, then inserts, in one transaction.
315
+ *
316
+ * See DEC-015, amended by the `skippedFiles` decision below.
317
+ */
318
+ export interface IRBatch {
319
+ readonly repo: string;
320
+ /**
321
+ * The identity scope for workspace-scoped node types (DEC-054). Absent means
322
+ * "this repository alone". Carried on the batch and not only on the context so
323
+ * that a stored batch records the scope its ids were computed under — an id
324
+ * cannot be re-derived from the node alone once the config has moved on.
325
+ */
326
+ readonly workspace?: string | undefined;
327
+ readonly commitSha: string;
328
+ /** `adapterId@version` — must match every node and edge in the batch. */
329
+ readonly producedBy: string;
330
+ /**
331
+ * Repo-relative paths this batch's nodes and edges were actually derived
332
+ * from — files the adapter successfully analysed. **Not** every file it
333
+ * attempted; a file that failed belongs in `skippedFiles` instead, never
334
+ * here. This is the invalidation key: a file sitting in this list falsely
335
+ * (because it actually failed) makes the writer treat it as re-covered and
336
+ * silently evict its last-known-good rows instead of disclosing the
337
+ * regression via `evictedFiles` — see DEC-022.
338
+ */
339
+ readonly sourceFiles: readonly string[];
340
+ /**
341
+ * Files this batch's adapter attempted but that contributed no nodes or
342
+ * edges, with why. Never overlaps `sourceFiles` — a file is either analysed
343
+ * (named there) or skipped (named here), never both. Not yet persisted
344
+ * durably by the writer; surfaced as an in-request disclosure only.
345
+ */
346
+ readonly skippedFiles: readonly SkippedFile[];
347
+ /** The level `prepare()` actually reached, not the level hoped for. */
348
+ readonly reachedResolution: ResolutionLevel;
349
+ readonly nodes: readonly IRNode[];
350
+ readonly edges: readonly IREdge[];
351
+ readonly unresolved: readonly UnresolvedRef[];
352
+ /**
353
+ * Nodes in this batch reachable from a declared cross-language namespace
354
+ * peer, keyed by what that peer's own source would spell to name them.
355
+ * Empty or absent unless this batch's adapter declares
356
+ * `CapabilityMatrix.crossLanguageNamespace`. See DEC-161.
357
+ */
358
+ readonly crossLanguageDeclarations?: readonly CrossLanguageDeclaration[];
359
+ }
360
+ //# sourceMappingURL=ir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ir.d.ts","sourceRoot":"","sources":["../src/ir.ts"],"names":[],"mappings":"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"}
package/dist/ir.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The Canonical IR — the boundary every language reaches the graph through.
3
+ *
4
+ * Nothing above this boundary may name a language (architecture principle 7).
5
+ * `language` is carried as provenance so findings can be reported honestly; it is
6
+ * never branched on. The boundary lint enforces that mechanically.
7
+ */
8
+ export const RESOLUTION_LEVELS = [0, 1, 2, 3, 4];
9
+ /**
10
+ * The closed set `attrs.refusalClass` may hold on an `UnresolvedRef`.
11
+ *
12
+ * Producer-declared, never inferred from `reason`'s prose — a text/name
13
+ * heuristic over a free-text field is exactly the guess rule 3 bans, and
14
+ * `reason` is reworded by producers without the underlying question changing
15
+ * (`packages/core/src/query/unresolved.ts`'s own header is the measured
16
+ * account of that failure). Assigned only after the producer's own
17
+ * config-resolution pass has had a chance to answer the row from a source
18
+ * the repo already contains — see `UnresolvedRef.attrs`'s doc.
19
+ *
20
+ * - `"value-unknown"` — the row names something whose *value* the reader does
21
+ * not statically know AND does not find in any config source it reads (an
22
+ * env var with no matching `.env`/deploy-config entry). One human sentence
23
+ * can resolve it, and — because matching is exact-string on
24
+ * `(repo, blockedBy)` — the same sentence resolves every row in that repo
25
+ * sharing the identical `attrs.blockedBy` (DEC-223 §3). This is the class
26
+ * `blockedBy` was built for, and the only one a question queue may surface.
27
+ * - `"capability-gap"` — the reader cannot parse or resolve this shape at
28
+ * all: an enum-dispatched verb, a path pattern it does not trace, a
29
+ * language construct with no extractor yet. No sentence answers this —
30
+ * only code does — and it must never reach a question queue. Measured: 92
31
+ * of 95 censused C#/Java refusal rows are this class, not `value-unknown`.
32
+ * - `"out-of-scope"` — the reference resolved correctly to something outside
33
+ * this workspace (a third-party host, another team's service) and was
34
+ * filed as a refusal anyway. Correct information sitting in the wrong
35
+ * ledger, not a gap and not askable either.
36
+ * - `"varies-per-call"` — the blocked value is not fixed anywhere to ask
37
+ * about: a caller-supplied prop, a function parameter, a request-scoped
38
+ * value that is, by design, different on every call (`FileViewerModal`'s
39
+ * `url` prop; an upload helper's destination argument). Distinct from
40
+ * `"value-unknown"` on purpose — that class means *a* fixed value exists
41
+ * and nobody has stated it yet; this class means no single value exists to
42
+ * state. Asking a human "what is `url`?" here is a category error, not an
43
+ * unanswered question, and folding this into `"value-unknown"` behind a
44
+ * flag would let a consumer that forgets to check the flag put an
45
+ * unanswerable row in front of someone anyway — the same failure a missing
46
+ * class was already found to cause once (see the measured C#/Java case
47
+ * below). A dedicated class makes the omission a type error instead.
48
+ *
49
+ * **Absent means unclassified, not `"value-unknown"`.** A consumer building a
50
+ * question queue from this ledger must treat an unset `refusalClass` as
51
+ * not-askable — defaulting unset to askable is exactly how an unaudited row
52
+ * would reach a user as a question nobody vetted.
53
+ *
54
+ * This split is proposed from measured censuses across two cycles, not
55
+ * settled — whether `"out-of-scope"` belongs in this ledger at all, rather
56
+ * than being a class within it, is an open question for whoever reviews this
57
+ * contract next, not decided here.
58
+ */
59
+ export const UNRESOLVED_REF_CLASSES = [
60
+ "value-unknown",
61
+ "capability-gap",
62
+ "out-of-scope",
63
+ "varies-per-call",
64
+ ];
65
+ /**
66
+ * Why a file this batch's adapter attempted contributed no nodes or edges.
67
+ *
68
+ * Distinct from `UnresolvedRef` — that ledger is a reference the adapter *saw*
69
+ * and could not resolve to a node; this is a file the adapter could not read
70
+ * or parse at all, so it never got the chance to see anything in it. Mirrors
71
+ * `ParseFailure` (`@descryhq-wq/adapter-treesitter`) rather than inventing new
72
+ * vocabulary, since that shape is already what most producers compute — this
73
+ * field just gives it somewhere to go instead of dead-ending on `session`.
74
+ */
75
+ export const SKIP_REASONS = ["too-large", "unreadable", "grammar-error", "other"];
76
+ //# sourceMappingURL=ir.js.map
package/dist/ir.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ir.js","sourceRoot":"","sources":["../src/ir.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU,CAAC;AA2H1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,iBAAiB;CACT,CAAC;AAuGX;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,CAAU,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * `DATABASE_TABLE`/`DATABASE_COLUMN` from a `MODEL`'s own mapped shape —
3
+ * DEC-240's Option 2.
4
+ *
5
+ * `adapter-sql` is the only producer of these two types until now, reading
6
+ * `*.sql` DDL. A Python (or, later, Java/C#) stack that maps its tables
7
+ * through an ORM and never checks in a migration file has no producer at
8
+ * all — 17 real tables on the stack this was measured against, zero
9
+ * `DATABASE_TABLE` nodes. DEC-240 ruled that the fix is not "teach
10
+ * `adapter-python`'s `orm.ts` to mint these too": a language adapter that did
11
+ * would also have to decide Django's/JPA's/EF Core's own implicit
12
+ * table-naming convention (app label + lowercased model name, a class name,
13
+ * a pluralisation rule) to cover the common case where no explicit table
14
+ * name is written — three adapters each independently inventing, or
15
+ * independently refusing, the same class of naming inference is three
16
+ * chances to disagree on the one byte that has to be identical for a join to
17
+ * happen at all. So the naming judgement — explicit name or refuse, never
18
+ * infer — lives here instead, once, and every adapter calls this function
19
+ * at its own emit time rather than re-deriving the rule.
20
+ *
21
+ * ## The part DEC-240's own ruling did not check, and the fix could not
22
+ * ship correctly without
23
+ *
24
+ * `nodeId` hashes `[scope, language, kind, qualifiedSymbolPath]` for every
25
+ * node type **except** the three in `FILELESS_NODE_TYPES` — and
26
+ * `DATABASE_TABLE`/`DATABASE_COLUMN` are not in that list. `adapter-sql`
27
+ * already hashes its own nodes with `language: "sql"`. A second producer
28
+ * hashing with its own language — `"python"`, later `"java"`, `"csharp"` —
29
+ * would mint a *different* id for the same real table than every other
30
+ * producer, and DEC-054's whole point (a Python `DATABASE_COLUMN` and a
31
+ * TypeScript `COMPONENT` meeting through one `API_ENDPOINT`) fails silently
32
+ * the identical way DEC-055 already found once: everything resolves,
33
+ * nothing errors, and the join returns nothing. So this function — and, to
34
+ * match it, `adapter-sql`'s own two call sites — hash with `language: null`
35
+ * unconditionally.
36
+ *
37
+ * **The emitted node's own `language` field is `null` too, not the real
38
+ * per-producer language.** The first cut of this function kept the real
39
+ * language there ("only the identity hash has to be language-blind, not the
40
+ * metadata") and that was wrong: the graph builder's collision detector
41
+ * (`identityFingerprint`, `packages/core/src/graph/merge.ts`) compares
42
+ * `[type, language]` for any two nodes that land on one id, precisely to
43
+ * catch two *different* things sharing an id by accident (DEC-024). A real
44
+ * per-producer language on this field means two genuine producers of the
45
+ * *same* table — a Python ORM and the `*.sql` DDL for it, this function's own
46
+ * motivating case — always disagree on `language` and always trip that
47
+ * detector, discarding one side's `DATABASE_COLUMN`s outright. `origin.language`
48
+ * is kept on {@link ModelTableOrigin} only as caller-supplied provenance for
49
+ * future use (e.g. `attrs`); it is not echoed onto the node. Per-producer
50
+ * provenance is still recoverable after a real merge through the node's
51
+ * `attrs.corroboratedBy`, the same mechanism every other cross-producer merge
52
+ * in this project already uses instead of a scalar field.
53
+ *
54
+ * `schema` defaults to `"public"` when a shape names none, matching
55
+ * `adapter-sql`'s own `DEFAULT_SCHEMA` — duplicated as a literal because
56
+ * `@descry/ir` cannot depend on an adapter package to import the constant.
57
+ * A model that maps to a non-default schema and never says so joins nothing
58
+ * either, the same silent-refusal shape `tableOf()` already accepts for an
59
+ * implicit table name.
60
+ */
61
+ import { type IdentityScope } from "./identity.ts";
62
+ import type { IRNode, ResolutionLevel } from "./ir.ts";
63
+ /** Matches `adapter-sql`'s own `DEFAULT_SCHEMA` (`packages/adapter-sql` in `descry-adapters`). */
64
+ export declare const DEFAULT_MODEL_SCHEMA = "public";
65
+ export interface ModelFieldShape {
66
+ readonly name: string;
67
+ readonly nullable: boolean;
68
+ /**
69
+ * The database column's own name, where the mapping states one explicitly
70
+ * and it differs from `name` — TypeORM's `@Column({ name: "..." })` is the
71
+ * first producer of this, mapping a camelCase property to a snake_case
72
+ * column the way real TypeScript ORM code commonly does. Defaults to
73
+ * `name` when absent, the same "explicit or refuse, never guess" rule
74
+ * this file's header already holds table names to: read, never derived by
75
+ * a naming-convention rule this function does not own.
76
+ */
77
+ readonly columnName?: string | undefined;
78
+ }
79
+ /**
80
+ * The mapped shape a `MODEL`'s own extractor already produced — `orm.ts`'s
81
+ * `OrmShape` and its future JPA/EF Core siblings all carry exactly this:
82
+ * a table name where the source wrote one explicitly, `null` where it did
83
+ * not, and the fields that shape maps.
84
+ */
85
+ export interface ModelTableShape {
86
+ /** `null` when the source names no explicit table. Refused below, never guessed. */
87
+ readonly table: string | null;
88
+ readonly fields: readonly ModelFieldShape[];
89
+ /** Explicit schema, where the source states one. Defaults to `DEFAULT_MODEL_SCHEMA`. */
90
+ readonly schema?: string | undefined;
91
+ }
92
+ export interface ModelTableOrigin {
93
+ readonly file: string;
94
+ readonly line: number;
95
+ readonly producedBy: string;
96
+ readonly resolution: ResolutionLevel;
97
+ /**
98
+ * The emitting adapter's own language. Carried on neither the node nor the
99
+ * id — see this file's header for why the node's `language` field is `null`
100
+ * too, not just the hash. Kept on this type as caller-supplied provenance
101
+ * for possible future use.
102
+ */
103
+ readonly language: string;
104
+ }
105
+ /**
106
+ * `DATABASE_TABLE` plus one `DATABASE_COLUMN` per mapped field, or `[]` when
107
+ * `shape.table` is `null` — the disclosed refusal `tableOf()` already makes
108
+ * for an implicit table name, carried through rather than re-decided here.
109
+ */
110
+ export declare function databaseTableNodesFromModel(scope: IdentityScope, shape: ModelTableShape, origin: ModelTableOrigin): readonly IRNode[];
111
+ //# sourceMappingURL=model-tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-tables.d.ts","sourceRoot":"","sources":["../src/model-tables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEvD,kGAAkG;AAClG,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,wFAAwF;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,gBAAgB,GACvB,SAAS,MAAM,EAAE,CAmCnB"}