@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,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backing for `refusal_fetch` — `ai-tool-contract.md` §5.4, §8 endpoint 14.
|
|
3
|
+
*
|
|
4
|
+
* ## The premise this corrects
|
|
5
|
+
*
|
|
6
|
+
* `ai-layer-plan.md` §3 originally claimed this tool was cheap because
|
|
7
|
+
* "`askableHandle` exists in core and nothing serves it." Checked against
|
|
8
|
+
* `refusal-questions.ts` rather than taken on the plan's word, both halves
|
|
9
|
+
* were false: `askableHandle` is the *askable predicate* — `blockedBy` only
|
|
10
|
+
* for `refusalClass === "value-unknown"`, `null` for `capability-gap` /
|
|
11
|
+
* `out-of-scope` / `varies-per-call` / unset — and it is already served, by
|
|
12
|
+
* `packages/mcp/src/tools/questions.ts` via `groupRefusalsIntoQuestions`.
|
|
13
|
+
* The contract's own §5.4 records the correction; this module is the new
|
|
14
|
+
* work it points at.
|
|
15
|
+
*
|
|
16
|
+
* ## Why `groupRefusalsIntoQuestions` cannot be reused here
|
|
17
|
+
*
|
|
18
|
+
* That grouper drops every row that is not `value-unknown` **by design** —
|
|
19
|
+
* correct for its own purpose (a question queue only wants rows a human
|
|
20
|
+
* sentence can close), wrong for this one. A `refusal_fetch` that silently
|
|
21
|
+
* returned only the askable subset would be the empty-denominator defect
|
|
22
|
+
* (`ai-tool-contract.md` §1's "count over something that was not there,
|
|
23
|
+
* presented as a count of zero") wearing technique 4's clothes — the same
|
|
24
|
+
* defect class this project has already hit three times elsewhere
|
|
25
|
+
* (shopizer's 0-of-370 route count, airbyte's "not on disk", koa's silent
|
|
26
|
+
* `routers.size === 0`). This module therefore counts and pages **every**
|
|
27
|
+
* `UnresolvedRef`, of every `refusalClass` and unset alike.
|
|
28
|
+
*
|
|
29
|
+
* ## Call-site trap, named so it is not rediscovered the expensive way
|
|
30
|
+
*
|
|
31
|
+
* `store/reader.ts#unresolvedRefs` defaults `classified: true`, which
|
|
32
|
+
* restricts to `attrs IS NOT NULL` — exactly the rows a producer has
|
|
33
|
+
* classified at all. A caller that wires this module up to that reader
|
|
34
|
+
* without passing `{ classified: false }` will silently exclude every
|
|
35
|
+
* *unclassified* row from `count`, reproducing the same defect this module
|
|
36
|
+
* exists to prevent, one call site later. `summariseRefusals`/
|
|
37
|
+
* `fetchRefusalPage` cannot detect that mistake — they only know the
|
|
38
|
+
* population they were handed — so the honesty of `count` here is only as
|
|
39
|
+
* good as the completeness of what a caller passes in.
|
|
40
|
+
*
|
|
41
|
+
* ## The handle
|
|
42
|
+
*
|
|
43
|
+
* Stateless and self-describing (a base64url-encoded cursor plus a content
|
|
44
|
+
* fingerprint of the population it was issued against), not a session-cache
|
|
45
|
+
* lookup — so redemption is correct across process restarts and does not
|
|
46
|
+
* need this module to keep anything in memory between calls. An MCP surface
|
|
47
|
+
* that wants a prettier, shorter token (the contract's own illustrative
|
|
48
|
+
* `ref_9f2c1a`) is free to wrap this in a session cache; that is a
|
|
49
|
+
* presentation choice for P27, not a correctness requirement for this
|
|
50
|
+
* module.
|
|
51
|
+
*
|
|
52
|
+
* `UnresolvedRef` carries no stable id once it has crossed the store
|
|
53
|
+
* boundary (`store/reader.ts#toUnresolvedRef` does not carry the row's
|
|
54
|
+
* content-hash `id` column into the IR shape), so this module derives its
|
|
55
|
+
* own deterministic ordering key from the fields the type does carry. That
|
|
56
|
+
* key is what makes "same handle + same underlying population → same page
|
|
57
|
+
* ordering every time" true regardless of what order the caller's array
|
|
58
|
+
* happens to be in — a concatenation from several producers, or a
|
|
59
|
+
* differently-ordered SQL read, must not change which row lands on which
|
|
60
|
+
* page.
|
|
61
|
+
*
|
|
62
|
+
* Redeeming a handle that cannot be decoded, or whose population fingerprint
|
|
63
|
+
* no longer matches what is being read, is `refused` with a named reason —
|
|
64
|
+
* never a page that happens to be empty, and never a page silently drawn
|
|
65
|
+
* from the wrong cursor position. That is the rule the whole contract rests
|
|
66
|
+
* on (§1: `refused` is a fact about Descry, never a guess dressed as one),
|
|
67
|
+
* and this endpoint is the easiest place in the system to violate it
|
|
68
|
+
* silently.
|
|
69
|
+
*/
|
|
70
|
+
import { createHash } from "node:crypto";
|
|
71
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
72
|
+
const HANDLE_VERSION = 1;
|
|
73
|
+
const HANDLE_PREFIX = "ref_";
|
|
74
|
+
function isHandlePayload(value) {
|
|
75
|
+
if (typeof value !== "object" || value === null)
|
|
76
|
+
return false;
|
|
77
|
+
const v = value;
|
|
78
|
+
return (typeof v["v"] === "number" &&
|
|
79
|
+
typeof v["offset"] === "number" &&
|
|
80
|
+
typeof v["pageSize"] === "number" &&
|
|
81
|
+
typeof v["populationHash"] === "string");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A deterministic ordering key for one refusal row, built from every field
|
|
85
|
+
* `UnresolvedRef` carries except `attrs` — the same fields the store's own
|
|
86
|
+
* `unresolved_refs.id` content hash is built from (`schema.ts`'s own
|
|
87
|
+
* comment: classification is not part of what makes two refusals the same
|
|
88
|
+
* refusal). Restated here rather than imported, because the store's `id`
|
|
89
|
+
* never crosses into the `UnresolvedRef` shape this module receives.
|
|
90
|
+
*/
|
|
91
|
+
function sortKey(ref) {
|
|
92
|
+
return [
|
|
93
|
+
ref.fromNodeId,
|
|
94
|
+
ref.edgeType,
|
|
95
|
+
ref.rawTarget,
|
|
96
|
+
ref.file ?? "",
|
|
97
|
+
ref.line === null ? "" : String(ref.line),
|
|
98
|
+
ref.producedBy,
|
|
99
|
+
ref.reason,
|
|
100
|
+
].join("|");
|
|
101
|
+
}
|
|
102
|
+
function orderedBy(refs) {
|
|
103
|
+
return [...refs].sort((a, b) => {
|
|
104
|
+
const ka = sortKey(a);
|
|
105
|
+
const kb = sortKey(b);
|
|
106
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** A content fingerprint of the whole ordered population — the identity check that turns a
|
|
110
|
+
* graph change between "summarise" and "fetch" into an honest `refused` rather than a wrong page. */
|
|
111
|
+
function populationHash(ordered) {
|
|
112
|
+
const h = createHash("sha256");
|
|
113
|
+
for (const ref of ordered) {
|
|
114
|
+
h.update(sortKey(ref));
|
|
115
|
+
h.update("|");
|
|
116
|
+
}
|
|
117
|
+
return h.digest("hex");
|
|
118
|
+
}
|
|
119
|
+
function encodeHandle(payload) {
|
|
120
|
+
return HANDLE_PREFIX + Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
121
|
+
}
|
|
122
|
+
function decodeHandle(handle) {
|
|
123
|
+
if (!handle.startsWith(HANDLE_PREFIX))
|
|
124
|
+
return null;
|
|
125
|
+
try {
|
|
126
|
+
const json = Buffer.from(handle.slice(HANDLE_PREFIX.length), "base64url").toString("utf8");
|
|
127
|
+
const parsed = JSON.parse(json);
|
|
128
|
+
return isHandlePayload(parsed) ? parsed : null;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Summarise a population of refusal rows into the one-line shape the
|
|
136
|
+
* envelope carries. `refs` must already be the complete population the
|
|
137
|
+
* caller intends to disclose — see the module header's call-site trap for
|
|
138
|
+
* the classic way to under-supply it.
|
|
139
|
+
*/
|
|
140
|
+
export function summariseRefusals(refs, pageSize = DEFAULT_PAGE_SIZE) {
|
|
141
|
+
if (refs.length === 0) {
|
|
142
|
+
return { count: 0, exemplar: null, handle: null };
|
|
143
|
+
}
|
|
144
|
+
const ordered = orderedBy(refs);
|
|
145
|
+
const hash = populationHash(ordered);
|
|
146
|
+
return {
|
|
147
|
+
count: refs.length,
|
|
148
|
+
exemplar: ordered[0].reason,
|
|
149
|
+
handle: encodeHandle({
|
|
150
|
+
v: HANDLE_VERSION,
|
|
151
|
+
offset: 0,
|
|
152
|
+
pageSize: Math.max(1, Math.floor(pageSize)),
|
|
153
|
+
populationHash: hash,
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Redeem a handle against the current population. `refs` need not be
|
|
159
|
+
* pre-sorted or supplied in the same order as the call that minted the
|
|
160
|
+
* handle — ordering is re-derived deterministically every time.
|
|
161
|
+
*
|
|
162
|
+
* Three distinct ways to be `refused`, each named: the handle cannot be
|
|
163
|
+
* decoded at all; the population's content fingerprint no longer matches
|
|
164
|
+
* (the graph moved since the handle was issued); or the cursor position the
|
|
165
|
+
* handle names does not exist in the current population (only reachable via
|
|
166
|
+
* a handle not issued by this module's own chain, since a well-formed
|
|
167
|
+
* `nextHandle` is never offered past the end).
|
|
168
|
+
*/
|
|
169
|
+
export function fetchRefusalPage(refs, handle) {
|
|
170
|
+
const payload = decodeHandle(handle);
|
|
171
|
+
if (payload === null) {
|
|
172
|
+
return {
|
|
173
|
+
state: "refused",
|
|
174
|
+
reason: `refusal_fetch handle "${handle}" cannot be decoded — it was not issued by summariseRefusals/fetchRefusalPage, or it is corrupted`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const ordered = orderedBy(refs);
|
|
178
|
+
const hash = populationHash(ordered);
|
|
179
|
+
if (hash !== payload.populationHash) {
|
|
180
|
+
return {
|
|
181
|
+
state: "refused",
|
|
182
|
+
reason: "the refusal ledger has changed since this handle was issued (rows added, removed or reclassified) — " +
|
|
183
|
+
"re-fetch from a fresh summary rather than trusting this page",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (payload.offset < 0 || payload.offset >= ordered.length) {
|
|
187
|
+
return {
|
|
188
|
+
state: "refused",
|
|
189
|
+
reason: `this handle's cursor position (${payload.offset}) does not exist in a ledger of ${ordered.length} row(s)`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const pageSize = Math.max(1, Math.floor(payload.pageSize));
|
|
193
|
+
const items = ordered.slice(payload.offset, payload.offset + pageSize);
|
|
194
|
+
const nextOffset = payload.offset + items.length;
|
|
195
|
+
const nextHandle = nextOffset < ordered.length
|
|
196
|
+
? encodeHandle({ v: HANDLE_VERSION, offset: nextOffset, pageSize, populationHash: hash })
|
|
197
|
+
: null;
|
|
198
|
+
return { state: "ok", items, total: ordered.length, nextHandle };
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=refusal-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal-fetch.js","sourceRoot":"","sources":["../../src/query/refusal-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grouper — turns classified refusal rows into questions a person can
|
|
3
|
+
* actually answer.
|
|
4
|
+
*
|
|
5
|
+
* DEC-242 gave `UnresolvedRef` a machine-readable handle (`attrs.blockedBy`)
|
|
6
|
+
* and a producer-declared answerability class (`attrs.refusalClass`). Neither
|
|
7
|
+
* one is a question by itself: a ledger of 136 rows is still 136 rows until
|
|
8
|
+
* something collapses the ones that share a cause into one thing to ask.
|
|
9
|
+
* That collapse is all this module does.
|
|
10
|
+
*
|
|
11
|
+
* ## The filter is the whole point
|
|
12
|
+
*
|
|
13
|
+
* Only `"value-unknown"` rows become questions. `"capability-gap"`,
|
|
14
|
+
* `"out-of-scope"`, `"varies-per-call"` and **unset** never do — DEC-242's own
|
|
15
|
+
* measurement is why: 92 of 95 real C#/Java refusal rows are
|
|
16
|
+
* `"capability-gap"`, and asking a human to resolve one of those, or a
|
|
17
|
+
* `"varies-per-call"` row (*"what is `url`?"*), is a category error, not an
|
|
18
|
+
* unanswered question. Unset is the sharpest trap: it looks identical to
|
|
19
|
+
* `"value-unknown"` in shape (a row, a `rawTarget`, a `reason`) and is
|
|
20
|
+
* silently the majority case until every producer sets the field. Defaulting
|
|
21
|
+
* it to askable is exactly how an unaudited row reaches a user.
|
|
22
|
+
*
|
|
23
|
+
* ## Grouping key is `(repo, blockedBy)`, and this file only ever sees one repo
|
|
24
|
+
*
|
|
25
|
+
* `env.BACKEND_API_BASE_URL` in `sherpa-cf-worker` and the byte-identical
|
|
26
|
+
* string in a different repository are different facts about different
|
|
27
|
+
* runtimes — DEC-223 §3 already committed to exact-match scoped to one repo,
|
|
28
|
+
* and measured the failure mode directly (both repos use the same env var
|
|
29
|
+
* name for different backends). `groupRefusalsIntoQuestions` takes one repo's
|
|
30
|
+
* rows and returns that repo's questions; it has no way to merge across
|
|
31
|
+
* repos because it never sees more than one. `groupRefusalsAcrossRepos` is the
|
|
32
|
+
* only entry point that touches more than one repo's rows, and it keeps them
|
|
33
|
+
* in separate groups by construction — see its own doc for what that means in
|
|
34
|
+
* practice, and the test that proves it.
|
|
35
|
+
*
|
|
36
|
+
* ## What this does not do
|
|
37
|
+
*
|
|
38
|
+
* It does not run config resolution. DEC-242's ordering constraint —
|
|
39
|
+
* `refusalClass` is assigned only after a producer checks `.env` files,
|
|
40
|
+
* deploy config and the like — is the producer's job, upstream of this
|
|
41
|
+
* module entirely. A `"value-unknown"` row reaching here is trusted at face
|
|
42
|
+
* value; second-guessing it here would duplicate work a producer already did
|
|
43
|
+
* or silently paper over one that skipped it, and neither is this module's
|
|
44
|
+
* business to detect.
|
|
45
|
+
*
|
|
46
|
+
* It does not store, dedupe against a previously-answered `ConfirmedFact`
|
|
47
|
+
* (DEC-223 §3), or decide where a question surfaces. It is the piece in
|
|
48
|
+
* between: rows in, questions out, with everything dropped along the way
|
|
49
|
+
* counted rather than discarded.
|
|
50
|
+
*/
|
|
51
|
+
import type { UnresolvedRef } from "@descryy/ir";
|
|
52
|
+
/** One call site behind a question — enough for a reader to go look, not just a count. */
|
|
53
|
+
export interface RefusalSite {
|
|
54
|
+
readonly fromNodeId: string;
|
|
55
|
+
readonly file: string | null;
|
|
56
|
+
readonly line: number | null;
|
|
57
|
+
/** The callee text, unchanged from the row — `fetch`, `api.get`. */
|
|
58
|
+
readonly rawTarget: string;
|
|
59
|
+
}
|
|
60
|
+
/** One question: "what does `blockedBy` evaluate to, in `repo`?" (DEC-223 §2), plus its evidence. */
|
|
61
|
+
export interface RefusalQuestion {
|
|
62
|
+
readonly repo: string;
|
|
63
|
+
readonly blockedBy: string;
|
|
64
|
+
readonly sites: readonly RefusalSite[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What every accepted row not made into a question was instead, one bucket
|
|
68
|
+
* per cause. Never a single "dropped" count — `dropped["unset"]` and
|
|
69
|
+
* `dropped["capability-gap"]` are different facts with different remedies,
|
|
70
|
+
* and collapsing them would recreate the conflation DEC-223's own
|
|
71
|
+
* `query/unresolved.ts` header already measured once.
|
|
72
|
+
*
|
|
73
|
+
* `"value-unknown-no-handle"` is its own bucket rather than folded into
|
|
74
|
+
* `"value-unknown"`: a row can legally declare itself askable while naming no
|
|
75
|
+
* expression to ask about (`attrs.blockedBy` absent or `null`, DEC-242) — a
|
|
76
|
+
* producer contradiction, not the same thing as a row this module correctly
|
|
77
|
+
* turned into a question, and not the same thing as a row a producer
|
|
78
|
+
* correctly marked unanswerable either.
|
|
79
|
+
*/
|
|
80
|
+
export interface RefusalDropCounts {
|
|
81
|
+
readonly [refusalClass: string]: number;
|
|
82
|
+
}
|
|
83
|
+
export interface GroupingResult {
|
|
84
|
+
readonly repo: string;
|
|
85
|
+
readonly totalRows: number;
|
|
86
|
+
readonly questions: readonly RefusalQuestion[];
|
|
87
|
+
readonly dropped: RefusalDropCounts;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The askable predicate, exported so a second consumer (DEC-244's
|
|
91
|
+
* `applyDeclaredValues`) does not re-derive it a third time — `unresolved.ts`
|
|
92
|
+
* and this file already had one independent copy each before DEC-244, and a
|
|
93
|
+
* third divergent copy is exactly the drift DEC-098's own guard-omission
|
|
94
|
+
* incident (§3.1) named. Returns the row's `blockedBy` when, and only when,
|
|
95
|
+
* this row is `"value-unknown"` with a non-empty handle to ask about — `null`
|
|
96
|
+
* for every other case, unset included.
|
|
97
|
+
*/
|
|
98
|
+
export declare function askableHandle(ref: UnresolvedRef): string | null;
|
|
99
|
+
/**
|
|
100
|
+
* Group one repository's refusal rows into questions.
|
|
101
|
+
*
|
|
102
|
+
* The reconciliation this makes checkable: every row in `refs` is accounted
|
|
103
|
+
* for in exactly one place — a site under some question's `sites`, or a count
|
|
104
|
+
* under `dropped` — so `totalRows === sum(dropped) + sum(questions[].sites.length)`
|
|
105
|
+
* always. The DEC-241-shaped law, one level down again.
|
|
106
|
+
*/
|
|
107
|
+
export declare function groupRefusalsIntoQuestions(repo: string, refs: readonly UnresolvedRef[]): GroupingResult;
|
|
108
|
+
/**
|
|
109
|
+
* Group several repositories' rows without ever merging across them.
|
|
110
|
+
*
|
|
111
|
+
* This exists because the risk this module guards against is a *caller's*
|
|
112
|
+
* mistake, not this file's: nothing stops a consumer from concatenating two
|
|
113
|
+
* repos' `unresolved` arrays before calling `groupRefusalsIntoQuestions` once,
|
|
114
|
+
* and a byte-identical `blockedBy` string would then merge two unrelated
|
|
115
|
+
* facts into one question (DEC-223 §3's measured failure). This function is
|
|
116
|
+
* the version of the API where that mistake cannot be made — one
|
|
117
|
+
* `GroupingResult` per repo, always, however many repos come in.
|
|
118
|
+
*/
|
|
119
|
+
export declare function groupRefusalsAcrossRepos(inputs: readonly {
|
|
120
|
+
readonly repo: string;
|
|
121
|
+
readonly refs: readonly UnresolvedRef[];
|
|
122
|
+
}[]): readonly GroupingResult[];
|
|
123
|
+
//# sourceMappingURL=refusal-questions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal-questions.d.ts","sourceRoot":"","sources":["../../src/query/refusal-questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qGAAqG;AACrG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CACxC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAYD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,aAAa,EAAE,GAC7B,cAAc,CA6BhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,EAAE,GACpF,SAAS,cAAc,EAAE,CAE3B"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grouper — turns classified refusal rows into questions a person can
|
|
3
|
+
* actually answer.
|
|
4
|
+
*
|
|
5
|
+
* DEC-242 gave `UnresolvedRef` a machine-readable handle (`attrs.blockedBy`)
|
|
6
|
+
* and a producer-declared answerability class (`attrs.refusalClass`). Neither
|
|
7
|
+
* one is a question by itself: a ledger of 136 rows is still 136 rows until
|
|
8
|
+
* something collapses the ones that share a cause into one thing to ask.
|
|
9
|
+
* That collapse is all this module does.
|
|
10
|
+
*
|
|
11
|
+
* ## The filter is the whole point
|
|
12
|
+
*
|
|
13
|
+
* Only `"value-unknown"` rows become questions. `"capability-gap"`,
|
|
14
|
+
* `"out-of-scope"`, `"varies-per-call"` and **unset** never do — DEC-242's own
|
|
15
|
+
* measurement is why: 92 of 95 real C#/Java refusal rows are
|
|
16
|
+
* `"capability-gap"`, and asking a human to resolve one of those, or a
|
|
17
|
+
* `"varies-per-call"` row (*"what is `url`?"*), is a category error, not an
|
|
18
|
+
* unanswered question. Unset is the sharpest trap: it looks identical to
|
|
19
|
+
* `"value-unknown"` in shape (a row, a `rawTarget`, a `reason`) and is
|
|
20
|
+
* silently the majority case until every producer sets the field. Defaulting
|
|
21
|
+
* it to askable is exactly how an unaudited row reaches a user.
|
|
22
|
+
*
|
|
23
|
+
* ## Grouping key is `(repo, blockedBy)`, and this file only ever sees one repo
|
|
24
|
+
*
|
|
25
|
+
* `env.BACKEND_API_BASE_URL` in `sherpa-cf-worker` and the byte-identical
|
|
26
|
+
* string in a different repository are different facts about different
|
|
27
|
+
* runtimes — DEC-223 §3 already committed to exact-match scoped to one repo,
|
|
28
|
+
* and measured the failure mode directly (both repos use the same env var
|
|
29
|
+
* name for different backends). `groupRefusalsIntoQuestions` takes one repo's
|
|
30
|
+
* rows and returns that repo's questions; it has no way to merge across
|
|
31
|
+
* repos because it never sees more than one. `groupRefusalsAcrossRepos` is the
|
|
32
|
+
* only entry point that touches more than one repo's rows, and it keeps them
|
|
33
|
+
* in separate groups by construction — see its own doc for what that means in
|
|
34
|
+
* practice, and the test that proves it.
|
|
35
|
+
*
|
|
36
|
+
* ## What this does not do
|
|
37
|
+
*
|
|
38
|
+
* It does not run config resolution. DEC-242's ordering constraint —
|
|
39
|
+
* `refusalClass` is assigned only after a producer checks `.env` files,
|
|
40
|
+
* deploy config and the like — is the producer's job, upstream of this
|
|
41
|
+
* module entirely. A `"value-unknown"` row reaching here is trusted at face
|
|
42
|
+
* value; second-guessing it here would duplicate work a producer already did
|
|
43
|
+
* or silently paper over one that skipped it, and neither is this module's
|
|
44
|
+
* business to detect.
|
|
45
|
+
*
|
|
46
|
+
* It does not store, dedupe against a previously-answered `ConfirmedFact`
|
|
47
|
+
* (DEC-223 §3), or decide where a question surfaces. It is the piece in
|
|
48
|
+
* between: rows in, questions out, with everything dropped along the way
|
|
49
|
+
* counted rather than discarded.
|
|
50
|
+
*/
|
|
51
|
+
function classOf(ref) {
|
|
52
|
+
const cls = ref.attrs?.["refusalClass"];
|
|
53
|
+
return typeof cls === "string" ? cls : "unset";
|
|
54
|
+
}
|
|
55
|
+
function handleOf(ref) {
|
|
56
|
+
const blockedBy = ref.attrs?.["blockedBy"];
|
|
57
|
+
return typeof blockedBy === "string" && blockedBy !== "" ? blockedBy : null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The askable predicate, exported so a second consumer (DEC-244's
|
|
61
|
+
* `applyDeclaredValues`) does not re-derive it a third time — `unresolved.ts`
|
|
62
|
+
* and this file already had one independent copy each before DEC-244, and a
|
|
63
|
+
* third divergent copy is exactly the drift DEC-098's own guard-omission
|
|
64
|
+
* incident (§3.1) named. Returns the row's `blockedBy` when, and only when,
|
|
65
|
+
* this row is `"value-unknown"` with a non-empty handle to ask about — `null`
|
|
66
|
+
* for every other case, unset included.
|
|
67
|
+
*/
|
|
68
|
+
export function askableHandle(ref) {
|
|
69
|
+
return classOf(ref) === "value-unknown" ? handleOf(ref) : null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Group one repository's refusal rows into questions.
|
|
73
|
+
*
|
|
74
|
+
* The reconciliation this makes checkable: every row in `refs` is accounted
|
|
75
|
+
* for in exactly one place — a site under some question's `sites`, or a count
|
|
76
|
+
* under `dropped` — so `totalRows === sum(dropped) + sum(questions[].sites.length)`
|
|
77
|
+
* always. The DEC-241-shaped law, one level down again.
|
|
78
|
+
*/
|
|
79
|
+
export function groupRefusalsIntoQuestions(repo, refs) {
|
|
80
|
+
const groups = new Map();
|
|
81
|
+
const dropped = {};
|
|
82
|
+
const drop = (bucket) => {
|
|
83
|
+
dropped[bucket] = (dropped[bucket] ?? 0) + 1;
|
|
84
|
+
};
|
|
85
|
+
for (const ref of refs) {
|
|
86
|
+
const cls = classOf(ref);
|
|
87
|
+
if (cls !== "value-unknown") {
|
|
88
|
+
drop(cls);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const handle = askableHandle(ref);
|
|
92
|
+
if (handle === null) {
|
|
93
|
+
drop("value-unknown-no-handle");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const site = { fromNodeId: ref.fromNodeId, file: ref.file, line: ref.line, rawTarget: ref.rawTarget };
|
|
97
|
+
const existing = groups.get(handle);
|
|
98
|
+
if (existing === undefined)
|
|
99
|
+
groups.set(handle, [site]);
|
|
100
|
+
else
|
|
101
|
+
existing.push(site);
|
|
102
|
+
}
|
|
103
|
+
const questions = [...groups.entries()]
|
|
104
|
+
.map(([blockedBy, sites]) => ({ repo, blockedBy, sites: [...sites] }))
|
|
105
|
+
.sort((a, b) => b.sites.length - a.sites.length || a.blockedBy.localeCompare(b.blockedBy));
|
|
106
|
+
return { repo, totalRows: refs.length, questions, dropped };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Group several repositories' rows without ever merging across them.
|
|
110
|
+
*
|
|
111
|
+
* This exists because the risk this module guards against is a *caller's*
|
|
112
|
+
* mistake, not this file's: nothing stops a consumer from concatenating two
|
|
113
|
+
* repos' `unresolved` arrays before calling `groupRefusalsIntoQuestions` once,
|
|
114
|
+
* and a byte-identical `blockedBy` string would then merge two unrelated
|
|
115
|
+
* facts into one question (DEC-223 §3's measured failure). This function is
|
|
116
|
+
* the version of the API where that mistake cannot be made — one
|
|
117
|
+
* `GroupingResult` per repo, always, however many repos come in.
|
|
118
|
+
*/
|
|
119
|
+
export function groupRefusalsAcrossRepos(inputs) {
|
|
120
|
+
return inputs.map(({ repo, refs }) => groupRefusalsIntoQuestions(repo, refs));
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=refusal-questions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal-questions.js","sourceRoot":"","sources":["../../src/query/refusal-questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AA6CH,SAAS,OAAO,CAAC,GAAkB;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,cAAc,CAAC,CAAC;IACxC,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AACjD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAkB;IAClC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC;IAC3C,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,GAAkB;IAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,IAAY,EACZ,IAA8B;IAE9B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,MAAM,IAAI,GAAG,CAAC,MAAc,EAAQ,EAAE;QACpC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC/C,CAAC,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAChC,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAgB,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;QACnH,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;;YAClD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SACpC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;SACrE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAE7F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAqF;IAErF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,0BAA0B,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAChF,CAAC"}
|