@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,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §14.6 — two open changes each adding a migration from the same parent, caught
|
|
3
|
+
* before the merge that turns it into a broken deploy.
|
|
4
|
+
*
|
|
5
|
+
* ## What this computes, and why it is the real computation rather than a proxy
|
|
6
|
+
*
|
|
7
|
+
* Migrations form a DAG: each revision names its parent(s), and a **head** is a
|
|
8
|
+
* revision nothing else descends from. Every migration runner refuses to run
|
|
9
|
+
* with more than one — Alembic's *"Multiple head revisions are present"* is the
|
|
10
|
+
* canonical spelling, and the equivalent exists in every tool that versions
|
|
11
|
+
* migrations at all.
|
|
12
|
+
*
|
|
13
|
+
* The obvious shortcut is to group migrations by declared parent and flag any
|
|
14
|
+
* parent claimed by two different changes. **That is wrong**, and wrong in the
|
|
15
|
+
* direction that produces false findings: a merge migration declaring two
|
|
16
|
+
* parents exists precisely to resolve such a fork, and the shortcut cannot see
|
|
17
|
+
* it. So this computes heads over the union the way the runner does. The check
|
|
18
|
+
* is only worth its Class A ceiling if it computes what the tool computes.
|
|
19
|
+
*
|
|
20
|
+
* ## Nothing here names a framework, and that is §14.6's own argument
|
|
21
|
+
*
|
|
22
|
+
* §14.6 says the SQL adapter makes this work identically across ORMs and raw-SQL
|
|
23
|
+
* tooling "rather than being written once per framework". So the input is a
|
|
24
|
+
* revision, its declared parents and where it came from — the shape every
|
|
25
|
+
* migration tool has, whatever it calls the fields. Reading `down_revision` out
|
|
26
|
+
* of an Alembic file, `dependencies` out of a Django one or a timestamp chain
|
|
27
|
+
* out of a Rails one is producer work. Rule 1: none of those words appear below
|
|
28
|
+
* this sentence.
|
|
29
|
+
*
|
|
30
|
+
* ## The class, and a disagreement with §15.2 that is already settled
|
|
31
|
+
*
|
|
32
|
+
* §15.2 lists "migration head conflict" as **Class A — deterministically
|
|
33
|
+
* verifiable, a structural fact, not an inference**. That is right about the
|
|
34
|
+
* *inference*: given both parent lists, the conclusion follows with no
|
|
35
|
+
* judgement at all. It is not right unconditionally, because the parent lists
|
|
36
|
+
* still have to be obtained, and reading `down_revision` with a regex is R0
|
|
37
|
+
* evidence however certain the arithmetic on top of it.
|
|
38
|
+
*
|
|
39
|
+
* This is the same disagreement the env/config check hit, and §15.3 already
|
|
40
|
+
* records the resolution: **the Class column is a ceiling, not a floor** —
|
|
41
|
+
* "corrected after the env/config check was built and the two disagreed". So A
|
|
42
|
+
* is what this check reaches when the records come from the framework's own
|
|
43
|
+
* migration history (DEC-115's promotion path, R3), and a source-read set lands
|
|
44
|
+
* where its evidence lands. DEC-058 at the finding level: the claim carries the
|
|
45
|
+
* resolution of its own evidence, not of the run.
|
|
46
|
+
*
|
|
47
|
+
* That is now the second check to disagree with §15.2's column in the same
|
|
48
|
+
* direction, which is worth someone's attention as a pattern rather than as two
|
|
49
|
+
* incidents.
|
|
50
|
+
*
|
|
51
|
+
* ## An unread migration is not a migration with no parents
|
|
52
|
+
*
|
|
53
|
+
* An initial migration genuinely has zero parents. A migration whose parent line
|
|
54
|
+
* could not be read has *unknown* parents. Both are "no parents in the record",
|
|
55
|
+
* and collapsing them silently corrupts the head computation — an unread
|
|
56
|
+
* migration might be exactly the merge that resolves the fork being reported.
|
|
57
|
+
*
|
|
58
|
+
* So `parents` is `readonly string[] | "unknown"` and the empty array cannot be
|
|
59
|
+
* used to mean the second thing. The distinction is made unrepresentable at the
|
|
60
|
+
* type rather than handled downstream, which is the repair
|
|
61
|
+
* `bench/lib/README.md` asks for at construction time rather than afterwards.
|
|
62
|
+
*/
|
|
63
|
+
import { reliabilityCap, reliabilityNote } from "@descryy/ir";
|
|
64
|
+
/**
|
|
65
|
+
* Head conflicts across a set of in-flight changes.
|
|
66
|
+
*
|
|
67
|
+
* Pure over records: no store, no graph, no framework. The producer that reads
|
|
68
|
+
* migration files decides `resolution`; this function propagates it.
|
|
69
|
+
*/
|
|
70
|
+
export function findMigrationHeadConflicts(records, options = {}) {
|
|
71
|
+
const notes = [];
|
|
72
|
+
const changes = new Set(records.map((r) => r.prId)).size;
|
|
73
|
+
if (records.length === 0) {
|
|
74
|
+
notes.push("No change in this set adds a migration, so no head computation was performed. That is " +
|
|
75
|
+
"not a finding that the migrations are consistent — there are none to be consistent.");
|
|
76
|
+
return {
|
|
77
|
+
conflict: null,
|
|
78
|
+
dependencies: [],
|
|
79
|
+
duplicateRevisions: [],
|
|
80
|
+
analysed: false,
|
|
81
|
+
complete: true,
|
|
82
|
+
checked: { changes, migrations: 0, withUnknownParents: 0, heads: 0 },
|
|
83
|
+
notes,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const unknown = records.filter((r) => r.parents === "unknown");
|
|
87
|
+
const known = records.filter((r) => r.parents !== "unknown");
|
|
88
|
+
const complete = unknown.length === 0;
|
|
89
|
+
// Two changes minting the same revision id is a defect on its own, and it also
|
|
90
|
+
// makes the DAG ill-formed — so it is detected before the heads are computed
|
|
91
|
+
// rather than being allowed to distort them.
|
|
92
|
+
const byRevision = new Map();
|
|
93
|
+
for (const r of records) {
|
|
94
|
+
const list = byRevision.get(r.revision);
|
|
95
|
+
if (list === undefined)
|
|
96
|
+
byRevision.set(r.revision, [r]);
|
|
97
|
+
else
|
|
98
|
+
list.push(r);
|
|
99
|
+
}
|
|
100
|
+
const duplicateRevisions = [...byRevision]
|
|
101
|
+
.filter(([, rs]) => new Set(rs.map((r) => r.prId)).size > 1)
|
|
102
|
+
.map(([revision, rs]) => ({ revision, prIds: [...new Set(rs.map((r) => r.prId))].sort() }));
|
|
103
|
+
// A revision is a head when nothing in the merged set declares it as a parent.
|
|
104
|
+
// Only revisions the set DECLARES are candidates: a parent that nothing here
|
|
105
|
+
// declares lives in the base history and has, by construction, at least one
|
|
106
|
+
// child — the migration that named it.
|
|
107
|
+
const claimedAsParent = new Set();
|
|
108
|
+
for (const r of known) {
|
|
109
|
+
for (const p of r.parents)
|
|
110
|
+
claimedAsParent.add(p);
|
|
111
|
+
}
|
|
112
|
+
const heads = known
|
|
113
|
+
.filter((r) => !claimedAsParent.has(r.revision))
|
|
114
|
+
.map((r) => ({ revision: r.revision, prId: r.prId, file: r.file }))
|
|
115
|
+
.sort((a, b) => a.revision.localeCompare(b.revision));
|
|
116
|
+
// The weakest record decides the class — the same pair rule overlap scoring
|
|
117
|
+
// uses, for the same reason: a claim resting on several observations cannot
|
|
118
|
+
// be asserted at the strength of its best one.
|
|
119
|
+
const weakest = known.reduce((min, r) => (r.resolution < min ? r.resolution : min), 4);
|
|
120
|
+
const evidenceClass = reliabilityCap(weakest, true);
|
|
121
|
+
// A revision identifier is a name, so the R2 carve-out applies — but a partial
|
|
122
|
+
// DAG makes the conclusion conditional on something unread, which no amount of
|
|
123
|
+
// resolution on the read records repairs.
|
|
124
|
+
const reliability = complete ? evidenceClass : "C";
|
|
125
|
+
let disclosure = reliabilityNote(weakest, true);
|
|
126
|
+
if (!complete) {
|
|
127
|
+
disclosure +=
|
|
128
|
+
` ${unknown.length} migration(s) in this set had parents that could not be read, so the ` +
|
|
129
|
+
"revision graph is partial. One of them may be the merge that resolves this, which is " +
|
|
130
|
+
"why this is reported as a possibility rather than as the structural fact it would " +
|
|
131
|
+
"otherwise be.";
|
|
132
|
+
}
|
|
133
|
+
const conflict = heads.length > 1
|
|
134
|
+
? {
|
|
135
|
+
prIds: [...new Set(heads.map((h) => h.prId))].sort(),
|
|
136
|
+
heads,
|
|
137
|
+
reliability,
|
|
138
|
+
disclosure,
|
|
139
|
+
}
|
|
140
|
+
: null;
|
|
141
|
+
// A migration whose parent only exists inside another open change. Not a
|
|
142
|
+
// conflict — the merged set still has one head — but merging in the wrong
|
|
143
|
+
// order leaves a revision pointing at a parent that is not there yet.
|
|
144
|
+
const revisionOwner = new Map();
|
|
145
|
+
for (const r of records)
|
|
146
|
+
if (!revisionOwner.has(r.revision))
|
|
147
|
+
revisionOwner.set(r.revision, r.prId);
|
|
148
|
+
const dependencies = [];
|
|
149
|
+
for (const r of known) {
|
|
150
|
+
for (const parent of r.parents) {
|
|
151
|
+
const owner = revisionOwner.get(parent);
|
|
152
|
+
if (owner === undefined || owner === r.prId)
|
|
153
|
+
continue;
|
|
154
|
+
dependencies.push({
|
|
155
|
+
prId: r.prId,
|
|
156
|
+
revision: r.revision,
|
|
157
|
+
dependsOnRevision: parent,
|
|
158
|
+
dependsOnPrId: owner,
|
|
159
|
+
reliability,
|
|
160
|
+
disclosure,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (conflict !== null) {
|
|
165
|
+
notes.push(`Merging these ${conflict.prIds.length} change(s) leaves ${heads.length} migration heads. ` +
|
|
166
|
+
"A migration runner refuses to advance with more than one, so this fails at deploy " +
|
|
167
|
+
"time rather than at merge time — which is the point of computing it now.");
|
|
168
|
+
notes.push(disclosure);
|
|
169
|
+
}
|
|
170
|
+
if (!complete) {
|
|
171
|
+
notes.push(`${unknown.length} of ${records.length} migration(s) had unreadable parent revisions and ` +
|
|
172
|
+
`were excluded from the graph: ${unknown
|
|
173
|
+
.slice(0, 5)
|
|
174
|
+
.map((r) => r.file)
|
|
175
|
+
.join(", ")}${unknown.length > 5 ? ", …" : ""}. They were not treated as having no ` +
|
|
176
|
+
"parents — an initial migration and an unread one are different, and conflating them " +
|
|
177
|
+
"would silently change the head count.");
|
|
178
|
+
}
|
|
179
|
+
if (duplicateRevisions.length > 0) {
|
|
180
|
+
notes.push(`${duplicateRevisions.length} revision identifier(s) are declared by more than one change. ` +
|
|
181
|
+
"Whatever the head count says, two migrations cannot share an identifier.");
|
|
182
|
+
}
|
|
183
|
+
if (options.baseHead === undefined) {
|
|
184
|
+
notes.push("The base branch's current head was not supplied, so migrations branching from stale " +
|
|
185
|
+
"history were not checked. Only conflicts between the supplied changes were computed.");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
const stale = known.filter((r) => r.parents.length > 0 &&
|
|
189
|
+
!r.parents.includes(options.baseHead) &&
|
|
190
|
+
!r.parents.some((p) => revisionOwner.has(p)));
|
|
191
|
+
if (stale.length > 0) {
|
|
192
|
+
notes.push(`${stale.length} migration(s) descend from a revision that is neither the base head nor ` +
|
|
193
|
+
"added by any change in this set, so they were written against older history: " +
|
|
194
|
+
`${stale.map((r) => r.file).join(", ")}.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
conflict,
|
|
199
|
+
dependencies,
|
|
200
|
+
duplicateRevisions,
|
|
201
|
+
analysed: true,
|
|
202
|
+
complete,
|
|
203
|
+
checked: {
|
|
204
|
+
changes,
|
|
205
|
+
migrations: records.length,
|
|
206
|
+
withUnknownParents: unknown.length,
|
|
207
|
+
heads: heads.length,
|
|
208
|
+
},
|
|
209
|
+
notes,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=migration-heads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-heads.js","sourceRoot":"","sources":["../../src/multipr/migration-heads.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §14.3 — where two in-flight changes touch the same thing, and whether that
|
|
3
|
+
* matters.
|
|
4
|
+
*
|
|
5
|
+
* ## A shared node alone is not a conflict
|
|
6
|
+
*
|
|
7
|
+
* §14.3 says this outright and it is the whole design. At real team scale many
|
|
8
|
+
* pull requests incidentally touch the same low-importance code — a shared
|
|
9
|
+
* logging helper, a barrel file, a constants module. A tool that reports every
|
|
10
|
+
* shared node reports mostly noise, and a developer who has dismissed six
|
|
11
|
+
* incidental overlaps will dismiss the seventh without reading it.
|
|
12
|
+
*
|
|
13
|
+
* So severity is a product of *what* is shared, not *that* something is:
|
|
14
|
+
*
|
|
15
|
+
* score(node) = impact × NODE_TYPE_WEIGHT[type]
|
|
16
|
+
*
|
|
17
|
+
* A shared `API_ENDPOINT` is two changes to one contract. A shared `FUNCTION`
|
|
18
|
+
* in a utility module is two people editing a file. The weights are the only
|
|
19
|
+
* thing separating them and they are tuning values, labelled as such.
|
|
20
|
+
*
|
|
21
|
+
* ## Resolution is carried per node, and that is §14.2's one addition
|
|
22
|
+
*
|
|
23
|
+
* §14.2 adds `Resolution` to the persisted row with a reason: *so overlap
|
|
24
|
+
* severity is not computed across incomparable evidence*. If one change's node
|
|
25
|
+
* was resolved by a type checker and the other's by syntax alone, an overlap
|
|
26
|
+
* asserted at the stronger level claims more than half of its own evidence
|
|
27
|
+
* supports.
|
|
28
|
+
*
|
|
29
|
+
* The rule follows DEC-058 exactly — a claim carries the resolution of ITS OWN
|
|
30
|
+
* evidence, not of the run that produced it. Here the claim rests on two
|
|
31
|
+
* observations, so it carries **the weaker of the two**, and the finding's
|
|
32
|
+
* reliability class follows from that rather than from either side alone.
|
|
33
|
+
*
|
|
34
|
+
* ## What this deliberately does not do
|
|
35
|
+
*
|
|
36
|
+
* §14.4's mechanical-versus-semantic classification needs the actual diff lines
|
|
37
|
+
* at the shared node, which this module never sees. It reports the overlap and
|
|
38
|
+
* stops; classifying it — and the auto-apply path that is the single documented
|
|
39
|
+
* exception to approval-before-code-change — belongs where the diff is.
|
|
40
|
+
* Guessing "mechanical" without reading the lines would be the one place in
|
|
41
|
+
* this system where a wrong guess edits somebody's branch.
|
|
42
|
+
*/
|
|
43
|
+
import type { NodeType, ResolutionLevel } from "@descryy/ir";
|
|
44
|
+
import type { ReliabilityClass } from "@descryy/ir";
|
|
45
|
+
/** How a change touched a node. §14.2's `DiffType`. */
|
|
46
|
+
export type DiffType = "added" | "modified" | "deleted";
|
|
47
|
+
/**
|
|
48
|
+
* One row of a change's scoped subgraph — §14.2's shape, without persistence.
|
|
49
|
+
*
|
|
50
|
+
* Defined here rather than in a store module so overlap can be computed and
|
|
51
|
+
* tested with no database, the same reason `AdjacencyProvider` exists.
|
|
52
|
+
*/
|
|
53
|
+
export interface PrScopeEntry {
|
|
54
|
+
readonly nodeId: string;
|
|
55
|
+
readonly nodeType: NodeType;
|
|
56
|
+
/** Present when known; used only for reporting, never for identity. */
|
|
57
|
+
readonly file?: string | undefined;
|
|
58
|
+
/** §12.4's impact score, for this change. */
|
|
59
|
+
readonly impactScore: number;
|
|
60
|
+
/** §12.7's tier, for this change. */
|
|
61
|
+
readonly tier: number;
|
|
62
|
+
readonly diffType: DiffType;
|
|
63
|
+
/** The level the graph reached for this node **in this change's run**. */
|
|
64
|
+
readonly resolution: ResolutionLevel;
|
|
65
|
+
}
|
|
66
|
+
export interface PrScope {
|
|
67
|
+
/** Whatever identifies the change — a PR number, a branch, a ref. */
|
|
68
|
+
readonly prId: string;
|
|
69
|
+
/** Part of scope identity: node ids are repo-scoped, so scopes are too. */
|
|
70
|
+
readonly repo: string;
|
|
71
|
+
readonly entries: readonly PrScopeEntry[];
|
|
72
|
+
/** When this scope was computed, for §14.2's staleness handling. */
|
|
73
|
+
readonly computedAt: number;
|
|
74
|
+
/** The head commit of the change itself. Identifies it; never compares it. */
|
|
75
|
+
readonly headSha: string;
|
|
76
|
+
/**
|
|
77
|
+
* The commit **the graph** was at when this scope was computed.
|
|
78
|
+
*
|
|
79
|
+
* Not the same as `headSha`, and the distinction is load-bearing: two scopes
|
|
80
|
+
* are comparable only when this agrees. A single `baseSha` field stood here
|
|
81
|
+
* first, described as "the base commit it was computed against", which is
|
|
82
|
+
* ambiguous between the two and would have let a comparison key on the wrong
|
|
83
|
+
* one. §14.2's column comment is what caught it.
|
|
84
|
+
*/
|
|
85
|
+
readonly graphCommitSha: string;
|
|
86
|
+
}
|
|
87
|
+
/** One node two changes both touch. */
|
|
88
|
+
export interface SharedNode {
|
|
89
|
+
readonly nodeId: string;
|
|
90
|
+
readonly nodeType: NodeType;
|
|
91
|
+
readonly file?: string | undefined;
|
|
92
|
+
/** `max` of the two impact scores — see `impactOfPair`. */
|
|
93
|
+
readonly impact: number;
|
|
94
|
+
readonly weight: number;
|
|
95
|
+
readonly score: number;
|
|
96
|
+
/** How each side touched it. Two deletions and two additions differ. */
|
|
97
|
+
readonly diffTypes: readonly [DiffType, DiffType];
|
|
98
|
+
/** The **weaker** of the two resolutions. */
|
|
99
|
+
readonly resolution: ResolutionLevel;
|
|
100
|
+
readonly reliability: ReliabilityClass;
|
|
101
|
+
/** True when the two sides disagree about how well this node was resolved. */
|
|
102
|
+
readonly resolutionMismatch: boolean;
|
|
103
|
+
}
|
|
104
|
+
export interface OverlapFinding {
|
|
105
|
+
readonly a: string;
|
|
106
|
+
readonly b: string;
|
|
107
|
+
/** Every shared node, including those below the threshold. */
|
|
108
|
+
readonly overlapSize: number;
|
|
109
|
+
/** Shared nodes at or above the threshold, strongest first. */
|
|
110
|
+
readonly surfaced: readonly SharedNode[];
|
|
111
|
+
/** Sum of surfaced scores — see the note on §14.3's formula. */
|
|
112
|
+
readonly conflictScore: number;
|
|
113
|
+
/** The sentence a report must carry with this finding. */
|
|
114
|
+
readonly disclosure: string;
|
|
115
|
+
}
|
|
116
|
+
/** A pair that was never compared, and why. Never silently absent. */
|
|
117
|
+
export interface IncomparablePair {
|
|
118
|
+
readonly a: string;
|
|
119
|
+
readonly b: string;
|
|
120
|
+
readonly reason: "different-graph" | "different-repo";
|
|
121
|
+
}
|
|
122
|
+
export interface OverlapReport {
|
|
123
|
+
readonly findings: readonly OverlapFinding[];
|
|
124
|
+
/** The count before `limit` capped it — P27's disclosed remainder: a structured field, not only a prose note. */
|
|
125
|
+
readonly findingsTotal: number;
|
|
126
|
+
readonly checked: {
|
|
127
|
+
readonly scopes: number;
|
|
128
|
+
readonly pairs: number;
|
|
129
|
+
/** Pairs sharing at least one node, before the threshold. */
|
|
130
|
+
readonly pairsOverlapping: number;
|
|
131
|
+
/** Shared nodes dropped for scoring below the threshold. */
|
|
132
|
+
readonly belowThreshold: number;
|
|
133
|
+
/** Pairs refused before comparison — see `incomparable`. */
|
|
134
|
+
readonly incomparable: number;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Pairs that could not be compared at all.
|
|
138
|
+
*
|
|
139
|
+
* **An incomparable pair and a non-conflicting pair produce the same empty
|
|
140
|
+
* overlap**, so they are separated here rather than left to be inferred from
|
|
141
|
+
* a count that does not distinguish them.
|
|
142
|
+
*/
|
|
143
|
+
readonly incomparable: readonly IncomparablePair[];
|
|
144
|
+
readonly notes: readonly string[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* §14.3's `NodeTypeWeight`. **Tuning values, and labelled as such.**
|
|
148
|
+
*
|
|
149
|
+
* The architecture gives the shape and one worked contrast — a shared utility
|
|
150
|
+
* function scores low, a shared API contract scores high — and fixes no
|
|
151
|
+
* numbers. These are a defensible starting point and are expected to move once
|
|
152
|
+
* there is data. The ordering is the part that is argued:
|
|
153
|
+
*
|
|
154
|
+
* - **Contract surfaces highest.** `API_ENDPOINT` and `API_ROUTE` are the join
|
|
155
|
+
* between two services; two changes to one is the case where a merge compiles
|
|
156
|
+
* and the system breaks.
|
|
157
|
+
* - **Schema next.** A `DATABASE_TABLE` or `DATABASE_COLUMN` touched twice is
|
|
158
|
+
* two migrations against one object, which §14.6 checks separately and which
|
|
159
|
+
* fails at merge rather than at review.
|
|
160
|
+
* - **Types above callables.** Two changes to a `DTO` or `MODEL` shape reach
|
|
161
|
+
* every consumer; two changes to one `FUNCTION` reach its callers.
|
|
162
|
+
* - **`FILE` lowest of the real types.** File-level overlap is what every naive
|
|
163
|
+
* tool already reports and it is the noise this weighting exists to suppress.
|
|
164
|
+
*
|
|
165
|
+
* Exhaustive over the frozen vocabulary on purpose: `Record<NodeType, number>`
|
|
166
|
+
* means adding a node type is a compile error here rather than a silent zero,
|
|
167
|
+
* and a silent zero would make the new type invisible to conflict detection
|
|
168
|
+
* without any test failing.
|
|
169
|
+
*/
|
|
170
|
+
export declare const NODE_TYPE_WEIGHTS: Readonly<Record<NodeType, number>>;
|
|
171
|
+
/** Default score below which a shared node is incidental rather than a conflict. */
|
|
172
|
+
export declare const DEFAULT_CONFLICT_THRESHOLD = 0.3;
|
|
173
|
+
export interface OverlapOptions {
|
|
174
|
+
readonly threshold?: number;
|
|
175
|
+
readonly weights?: Readonly<Record<NodeType, number>>;
|
|
176
|
+
/** Cap on findings, per rule 5. Truncation is reported. */
|
|
177
|
+
readonly limit?: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Overlaps between every pair of in-flight changes.
|
|
181
|
+
*
|
|
182
|
+
* Pure over scopes, so it needs no store and no graph. §14.2's persistence is a
|
|
183
|
+
* layer above this, not a prerequisite for it.
|
|
184
|
+
*/
|
|
185
|
+
export declare function findOverlaps(scopes: readonly PrScope[], options?: OverlapOptions): OverlapReport;
|
|
186
|
+
//# sourceMappingURL=overlap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlap.d.ts","sourceRoot":"","sources":["../../src/multipr/overlap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §14.3 — where two in-flight changes touch the same thing, and whether that
|
|
3
|
+
* matters.
|
|
4
|
+
*
|
|
5
|
+
* ## A shared node alone is not a conflict
|
|
6
|
+
*
|
|
7
|
+
* §14.3 says this outright and it is the whole design. At real team scale many
|
|
8
|
+
* pull requests incidentally touch the same low-importance code — a shared
|
|
9
|
+
* logging helper, a barrel file, a constants module. A tool that reports every
|
|
10
|
+
* shared node reports mostly noise, and a developer who has dismissed six
|
|
11
|
+
* incidental overlaps will dismiss the seventh without reading it.
|
|
12
|
+
*
|
|
13
|
+
* So severity is a product of *what* is shared, not *that* something is:
|
|
14
|
+
*
|
|
15
|
+
* score(node) = impact × NODE_TYPE_WEIGHT[type]
|
|
16
|
+
*
|
|
17
|
+
* A shared `API_ENDPOINT` is two changes to one contract. A shared `FUNCTION`
|
|
18
|
+
* in a utility module is two people editing a file. The weights are the only
|
|
19
|
+
* thing separating them and they are tuning values, labelled as such.
|
|
20
|
+
*
|
|
21
|
+
* ## Resolution is carried per node, and that is §14.2's one addition
|
|
22
|
+
*
|
|
23
|
+
* §14.2 adds `Resolution` to the persisted row with a reason: *so overlap
|
|
24
|
+
* severity is not computed across incomparable evidence*. If one change's node
|
|
25
|
+
* was resolved by a type checker and the other's by syntax alone, an overlap
|
|
26
|
+
* asserted at the stronger level claims more than half of its own evidence
|
|
27
|
+
* supports.
|
|
28
|
+
*
|
|
29
|
+
* The rule follows DEC-058 exactly — a claim carries the resolution of ITS OWN
|
|
30
|
+
* evidence, not of the run that produced it. Here the claim rests on two
|
|
31
|
+
* observations, so it carries **the weaker of the two**, and the finding's
|
|
32
|
+
* reliability class follows from that rather than from either side alone.
|
|
33
|
+
*
|
|
34
|
+
* ## What this deliberately does not do
|
|
35
|
+
*
|
|
36
|
+
* §14.4's mechanical-versus-semantic classification needs the actual diff lines
|
|
37
|
+
* at the shared node, which this module never sees. It reports the overlap and
|
|
38
|
+
* stops; classifying it — and the auto-apply path that is the single documented
|
|
39
|
+
* exception to approval-before-code-change — belongs where the diff is.
|
|
40
|
+
* Guessing "mechanical" without reading the lines would be the one place in
|
|
41
|
+
* this system where a wrong guess edits somebody's branch.
|
|
42
|
+
*/
|
|
43
|
+
import { reliabilityCap, reliabilityNote } from "@descryy/ir";
|
|
44
|
+
/**
|
|
45
|
+
* §14.3's `NodeTypeWeight`. **Tuning values, and labelled as such.**
|
|
46
|
+
*
|
|
47
|
+
* The architecture gives the shape and one worked contrast — a shared utility
|
|
48
|
+
* function scores low, a shared API contract scores high — and fixes no
|
|
49
|
+
* numbers. These are a defensible starting point and are expected to move once
|
|
50
|
+
* there is data. The ordering is the part that is argued:
|
|
51
|
+
*
|
|
52
|
+
* - **Contract surfaces highest.** `API_ENDPOINT` and `API_ROUTE` are the join
|
|
53
|
+
* between two services; two changes to one is the case where a merge compiles
|
|
54
|
+
* and the system breaks.
|
|
55
|
+
* - **Schema next.** A `DATABASE_TABLE` or `DATABASE_COLUMN` touched twice is
|
|
56
|
+
* two migrations against one object, which §14.6 checks separately and which
|
|
57
|
+
* fails at merge rather than at review.
|
|
58
|
+
* - **Types above callables.** Two changes to a `DTO` or `MODEL` shape reach
|
|
59
|
+
* every consumer; two changes to one `FUNCTION` reach its callers.
|
|
60
|
+
* - **`FILE` lowest of the real types.** File-level overlap is what every naive
|
|
61
|
+
* tool already reports and it is the noise this weighting exists to suppress.
|
|
62
|
+
*
|
|
63
|
+
* Exhaustive over the frozen vocabulary on purpose: `Record<NodeType, number>`
|
|
64
|
+
* means adding a node type is a compile error here rather than a silent zero,
|
|
65
|
+
* and a silent zero would make the new type invisible to conflict detection
|
|
66
|
+
* without any test failing.
|
|
67
|
+
*/
|
|
68
|
+
export const NODE_TYPE_WEIGHTS = {
|
|
69
|
+
API_ENDPOINT: 1.0,
|
|
70
|
+
API_ROUTE: 1.0,
|
|
71
|
+
DATABASE_COLUMN: 0.9,
|
|
72
|
+
DATABASE_TABLE: 0.9,
|
|
73
|
+
DTO: 0.8,
|
|
74
|
+
MODEL: 0.8,
|
|
75
|
+
CLASS: 0.6,
|
|
76
|
+
COMPONENT: 0.6,
|
|
77
|
+
PAGE: 0.5,
|
|
78
|
+
MODULE: 0.5,
|
|
79
|
+
FUNCTION: 0.4,
|
|
80
|
+
TEST_CASE: 0.3,
|
|
81
|
+
FILE: 0.2,
|
|
82
|
+
// History artefacts. Two changes cannot both "touch" an incident; if these
|
|
83
|
+
// ever appear in a diff scope something upstream is wrong, and a weight of
|
|
84
|
+
// zero would hide that rather than surface it.
|
|
85
|
+
INCIDENT: 0.1,
|
|
86
|
+
FIX_PATTERN: 0.1,
|
|
87
|
+
};
|
|
88
|
+
/** Default score below which a shared node is incidental rather than a conflict. */
|
|
89
|
+
export const DEFAULT_CONFLICT_THRESHOLD = 0.3;
|
|
90
|
+
/**
|
|
91
|
+
* The impact a shared node carries is the **higher** of the two.
|
|
92
|
+
*
|
|
93
|
+
* A node peripheral to one change and central to the other is a real conflict:
|
|
94
|
+
* somebody is about to have their central concern edited from underneath them.
|
|
95
|
+
* Taking the minimum would let a large blast radius be discounted by a
|
|
96
|
+
* one-line drive-by touching the same node, which is the case most worth
|
|
97
|
+
* catching.
|
|
98
|
+
*/
|
|
99
|
+
function impactOfPair(a, b) {
|
|
100
|
+
return Math.max(a.impactScore, b.impactScore);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Overlaps between every pair of in-flight changes.
|
|
104
|
+
*
|
|
105
|
+
* Pure over scopes, so it needs no store and no graph. §14.2's persistence is a
|
|
106
|
+
* layer above this, not a prerequisite for it.
|
|
107
|
+
*/
|
|
108
|
+
export function findOverlaps(scopes, options = {}) {
|
|
109
|
+
const threshold = options.threshold ?? DEFAULT_CONFLICT_THRESHOLD;
|
|
110
|
+
const weights = options.weights ?? NODE_TYPE_WEIGHTS;
|
|
111
|
+
const limit = options.limit ?? 10;
|
|
112
|
+
const notes = [];
|
|
113
|
+
const incomparable = [];
|
|
114
|
+
let belowThreshold = 0;
|
|
115
|
+
let pairsOverlapping = 0;
|
|
116
|
+
let pairs = 0;
|
|
117
|
+
const byId = scopes.map((s) => ({ scope: s, index: new Map(s.entries.map((e) => [e.nodeId, e])) }));
|
|
118
|
+
const findings = [];
|
|
119
|
+
for (let i = 0; i < byId.length; i += 1) {
|
|
120
|
+
for (let j = i + 1; j < byId.length; j += 1) {
|
|
121
|
+
pairs += 1;
|
|
122
|
+
const left = byId[i];
|
|
123
|
+
const right = byId[j];
|
|
124
|
+
// Refused BEFORE any node is compared. §14.2's schema puts the graph
|
|
125
|
+
// commit in the primary key so two graph states of one PR cannot
|
|
126
|
+
// overwrite each other, and warns that a reader ignoring it "will happily
|
|
127
|
+
// overlap a scope from last week with one from today and report a
|
|
128
|
+
// number". That warning is honoured here rather than in the store,
|
|
129
|
+
// because a caller constructing scopes by hand never touches a store —
|
|
130
|
+
// and because an incomparable pair returns the same empty overlap a
|
|
131
|
+
// genuinely non-conflicting one does.
|
|
132
|
+
const reason = left.scope.repo !== right.scope.repo
|
|
133
|
+
? "different-repo"
|
|
134
|
+
: left.scope.graphCommitSha !== right.scope.graphCommitSha
|
|
135
|
+
? "different-graph"
|
|
136
|
+
: null;
|
|
137
|
+
if (reason !== null) {
|
|
138
|
+
incomparable.push({ a: left.scope.prId, b: right.scope.prId, reason });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const shared = [];
|
|
142
|
+
let overlapSize = 0;
|
|
143
|
+
let mismatches = 0;
|
|
144
|
+
for (const [nodeId, a] of left.index) {
|
|
145
|
+
const b = right.index.get(nodeId);
|
|
146
|
+
if (b === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
overlapSize += 1;
|
|
149
|
+
// DEC-058 at the pair level: the claim rests on two observations, so it
|
|
150
|
+
// carries the weaker. Asserting at the stronger would claim more than
|
|
151
|
+
// half its own evidence supports.
|
|
152
|
+
const resolution = Math.min(a.resolution, b.resolution);
|
|
153
|
+
const resolutionMismatch = a.resolution !== b.resolution;
|
|
154
|
+
if (resolutionMismatch)
|
|
155
|
+
mismatches += 1;
|
|
156
|
+
const impact = impactOfPair(a, b);
|
|
157
|
+
const weight = weights[a.nodeType];
|
|
158
|
+
const score = impact * weight;
|
|
159
|
+
if (score < threshold) {
|
|
160
|
+
belowThreshold += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
shared.push({
|
|
164
|
+
nodeId,
|
|
165
|
+
nodeType: a.nodeType,
|
|
166
|
+
file: a.file ?? b.file,
|
|
167
|
+
impact,
|
|
168
|
+
weight,
|
|
169
|
+
score,
|
|
170
|
+
diffTypes: [a.diffType, b.diffType],
|
|
171
|
+
resolution,
|
|
172
|
+
// An overlap is a claim about which symbols two changes both name —
|
|
173
|
+
// a name-level fact, not a claim about shapes. R2 therefore permits
|
|
174
|
+
// class A here where a shape claim on the same evidence would not.
|
|
175
|
+
reliability: reliabilityCap(resolution, true),
|
|
176
|
+
resolutionMismatch,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (overlapSize > 0)
|
|
180
|
+
pairsOverlapping += 1;
|
|
181
|
+
if (shared.length === 0)
|
|
182
|
+
continue;
|
|
183
|
+
shared.sort((x, y) => y.score - x.score || x.nodeId.localeCompare(y.nodeId));
|
|
184
|
+
const weakest = shared.reduce((min, s) => (s.resolution < min ? s.resolution : min), 4);
|
|
185
|
+
let disclosure = reliabilityNote(weakest, true);
|
|
186
|
+
if (mismatches > 0) {
|
|
187
|
+
disclosure +=
|
|
188
|
+
` ${mismatches} of the ${overlapSize} shared node(s) were resolved to different ` +
|
|
189
|
+
"levels by the two changes; each is reported at the weaker of the two, because an " +
|
|
190
|
+
"overlap asserted at the stronger level would claim more than half its evidence " +
|
|
191
|
+
"supports.";
|
|
192
|
+
}
|
|
193
|
+
findings.push({
|
|
194
|
+
a: left.scope.prId,
|
|
195
|
+
b: right.scope.prId,
|
|
196
|
+
overlapSize,
|
|
197
|
+
surfaced: shared,
|
|
198
|
+
// §14.3 writes `OverlapSize × ImpactScore × NodeTypeWeight`. Read
|
|
199
|
+
// literally that multiplies a pair-level count by two node-level
|
|
200
|
+
// quantities, which lets one shared trivial node scale with the size of
|
|
201
|
+
// an unrelated overlap. Summing the per-node scores keeps the same
|
|
202
|
+
// intent — more shared, and more important, scores higher — and stays
|
|
203
|
+
// monotonic in both. The divergence is deliberate and recorded rather
|
|
204
|
+
// than silently "interpreted".
|
|
205
|
+
conflictScore: shared.reduce((sum, s) => sum + s.score, 0),
|
|
206
|
+
disclosure,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
findings.sort((x, y) => y.conflictScore - x.conflictScore || x.a.localeCompare(y.a));
|
|
211
|
+
const kept = findings.slice(0, limit);
|
|
212
|
+
if (findings.length > limit) {
|
|
213
|
+
notes.push(`${findings.length} overlapping pairs scored above the threshold and the ${kept.length} ` +
|
|
214
|
+
"highest are reported.");
|
|
215
|
+
}
|
|
216
|
+
if (belowThreshold > 0) {
|
|
217
|
+
notes.push(`${belowThreshold} shared node(s) scored below the conflict threshold and were not ` +
|
|
218
|
+
"surfaced. A shared node is not a conflict on its own — at team scale many changes " +
|
|
219
|
+
"incidentally touch the same low-importance code, and reporting all of it is how a " +
|
|
220
|
+
"reader learns to dismiss the report.");
|
|
221
|
+
}
|
|
222
|
+
if (scopes.length < 2) {
|
|
223
|
+
notes.push("Fewer than two scopes were supplied, so no pair could be compared. That is not a " +
|
|
224
|
+
"finding that nothing conflicts.");
|
|
225
|
+
}
|
|
226
|
+
const differentGraph = incomparable.filter((p) => p.reason === "different-graph").length;
|
|
227
|
+
if (differentGraph > 0) {
|
|
228
|
+
notes.push(`${differentGraph} pair(s) were computed against different graph states and were not ` +
|
|
229
|
+
"compared. A scope is only meaningful against the graph it was computed on; comparing " +
|
|
230
|
+
"across two answers a different question and returns a plausible answer to it. These " +
|
|
231
|
+
"pairs are neither conflicting nor clear — they are unexamined, and rescoping both " +
|
|
232
|
+
"against one graph is what would settle them.");
|
|
233
|
+
}
|
|
234
|
+
const differentRepo = incomparable.filter((p) => p.reason === "different-repo").length;
|
|
235
|
+
if (differentRepo > 0) {
|
|
236
|
+
notes.push(`${differentRepo} pair(s) belong to different repositories and were not compared. Node ` +
|
|
237
|
+
"identity is repo-scoped, so such a pair shares no node by construction and would " +
|
|
238
|
+
"report as conflict-free rather than as unexamined.");
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
findings: kept,
|
|
242
|
+
findingsTotal: findings.length,
|
|
243
|
+
checked: {
|
|
244
|
+
scopes: scopes.length,
|
|
245
|
+
pairs,
|
|
246
|
+
pairsOverlapping,
|
|
247
|
+
belowThreshold,
|
|
248
|
+
incomparable: incomparable.length,
|
|
249
|
+
},
|
|
250
|
+
incomparable,
|
|
251
|
+
notes,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=overlap.js.map
|