@descryy/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/capabilities/index.d.ts +3 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +2 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/preconditions.d.ts +99 -0
- package/dist/capabilities/preconditions.d.ts.map +1 -0
- package/dist/capabilities/preconditions.js +94 -0
- package/dist/capabilities/preconditions.js.map +1 -0
- package/dist/contracts/engine.d.ts +90 -0
- package/dist/contracts/engine.d.ts.map +1 -0
- package/dist/contracts/engine.js +291 -0
- package/dist/contracts/engine.js.map +1 -0
- package/dist/contracts/index.d.ts +9 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/orphans.d.ts +96 -0
- package/dist/contracts/orphans.d.ts.map +1 -0
- package/dist/contracts/orphans.js +241 -0
- package/dist/contracts/orphans.js.map +1 -0
- package/dist/contracts/paths.d.ts +132 -0
- package/dist/contracts/paths.d.ts.map +1 -0
- package/dist/contracts/paths.js +245 -0
- package/dist/contracts/paths.js.map +1 -0
- package/dist/contracts/shapes.d.ts +99 -0
- package/dist/contracts/shapes.d.ts.map +1 -0
- package/dist/contracts/shapes.js +196 -0
- package/dist/contracts/shapes.js.map +1 -0
- package/dist/governance/budget.d.ts +89 -0
- package/dist/governance/budget.d.ts.map +1 -0
- package/dist/governance/budget.js +101 -0
- package/dist/governance/budget.js.map +1 -0
- package/dist/governance/candidate-boundary.d.ts +49 -0
- package/dist/governance/candidate-boundary.d.ts.map +1 -0
- package/dist/governance/candidate-boundary.js +51 -0
- package/dist/governance/candidate-boundary.js.map +1 -0
- package/dist/governance/escalation.d.ts +63 -0
- package/dist/governance/escalation.d.ts.map +1 -0
- package/dist/governance/escalation.js +72 -0
- package/dist/governance/escalation.js.map +1 -0
- package/dist/governance/fact-boundary.d.ts +77 -0
- package/dist/governance/fact-boundary.d.ts.map +1 -0
- package/dist/governance/fact-boundary.js +91 -0
- package/dist/governance/fact-boundary.js.map +1 -0
- package/dist/governance/finding-funnel.d.ts +109 -0
- package/dist/governance/finding-funnel.d.ts.map +1 -0
- package/dist/governance/finding-funnel.js +137 -0
- package/dist/governance/finding-funnel.js.map +1 -0
- package/dist/governance/index.d.ts +11 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +6 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/graph/build.d.ts +127 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +0 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/cross-language.d.ts +70 -0
- package/dist/graph/cross-language.d.ts.map +1 -0
- package/dist/graph/cross-language.js +104 -0
- package/dist/graph/cross-language.js.map +1 -0
- package/dist/graph/index.d.ts +11 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +6 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/merge.d.ts +95 -0
- package/dist/graph/merge.d.ts.map +1 -0
- package/dist/graph/merge.js +131 -0
- package/dist/graph/merge.js.map +1 -0
- package/dist/graph/persist.d.ts +42 -0
- package/dist/graph/persist.d.ts.map +1 -0
- package/dist/graph/persist.js +52 -0
- package/dist/graph/persist.js.map +1 -0
- package/dist/graph/runtime-confirmation.d.ts +169 -0
- package/dist/graph/runtime-confirmation.d.ts.map +1 -0
- package/dist/graph/runtime-confirmation.js +318 -0
- package/dist/graph/runtime-confirmation.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger/index.d.ts +5 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +3 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/ledger/mint.d.ts +81 -0
- package/dist/ledger/mint.d.ts.map +1 -0
- package/dist/ledger/mint.js +138 -0
- package/dist/ledger/mint.js.map +1 -0
- package/dist/ledger/resolve.d.ts +124 -0
- package/dist/ledger/resolve.d.ts.map +1 -0
- package/dist/ledger/resolve.js +290 -0
- package/dist/ledger/resolve.js.map +1 -0
- package/dist/multipr/fingerprint.d.ts +56 -0
- package/dist/multipr/fingerprint.d.ts.map +1 -0
- package/dist/multipr/fingerprint.js +169 -0
- package/dist/multipr/fingerprint.js.map +1 -0
- package/dist/multipr/hidden-dependency.d.ts +120 -0
- package/dist/multipr/hidden-dependency.d.ts.map +1 -0
- package/dist/multipr/hidden-dependency.js +185 -0
- package/dist/multipr/hidden-dependency.js.map +1 -0
- package/dist/multipr/index.d.ts +14 -0
- package/dist/multipr/index.d.ts.map +1 -0
- package/dist/multipr/index.js +8 -0
- package/dist/multipr/index.js.map +1 -0
- package/dist/multipr/mechanical.d.ts +239 -0
- package/dist/multipr/mechanical.d.ts.map +1 -0
- package/dist/multipr/mechanical.js +333 -0
- package/dist/multipr/mechanical.js.map +1 -0
- package/dist/multipr/migration-heads.d.ts +160 -0
- package/dist/multipr/migration-heads.d.ts.map +1 -0
- package/dist/multipr/migration-heads.js +212 -0
- package/dist/multipr/migration-heads.js.map +1 -0
- package/dist/multipr/overlap.d.ts +186 -0
- package/dist/multipr/overlap.d.ts.map +1 -0
- package/dist/multipr/overlap.js +254 -0
- package/dist/multipr/overlap.js.map +1 -0
- package/dist/multipr/scope-store.d.ts +105 -0
- package/dist/multipr/scope-store.d.ts.map +1 -0
- package/dist/multipr/scope-store.js +182 -0
- package/dist/multipr/scope-store.js.map +1 -0
- package/dist/multipr/superseded.d.ts +133 -0
- package/dist/multipr/superseded.d.ts.map +1 -0
- package/dist/multipr/superseded.js +128 -0
- package/dist/multipr/superseded.js.map +1 -0
- package/dist/query/confirmed-facts.d.ts +56 -0
- package/dist/query/confirmed-facts.d.ts.map +1 -0
- package/dist/query/confirmed-facts.js +64 -0
- package/dist/query/confirmed-facts.js.map +1 -0
- package/dist/query/declared-value-closure.d.ts +126 -0
- package/dist/query/declared-value-closure.d.ts.map +1 -0
- package/dist/query/declared-value-closure.js +156 -0
- package/dist/query/declared-value-closure.js.map +1 -0
- package/dist/query/index.d.ts +28 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +15 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/memory.d.ts +20 -0
- package/dist/query/memory.d.ts.map +1 -0
- package/dist/query/memory.js +55 -0
- package/dist/query/memory.js.map +1 -0
- package/dist/query/provider.d.ts +127 -0
- package/dist/query/provider.d.ts.map +1 -0
- package/dist/query/provider.js +54 -0
- package/dist/query/provider.js.map +1 -0
- package/dist/query/queries.d.ts +119 -0
- package/dist/query/queries.d.ts.map +1 -0
- package/dist/query/queries.js +267 -0
- package/dist/query/queries.js.map +1 -0
- package/dist/query/refusal-fetch.d.ts +114 -0
- package/dist/query/refusal-fetch.d.ts.map +1 -0
- package/dist/query/refusal-fetch.js +200 -0
- package/dist/query/refusal-fetch.js.map +1 -0
- package/dist/query/refusal-questions.d.ts +123 -0
- package/dist/query/refusal-questions.d.ts.map +1 -0
- package/dist/query/refusal-questions.js +122 -0
- package/dist/query/refusal-questions.js.map +1 -0
- package/dist/query/root-cause-score.d.ts +218 -0
- package/dist/query/root-cause-score.d.ts.map +1 -0
- package/dist/query/root-cause-score.js +251 -0
- package/dist/query/root-cause-score.js.map +1 -0
- package/dist/query/row-closure-picture.d.ts +108 -0
- package/dist/query/row-closure-picture.d.ts.map +1 -0
- package/dist/query/row-closure-picture.js +131 -0
- package/dist/query/row-closure-picture.js.map +1 -0
- package/dist/query/similar-incidents.d.ts +76 -0
- package/dist/query/similar-incidents.d.ts.map +1 -0
- package/dist/query/similar-incidents.js +169 -0
- package/dist/query/similar-incidents.js.map +1 -0
- package/dist/query/sqlite.d.ts +13 -0
- package/dist/query/sqlite.d.ts.map +1 -0
- package/dist/query/sqlite.js +54 -0
- package/dist/query/sqlite.js.map +1 -0
- package/dist/query/traverse.d.ts +110 -0
- package/dist/query/traverse.d.ts.map +1 -0
- package/dist/query/traverse.js +0 -0
- package/dist/query/traverse.js.map +1 -0
- package/dist/query/unresolved.d.ts +195 -0
- package/dist/query/unresolved.d.ts.map +1 -0
- package/dist/query/unresolved.js +225 -0
- package/dist/query/unresolved.js.map +1 -0
- package/dist/query/verification-status.d.ts +134 -0
- package/dist/query/verification-status.d.ts.map +1 -0
- package/dist/query/verification-status.js +163 -0
- package/dist/query/verification-status.js.map +1 -0
- package/dist/recording/index.d.ts +26 -0
- package/dist/recording/index.d.ts.map +1 -0
- package/dist/recording/index.js +30 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording/migrate.d.ts +22 -0
- package/dist/recording/migrate.d.ts.map +1 -0
- package/dist/recording/migrate.js +51 -0
- package/dist/recording/migrate.js.map +1 -0
- package/dist/recording/reader.d.ts +27 -0
- package/dist/recording/reader.d.ts.map +1 -0
- package/dist/recording/reader.js +55 -0
- package/dist/recording/reader.js.map +1 -0
- package/dist/recording/redact.d.ts +63 -0
- package/dist/recording/redact.d.ts.map +1 -0
- package/dist/recording/redact.js +98 -0
- package/dist/recording/redact.js.map +1 -0
- package/dist/recording/schema.d.ts +41 -0
- package/dist/recording/schema.d.ts.map +1 -0
- package/dist/recording/schema.js +104 -0
- package/dist/recording/schema.js.map +1 -0
- package/dist/recording/writer.d.ts +120 -0
- package/dist/recording/writer.d.ts.map +1 -0
- package/dist/recording/writer.js +81 -0
- package/dist/recording/writer.js.map +1 -0
- package/dist/scoping/fanout.d.ts +86 -0
- package/dist/scoping/fanout.d.ts.map +1 -0
- package/dist/scoping/fanout.js +95 -0
- package/dist/scoping/fanout.js.map +1 -0
- package/dist/scoping/index.d.ts +10 -0
- package/dist/scoping/index.d.ts.map +1 -0
- package/dist/scoping/index.js +6 -0
- package/dist/scoping/index.js.map +1 -0
- package/dist/scoping/score.d.ts +83 -0
- package/dist/scoping/score.d.ts.map +1 -0
- package/dist/scoping/score.js +125 -0
- package/dist/scoping/score.js.map +1 -0
- package/dist/scoping/tiers.d.ts +32 -0
- package/dist/scoping/tiers.d.ts.map +1 -0
- package/dist/scoping/tiers.js +35 -0
- package/dist/scoping/tiers.js.map +1 -0
- package/dist/scoping/traverse.d.ts +60 -0
- package/dist/scoping/traverse.d.ts.map +1 -0
- package/dist/scoping/traverse.js +172 -0
- package/dist/scoping/traverse.js.map +1 -0
- package/dist/scoping/weights.d.ts +97 -0
- package/dist/scoping/weights.d.ts.map +1 -0
- package/dist/scoping/weights.js +167 -0
- package/dist/scoping/weights.js.map +1 -0
- package/dist/sources/git/diff.d.ts +53 -0
- package/dist/sources/git/diff.d.ts.map +1 -0
- package/dist/sources/git/diff.js +140 -0
- package/dist/sources/git/diff.js.map +1 -0
- package/dist/sources/git/env.d.ts +2 -0
- package/dist/sources/git/env.d.ts.map +1 -0
- package/dist/sources/git/env.js +21 -0
- package/dist/sources/git/env.js.map +1 -0
- package/dist/sources/git/history.d.ts +140 -0
- package/dist/sources/git/history.d.ts.map +1 -0
- package/dist/sources/git/history.js +314 -0
- package/dist/sources/git/history.js.map +1 -0
- package/dist/sources/git/index.d.ts +7 -0
- package/dist/sources/git/index.d.ts.map +1 -0
- package/dist/sources/git/index.js +4 -0
- package/dist/sources/git/index.js.map +1 -0
- package/dist/sources/git/source.d.ts +95 -0
- package/dist/sources/git/source.d.ts.map +1 -0
- package/dist/sources/git/source.js +0 -0
- package/dist/sources/git/source.js.map +1 -0
- package/dist/sources/migrations/dialects.d.ts +59 -0
- package/dist/sources/migrations/dialects.d.ts.map +1 -0
- package/dist/sources/migrations/dialects.js +84 -0
- package/dist/sources/migrations/dialects.js.map +1 -0
- package/dist/sources/migrations/index.d.ts +3 -0
- package/dist/sources/migrations/index.d.ts.map +1 -0
- package/dist/sources/migrations/index.js +2 -0
- package/dist/sources/migrations/index.js.map +1 -0
- package/dist/sources/migrations/read.d.ts +113 -0
- package/dist/sources/migrations/read.d.ts.map +1 -0
- package/dist/sources/migrations/read.js +270 -0
- package/dist/sources/migrations/read.js.map +1 -0
- package/dist/sources/workspace/index.d.ts +3 -0
- package/dist/sources/workspace/index.d.ts.map +1 -0
- package/dist/sources/workspace/index.js +2 -0
- package/dist/sources/workspace/index.js.map +1 -0
- package/dist/sources/workspace/workspace.d.ts +64 -0
- package/dist/sources/workspace/workspace.d.ts.map +1 -0
- package/dist/sources/workspace/workspace.js +294 -0
- package/dist/sources/workspace/workspace.js.map +1 -0
- package/dist/store/driver/driver.d.ts +82 -0
- package/dist/store/driver/driver.d.ts.map +1 -0
- package/dist/store/driver/driver.js +31 -0
- package/dist/store/driver/driver.js.map +1 -0
- package/dist/store/driver/node-sqlite.d.ts +28 -0
- package/dist/store/driver/node-sqlite.d.ts.map +1 -0
- package/dist/store/driver/node-sqlite.js +188 -0
- package/dist/store/driver/node-sqlite.js.map +1 -0
- package/dist/store/index.d.ts +33 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +46 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/migrate.d.ts +68 -0
- package/dist/store/migrate.d.ts.map +1 -0
- package/dist/store/migrate.js +140 -0
- package/dist/store/migrate.js.map +1 -0
- package/dist/store/reader.d.ts +127 -0
- package/dist/store/reader.d.ts.map +1 -0
- package/dist/store/reader.js +269 -0
- package/dist/store/reader.js.map +1 -0
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +322 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/writer.d.ts +116 -0
- package/dist/store/writer.d.ts.map +1 -0
- package/dist/store/writer.js +279 -0
- package/dist/store/writer.js.map +1 -0
- package/dist/tiers/certify.d.ts +375 -0
- package/dist/tiers/certify.d.ts.map +1 -0
- package/dist/tiers/certify.js +434 -0
- package/dist/tiers/certify.js.map +1 -0
- package/dist/tiers/index.d.ts +5 -0
- package/dist/tiers/index.d.ts.map +1 -0
- package/dist/tiers/index.js +3 -0
- package/dist/tiers/index.js.map +1 -0
- package/dist/tiers/ladder.d.ts +73 -0
- package/dist/tiers/ladder.d.ts.map +1 -0
- package/dist/tiers/ladder.js +197 -0
- package/dist/tiers/ladder.js.map +1 -0
- package/dist/validation/config-graph.d.ts +104 -0
- package/dist/validation/config-graph.d.ts.map +1 -0
- package/dist/validation/config-graph.js +205 -0
- package/dist/validation/config-graph.js.map +1 -0
- package/dist/validation/env.d.ts +243 -0
- package/dist/validation/env.d.ts.map +1 -0
- package/dist/validation/env.js +447 -0
- package/dist/validation/env.js.map +1 -0
- package/dist/validation/index.d.ts +16 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/join-substitution.d.ts +186 -0
- package/dist/validation/join-substitution.d.ts.map +1 -0
- package/dist/validation/join-substitution.js +191 -0
- package/dist/validation/join-substitution.js.map +1 -0
- package/dist/validation/migrations.d.ts +133 -0
- package/dist/validation/migrations.d.ts.map +1 -0
- package/dist/validation/migrations.js +315 -0
- package/dist/validation/migrations.js.map +1 -0
- package/dist/validation/nodes.d.ts +32 -0
- package/dist/validation/nodes.d.ts.map +1 -0
- package/dist/validation/nodes.js +45 -0
- package/dist/validation/nodes.js.map +1 -0
- package/dist/validation/rollback.d.ts +124 -0
- package/dist/validation/rollback.d.ts.map +1 -0
- package/dist/validation/rollback.js +302 -0
- package/dist/validation/rollback.js.map +1 -0
- package/dist/validation/route-drift.d.ts +108 -0
- package/dist/validation/route-drift.d.ts.map +1 -0
- package/dist/validation/route-drift.js +103 -0
- package/dist/validation/route-drift.js.map +1 -0
- package/dist/validation/sources.d.ts +153 -0
- package/dist/validation/sources.d.ts.map +1 -0
- package/dist/validation/sources.js +409 -0
- package/dist/validation/sources.js.map +1 -0
- package/dist/worker/index.d.ts +5 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +3 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/pool.d.ts +73 -0
- package/dist/worker/pool.d.ts.map +1 -0
- package/dist/worker/pool.js +203 -0
- package/dist/worker/pool.js.map +1 -0
- package/dist/worker/protocol.d.ts +91 -0
- package/dist/worker/protocol.d.ts.map +1 -0
- package/dist/worker/protocol.js +19 -0
- package/dist/worker/protocol.js.map +1 -0
- package/dist/worker/traversal.worker.d.ts +25 -0
- package/dist/worker/traversal.worker.d.ts.map +1 -0
- package/dist/worker/traversal.worker.js +124 -0
- package/dist/worker/traversal.worker.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The batch writer — DEC-015.
|
|
3
|
+
*
|
|
4
|
+
* "The writer deletes everything previously written by `producedBy` for
|
|
5
|
+
* `sourceFiles`, then inserts, in one transaction." That sentence hides three
|
|
6
|
+
* decisions the decision entry does not make, and getting any of them wrong
|
|
7
|
+
* loses data silently. They are made here and each is tested.
|
|
8
|
+
*
|
|
9
|
+
* ## 1. Invalidation matches on adapter *id* and *repo*, not `producedBy` alone
|
|
10
|
+
*
|
|
11
|
+
* `producedBy` is `id@version`. Matching the full string would mean an adapter
|
|
12
|
+
* upgrade never invalidates anything the old version wrote — the two producers
|
|
13
|
+
* would simply coexist, and the graph would hold both readings of every file
|
|
14
|
+
* forever. §24.1 is explicit: "an adapter version bump invalidates every node
|
|
15
|
+
* and edge that adapter produced". So overlap is computed on the id, and the
|
|
16
|
+
* version is what makes the *stale* rows recognisable.
|
|
17
|
+
*
|
|
18
|
+
* `repo` is a second, equally load-bearing part of the same match, added after
|
|
19
|
+
* a real defect: a store holding more than one repository (`descry-runtime`'s
|
|
20
|
+
* graph correlator merges a frontend and a backend into one store to trace
|
|
21
|
+
* across them, per DEC-228) can have two repos using the same adapter with an
|
|
22
|
+
* overlapping relative file path — `src/index.ts`, `package.json`, anything
|
|
23
|
+
* common. Without `repo` in this match, writing the second repo's batch reads
|
|
24
|
+
* as "adapter X re-ran over a file it already owned" and evicts the first
|
|
25
|
+
* repo's run outright — silent cross-repository data loss, not a wrong
|
|
26
|
+
* answer. `invalidateAdapter` below is intentionally *not* repo-scoped — its
|
|
27
|
+
* own contract is "every repo," a different, explicitly global operation.
|
|
28
|
+
*
|
|
29
|
+
* ## 2. Prior runs are matched by file-set *overlap*, not equality
|
|
30
|
+
*
|
|
31
|
+
* A batch is the unit of invalidation, but the batch that previously owned a
|
|
32
|
+
* file may have covered a different set of files. Deleting only exact-matching
|
|
33
|
+
* runs leaves two rows for the same symbol; deleting only the intersection is
|
|
34
|
+
* impossible, because an edge has no file. So every prior run of the same
|
|
35
|
+
* adapter that touches *any* of these files is replaced whole — and the files
|
|
36
|
+
* it covered that this batch does not re-supply are reported as `evictedFiles`.
|
|
37
|
+
* Reporting them is the honest-degradation rule (principle 7): the caller is
|
|
38
|
+
* told what fell out, rather than discovering it as a missing edge next week.
|
|
39
|
+
*
|
|
40
|
+
* ## 3. A run also cleans up after its own previous write
|
|
41
|
+
*
|
|
42
|
+
* The run id is content-derived from producer, repo, commit and file set — not
|
|
43
|
+
* from the nodes. So an adapter re-run at the same commit over the same files
|
|
44
|
+
* lands on the *same* run id, and "delete the prior runs" excludes it by
|
|
45
|
+
* definition. Without a self-clean pass, rows that run no longer produces stay
|
|
46
|
+
* forever: the upsert refreshes what is still there and nothing removes what is
|
|
47
|
+
* not. Every write therefore ends by deleting its own run's rows that this batch
|
|
48
|
+
* did not supply.
|
|
49
|
+
*
|
|
50
|
+
* ## 4. Re-supplied nodes are updated, not deleted and recreated
|
|
51
|
+
*
|
|
52
|
+
* A node id is stable across file moves by construction (DEC-004), so re-parsing
|
|
53
|
+
* a file normally yields the ids that were already there. Deleting a prior run's
|
|
54
|
+
* nodes wholesale would cascade away every edge *other* runs had pointing into
|
|
55
|
+
* them, and those runs will not be re-run. Instead: upsert the ids this batch
|
|
56
|
+
* supplies, then delete the prior run — whose remaining nodes are exactly those
|
|
57
|
+
* it no longer produces. Edges into a node that genuinely disappeared do cascade
|
|
58
|
+
* away, and are reported as `orphanedEdges`, because keeping an edge to a node
|
|
59
|
+
* that no longer exists is the wrong-edge failure the precision rule forbids.
|
|
60
|
+
*/
|
|
61
|
+
import { createHash } from "node:crypto";
|
|
62
|
+
import { canonicalJson, edgeId } from "@descryy/ir";
|
|
63
|
+
import { EDGE_MERGE_CONDITION, NODE_MERGE_CONDITION } from "../graph/merge.js";
|
|
64
|
+
/** Split `adapterId@version`. The Normaliser has already guaranteed the shape. */
|
|
65
|
+
function splitProducer(producedBy) {
|
|
66
|
+
const at = producedBy.lastIndexOf("@");
|
|
67
|
+
return { id: producedBy.slice(0, at), version: producedBy.slice(at + 1) };
|
|
68
|
+
}
|
|
69
|
+
function hash128(input) {
|
|
70
|
+
return createHash("sha256").update(input, "utf8").digest("hex").slice(0, 32);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A run id derived from the batch's content, never a counter.
|
|
74
|
+
*
|
|
75
|
+
* Re-ingesting an identical batch must land on the same row, or "idempotent"
|
|
76
|
+
* cannot even be stated as a property. `sourceFiles` is sorted first so that an
|
|
77
|
+
* adapter listing the same files in a different order does not create a second
|
|
78
|
+
* run for identical work.
|
|
79
|
+
*/
|
|
80
|
+
export function runIdFor(batch) {
|
|
81
|
+
return `r1:${hash128(canonicalJson([batch.producedBy, batch.repo, batch.commitSha, [...batch.sourceFiles].sort()]))}`;
|
|
82
|
+
}
|
|
83
|
+
function unresolvedId(ref) {
|
|
84
|
+
return `u1:${hash128(canonicalJson([ref.fromNodeId, ref.edgeType, ref.rawTarget, ref.file, ref.line, ref.producedBy]))}`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* **Delegates to `@descryy/ir`'s `edgeId()` — it must not re-derive this.**
|
|
88
|
+
*
|
|
89
|
+
* It used to, as a hand-rolled `` `e1:${hash128(...)}` ``, and that was a
|
|
90
|
+
* latent identity split: DEC-054 moved the prefixes to `n2:`/`e2:` and this
|
|
91
|
+
* copy was never moved with them, so `edgeId()` and the stored id agreed on
|
|
92
|
+
* every byte of the hash and disagreed on the prefix. Nothing caught it
|
|
93
|
+
* because `edgeId()` had zero callers — the first caller to look an edge up
|
|
94
|
+
* by its public id would have silently matched nothing, which is DEC-055's
|
|
95
|
+
* measured failure (16 joins to 0, no error anywhere) reproduced inside the
|
|
96
|
+
* engine rather than across two adapters.
|
|
97
|
+
*
|
|
98
|
+
* One identity, one function. See DEC-NEXT-edge-id-prefix-split.
|
|
99
|
+
*/
|
|
100
|
+
function edgeRowId(edge) {
|
|
101
|
+
return edgeId(edge.from, edge.to, edge.type);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Write one normalised batch. **Input must already have passed the Normaliser** —
|
|
105
|
+
* this function does no validation, on purpose. Two validators drift; one
|
|
106
|
+
* validator at the boundary is a boundary.
|
|
107
|
+
*/
|
|
108
|
+
export function writeBatch(driver, batch, options = {}) {
|
|
109
|
+
const now = options.now ?? Date.now;
|
|
110
|
+
const runId = runIdFor(batch);
|
|
111
|
+
const { id: adapterId, version } = splitProducer(batch.producedBy);
|
|
112
|
+
const incomingFiles = new Set(batch.sourceFiles);
|
|
113
|
+
return driver.transaction(() => {
|
|
114
|
+
// --- find the prior runs this batch supersedes --------------------------
|
|
115
|
+
const priorRuns = driver
|
|
116
|
+
.prepare("SELECT id, source_files FROM adapter_runs WHERE adapter_id = ? AND repo = ? AND id != ?")
|
|
117
|
+
.all(adapterId, batch.repo, runId)
|
|
118
|
+
.filter((row) => {
|
|
119
|
+
const files = JSON.parse(row.source_files);
|
|
120
|
+
return files.some((f) => incomingFiles.has(f));
|
|
121
|
+
});
|
|
122
|
+
const evicted = new Set();
|
|
123
|
+
for (const run of priorRuns) {
|
|
124
|
+
for (const file of JSON.parse(run.source_files)) {
|
|
125
|
+
if (!incomingFiles.has(file))
|
|
126
|
+
evicted.add(file);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const ownRunIds = [runId, ...priorRuns.map((r) => r.id)];
|
|
130
|
+
const foreignEdgesBefore = countForeignEdges(driver, ownRunIds);
|
|
131
|
+
// A scratch list of what this batch supplies, so the self-clean below is a
|
|
132
|
+
// set difference rather than a `NOT IN (?, ?, ...)` with one parameter per
|
|
133
|
+
// row — which caps out at SQLITE_MAX_VARIABLE_NUMBER on a large batch.
|
|
134
|
+
driver.exec(`CREATE TEMP TABLE IF NOT EXISTS supplied (kind TEXT NOT NULL, id TEXT NOT NULL, PRIMARY KEY (kind, id))`);
|
|
135
|
+
driver.exec("DELETE FROM temp.supplied");
|
|
136
|
+
const remember = driver.prepare("INSERT OR IGNORE INTO temp.supplied (kind, id) VALUES (?, ?)");
|
|
137
|
+
// Rows owned by a run this write replaces are always overwritten, never
|
|
138
|
+
// merged. DEC-012 arbitrates between two *different* producers claiming the
|
|
139
|
+
// same thing; the same adapter re-running over the same files is
|
|
140
|
+
// invalidation, and the fresh reading wins even when it reached a lower
|
|
141
|
+
// resolution — stale R3 facts about code that has since changed are worse
|
|
142
|
+
// than accurate R1 ones. Without this, a degraded re-run would fail the
|
|
143
|
+
// merge test, leave the row owned by the prior run, and then lose it to that
|
|
144
|
+
// run's cascade a few statements later.
|
|
145
|
+
for (const run of priorRuns)
|
|
146
|
+
remember.run("run", run.id);
|
|
147
|
+
// --- the run row, before anything references it -------------------------
|
|
148
|
+
driver
|
|
149
|
+
.prepare(`INSERT INTO adapter_runs
|
|
150
|
+
(id, repo, workspace, commit_sha, adapter_id, adapter_version, produced_by,
|
|
151
|
+
reached_resolution, source_files, file_count, written_at)
|
|
152
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
153
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
154
|
+
commit_sha = excluded.commit_sha,
|
|
155
|
+
adapter_version = excluded.adapter_version,
|
|
156
|
+
produced_by = excluded.produced_by,
|
|
157
|
+
reached_resolution = excluded.reached_resolution,
|
|
158
|
+
written_at = excluded.written_at`)
|
|
159
|
+
.run(runId, batch.repo, batch.workspace ?? null, batch.commitSha, adapterId, version, batch.producedBy, batch.reachedResolution, JSON.stringify([...batch.sourceFiles].sort()), batch.sourceFiles.length, now());
|
|
160
|
+
// ON CONFLICT DO UPDATE, never INSERT OR REPLACE. `REPLACE` is a DELETE
|
|
161
|
+
// followed by an INSERT, so with foreign keys on it would cascade away this
|
|
162
|
+
// run's own nodes and edges before re-inserting them — and because the run
|
|
163
|
+
// id is content-derived, re-ingesting an identical batch hits that path
|
|
164
|
+
// every single time.
|
|
165
|
+
// --- nodes: upsert first, so ids that survive keep their inbound edges ---
|
|
166
|
+
const insertNode = driver.prepare(`INSERT INTO nodes
|
|
167
|
+
(id, type, name, file_path, start_line, end_line, language, produced_by, resolution, attrs, run_id)
|
|
168
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
169
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
170
|
+
type = excluded.type, name = excluded.name, file_path = excluded.file_path,
|
|
171
|
+
start_line = excluded.start_line, end_line = excluded.end_line,
|
|
172
|
+
language = excluded.language, produced_by = excluded.produced_by,
|
|
173
|
+
resolution = excluded.resolution, attrs = excluded.attrs, run_id = excluded.run_id
|
|
174
|
+
WHERE nodes.run_id = excluded.run_id
|
|
175
|
+
OR nodes.run_id IN (SELECT id FROM temp.supplied WHERE kind = 'run')
|
|
176
|
+
OR (${NODE_MERGE_CONDITION})`);
|
|
177
|
+
for (const node of batch.nodes) {
|
|
178
|
+
insertNode.run(node.id, node.type, node.name, node.file, node.range?.startLine ?? null, node.range?.endLine ?? null, node.language, node.producedBy, node.resolution, canonicalJson(node.attrs), runId);
|
|
179
|
+
remember.run("node", node.id);
|
|
180
|
+
}
|
|
181
|
+
// --- retire the prior runs ----------------------------------------------
|
|
182
|
+
// Everything still pointing at them is, by definition, what this batch no
|
|
183
|
+
// longer produces. The cascade removes it; the counts above and below say
|
|
184
|
+
// how much.
|
|
185
|
+
if (priorRuns.length > 0) {
|
|
186
|
+
const del = driver.prepare("DELETE FROM adapter_runs WHERE id = ?");
|
|
187
|
+
for (const run of priorRuns)
|
|
188
|
+
del.run(run.id);
|
|
189
|
+
}
|
|
190
|
+
// ...and after this run's own previous write. See note 3 above: an adapter
|
|
191
|
+
// re-run at the same commit reuses the run id, so this is the only thing
|
|
192
|
+
// that removes what it stopped producing.
|
|
193
|
+
driver
|
|
194
|
+
.prepare(`DELETE FROM nodes WHERE run_id = ?
|
|
195
|
+
AND id NOT IN (SELECT id FROM temp.supplied WHERE kind = 'node')`)
|
|
196
|
+
.run(runId);
|
|
197
|
+
// --- edges and unresolved references -------------------------------------
|
|
198
|
+
const insertEdge = driver.prepare(`INSERT INTO edges
|
|
199
|
+
(id, source, target, type, resolution, confidence, produced_by, observed_by_run, attrs, run_id)
|
|
200
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
201
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
202
|
+
resolution = excluded.resolution, confidence = excluded.confidence,
|
|
203
|
+
produced_by = excluded.produced_by, observed_by_run = excluded.observed_by_run,
|
|
204
|
+
attrs = excluded.attrs, run_id = excluded.run_id
|
|
205
|
+
WHERE edges.run_id = excluded.run_id
|
|
206
|
+
OR edges.run_id IN (SELECT id FROM temp.supplied WHERE kind = 'run')
|
|
207
|
+
OR (${EDGE_MERGE_CONDITION})`);
|
|
208
|
+
for (const edge of batch.edges) {
|
|
209
|
+
const id = edgeRowId(edge);
|
|
210
|
+
remember.run("edge", id);
|
|
211
|
+
insertEdge.run(id, edge.from, edge.to, edge.type, edge.resolution, edge.confidence, edge.producedBy, edge.observedByRun ?? null, edge.attrs === undefined ? null : canonicalJson(edge.attrs), runId);
|
|
212
|
+
}
|
|
213
|
+
const insertRef = driver.prepare(`INSERT INTO unresolved_refs
|
|
214
|
+
(id, from_node_id, edge_type, raw_target, file_path, line, produced_by, reason, attrs, run_id)
|
|
215
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
216
|
+
ON CONFLICT(id) DO UPDATE SET reason = excluded.reason, attrs = excluded.attrs, run_id = excluded.run_id`);
|
|
217
|
+
driver
|
|
218
|
+
.prepare(`DELETE FROM edges WHERE run_id = ?
|
|
219
|
+
AND id NOT IN (SELECT id FROM temp.supplied WHERE kind = 'edge')`)
|
|
220
|
+
.run(runId);
|
|
221
|
+
for (const ref of batch.unresolved) {
|
|
222
|
+
const id = unresolvedId(ref);
|
|
223
|
+
remember.run("ref", id);
|
|
224
|
+
insertRef.run(id, ref.fromNodeId, ref.edgeType, ref.rawTarget, ref.file, ref.line, ref.producedBy, ref.reason, ref.attrs === undefined ? null : canonicalJson(ref.attrs), runId);
|
|
225
|
+
}
|
|
226
|
+
driver
|
|
227
|
+
.prepare(`DELETE FROM unresolved_refs WHERE run_id = ?
|
|
228
|
+
AND id NOT IN (SELECT id FROM temp.supplied WHERE kind = 'ref')`)
|
|
229
|
+
.run(runId);
|
|
230
|
+
// Edges belonging to neither this run nor the runs it replaced can only have
|
|
231
|
+
// vanished by cascade — an endpoint node ceased to exist. Measured directly
|
|
232
|
+
// rather than inferred from a before/after total, which dedupe would skew.
|
|
233
|
+
const orphaned = foreignEdgesBefore - countForeignEdges(driver, ownRunIds);
|
|
234
|
+
return {
|
|
235
|
+
runId,
|
|
236
|
+
nodesWritten: batch.nodes.length,
|
|
237
|
+
edgesWritten: batch.edges.length,
|
|
238
|
+
unresolvedWritten: batch.unresolved.length,
|
|
239
|
+
runsReplaced: priorRuns.length,
|
|
240
|
+
evictedFiles: [...evicted].sort(),
|
|
241
|
+
orphanedEdges: orphaned,
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** Edges owned by some run other than the ones this write is touching. */
|
|
246
|
+
function countForeignEdges(driver, ownRunIds) {
|
|
247
|
+
const placeholders = ownRunIds.map(() => "?").join(", ");
|
|
248
|
+
return (driver
|
|
249
|
+
.prepare(`SELECT COUNT(*) AS n FROM edges WHERE run_id NOT IN (${placeholders})`)
|
|
250
|
+
.get(...ownRunIds)?.n ?? 0);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Remove everything an adapter ever produced, at every version, for every repo.
|
|
254
|
+
*
|
|
255
|
+
* §24.1's invalidation rule, as its own operation rather than a side effect of
|
|
256
|
+
* writing. It is deliberately separate from `writeBatch`: an upgraded adapter
|
|
257
|
+
* usually re-runs over a subset of files first, and folding this into the write
|
|
258
|
+
* path would make a partial re-run delete data it was never going to replace.
|
|
259
|
+
*/
|
|
260
|
+
export function invalidateAdapter(driver, adapterId) {
|
|
261
|
+
return driver.transaction(() => {
|
|
262
|
+
const result = driver.prepare("DELETE FROM adapter_runs WHERE adapter_id = ?").run(adapterId);
|
|
263
|
+
return { runsRemoved: result.changes };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/** §11.14 rebuild acceleration. Layer 1 change detection reads this. */
|
|
267
|
+
export function putFileHash(driver, filePath, hash, parsedAt) {
|
|
268
|
+
driver
|
|
269
|
+
.prepare(`INSERT INTO file_hashes (file_path, hash, last_parsed_at) VALUES (?, ?, ?)
|
|
270
|
+
ON CONFLICT(file_path) DO UPDATE SET hash = excluded.hash, last_parsed_at = excluded.last_parsed_at`)
|
|
271
|
+
.run(filePath, hash, parsedAt);
|
|
272
|
+
}
|
|
273
|
+
/** Nodes touching these files, unchanged. */
|
|
274
|
+
export function getFileHash(driver, filePath) {
|
|
275
|
+
return driver
|
|
276
|
+
.prepare("SELECT hash FROM file_hashes WHERE file_path = ?")
|
|
277
|
+
.get(filePath)?.hash;
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/store/writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The capability-tier generator. DEC-075 §6, in code.
|
|
3
|
+
*
|
|
4
|
+
* > *"A tier is generated from `capabilities()`, never written in prose, and may
|
|
5
|
+
* > be published for a language only when the corpus passes at the resolution
|
|
6
|
+
* > the tier claims, a ≥200-edge precision draw passes on at least two
|
|
7
|
+
* > repositories the adapter's author did not choose, and `capabilities()`
|
|
8
|
+
* > declares exactly the node and edge types the adapter emitted."*
|
|
9
|
+
*
|
|
10
|
+
* ## The design point: this thing exists to say no
|
|
11
|
+
*
|
|
12
|
+
* A generator that awards a tier to everything it is shown is a rubber stamp
|
|
13
|
+
* with a build step. The output that matters is **the row that failed**, which
|
|
14
|
+
* is why `unmet` is a list of requirements with their statements rather than a
|
|
15
|
+
* boolean, and why `certify` never returns a bare tier without also returning
|
|
16
|
+
* what the next tier up is missing.
|
|
17
|
+
*
|
|
18
|
+
* Two rules follow from that, and both are the opposite of what is convenient:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Absent evidence is not a pass.** No precision draw recorded means the
|
|
21
|
+
* attested requirement is unmet, exactly as if a recorded draw had failed.
|
|
22
|
+
* The alternative — treating silence as satisfaction — is how every unearned
|
|
23
|
+
* claim in this repository would have been generated.
|
|
24
|
+
* 2. **An attested requirement is labelled attested, in the output, always.**
|
|
25
|
+
* A machine can check "≥200 edges" and "two repositories". It cannot check
|
|
26
|
+
* *"the author did not choose them"*, because that is a fact about a person's
|
|
27
|
+
* intent. Marking it as declared-by-a-human is what stops this function
|
|
28
|
+
* laundering a judgement into a computed result. `certify` therefore reports
|
|
29
|
+
* `attestedBy` alongside the tier, and a consumer that prints the tier
|
|
30
|
+
* without it has dropped the only part that says who is responsible.
|
|
31
|
+
*
|
|
32
|
+
* ## What it does not do
|
|
33
|
+
*
|
|
34
|
+
* It does not run the conformance harness or the precision benches. It is given
|
|
35
|
+
* their results. That keeps it pure and testable, and it keeps the awkward fact
|
|
36
|
+
* visible: the precision evidence is **recorded by hand today**, because the
|
|
37
|
+
* benches are throwaway scripts that print to a terminal. `GateEvidence` is
|
|
38
|
+
* where that hand-recording is written down and dated, rather than living in a
|
|
39
|
+
* decision document that nothing reads.
|
|
40
|
+
*/
|
|
41
|
+
import type { CapabilityMatrix, EdgeType, NodeType, ResolutionLevel } from "@descryy/ir";
|
|
42
|
+
import type { RefusalCensus } from "../query/unresolved.ts";
|
|
43
|
+
import { type Requirement, type Tier } from "./ladder.ts";
|
|
44
|
+
/**
|
|
45
|
+
* One adjudicated precision draw, as DEC-075 §6.2 requires it.
|
|
46
|
+
*
|
|
47
|
+
* `authorChose` is the field that makes this honest. It defaults to nothing —
|
|
48
|
+
* a recorder who has not thought about it produces an incomplete record rather
|
|
49
|
+
* than an accidental pass, the same reason `reliabilityCap`'s `nameLevel` has
|
|
50
|
+
* no default.
|
|
51
|
+
*/
|
|
52
|
+
export interface PrecisionDraw {
|
|
53
|
+
readonly repository: string;
|
|
54
|
+
readonly edgeType: EdgeType | "mixed";
|
|
55
|
+
readonly drawn: number;
|
|
56
|
+
readonly correct: number;
|
|
57
|
+
/**
|
|
58
|
+
* How many of `drawn` the adjudicator actually rendered a verdict on —
|
|
59
|
+
* `undefined` when every adjudicator this project has ever recorded a draw
|
|
60
|
+
* with decides every row it draws, so `decided` and `drawn` have always
|
|
61
|
+
* been the same number and no draw needed to say so until now.
|
|
62
|
+
*
|
|
63
|
+
* **Kotlin is the first adapter where they diverge, and the divergence
|
|
64
|
+
* exposed a real instrument defect.** `correct / drawn` silently punishes
|
|
65
|
+
* an adjudicator for honestly declining to decide a row exactly as if it
|
|
66
|
+
* had decided that row wrong — okhttp drew 200, decided 192, and every one
|
|
67
|
+
* of the 192 it *did* decide was correct (192/192 = 100%, its own
|
|
68
|
+
* pre-registered gate: GATE PASSED); `correct / drawn` reads that as
|
|
69
|
+
* 192/200 = 96%, below the 98% floor, for a reason that has nothing to do
|
|
70
|
+
* with the adapter's precision. Precision is measured over `decided` rows
|
|
71
|
+
* only (see `checkPrecisionDraws`); `decided / drawn` — coverage — is a
|
|
72
|
+
* separate, always-disclosed axis (`bench/tier-matrix.mjs`'s
|
|
73
|
+
* `describeDrawCoverage`), never folded into the precision figure: a draw
|
|
74
|
+
* with high coverage and lower precision and one with low coverage and
|
|
75
|
+
* clean precision are different facts, and burying the second inside a
|
|
76
|
+
* clean-looking percentage is the failure this field exists to prevent.
|
|
77
|
+
*/
|
|
78
|
+
readonly decided?: number;
|
|
79
|
+
/**
|
|
80
|
+
* How many candidates of this edge type the repository contained — the
|
|
81
|
+
* population the draw was taken **from**, not the sample size.
|
|
82
|
+
*
|
|
83
|
+
* ## Zero out of zero is undefined here too
|
|
84
|
+
*
|
|
85
|
+
* Without it, `0 edges drawn, below 200` and `47 edges drawn, below 200` print
|
|
86
|
+
* identically, and they are different facts with different remedies:
|
|
87
|
+
*
|
|
88
|
+
* | | statement about | what to do |
|
|
89
|
+
* | --- | --- | --- |
|
|
90
|
+
* | population 0 | **the corpus** — this reference set contains nothing to sample | find other repositories, or accept the bar is unreachable here |
|
|
91
|
+
* | population > 0, drawn short | **the adapter** — measured and short | measure more |
|
|
92
|
+
*
|
|
93
|
+
* Python is the live case: `USES_API` is emitted and measures **0 edges** on
|
|
94
|
+
* both reference repositories, and that zero is *correct* — a backend service
|
|
95
|
+
* in this reference set is a provider rather than a consumer, so no 200-edge
|
|
96
|
+
* draw is reachable from it at all. Reporting that as "below 200" tells a
|
|
97
|
+
* reader to measure harder, when the honest answer is that **this reference
|
|
98
|
+
* set cannot answer the question**.
|
|
99
|
+
*
|
|
100
|
+
* `undefined` means the population was not recorded, which is neither zero nor
|
|
101
|
+
* non-zero and is reported as its own case — the same reason
|
|
102
|
+
* `capabilitiesHonest` has an `"unknown"`.
|
|
103
|
+
*/
|
|
104
|
+
readonly population?: number;
|
|
105
|
+
/**
|
|
106
|
+
* The refusal census for this draw's edge type, from `classifyRefusals`.
|
|
107
|
+
*
|
|
108
|
+
* ## Why the census rather than a recorded "drawable" count
|
|
109
|
+
*
|
|
110
|
+
* The number that decides reachability is **population minus `outOfScope`**:
|
|
111
|
+
* a call read correctly that targets something this workspace does not serve
|
|
112
|
+
* can never become an edge here, however good the adapter gets.
|
|
113
|
+
*
|
|
114
|
+
* | | population | outOfScope | drawable | reads as |
|
|
115
|
+
* | --- | --- | --- | --- | --- |
|
|
116
|
+
* | `saleor` | 0 | 0 | **0** | nothing to sample |
|
|
117
|
+
* | `dispatch` | 4 | 4 | **0** | four third-party calls, refused forever |
|
|
118
|
+
* | `discourse` | 20 | 0 | **20** | a capability gap the adapter owns |
|
|
119
|
+
*
|
|
120
|
+
* Carrying the census rather than a recorded count keeps this **derived**, so
|
|
121
|
+
* a measured zero still attests to itself and needs no attribution — unlike a
|
|
122
|
+
* `StructuralLimit`. And it cannot drift from the ledger, **because it is the
|
|
123
|
+
* ledger**: a separately recorded "drawable" number is the same fact in two
|
|
124
|
+
* places, and this project has measured what that costs.
|
|
125
|
+
*
|
|
126
|
+
* ## The abuse route, which is why `scopeKnown` is load-bearing
|
|
127
|
+
*
|
|
128
|
+
* With no internal hosts supplied, `classifyRefusals` classifies **every**
|
|
129
|
+
* absolute target as `outOfScope` and says so — it "understates the deficit".
|
|
130
|
+
* Subtracting blindly there would let an adapter that declares no hosts write
|
|
131
|
+
* off every external-looking call as unreachable and **improve its tier by
|
|
132
|
+
* supplying less information**. So `scopeKnown === false` makes the
|
|
133
|
+
* subtraction **unavailable**, never zero.
|
|
134
|
+
*
|
|
135
|
+
* Absent entirely, `drawable` falls back to the full population — which blames
|
|
136
|
+
* the **adapter** rather than the corpus. That is the safe direction: the
|
|
137
|
+
* failure being guarded is a tier improved by withholding evidence.
|
|
138
|
+
*/
|
|
139
|
+
readonly refusals?: RefusalCensus;
|
|
140
|
+
/** The negative control over the same adjudicator. Must be ~0. */
|
|
141
|
+
readonly controlDrawn: number;
|
|
142
|
+
readonly controlCorrect: number;
|
|
143
|
+
/**
|
|
144
|
+
* Did the adapter's author pick this repository? DEC-075 §6.2 requires two
|
|
145
|
+
* where the answer is **no**. Declared, never inferred — see the header.
|
|
146
|
+
*/
|
|
147
|
+
readonly authorChose: boolean;
|
|
148
|
+
/** Who recorded this, so an attested claim has a name against it. */
|
|
149
|
+
readonly recordedBy: string;
|
|
150
|
+
readonly recordedOn: string;
|
|
151
|
+
/**
|
|
152
|
+
* The mixed draw's own composition, by edge type — `undefined` for every
|
|
153
|
+
* draw recorded before this field existed, and that absence is itself
|
|
154
|
+
* meaningful (see `checkCapability`'s use of it: no data means the
|
|
155
|
+
* type-floor check does not run for that draw, not that it fails).
|
|
156
|
+
*
|
|
157
|
+
* The gap this closes: DEC-221 made the precision draw whole-graph and
|
|
158
|
+
* NOT edge-type-scoped (a `USES_API`-only draw structurally cannot carry
|
|
159
|
+
* a negative control, DEC-164 §2). That is still correct for what a
|
|
160
|
+
* *draw* needs to qualify. But a language's tier can rest on a capability
|
|
161
|
+
* (`A.callers` on `USES_API`, `A.routes` on `SERVES_API`) that the draw
|
|
162
|
+
* happened to sample almost none of — Kotlin's ktor draw cleared 98%
|
|
163
|
+
* precision overall while carrying exactly one decided `USES_API` row,
|
|
164
|
+
* which is evidence of nothing about that specific capability. This field
|
|
165
|
+
* is what lets `checkCapability` tell "the capability was validated" from
|
|
166
|
+
* "the capability was declared and the aggregate draw happened to pass".
|
|
167
|
+
*/
|
|
168
|
+
readonly perEdgeType?: Readonly<Record<string, {
|
|
169
|
+
readonly decided: number;
|
|
170
|
+
readonly correct: number;
|
|
171
|
+
}>>;
|
|
172
|
+
}
|
|
173
|
+
export interface ConformanceEvidence {
|
|
174
|
+
readonly resolution: ResolutionLevel;
|
|
175
|
+
readonly passed: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* DEC-070's **adapter** verdict — no type emitted that `capabilities()` did
|
|
178
|
+
* not declare.
|
|
179
|
+
*
|
|
180
|
+
* Deliberately not "the manifest check produced no findings". That check
|
|
181
|
+
* reports two different things and only one of them is the adapter's fault:
|
|
182
|
+
* `adapterFailed` (emitted-but-undeclared, a real overclaim) and `corpusThin`
|
|
183
|
+
* (declared-but-unexercised, a fact about the corpus). Reading them as one
|
|
184
|
+
* failed every adapter in the repository at Tier C on its first run here,
|
|
185
|
+
* because a low-resolution sweep leaves plenty declared and unexercised.
|
|
186
|
+
*/
|
|
187
|
+
readonly manifestClean: boolean;
|
|
188
|
+
}
|
|
189
|
+
export interface GateEvidence {
|
|
190
|
+
readonly adapter: string;
|
|
191
|
+
readonly language: string;
|
|
192
|
+
readonly capabilities: CapabilityMatrix;
|
|
193
|
+
/** What the adapter actually emitted, if a run is available to compare. */
|
|
194
|
+
readonly emittedNodeTypes?: readonly NodeType[];
|
|
195
|
+
readonly emittedEdgeTypes?: readonly EdgeType[];
|
|
196
|
+
readonly conformance: readonly ConformanceEvidence[];
|
|
197
|
+
readonly precisionDraws: readonly PrecisionDraw[];
|
|
198
|
+
/**
|
|
199
|
+
* Types the adapter declares that **no corpus pattern exercised**.
|
|
200
|
+
*
|
|
201
|
+
* `checkManifest` already computes this and calls it `unpublishable`, with
|
|
202
|
+
* the comment *"not publishable as a tier claim"* — written before any tier
|
|
203
|
+
* generator existed. It is exactly right: a declared type nothing exercised
|
|
204
|
+
* is an untested claim, and a tier resting on it rests on a promise. Treated
|
|
205
|
+
* here as not satisfying a capability requirement at all, with its own reason
|
|
206
|
+
* so it is distinguishable from a type that was never declared.
|
|
207
|
+
*/
|
|
208
|
+
readonly unpublishableTypes?: readonly string[];
|
|
209
|
+
/**
|
|
210
|
+
* Capabilities that **cannot exist in this language**, whatever anyone builds.
|
|
211
|
+
*
|
|
212
|
+
* DEC-123's case: `REQUIRED_NOT_DECLARED` fires only when an environment
|
|
213
|
+
* accessor *fails* on absence, and JavaScript has no such form — `process.env.X`
|
|
214
|
+
* yields undefined. No adapter effort makes that finding reachable in
|
|
215
|
+
* TypeScript. Without this, the generator scores it as **absent evidence**,
|
|
216
|
+
* which is the verdict for *"nobody has built it yet"*, and the two collapse
|
|
217
|
+
* into one output.
|
|
218
|
+
*
|
|
219
|
+
* That collapse is the one distinction §20.2 exists to hold: *"we found
|
|
220
|
+
* nothing"* and *"we cannot check this"* are different sentences. At the tier
|
|
221
|
+
* layer the difference is between a gap someone should close and a boundary
|
|
222
|
+
* nobody can.
|
|
223
|
+
*
|
|
224
|
+
* **A limit does not block the tier, and it does not vanish either.** It is
|
|
225
|
+
* carried onto the certificate and printed, so the tier is published *with*
|
|
226
|
+
* its qualifier. Awarding the tier silently would claim the capability;
|
|
227
|
+
* withholding it forever would punish a language for its own semantics.
|
|
228
|
+
*
|
|
229
|
+
* `id` matches a requirement id (`A.callers`) or names a check
|
|
230
|
+
* (`REQUIRED_NOT_DECLARED`). `declaredBy` is required for the same reason
|
|
231
|
+
* `PrecisionDraw.recordedBy` is: **this is a human judgement, and an
|
|
232
|
+
* unattributable one is a way to dodge a requirement that is merely unbuilt.**
|
|
233
|
+
* A limit with no reason or no declarer is ignored, and the requirement is
|
|
234
|
+
* checked normally.
|
|
235
|
+
*/
|
|
236
|
+
readonly structuralLimits?: readonly StructuralLimit[];
|
|
237
|
+
/**
|
|
238
|
+
* Capabilities claimed **below** what the evidence supports, pending an open
|
|
239
|
+
* policy. See `PolicyCap` — this neither waives a requirement nor blocks a
|
|
240
|
+
* tier; it stops a deliberate cap being published as an absence.
|
|
241
|
+
*/
|
|
242
|
+
readonly policyCaps?: readonly PolicyCap[];
|
|
243
|
+
}
|
|
244
|
+
/** A capability this language cannot have. Declared, never inferred. */
|
|
245
|
+
export interface StructuralLimit {
|
|
246
|
+
/** A requirement id, or the name of a check. */
|
|
247
|
+
readonly id: string;
|
|
248
|
+
/** Why it is unreachable *in this language*, in terms of the language. */
|
|
249
|
+
readonly reason: string;
|
|
250
|
+
/** Who declared it. An anonymous structural limit is not admissible. */
|
|
251
|
+
readonly declaredBy: string;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A capability we **could** claim higher and are deliberately claiming lower,
|
|
255
|
+
* pending a policy that has not been written.
|
|
256
|
+
*
|
|
257
|
+
* ## The third state, and why two were not enough
|
|
258
|
+
*
|
|
259
|
+
* The certificate could say two things about a capability it does not claim:
|
|
260
|
+
*
|
|
261
|
+
* | | meaning |
|
|
262
|
+
* | --- | --- |
|
|
263
|
+
* | **absent** | we have not measured it |
|
|
264
|
+
* | **unreachable** (`StructuralLimit`) | this language structurally cannot produce it |
|
|
265
|
+
* | **capped** *(this)* | we could claim more and are choosing not to, pending a policy |
|
|
266
|
+
*
|
|
267
|
+
* Publishing the third as the first **understates a capability we have** — the
|
|
268
|
+
* tier layer would tell a user we cannot do something we deliberately declined
|
|
269
|
+
* to claim. That is §20.2's distinction one layer up, and the same family as the
|
|
270
|
+
* `FILE` gap: two different sentences collapsing into one because there was only
|
|
271
|
+
* one field to put them in.
|
|
272
|
+
*
|
|
273
|
+
* The case that produced it: an introspection-fed route table is R4 on fidelity
|
|
274
|
+
* — it is read from a running process — and is claimed at R3 because R4's
|
|
275
|
+
* promotion and expiry policy is open, R4 requires `observedByRun`, and R4 is
|
|
276
|
+
* unconditionally class A (DEC-115). Nothing is lost, because R3 already reaches
|
|
277
|
+
* class A for a name-level fact; what is lost is the *promise about expiry*, and
|
|
278
|
+
* that is exactly what wants recording.
|
|
279
|
+
*
|
|
280
|
+
* ## It does not waive anything
|
|
281
|
+
*
|
|
282
|
+
* Unlike a `StructuralLimit`, a cap is **not** removed from the tier check. The
|
|
283
|
+
* requirement is met at the lower claim, so there is nothing to waive — and that
|
|
284
|
+
* asymmetry is deliberate: if a cap could skip a requirement it would become the
|
|
285
|
+
* cheapest way to collect a tier, which is the abuse `admissibleLimits` already
|
|
286
|
+
* guards against for the other kind.
|
|
287
|
+
*/
|
|
288
|
+
export interface PolicyCap {
|
|
289
|
+
/** A requirement id, or the name of a check. */
|
|
290
|
+
readonly id: string;
|
|
291
|
+
/** What the evidence would support claiming. */
|
|
292
|
+
readonly claimable: string;
|
|
293
|
+
/** What is actually claimed. */
|
|
294
|
+
readonly claimed: string;
|
|
295
|
+
/**
|
|
296
|
+
* The open policy that would lift the cap.
|
|
297
|
+
*
|
|
298
|
+
* **Required, and a cap without one is ignored.** "Capped pending nothing" is
|
|
299
|
+
* not a cap, it is a decision — and a cap that names no condition can never be
|
|
300
|
+
* discharged, so it would sit on the certificate forever looking like caution.
|
|
301
|
+
*/
|
|
302
|
+
readonly pendingOn: string;
|
|
303
|
+
/** Who declared it. Same rule as `StructuralLimit`: unattributable is not admissible. */
|
|
304
|
+
readonly declaredBy: string;
|
|
305
|
+
}
|
|
306
|
+
/** A bar the reference set cannot answer, because it holds nothing to sample. */
|
|
307
|
+
export interface EmptyPopulation {
|
|
308
|
+
readonly repository: string;
|
|
309
|
+
readonly edgeType: EdgeType | "mixed";
|
|
310
|
+
}
|
|
311
|
+
export interface UnmetRequirement {
|
|
312
|
+
readonly requirement: Requirement;
|
|
313
|
+
/** Why it is unmet, in terms of what was found rather than what was wanted. */
|
|
314
|
+
readonly because: string;
|
|
315
|
+
}
|
|
316
|
+
export interface TierCertificate {
|
|
317
|
+
readonly adapter: string;
|
|
318
|
+
readonly language: string;
|
|
319
|
+
/** `null` means not even Tier C — and `unmetForNext` says what is missing. */
|
|
320
|
+
readonly tier: Tier | null;
|
|
321
|
+
/** The tier immediately above `tier`, and every requirement it lacks. */
|
|
322
|
+
readonly nextTier: Tier | null;
|
|
323
|
+
readonly unmetForNext: readonly UnmetRequirement[];
|
|
324
|
+
/** Every tier's verdict, so a reader can see the whole ladder at once. */
|
|
325
|
+
readonly byTier: Readonly<Record<Tier, readonly UnmetRequirement[]>>;
|
|
326
|
+
/**
|
|
327
|
+
* Names of the people whose declarations this tier rests on. Empty when the
|
|
328
|
+
* tier needed no attested requirement — C and B do not.
|
|
329
|
+
*/
|
|
330
|
+
readonly attestedBy: readonly string[];
|
|
331
|
+
/**
|
|
332
|
+
* Edge types the qualifying precision draws actually covered. Empty under
|
|
333
|
+
* the same condition as `attestedBy`. See the generator's own comment: a
|
|
334
|
+
* tier resting on, say, an `IMPORTS`-only draw has not had `USES_API`
|
|
335
|
+
* itself sampled, and this is where a reader finds that out rather than
|
|
336
|
+
* assuming "Tier A" answered a question it did not.
|
|
337
|
+
*/
|
|
338
|
+
readonly attestedEdgeTypes: readonly (EdgeType | "mixed")[];
|
|
339
|
+
/**
|
|
340
|
+
* Requirements waived because this language cannot satisfy them, and checks
|
|
341
|
+
* declared unreachable. **Never empty-by-omission in the printed form** — a
|
|
342
|
+
* consumer that prints the tier without these has published a capability
|
|
343
|
+
* claim the certificate does not make.
|
|
344
|
+
*/
|
|
345
|
+
readonly limitations: readonly StructuralLimit[];
|
|
346
|
+
/**
|
|
347
|
+
* Deliberate under-claims, printed for the same reason `limitations` are:
|
|
348
|
+
* omitting them publishes a cap as an absence, which understates a capability
|
|
349
|
+
* the adapter has.
|
|
350
|
+
*/
|
|
351
|
+
readonly policyCaps: readonly PolicyCap[];
|
|
352
|
+
/**
|
|
353
|
+
* Bars this **reference set** cannot answer — every recorded draw whose
|
|
354
|
+
* population was zero.
|
|
355
|
+
*
|
|
356
|
+
* Printed beside the structural limitations because it is the same kind of
|
|
357
|
+
* statement: not *"the adapter fell short"* but *"there was nothing here to
|
|
358
|
+
* measure"*. The remedy is other repositories, and a certificate that does not
|
|
359
|
+
* say so sends a reader to measure harder against an empty population.
|
|
360
|
+
*/
|
|
361
|
+
readonly unreachableHere: readonly EmptyPopulation[];
|
|
362
|
+
/** DEC-075 §6.3: does `capabilities()` match what was emitted? */
|
|
363
|
+
readonly capabilitiesHonest: boolean | "unknown";
|
|
364
|
+
}
|
|
365
|
+
export declare function certify(evidence: GateEvidence): TierCertificate;
|
|
366
|
+
/**
|
|
367
|
+
* The certificate as a developer should read it: the tier, then immediately the
|
|
368
|
+
* reason it is not higher.
|
|
369
|
+
*
|
|
370
|
+
* The failing rows are not an appendix. A support matrix that prints "B" and
|
|
371
|
+
* stops teaches a reader that B is the ceiling; printing what A is missing
|
|
372
|
+
* turns the same fact into a work item.
|
|
373
|
+
*/
|
|
374
|
+
export declare function formatCertificate(certificate: TierCertificate): string;
|
|
375
|
+
//# sourceMappingURL=certify.d.ts.map
|