@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,9 @@
|
|
|
1
|
+
export { nearestMiss, sameEndpoint } from "./paths.ts";
|
|
2
|
+
export type { EndpointKey, NearMiss, NearMissKind } from "./paths.ts";
|
|
3
|
+
export { readShape, compareShapes } from "./shapes.ts";
|
|
4
|
+
export type { Shape, ShapeField, ShapeComparison, ShapeDifference, ShapeDifferenceKind, } from "./shapes.ts";
|
|
5
|
+
export { findContractOrphans } from "./orphans.ts";
|
|
6
|
+
export type { ContractFinding, ContractFindingKind, ContractReport, OrphanOptions, } from "./orphans.ts";
|
|
7
|
+
export { checkContracts } from "./engine.ts";
|
|
8
|
+
export type { ContractCheck, ContractOptions, ShapeCheck, ShapeDirection, ShapeMismatch, } from "./engine.ts";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EACV,KAAK,EACL,UAAU,EACV,eAAe,EACf,eAAe,EACf,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACV,aAAa,EACb,eAAe,EACf,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGvD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAQnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract engine's R2 half: who calls what, and who serves it.
|
|
3
|
+
*
|
|
4
|
+
* §13's mechanism is shape comparison at R3. This is the part that works
|
|
5
|
+
* **below** it, and the architecture is explicit that it should exist: at R2,
|
|
6
|
+
* report path-level orphans only, and say so. Splitting the engine this way
|
|
7
|
+
* means half the value lands on graphs no type checker has touched, and the R3
|
|
8
|
+
* half does not hold it hostage.
|
|
9
|
+
*
|
|
10
|
+
* ## Three findings, and the reason there are only three
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* API_ROUTE ──SERVES_API──▶ API_ENDPOINT ◀──USES_API── caller
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* An `API_ENDPOINT` is the join. Every question here is about which of its two
|
|
17
|
+
* sides is present:
|
|
18
|
+
*
|
|
19
|
+
* | both sides | one side | finding |
|
|
20
|
+
* | --- | --- | --- |
|
|
21
|
+
* | caller + route | — | the contract holds at path level |
|
|
22
|
+
* | — | caller only | **the call reaches nothing that is served** |
|
|
23
|
+
* | — | route only | **nothing in this graph calls the route** |
|
|
24
|
+
*
|
|
25
|
+
* ## The trap, and it is the whole design
|
|
26
|
+
*
|
|
27
|
+
* On two real repositories: **117 callers, 24 routes, 16 joined.** The other 101
|
|
28
|
+
* callers are, overwhelmingly, calls to services that are not in the graph at
|
|
29
|
+
* all — a third-party API, a backend nobody analysed. Reporting them as "no
|
|
30
|
+
* route serves this" would be 101 confident wrong statements that bury the
|
|
31
|
+
* handful that are real.
|
|
32
|
+
*
|
|
33
|
+
* So an unmatched caller is a **finding** only when a served route exists that
|
|
34
|
+
* it nearly matches (see `paths.ts`, where every rule is a specific mechanical
|
|
35
|
+
* mistake rather than a similarity threshold). Everything else is counted and
|
|
36
|
+
* disclosed as *not analysable*, because the honest description of it is "the
|
|
37
|
+
* provider side of this call was never read", not "the provider side is wrong".
|
|
38
|
+
*
|
|
39
|
+
* Two more refusals in the same spirit:
|
|
40
|
+
*
|
|
41
|
+
* - **No routes at all in the graph → no caller findings, at all.** With nothing
|
|
42
|
+
* on the provider side, every caller is unmatched by construction and the
|
|
43
|
+
* comparison has no content. Reported as not analysable, with the reason.
|
|
44
|
+
* - **`ROUTE_NOT_CALLED` is stated weakly on purpose.** A route nothing calls is
|
|
45
|
+
* usually a route called by something outside this graph — a mobile client,
|
|
46
|
+
* another service, curl. It is a lead, not a defect, and the wording says so.
|
|
47
|
+
*/
|
|
48
|
+
import type { IRNode, ResolutionLevel } from "@descryy/ir";
|
|
49
|
+
import type { AdjacencyProvider, QueryResult } from "../query/provider.ts";
|
|
50
|
+
import { type NearMiss } from "./paths.ts";
|
|
51
|
+
export type ContractFindingKind = "CALL_NOT_SERVED" | "ROUTE_NOT_CALLED";
|
|
52
|
+
export interface ContractFinding {
|
|
53
|
+
readonly kind: ContractFindingKind;
|
|
54
|
+
readonly endpoint: IRNode;
|
|
55
|
+
readonly method: string;
|
|
56
|
+
readonly pathTemplate: string;
|
|
57
|
+
/** Callers reaching this endpoint. Populated for `CALL_NOT_SERVED`. */
|
|
58
|
+
readonly callers: readonly IRNode[];
|
|
59
|
+
/** Routes serving it. Populated for `ROUTE_NOT_CALLED`. */
|
|
60
|
+
readonly routes: readonly IRNode[];
|
|
61
|
+
/** Why this is reportable rather than merely unmatched. Null for `ROUTE_NOT_CALLED`. */
|
|
62
|
+
readonly nearMiss: NearMiss | null;
|
|
63
|
+
/** The sentence a developer reads. */
|
|
64
|
+
readonly detail: string;
|
|
65
|
+
/** Lowest resolution among the edges this finding rests on. Caps its class. */
|
|
66
|
+
readonly resolution: ResolutionLevel;
|
|
67
|
+
}
|
|
68
|
+
export interface ContractReport {
|
|
69
|
+
readonly findings: readonly ContractFinding[];
|
|
70
|
+
readonly checked: {
|
|
71
|
+
readonly endpoints: number;
|
|
72
|
+
readonly withCallers: number;
|
|
73
|
+
readonly withRoutes: number;
|
|
74
|
+
readonly joined: number;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Unmatched callers with nothing on the provider side that resembles them.
|
|
78
|
+
*
|
|
79
|
+
* Not findings, and the number matters: it is the size of the region this
|
|
80
|
+
* analysis could not speak about.
|
|
81
|
+
*/
|
|
82
|
+
readonly unmatchedWithoutNearMiss: number;
|
|
83
|
+
/** True when the R3 shape comparison did not run. Always true in this half. */
|
|
84
|
+
readonly shapesCompared: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface OrphanOptions {
|
|
87
|
+
/** Cap on endpoints enumerated. Truncation is reported, never silent. */
|
|
88
|
+
readonly limit?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Cap on findings returned — rule 5's "a few high-confidence findings, never
|
|
91
|
+
* an exhaustive maybe-list". Overflow is disclosed, not dropped quietly.
|
|
92
|
+
*/
|
|
93
|
+
readonly maxFindings?: number;
|
|
94
|
+
}
|
|
95
|
+
export declare function findContractOrphans(provider: AdjacencyProvider, options?: OrphanOptions): QueryResult<ContractReport>;
|
|
96
|
+
//# sourceMappingURL=orphans.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphans.d.ts","sourceRoot":"","sources":["../../src/contracts/orphans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAc,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAAiC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE1E,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAEzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IACnC,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAYD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,aAAkB,GAC1B,WAAW,CAAC,cAAc,CAAC,CA6M7B"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract engine's R2 half: who calls what, and who serves it.
|
|
3
|
+
*
|
|
4
|
+
* §13's mechanism is shape comparison at R3. This is the part that works
|
|
5
|
+
* **below** it, and the architecture is explicit that it should exist: at R2,
|
|
6
|
+
* report path-level orphans only, and say so. Splitting the engine this way
|
|
7
|
+
* means half the value lands on graphs no type checker has touched, and the R3
|
|
8
|
+
* half does not hold it hostage.
|
|
9
|
+
*
|
|
10
|
+
* ## Three findings, and the reason there are only three
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* API_ROUTE ──SERVES_API──▶ API_ENDPOINT ◀──USES_API── caller
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* An `API_ENDPOINT` is the join. Every question here is about which of its two
|
|
17
|
+
* sides is present:
|
|
18
|
+
*
|
|
19
|
+
* | both sides | one side | finding |
|
|
20
|
+
* | --- | --- | --- |
|
|
21
|
+
* | caller + route | — | the contract holds at path level |
|
|
22
|
+
* | — | caller only | **the call reaches nothing that is served** |
|
|
23
|
+
* | — | route only | **nothing in this graph calls the route** |
|
|
24
|
+
*
|
|
25
|
+
* ## The trap, and it is the whole design
|
|
26
|
+
*
|
|
27
|
+
* On two real repositories: **117 callers, 24 routes, 16 joined.** The other 101
|
|
28
|
+
* callers are, overwhelmingly, calls to services that are not in the graph at
|
|
29
|
+
* all — a third-party API, a backend nobody analysed. Reporting them as "no
|
|
30
|
+
* route serves this" would be 101 confident wrong statements that bury the
|
|
31
|
+
* handful that are real.
|
|
32
|
+
*
|
|
33
|
+
* So an unmatched caller is a **finding** only when a served route exists that
|
|
34
|
+
* it nearly matches (see `paths.ts`, where every rule is a specific mechanical
|
|
35
|
+
* mistake rather than a similarity threshold). Everything else is counted and
|
|
36
|
+
* disclosed as *not analysable*, because the honest description of it is "the
|
|
37
|
+
* provider side of this call was never read", not "the provider side is wrong".
|
|
38
|
+
*
|
|
39
|
+
* Two more refusals in the same spirit:
|
|
40
|
+
*
|
|
41
|
+
* - **No routes at all in the graph → no caller findings, at all.** With nothing
|
|
42
|
+
* on the provider side, every caller is unmatched by construction and the
|
|
43
|
+
* comparison has no content. Reported as not analysable, with the reason.
|
|
44
|
+
* - **`ROUTE_NOT_CALLED` is stated weakly on purpose.** A route nothing calls is
|
|
45
|
+
* usually a route called by something outside this graph — a mobile client,
|
|
46
|
+
* another service, curl. It is a lead, not a defect, and the wording says so.
|
|
47
|
+
*/
|
|
48
|
+
import { nearestMiss } from "./paths.js";
|
|
49
|
+
const DEFAULT_MAX_FINDINGS = 25;
|
|
50
|
+
function keyOf(endpoint) {
|
|
51
|
+
const method = endpoint.attrs["method"];
|
|
52
|
+
const pathTemplate = endpoint.attrs["pathTemplate"];
|
|
53
|
+
return typeof method === "string" && typeof pathTemplate === "string"
|
|
54
|
+
? { method, pathTemplate }
|
|
55
|
+
: null;
|
|
56
|
+
}
|
|
57
|
+
export function findContractOrphans(provider, options = {}) {
|
|
58
|
+
const maxFindings = options.maxFindings ?? DEFAULT_MAX_FINDINGS;
|
|
59
|
+
const set = provider.nodesOfType("API_ENDPOINT", ...(options.limit === undefined ? [] : [options.limit]));
|
|
60
|
+
const notes = [];
|
|
61
|
+
const sided = [];
|
|
62
|
+
let unkeyed = 0;
|
|
63
|
+
for (const endpoint of set.nodes) {
|
|
64
|
+
const key = keyOf(endpoint);
|
|
65
|
+
if (key === null) {
|
|
66
|
+
// An endpoint with no method or path template cannot be compared to
|
|
67
|
+
// anything. Counted rather than skipped: it means a producer emitted a
|
|
68
|
+
// join node without the two attributes the join is made of.
|
|
69
|
+
unkeyed += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const callers = [];
|
|
73
|
+
const routes = [];
|
|
74
|
+
let resolution = 4;
|
|
75
|
+
for (const edge of provider.incoming(endpoint.id, { types: ["USES_API", "SERVES_API"] })) {
|
|
76
|
+
const other = provider.node(edge.from);
|
|
77
|
+
if (other === undefined)
|
|
78
|
+
continue;
|
|
79
|
+
if (edge.resolution < resolution)
|
|
80
|
+
resolution = edge.resolution;
|
|
81
|
+
(edge.type === "USES_API" ? callers : routes).push(other);
|
|
82
|
+
}
|
|
83
|
+
sided.push({ endpoint, key, callers, routes, resolution });
|
|
84
|
+
}
|
|
85
|
+
const withCallers = sided.filter((s) => s.callers.length > 0);
|
|
86
|
+
const withRoutes = sided.filter((s) => s.routes.length > 0);
|
|
87
|
+
const joined = sided.filter((s) => s.callers.length > 0 && s.routes.length > 0);
|
|
88
|
+
const servedKeys = withRoutes.map((s) => s.key);
|
|
89
|
+
// --- the refusals --------------------------------------------------------
|
|
90
|
+
if (sided.length === 0) {
|
|
91
|
+
notes.push("No API endpoints are in the graph, so there is no contract to check. Either no adapter " +
|
|
92
|
+
"read a route or a client call, or none of the configured adapters produces them. This " +
|
|
93
|
+
"is not a clean result.");
|
|
94
|
+
return report(provider, emptyReport(sided.length, 0, 0, 0), 0, false, notes);
|
|
95
|
+
}
|
|
96
|
+
const findings = [];
|
|
97
|
+
let unmatchedWithoutNearMiss = 0;
|
|
98
|
+
if (withRoutes.length === 0) {
|
|
99
|
+
// Every caller is unmatched by construction. Comparing against an empty
|
|
100
|
+
// provider side produces one finding per call site and means nothing.
|
|
101
|
+
unmatchedWithoutNearMiss = withCallers.length;
|
|
102
|
+
notes.push(`${withCallers.length} endpoint(s) are called and no route is in the graph at all, so no ` +
|
|
103
|
+
"comparison was possible. Every call would be reported as unserved for the same reason, " +
|
|
104
|
+
"which is a property of what was analysed rather than of the code. Add the service that " +
|
|
105
|
+
"serves these routes, or a published contract for it.");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
for (const entry of withCallers) {
|
|
109
|
+
if (entry.routes.length > 0)
|
|
110
|
+
continue;
|
|
111
|
+
const miss = nearestMiss(entry.key, servedKeys);
|
|
112
|
+
if (miss === null) {
|
|
113
|
+
// Nothing on the provider side resembles it. Far more likely an external
|
|
114
|
+
// service than a defect, and stating otherwise would be 101 confident
|
|
115
|
+
// wrong sentences on the repositories this was measured against.
|
|
116
|
+
unmatchedWithoutNearMiss += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
findings.push({
|
|
120
|
+
kind: "CALL_NOT_SERVED",
|
|
121
|
+
endpoint: entry.endpoint,
|
|
122
|
+
method: entry.key.method,
|
|
123
|
+
pathTemplate: entry.key.pathTemplate,
|
|
124
|
+
callers: entry.callers,
|
|
125
|
+
routes: [],
|
|
126
|
+
nearMiss: miss,
|
|
127
|
+
detail: `${entry.key.method} ${entry.key.pathTemplate} is called from ` +
|
|
128
|
+
`${describeCallers(entry.callers)} and no route serves it — but ${miss.detail}`,
|
|
129
|
+
resolution: entry.resolution,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (withCallers.length === 0) {
|
|
134
|
+
// The mirror of the refusal above, and it was missing until a per-source
|
|
135
|
+
// ablation on `bench/contracts-real.mjs` removed the consumer side: the
|
|
136
|
+
// engine reported 24 ROUTE_NOT_CALLED findings and no disclosure, about a
|
|
137
|
+
// graph containing no callers at all.
|
|
138
|
+
//
|
|
139
|
+
// "Nothing in this graph calls this route" is literally true there — and so
|
|
140
|
+
// is the sentence this engine declines to say in the other direction.
|
|
141
|
+
// Whichever side is empty, the cause is ONE fact about what was analysed,
|
|
142
|
+
// not N facts about the artefacts. It is not only an ablation state either:
|
|
143
|
+
// a workspace where only the backend has been indexed is the normal first
|
|
144
|
+
// day of a deployment.
|
|
145
|
+
notes.push(`${withRoutes.length} route(s) are served and no call site is in the graph at all, so no ` +
|
|
146
|
+
"comparison was possible. Every route would be reported as uncalled for the same reason, " +
|
|
147
|
+
"which is a property of what was analysed rather than of the code. Add the client that " +
|
|
148
|
+
"calls these routes.");
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
for (const entry of withRoutes) {
|
|
152
|
+
if (entry.callers.length > 0)
|
|
153
|
+
continue;
|
|
154
|
+
findings.push({
|
|
155
|
+
kind: "ROUTE_NOT_CALLED",
|
|
156
|
+
endpoint: entry.endpoint,
|
|
157
|
+
method: entry.key.method,
|
|
158
|
+
pathTemplate: entry.key.pathTemplate,
|
|
159
|
+
callers: [],
|
|
160
|
+
routes: entry.routes,
|
|
161
|
+
nearMiss: null,
|
|
162
|
+
detail: `${entry.key.method} ${entry.key.pathTemplate} is served by ` +
|
|
163
|
+
`${describeRoutes(entry.routes)} and nothing in this graph calls it. That is a lead, ` +
|
|
164
|
+
"not a defect: a mobile client, another service or an external consumer would not " +
|
|
165
|
+
"appear here.",
|
|
166
|
+
resolution: entry.resolution,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Findings that rest on better evidence first, then the stronger kind, then by
|
|
171
|
+
// path so the order is identical between runs.
|
|
172
|
+
findings.sort((a, b) => b.resolution - a.resolution ||
|
|
173
|
+
(a.kind === b.kind ? 0 : a.kind === "CALL_NOT_SERVED" ? -1 : 1) ||
|
|
174
|
+
`${a.method} ${a.pathTemplate}`.localeCompare(`${b.method} ${b.pathTemplate}`));
|
|
175
|
+
const kept = findings.slice(0, maxFindings);
|
|
176
|
+
const truncated = set.truncated || kept.length < findings.length;
|
|
177
|
+
if (kept.length < findings.length) {
|
|
178
|
+
notes.push(`${findings.length - kept.length} further finding(s) were withheld: a report is capped at ` +
|
|
179
|
+
`${maxFindings} so that a short list stays worth reading. They were found, not missed — ` +
|
|
180
|
+
'raise "maxFindings" to see them.');
|
|
181
|
+
}
|
|
182
|
+
if (set.truncated) {
|
|
183
|
+
notes.push("More endpoints exist than were enumerated, so this report covers a prefix of the graph " +
|
|
184
|
+
"rather than all of it. Endpoints beyond the cap were not examined and are not clean.");
|
|
185
|
+
}
|
|
186
|
+
if (unkeyed > 0) {
|
|
187
|
+
notes.push(`${unkeyed} endpoint(s) carry no method or path template and could not be compared to ` +
|
|
188
|
+
"anything. A producer emitted a join node without the two attributes the join is made of.");
|
|
189
|
+
}
|
|
190
|
+
if (unmatchedWithoutNearMiss > 0 && withRoutes.length > 0) {
|
|
191
|
+
notes.push(`${unmatchedWithoutNearMiss} called endpoint(s) have no route in this graph and nothing on ` +
|
|
192
|
+
"the provider side that resembles them. They are not reported as broken: the likeliest " +
|
|
193
|
+
"explanation is a service outside this workspace, and the honest statement is that their " +
|
|
194
|
+
"provider was never read.");
|
|
195
|
+
}
|
|
196
|
+
notes.push("Path level only. Request and response shapes were not compared — that requires R3 on both " +
|
|
197
|
+
"sides, because it compares shapes rather than names.");
|
|
198
|
+
let floor = 4;
|
|
199
|
+
for (const finding of kept)
|
|
200
|
+
if (finding.resolution < floor)
|
|
201
|
+
floor = finding.resolution;
|
|
202
|
+
if (kept.length === 0)
|
|
203
|
+
floor = 2;
|
|
204
|
+
return report(provider, {
|
|
205
|
+
findings: kept,
|
|
206
|
+
checked: {
|
|
207
|
+
endpoints: sided.length,
|
|
208
|
+
withCallers: withCallers.length,
|
|
209
|
+
withRoutes: withRoutes.length,
|
|
210
|
+
joined: joined.length,
|
|
211
|
+
},
|
|
212
|
+
unmatchedWithoutNearMiss,
|
|
213
|
+
shapesCompared: false,
|
|
214
|
+
}, floor, truncated, notes);
|
|
215
|
+
}
|
|
216
|
+
function describeCallers(callers) {
|
|
217
|
+
const first = callers[0];
|
|
218
|
+
if (first === undefined)
|
|
219
|
+
return "an unknown caller";
|
|
220
|
+
const where = first.file === null ? first.name : `${first.name} (${first.file})`;
|
|
221
|
+
return callers.length === 1 ? where : `${where} and ${callers.length - 1} other(s)`;
|
|
222
|
+
}
|
|
223
|
+
function describeRoutes(routes) {
|
|
224
|
+
const first = routes[0];
|
|
225
|
+
if (first === undefined)
|
|
226
|
+
return "an unknown route";
|
|
227
|
+
return routes.length === 1 ? first.name : `${first.name} and ${routes.length - 1} other(s)`;
|
|
228
|
+
}
|
|
229
|
+
function emptyReport(endpoints, withCallers, withRoutes, joined) {
|
|
230
|
+
return {
|
|
231
|
+
findings: [],
|
|
232
|
+
checked: { endpoints, withCallers, withRoutes, joined },
|
|
233
|
+
unmatchedWithoutNearMiss: 0,
|
|
234
|
+
shapesCompared: false,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function report(provider, data, resolutionFloor, truncated, notes) {
|
|
238
|
+
const stamp = { ...provider.baseStamp(), resolutionFloor };
|
|
239
|
+
return { data, stamp, truncated, notes };
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=orphans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphans.js","sourceRoot":"","sources":["../../src/contracts/orphans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAKH,OAAO,EAAE,WAAW,EAAmC,MAAM,YAAY,CAAC;AAkD1E,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,SAAS,KAAK,CAAC,QAAgB;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACpD,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,YAAY,KAAK,QAAQ;QACnE,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE;QAC1B,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,QAA2B,EAC3B,UAAyB,EAAE;IAE3B,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,CAC9B,cAAc,EACd,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,KAAK,CAAW,CAAC,CACnE,CAAC;IACF,MAAM,KAAK,GAAa,EAAE,CAAC;IAY3B,MAAM,KAAK,GAAY,EAAE,CAAC;IAC1B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC5B,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,oEAAoE;YACpE,uEAAuE;YACvE,4DAA4D;YAC5D,OAAO,IAAI,CAAC,CAAC;YACb,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,UAAU,GAAoB,CAAC,CAAC;QAEpC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;YACzF,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS;YAClC,IAAI,IAAI,CAAC,UAAU,GAAG,UAAU;gBAAE,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YAC/D,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEhD,4EAA4E;IAE5E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CACR,yFAAyF;YACvF,wFAAwF;YACxF,wBAAwB,CAC3B,CAAC;QACF,OAAO,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/E,CAAC;IAED,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,IAAI,wBAAwB,GAAG,CAAC,CAAC;IAEjC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,wEAAwE;QACxE,sEAAsE;QACtE,wBAAwB,GAAG,WAAW,CAAC,MAAM,CAAC;QAC9C,KAAK,CAAC,IAAI,CACR,GAAG,WAAW,CAAC,MAAM,qEAAqE;YACxF,yFAAyF;YACzF,yFAAyF;YACzF,sDAAsD,CACzD,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YACtC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAChD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,yEAAyE;gBACzE,sEAAsE;gBACtE,iEAAiE;gBACjE,wBAAwB,IAAI,CAAC,CAAC;gBAC9B,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,iBAAiB;gBACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM;gBACxB,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,YAAY;gBACpC,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,IAAI;gBACd,MAAM,EACJ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,kBAAkB;oBAC/D,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,iCAAiC,IAAI,CAAC,MAAM,EAAE;gBACjF,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,yEAAyE;QACzE,wEAAwE;QACxE,0EAA0E;QAC1E,sCAAsC;QACtC,EAAE;QACF,4EAA4E;QAC5E,sEAAsE;QACtE,0EAA0E;QAC1E,4EAA4E;QAC5E,0EAA0E;QAC1E,uBAAuB;QACvB,KAAK,CAAC,IAAI,CACR,GAAG,UAAU,CAAC,MAAM,sEAAsE;YACxF,0FAA0F;YAC1F,wFAAwF;YACxF,qBAAqB,CACxB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YACvC,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,kBAAkB;gBAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM;gBACtB,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,YAAY;gBACpC,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,QAAQ,EAAE,IAAI;gBACd,MAAM,EACJ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,gBAAgB;oBAC7D,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,uDAAuD;oBACtF,mFAAmF;oBACnF,cAAc;gBAChB,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,CACX,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU;QAC3B,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC,CACjF,CAAC;IAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEjE,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CACR,GAAG,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,2DAA2D;YACzF,GAAG,WAAW,2EAA2E;YACzF,kCAAkC,CACrC,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CACR,yFAAyF;YACvF,sFAAsF,CACzF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CACR,GAAG,OAAO,6EAA6E;YACrF,0FAA0F,CAC7F,CAAC;IACJ,CAAC;IACD,IAAI,wBAAwB,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,KAAK,CAAC,IAAI,CACR,GAAG,wBAAwB,iEAAiE;YAC1F,wFAAwF;YACxF,0FAA0F;YAC1F,0BAA0B,CAC7B,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CACR,4FAA4F;QAC1F,sDAAsD,CACzD,CAAC;IAEF,IAAI,KAAK,GAAoB,CAAC,CAAC;IAC/B,KAAK,MAAM,OAAO,IAAI,IAAI;QAAE,IAAI,OAAO,CAAC,UAAU,GAAG,KAAK;YAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;IACvF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,KAAK,GAAG,CAAC,CAAC;IAEjC,OAAO,MAAM,CACX,QAAQ,EACR;QACE,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE;YACP,SAAS,EAAE,KAAK,CAAC,MAAM;YACvB,WAAW,EAAE,WAAW,CAAC,MAAM;YAC/B,UAAU,EAAE,UAAU,CAAC,MAAM;YAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB;QACD,wBAAwB;QACxB,cAAc,EAAE,KAAK;KACtB,EACD,KAAK,EACL,SAAS,EACT,KAAK,CACN,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAA0B;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,mBAAmB,CAAC;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC;IACjF,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC;AACtF,CAAC;AAED,SAAS,cAAc,CAAC,MAAyB;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,kBAAkB,CAAC;IACnD,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,QAAQ,MAAM,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC;AAC9F,CAAC;AAED,SAAS,WAAW,CAClB,SAAiB,EACjB,WAAmB,EACnB,UAAkB,EAClB,MAAc;IAEd,OAAO;QACL,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE;QACvD,wBAAwB,EAAE,CAAC;QAC3B,cAAc,EAAE,KAAK;KACtB,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CACb,QAA2B,EAC3B,IAAoB,EACpB,eAAgC,EAChC,SAAkB,EAClB,KAAe;IAEf,MAAM,KAAK,GAAe,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,EAAE,eAAe,EAAE,CAAC;IACvE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparing two endpoint paths, and knowing when not to.
|
|
3
|
+
*
|
|
4
|
+
* This file is small and is the most dangerous part of the contract engine, so
|
|
5
|
+
* it is separate and tested on its own.
|
|
6
|
+
*
|
|
7
|
+
* ## The problem it exists to solve
|
|
8
|
+
*
|
|
9
|
+
* A caller reaching an endpoint no route serves is *sometimes* a bug and
|
|
10
|
+
* *usually* not. Measured on two real repositories: 117 callers, 24 routes, 16
|
|
11
|
+
* joined. The other 101 are overwhelmingly calls to services that are simply not
|
|
12
|
+
* in this graph — a third-party API, a backend nobody has analysed. Reporting
|
|
13
|
+
* all 101 as "no route serves this" would be 101 confident wrong statements, and
|
|
14
|
+
* would bury the handful that are real.
|
|
15
|
+
*
|
|
16
|
+
* So an unmatched caller is only a **finding** when a served route exists that
|
|
17
|
+
* it nearly matches. Nothing similar on the provider side means the provider was
|
|
18
|
+
* probably never analysed, which belongs in §20.2's *not analysable* category
|
|
19
|
+
* and is disclosed as a count rather than enumerated as defects.
|
|
20
|
+
*
|
|
21
|
+
* ## The near-miss rules are deliberately narrow
|
|
22
|
+
*
|
|
23
|
+
* Each one describes a specific, common, *mechanical* mistake. None of them is a
|
|
24
|
+
* similarity score, because a threshold on a score is a knob that turns "is this
|
|
25
|
+
* a bug" into "how high did we set it".
|
|
26
|
+
*
|
|
27
|
+
* | rule | example | why it is a real mistake |
|
|
28
|
+
* | --- | --- | --- |
|
|
29
|
+
* | method differs, path identical | `POST /orders` vs `GET /orders` | someone changed the verb on one side |
|
|
30
|
+
* | exactly one **literal** segment differs, **and the two differ by a typo's worth of characters** | `/order/{param}/items` vs `/orders/{param}/items` | a typo or a rename applied on one side |
|
|
31
|
+
*
|
|
32
|
+
* ## Rule 2's similarity floor, and the measurement that set it
|
|
33
|
+
*
|
|
34
|
+
* That second clause was missing for the whole life of this file, and its
|
|
35
|
+
* absence was not a small thing. "Exactly one segment differs" says nothing at
|
|
36
|
+
* all about *how* it differs: `/api/hmac` and `/api/novu-agent` differ in
|
|
37
|
+
* exactly one segment, share a method and share arity. Precision came only
|
|
38
|
+
* incidentally, from `{param}` segments happening to sit where the paths
|
|
39
|
+
* diverge.
|
|
40
|
+
*
|
|
41
|
+
* `DEC-317` found this from the GraphQL side — every operation path is arity-2
|
|
42
|
+
* with no `{param}` and a first segment drawn from a set of size three, so the
|
|
43
|
+
* whole GraphQL surface of a repository collapsed into three buckets in which
|
|
44
|
+
* every member near-missed every other — and gated it rather than fixing it,
|
|
45
|
+
* because a threshold chosen carelessly costs the near-typo detections this
|
|
46
|
+
* rule exists for, and costs them invisibly.
|
|
47
|
+
*
|
|
48
|
+
* So it was measured, against 3,524 routes from 27 real repositories, by
|
|
49
|
+
* `bench/near-miss-similarity.mjs` — which pre-registers the metric, the
|
|
50
|
+
* candidate thresholds and the decision rule in its header, and was committed
|
|
51
|
+
* before it was run. What it found:
|
|
52
|
+
*
|
|
53
|
+
* - **The rule was not a precision rule.** Treating each real route as an
|
|
54
|
+
* unmatched caller against the rest of its own application's table, it
|
|
55
|
+
* reported a `ONE_SEGMENT` near miss for **1,796 of 3,524 routes — 51%.**
|
|
56
|
+
* Every one is a false positive by construction: both sides are real,
|
|
57
|
+
* deliberately distinct routes of the same service.
|
|
58
|
+
* - **On the genuinely reachable population it was worse.** Across every
|
|
59
|
+
* unmatched `USES_API` caller in the corpus, the rule emitted **18 findings,
|
|
60
|
+
* and all 18 were nonsense** — 15 of them naming the same `/api/novu-agent`
|
|
61
|
+
* route from callers like `/api/hmac` and `/api/send-email`. There were no
|
|
62
|
+
* true positives to lose.
|
|
63
|
+
*
|
|
64
|
+
* The floor is `1 - distance / max(len) >= 0.4`, with any distance of 1 or less
|
|
65
|
+
* admitted outright so that a one-character mistake in a short segment (`/v1`
|
|
66
|
+
* against `/v2`) is not scored away. It rejects **87.4%** of those 1,796 and
|
|
67
|
+
* retains **100%** of a seeded near-typo population of 49,405 — real route
|
|
68
|
+
* segments mutated by transposition, deletion, insertion, substitution,
|
|
69
|
+
* singular/plural and separator variance. Not one seeded typo is lost, in any
|
|
70
|
+
* class.
|
|
71
|
+
*
|
|
72
|
+
* **The distance counts an adjacent transposition as one edit, not two, and
|
|
73
|
+
* that is load-bearing.** With plain Levenshtein a swap costs two, so `api` →
|
|
74
|
+
* `aip` scores `1 - 2/3 = 0.33` and every candidate threshold silently deleted
|
|
75
|
+
* the entire transposition class — 2,916 real detections, all on segments of
|
|
76
|
+
* three characters. The first measurement pass rejected every threshold for
|
|
77
|
+
* exactly this reason, and the reason was a defect in the metric rather than a
|
|
78
|
+
* fact about the corpus.
|
|
79
|
+
*
|
|
80
|
+
* **What the floor is not.** It is a similarity test, not an oracle. `activate`
|
|
81
|
+
* and `deactivate` are two real routes in one API and score 0.80, so they are
|
|
82
|
+
* still reported. Nothing here can distinguish a two-character prefix from a
|
|
83
|
+
* typo, and this does not claim to — it buys precision against *unrelated*
|
|
84
|
+
* words, which is what the corpus says the problem was.
|
|
85
|
+
*
|
|
86
|
+
* **A trailing-slash rule was written and then deleted, and the reason is worth
|
|
87
|
+
* keeping.** `/orders/` and `/orders` look like the classic contract mismatch,
|
|
88
|
+
* but `normaliseEndpointPath` strips the trailing slash *before hashing*, so the
|
|
89
|
+
* two sides mint the **same `API_ENDPOINT` id** and join correctly. The rule
|
|
90
|
+
* could never fire. It was caught by a test that asserted it would.
|
|
91
|
+
*
|
|
92
|
+
* That unification is the right default — the two paths mean the same endpoint
|
|
93
|
+
* to everyone who writes them — but it does make one real class of bug
|
|
94
|
+
* invisible here: a framework configured for strict routing genuinely serves
|
|
95
|
+
* only one of them. Whether it does depends on router configuration this graph
|
|
96
|
+
* does not read, so the limit is stated rather than papered over with a rule
|
|
97
|
+
* that fires on nothing.
|
|
98
|
+
*
|
|
99
|
+
* **Deliberately not a rule: a parameter where the other side has a literal.**
|
|
100
|
+
* `/orders/{param}` and `/orders/recent` legitimately coexist in every routing
|
|
101
|
+
* framework on earth — the literal is matched first and the parameter catches
|
|
102
|
+
* the rest. Treating that as a near miss would report the single most common
|
|
103
|
+
* correct pattern in REST routing as a defect.
|
|
104
|
+
*
|
|
105
|
+
* Segment counts must be equal for the segment rule. `/orders` and
|
|
106
|
+
* `/orders/{param}` are two different endpoints, not a near miss, and a rule
|
|
107
|
+
* that ignored length would pair every collection route with its item route.
|
|
108
|
+
*/
|
|
109
|
+
export interface EndpointKey {
|
|
110
|
+
readonly method: string;
|
|
111
|
+
/** Normalised: `:id`, `{id}` and `<int:id>` have all become `{param}`. */
|
|
112
|
+
readonly pathTemplate: string;
|
|
113
|
+
}
|
|
114
|
+
export type NearMissKind = "METHOD" | "ONE_SEGMENT";
|
|
115
|
+
export interface NearMiss {
|
|
116
|
+
readonly kind: NearMissKind;
|
|
117
|
+
/** The served endpoint this one nearly matches. */
|
|
118
|
+
readonly against: EndpointKey;
|
|
119
|
+
/** One sentence naming the exact difference. Goes straight into the report. */
|
|
120
|
+
readonly detail: string;
|
|
121
|
+
}
|
|
122
|
+
export declare function sameEndpoint(a: EndpointKey, b: EndpointKey): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* The closest served endpoint to `wanted`, or null if nothing is close enough.
|
|
125
|
+
*
|
|
126
|
+
* Rules are tried in order of how certain they are, and the first hit wins —
|
|
127
|
+
* a method mismatch on an otherwise identical path is a better explanation than
|
|
128
|
+
* a one-segment difference somewhere else, and reporting both would be reporting
|
|
129
|
+
* one mistake twice.
|
|
130
|
+
*/
|
|
131
|
+
export declare function nearestMiss(wanted: EndpointKey, served: readonly EndpointKey[]): NearMiss | null;
|
|
132
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/contracts/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2GG;AAkEH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEpD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAWD,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,QAAQ,GAAG,IAAI,CA0DjB"}
|