@shrkcrft/boundaries 0.1.0-alpha.30 → 0.1.0-alpha.31
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/dist/baseline/compute-baseline.d.ts +8 -0
- package/dist/baseline/compute-baseline.d.ts.map +1 -1
- package/dist/baseline/compute-baseline.js +8 -7
- package/dist/baseline/diff-baseline.d.ts +28 -0
- package/dist/baseline/diff-baseline.d.ts.map +1 -1
- package/dist/baseline/diff-baseline.js +29 -0
- package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
- package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-finding.js +21 -0
- package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
- package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-kind.js +14 -0
- package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
- package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
- package/dist/evaluate/evaluate-boundaries.js +481 -53
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-unit-finding.js +1 -0
- package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
- package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
- package/dist/evaluate/settle-boundary-rule.js +73 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
- package/dist/extract/code-zones.d.ts +95 -3
- package/dist/extract/code-zones.d.ts.map +1 -1
- package/dist/extract/code-zones.js +313 -4
- package/dist/extract/extract-tokens.d.ts +30 -0
- package/dist/extract/extract-tokens.d.ts.map +1 -1
- package/dist/extract/extract-tokens.js +233 -27
- package/dist/extract/i-labeled-source.d.ts +12 -0
- package/dist/extract/i-labeled-source.d.ts.map +1 -0
- package/dist/extract/i-labeled-source.js +1 -0
- package/dist/extract/import-edges.d.ts +1 -0
- package/dist/extract/import-edges.d.ts.map +1 -1
- package/dist/extract/import-edges.js +20 -7
- package/dist/extract/inspect-source.d.ts +32 -3
- package/dist/extract/inspect-source.d.ts.map +1 -1
- package/dist/extract/inspect-source.js +50 -7
- package/dist/extract/parse-imports.d.ts +34 -12
- package/dist/extract/parse-imports.d.ts.map +1 -1
- package/dist/extract/parse-imports.js +119 -20
- package/dist/extract/scan-literals.d.ts +12 -1
- package/dist/extract/scan-literals.d.ts.map +1 -1
- package/dist/extract/scan-literals.js +15 -1
- package/dist/extract/source-liveness-request.d.ts +24 -0
- package/dist/extract/source-liveness-request.d.ts.map +1 -0
- package/dist/extract/source-liveness-request.js +45 -0
- package/dist/generated/check-provenance.d.ts.map +1 -1
- package/dist/generated/check-provenance.js +3 -1
- package/dist/generated/read-regen-tree.d.ts +16 -0
- package/dist/generated/read-regen-tree.d.ts.map +1 -0
- package/dist/generated/read-regen-tree.js +61 -0
- package/dist/generated/scan-generated.d.ts +8 -0
- package/dist/generated/scan-generated.d.ts.map +1 -1
- package/dist/generated/scan-generated.js +25 -10
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -0
- package/dist/model/boundary-intended-empty-line.d.ts +10 -0
- package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
- package/dist/model/boundary-intended-empty-line.js +11 -0
- package/dist/model/boundary-markable-list.d.ts +15 -0
- package/dist/model/boundary-markable-list.d.ts.map +1 -0
- package/dist/model/boundary-markable-list.js +15 -0
- package/dist/model/boundary-rule-input-keys.d.ts +11 -0
- package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
- package/dist/model/boundary-rule-input-keys.js +29 -0
- package/dist/model/boundary-rule-input.d.ts +23 -0
- package/dist/model/boundary-rule-input.d.ts.map +1 -0
- package/dist/model/boundary-rule-input.js +1 -0
- package/dist/model/boundary-rule-key-problems.d.ts +11 -0
- package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-key-problems.js +36 -0
- package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
- package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-marker-problems.js +94 -0
- package/dist/model/boundary-rule-scope.d.ts +88 -0
- package/dist/model/boundary-rule-scope.d.ts.map +1 -0
- package/dist/model/boundary-rule-scope.js +175 -0
- package/dist/model/boundary-rule.d.ts +120 -5
- package/dist/model/boundary-rule.d.ts.map +1 -1
- package/dist/model/boundary-rule.js +148 -1
- package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
- package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
- package/dist/model/boundary-unit-problem-issue.js +16 -0
- package/dist/model/normalize-boundary-rule.d.ts +21 -0
- package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
- package/dist/model/normalize-boundary-rule.js +62 -0
- package/dist/policy/evaluate-policy.d.ts +80 -3
- package/dist/policy/evaluate-policy.d.ts.map +1 -1
- package/dist/policy/evaluate-policy.js +152 -19
- package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
- package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
- package/dist/policy/i-policy-rule-liveness.js +1 -0
- package/dist/policy/run-policy.d.ts +1 -1
- package/dist/policy/run-policy.d.ts.map +1 -1
- package/dist/policy/run-policy.js +171 -21
- package/dist/registry/load-boundary-rules.d.ts +29 -1
- package/dist/registry/load-boundary-rules.d.ts.map +1 -1
- package/dist/registry/load-boundary-rules.js +38 -8
- package/dist/scan/glob.d.ts +90 -0
- package/dist/scan/glob.d.ts.map +1 -1
- package/dist/scan/glob.js +205 -0
- package/dist/scan/i-glob-unit-measure.d.ts +22 -0
- package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
- package/dist/scan/i-glob-unit-measure.js +1 -0
- package/dist/scan/import-pattern.d.ts +109 -0
- package/dist/scan/import-pattern.d.ts.map +1 -0
- package/dist/scan/import-pattern.js +191 -0
- package/dist/scan/node-builtin-package-names.d.ts +13 -0
- package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
- package/dist/scan/node-builtin-package-names.js +25 -0
- package/dist/scan/scan-imports.d.ts +36 -3
- package/dist/scan/scan-imports.d.ts.map +1 -1
- package/dist/scan/scan-imports.js +114 -45
- package/dist/util/blank-run-hazard-finding.d.ts +34 -0
- package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
- package/dist/util/blank-run-hazard-finding.js +1 -0
- package/dist/util/blank-run-hazard.d.ts +29 -0
- package/dist/util/blank-run-hazard.d.ts.map +1 -0
- package/dist/util/blank-run-hazard.js +450 -0
- package/dist/util/dead-glob-units.d.ts +46 -0
- package/dist/util/dead-glob-units.d.ts.map +1 -0
- package/dist/util/dead-glob-units.js +116 -0
- package/dist/util/glob-list-liveness-input.d.ts +31 -0
- package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
- package/dist/util/glob-list-liveness-input.js +65 -0
- package/dist/util/i-dead-glob-unit.d.ts +24 -0
- package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
- package/dist/util/i-dead-glob-unit.js +1 -0
- package/dist/util/i-glob-list-units.d.ts +25 -0
- package/dist/util/i-glob-list-units.d.ts.map +1 -0
- package/dist/util/i-glob-list-units.js +1 -0
- package/dist/util/i-glob-liveness-list.d.ts +23 -0
- package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-list.js +1 -0
- package/dist/util/i-glob-liveness-request.d.ts +11 -0
- package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-request.js +1 -0
- package/dist/util/i-glob-negation.d.ts +13 -0
- package/dist/util/i-glob-negation.d.ts.map +1 -0
- package/dist/util/i-glob-negation.js +1 -0
- package/dist/util/matched-files.d.ts +22 -0
- package/dist/util/matched-files.d.ts.map +1 -0
- package/dist/util/matched-files.js +1 -0
- package/dist/util/negation-cause.d.ts +15 -0
- package/dist/util/negation-cause.d.ts.map +1 -0
- package/dist/util/negation-cause.js +21 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
- package/dist/util/plane-scan-exclude-dirs.js +22 -0
- package/dist/util/read-glob-list-liveness.d.ts +20 -0
- package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
- package/dist/util/read-glob-list-liveness.js +20 -0
- package/dist/util/read-scope-coverage.d.ts +90 -0
- package/dist/util/read-scope-coverage.d.ts.map +1 -0
- package/dist/util/read-scope-coverage.js +173 -0
- package/dist/util/read-scope.d.ts +14 -0
- package/dist/util/read-scope.d.ts.map +1 -0
- package/dist/util/read-scope.js +1 -0
- package/dist/util/read-selected-files.d.ts +16 -0
- package/dist/util/read-selected-files.d.ts.map +1 -0
- package/dist/util/read-selected-files.js +25 -0
- package/dist/util/settle-glob-lists.d.ts +12 -0
- package/dist/util/settle-glob-lists.d.ts.map +1 -0
- package/dist/util/settle-glob-lists.js +13 -0
- package/dist/util/unread-file-reason.d.ts +26 -0
- package/dist/util/unread-file-reason.d.ts.map +1 -0
- package/dist/util/unread-file-reason.js +26 -0
- package/dist/util/unread-file.d.ts +10 -0
- package/dist/util/unread-file.d.ts.map +1 -0
- package/dist/util/unread-file.js +1 -0
- package/dist/util/walk-files.d.ts +74 -10
- package/dist/util/walk-files.d.ts.map +1 -1
- package/dist/util/walk-files.js +137 -31
- package/dist/wiring/evaluate-wiring.d.ts +79 -5
- package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
- package/dist/wiring/evaluate-wiring.js +267 -12
- package/dist/wiring/explain-wiring.d.ts +49 -5
- package/dist/wiring/explain-wiring.d.ts.map +1 -1
- package/dist/wiring/explain-wiring.js +118 -10
- package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
- package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/i-idiom-role-coverage.js +1 -0
- package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
- package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/i-registration-query-verdict.js +1 -0
- package/dist/wiring/i-registration-roles.d.ts +71 -0
- package/dist/wiring/i-registration-roles.d.ts.map +1 -0
- package/dist/wiring/i-registration-roles.js +1 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/measure-idiom-role-coverage.js +20 -0
- package/dist/wiring/measure-registration-roles.d.ts +25 -0
- package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
- package/dist/wiring/measure-registration-roles.js +124 -0
- package/dist/wiring/plan-wiring-fix.js +2 -2
- package/dist/wiring/registration-graph.d.ts +12 -0
- package/dist/wiring/registration-graph.d.ts.map +1 -1
- package/dist/wiring/registration-graph.js +16 -5
- package/dist/wiring/registration-query-verdict.d.ts +23 -0
- package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/registration-query-verdict.js +118 -0
- package/dist/wiring/registry-query.d.ts +8 -0
- package/dist/wiring/registry-query.d.ts.map +1 -1
- package/dist/wiring/registry-query.js +12 -5
- package/dist/wiring/scan-wiring-files.d.ts +4 -2
- package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
- package/dist/wiring/scan-wiring-files.js +52 -15
- package/dist/wiring/sink-imports.d.ts +11 -0
- package/dist/wiring/sink-imports.d.ts.map +1 -1
- package/dist/wiring/sink-imports.js +11 -2
- package/dist/wiring/trace-literal.d.ts +6 -0
- package/dist/wiring/trace-literal.d.ts.map +1 -1
- package/dist/wiring/trace-literal.js +5 -2
- package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
- package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
- package/dist/wiring/wiring-labeled-sources.js +20 -0
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { type IWiringRule, type IWiringSource } from '@shrkcrft/core';
|
|
1
|
+
import { type ISettledUnitLiveness, type IUnitLiveness, type IUnitStateLists, type IVerdictCoverage, type IWiringRule, type IWiringSource } from '@shrkcrft/core';
|
|
2
2
|
import { type IExtractContext, type IExtractFileEntry, type IExtractedSite } from '../extract/extract-tokens.js';
|
|
3
|
+
import type { IUnreadFile } from '../util/unread-file.js';
|
|
4
|
+
import type { IGlobNegation } from '../util/i-glob-negation.js';
|
|
3
5
|
export declare const WIRING_SCHEMA: "sharkcraft.wiring/v1";
|
|
4
6
|
/** A file made available to the engine. */
|
|
5
7
|
export type IWiringFileEntry = IExtractFileEntry;
|
|
@@ -77,6 +79,43 @@ export interface IWiringRuleResult {
|
|
|
77
79
|
readonly sinkHint?: string;
|
|
78
80
|
/** Per-hop breakdown for a `chain` rule. */
|
|
79
81
|
readonly hops?: readonly IWiringHopResult[];
|
|
82
|
+
/**
|
|
83
|
+
* Registered sites whose membership key the declared side never produced —
|
|
84
|
+
* ONE derivation, two renderings. `parity` reports each as a
|
|
85
|
+
* `registered-missing` violation; `subset` (the default) reports them here,
|
|
86
|
+
* because a `declared ⊆ registered` rule never examined them: the declared
|
|
87
|
+
* selector may be narrower than reality, and the rule would pass by
|
|
88
|
+
* construction. Set for a classic (non-chain) subset/parity rule that ran,
|
|
89
|
+
* sorted by membership key. `explainWiring` reads this rather than
|
|
90
|
+
* recomputing it.
|
|
91
|
+
*/
|
|
92
|
+
readonly registeredOnly?: readonly IWiringTokenSite[];
|
|
93
|
+
/**
|
|
94
|
+
* What the rule examined against what it was asked to (see
|
|
95
|
+
* `IVerdictCoverage`). A subset rule whose registered side holds tokens the
|
|
96
|
+
* declared selector never produced reports them as unexamined `registered
|
|
97
|
+
* tokens`, so the gate envelope marks it `partial` and the verdict is not
|
|
98
|
+
* verified — unless the rule's `registeredExtras` accepts them explicitly.
|
|
99
|
+
* The engine fills it in; it never decides a verdict from it.
|
|
100
|
+
*/
|
|
101
|
+
readonly coverage: IVerdictCoverage;
|
|
102
|
+
/**
|
|
103
|
+
* Glob-matched files on any side of the rule that the one reader did not
|
|
104
|
+
* read (over the read cap, or unreadable). Set only when non-empty.
|
|
105
|
+
* `coverage` names them too: such a rule is never a pass.
|
|
106
|
+
*/
|
|
107
|
+
readonly unread?: readonly IUnreadFile[];
|
|
108
|
+
/**
|
|
109
|
+
* The acceptance of the rule's `expectEmpty` units — settle record B of
|
|
110
|
+
* every source's glob lists (round 13). Folded into the envelope's ONE
|
|
111
|
+
* settle beside {@link coverage}; when the rule is intended-empty it IS
|
|
112
|
+
* {@link coverage}.
|
|
113
|
+
*/
|
|
114
|
+
readonly unitAcceptance?: IVerdictCoverage;
|
|
115
|
+
/** The rule's dead / intended-empty / went-live glob units as printed lines (`unitStateLists`). */
|
|
116
|
+
readonly units?: IUnitStateLists;
|
|
117
|
+
/** The rule's non-live glob units, for `--fail-on-dead-units` (`selectorUnitFails`). */
|
|
118
|
+
readonly unitLiveness?: readonly IUnitLiveness[];
|
|
80
119
|
}
|
|
81
120
|
export interface IWiringReport {
|
|
82
121
|
readonly schema: typeof WIRING_SCHEMA;
|
|
@@ -87,11 +126,22 @@ export interface IWiringReport {
|
|
|
87
126
|
/** Rules that checked nothing, reported loudly instead of as a green pass. */
|
|
88
127
|
readonly skipped: readonly IWiringSkip[];
|
|
89
128
|
/**
|
|
90
|
-
* Count of rules that
|
|
91
|
-
*
|
|
92
|
-
*
|
|
129
|
+
* Count of rules that settled a verdict: ran a comparison, or were
|
|
130
|
+
* misconfigured (counted so their error is not swallowed), or were ACCEPTED
|
|
131
|
+
* as intended-empty (round 13 — every source inclusion glob marked
|
|
132
|
+
* `expectEmpty`, 0 files matched; counted so the verdict path's
|
|
133
|
+
* `evaluated === 0` NOT-VERIFIED guard never reads an accepted plan as
|
|
134
|
+
* "nothing ran"). A rule whose source side matched 0 files or extracted 0 ids
|
|
135
|
+
* WITHOUT that acceptance is NOT evaluated (see {@link skipped}). The printed
|
|
136
|
+
* "N evaluated" excludes the accepted ones: N = `evaluated − acceptedEmpty`.
|
|
93
137
|
*/
|
|
94
138
|
readonly evaluated: number;
|
|
139
|
+
/**
|
|
140
|
+
* Rules accepted as intended-empty (round 13, K6): they examined 0 files, so
|
|
141
|
+
* a renderer prints them apart — `N evaluated, M accepted as
|
|
142
|
+
* intended-empty` — never inside the evaluated count.
|
|
143
|
+
*/
|
|
144
|
+
readonly acceptedEmpty: number;
|
|
95
145
|
readonly verdict: 'pass' | 'errors' | 'warnings';
|
|
96
146
|
}
|
|
97
147
|
/** Resolves a rule-side's globs to the concrete files (path + content) to scan. */
|
|
@@ -117,6 +167,7 @@ export declare function wiringGlobsOf(rule: IWiringRule): string[];
|
|
|
117
167
|
export declare function collectSourceSites(source: IWiringSource, files: readonly IWiringFileEntry[], context?: IExtractContext): {
|
|
118
168
|
sites: readonly IWiringTokenSite[];
|
|
119
169
|
error?: string;
|
|
170
|
+
blankedChars?: number;
|
|
120
171
|
};
|
|
121
172
|
/** Structural validation of a whole rule (both forms), independent of the tree. */
|
|
122
173
|
export declare function validateWiringRule(rule: IWiringRule): string | undefined;
|
|
@@ -134,6 +185,29 @@ export declare function validateWiringRule(rule: IWiringRule): string | undefine
|
|
|
134
185
|
*
|
|
135
186
|
* The `resolve` callback supplies the files for a given rule-side (injected so
|
|
136
187
|
* the engine stays pure / testable — see `runWiring` for the fs-backed wiring).
|
|
188
|
+
*
|
|
189
|
+
* `unreadFor` supplies the files a side's globs matched that the one reader
|
|
190
|
+
* did NOT read. A rule with any in its scope has its coverage replaced by the
|
|
191
|
+
* file record (`readScopeCoverage`), so it is never a pass. When the unread
|
|
192
|
+
* file is on the source side and is why the rule compared nothing, the rule is
|
|
193
|
+
* PARTIAL rather than a (failOnEmpty) skip: it matched a file it could not read.
|
|
194
|
+
*
|
|
195
|
+
* `emptiedBy` names the negations that excluded EVERY file a source's
|
|
196
|
+
* inclusion globs matched (`undefined` when the list was not emptied that
|
|
197
|
+
* way). A source side emptied by its own `!` entries is skipped with THE
|
|
198
|
+
* negation-aware reason (`matched nothing after its own negations: …`), never
|
|
199
|
+
* "0 files matched the source globs" — a file matched, then was excluded.
|
|
200
|
+
*/
|
|
201
|
+
export declare function evaluateWiring(rules: readonly IWiringRule[], resolve: WiringFileResolver, context?: IExtractContext, unreadFor?: (source: IWiringSource) => readonly IUnreadFile[], emptiedBy?: (source: IWiringSource) => readonly IGlobNegation[] | undefined,
|
|
202
|
+
/**
|
|
203
|
+
* Every source's glob units settled with their `expectEmpty` markers
|
|
204
|
+
* (round 13) — `runWiring` supplies it (`sourceLivenessRequest` over the
|
|
205
|
+
* engine's side labels, `settleGlobLists`). A rule whose source side matched
|
|
206
|
+
* nothing is decided from it by THE rule-emptiness settle
|
|
207
|
+
* (`settleRuleEmptiness`): every inclusion glob of the source side
|
|
208
|
+
* intended-empty and no file matched → accepted, never a skip. Its
|
|
209
|
+
* acceptance and non-live units ride on the rule result. Absent: no unit is
|
|
210
|
+
* marked, so an empty rule is a loud skip exactly as before.
|
|
137
211
|
*/
|
|
138
|
-
|
|
212
|
+
ruleLivenessOf?: (rule: IWiringRule) => ISettledUnitLiveness): IWiringReport;
|
|
139
213
|
//# sourceMappingURL=evaluate-wiring.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,8BAA8B,CAAC;AAOtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGhE,eAAO,MAAM,aAAa,EAAG,sBAA+B,CAAC;AAE7D,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAEjD,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,GAAG,oBAAoB,GAAG,SAAS,CAAC;IAC3E,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,uFAAuF;IACvF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACjD,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtD;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAC3C,mGAAmG;IACnG,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,wFAAwF;IACxF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACjD,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC;;;;;;;;;OASG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;CAClD;AAED,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,SAAS,gBAAgB,EAAE,CAAC;AAExF,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,SAAS,aAAa,EAAE,CAG1F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,GAAG,SAAS,CAG7E;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,aAAa,EAAE,CAM3E;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAEzD;AAmBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,SAAS,gBAAgB,EAAE,EAClC,OAAO,GAAE,eAAoB,GAC5B;IAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAO/E;AAwHD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CA+BxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,eAAoB,EAC7B,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,SAAS,WAAW,EAAE,EAC7D,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,SAAS,aAAa,EAAE,GAAG,SAAS;AAC3E;;;;;;;;;GASG;AACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,oBAAoB,GAC3D,aAAa,CA0Wf"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { failsWhenEmpty, validateWiringSource, } from '@shrkcrft/core';
|
|
1
|
+
import { failsWhenEmpty, normalizeWiringRule, RuleEmptiness, settleRuleEmptiness, unitStateLists, UnitLivenessState, validateWiringSource, } from '@shrkcrft/core';
|
|
2
|
+
import { settleGlobLists } from "../util/settle-glob-lists.js";
|
|
2
3
|
import { extractTokens, } from "../extract/extract-tokens.js";
|
|
4
|
+
import { describeUnread, mergeReadScopes, readScopeCoverage, readScopeHasUnread, } from "../util/read-scope-coverage.js";
|
|
5
|
+
import { emptiedByNegationsReason } from "../util/negation-cause.js";
|
|
3
6
|
export const WIRING_SCHEMA = 'sharkcraft.wiring/v1';
|
|
4
7
|
/** Normalize the `registered` field (single source or union array) to an array. */
|
|
5
8
|
export function registeredSources(reg) {
|
|
@@ -56,7 +59,12 @@ function hopPairs(rule) {
|
|
|
56
59
|
*/
|
|
57
60
|
export function collectSourceSites(source, files, context = {}) {
|
|
58
61
|
const res = extractTokens(source, files, context);
|
|
59
|
-
|
|
62
|
+
if (res.error)
|
|
63
|
+
return { sites: [], error: res.error };
|
|
64
|
+
return {
|
|
65
|
+
sites: res.sites,
|
|
66
|
+
...(res.blankedChars !== undefined ? { blankedChars: res.blankedChars } : {}),
|
|
67
|
+
};
|
|
60
68
|
}
|
|
61
69
|
/** Group key for a token site under `groupBy` (dir = dirname; package = first two segments). */
|
|
62
70
|
function groupKeyOf(path, groupBy) {
|
|
@@ -88,6 +96,67 @@ function renderMessage(template, rule, site) {
|
|
|
88
96
|
.replace(/\{line\}/g, String(site.line))
|
|
89
97
|
.replace(/\{rule\}/g, rule.id);
|
|
90
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Registered sites whose membership key the declared side never produced,
|
|
101
|
+
* sorted by key. The ONE derivation behind both parity's `registered-missing`
|
|
102
|
+
* violations and a subset rule's coverage shortfall — two renderings of one set
|
|
103
|
+
* difference, so they cannot disagree.
|
|
104
|
+
*/
|
|
105
|
+
function registeredNotDeclared(fromKeys, unionKeys) {
|
|
106
|
+
return [...unionKeys.keys()]
|
|
107
|
+
.sort()
|
|
108
|
+
.filter((k) => !fromKeys.has(k))
|
|
109
|
+
.map((k) => unionKeys.get(k));
|
|
110
|
+
}
|
|
111
|
+
/** At most this many unexamined labels ride on one coverage record. */
|
|
112
|
+
const COVERAGE_LABEL_CAP = 20;
|
|
113
|
+
/**
|
|
114
|
+
* What one wiring rule examined, for the verdict's coverage guard.
|
|
115
|
+
*
|
|
116
|
+
* Only a classic SUBSET rule can pass over a scope it never examined: its
|
|
117
|
+
* relation is `declared ⊆ registered`, so a registered member the declared
|
|
118
|
+
* selector never produced is never looked at, and the rule weakens silently
|
|
119
|
+
* every time the tree grows past the selector. For it the unit is `registered
|
|
120
|
+
* tokens`; `registeredExtras` (literal ids, or `'allow'`) accepts known extras
|
|
121
|
+
* explicitly. Every other shape examines each declared token it extracted, so
|
|
122
|
+
* its unit is `declared tokens`. A skipped or misconfigured rule examined
|
|
123
|
+
* nothing.
|
|
124
|
+
*/
|
|
125
|
+
function wiringCoverage(rule, outcome) {
|
|
126
|
+
if (outcome.kind === 'error') {
|
|
127
|
+
return { unit: 'declared tokens', expected: 0, examined: 0, reason: 'the rule is misconfigured' };
|
|
128
|
+
}
|
|
129
|
+
if (outcome.kind === 'skipped') {
|
|
130
|
+
return { unit: 'declared tokens', expected: 0, examined: 0, reason: outcome.reason };
|
|
131
|
+
}
|
|
132
|
+
const only = outcome.registeredOnly;
|
|
133
|
+
const subset = rule.mode === undefined || rule.mode === 'subset';
|
|
134
|
+
if (only === undefined || !subset || outcome.registeredCount === 0) {
|
|
135
|
+
return { unit: 'declared tokens', expected: outcome.declaredCount, examined: outcome.declaredCount };
|
|
136
|
+
}
|
|
137
|
+
const extras = rule.registeredExtras;
|
|
138
|
+
const listed = new Set(extras === 'allow' ? [] : (extras ?? []));
|
|
139
|
+
const accepted = new Set(only.filter((s) => extras === 'allow' || listed.has(s.token)));
|
|
140
|
+
const unaccepted = only.filter((s) => !accepted.has(s));
|
|
141
|
+
const named = unaccepted.length > 0 ? unaccepted : [...accepted];
|
|
142
|
+
return {
|
|
143
|
+
unit: 'registered tokens',
|
|
144
|
+
expected: outcome.registeredCount,
|
|
145
|
+
examined: outcome.registeredCount - only.length,
|
|
146
|
+
...(named.length > 0
|
|
147
|
+
? {
|
|
148
|
+
unexamined: named.slice(0, COVERAGE_LABEL_CAP).map((s) => s.token),
|
|
149
|
+
unexaminedTotal: named.length,
|
|
150
|
+
reason: unaccepted.length > 0 && accepted.size > 0
|
|
151
|
+
? `registered with no declared site this selector produces (${accepted.size} more accepted by registeredExtras)`
|
|
152
|
+
: 'registered with no declared site this selector produces',
|
|
153
|
+
}
|
|
154
|
+
: {}),
|
|
155
|
+
...(unaccepted.length === 0 && accepted.size > 0
|
|
156
|
+
? { acceptedBy: extras === 'allow' ? "registeredExtras: 'allow'" : 'registeredExtras' }
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
91
160
|
/** Structural validation of a whole rule (both forms), independent of the tree. */
|
|
92
161
|
export function validateWiringRule(rule) {
|
|
93
162
|
const hasChain = Array.isArray(rule.chain) && rule.chain.length > 0;
|
|
@@ -95,6 +164,10 @@ export function validateWiringRule(rule) {
|
|
|
95
164
|
if (hasChain && hasClassic) {
|
|
96
165
|
return '`chain` is mutually exclusive with `declared`/`registered`';
|
|
97
166
|
}
|
|
167
|
+
if (rule.registeredExtras !== undefined &&
|
|
168
|
+
(hasChain || (rule.mode !== undefined && rule.mode !== 'subset'))) {
|
|
169
|
+
return '`registeredExtras` applies only to a classic subset rule (parity reports registered-only tokens as violations; disjoint / chain rules never examine them)';
|
|
170
|
+
}
|
|
98
171
|
if (hasChain) {
|
|
99
172
|
if (rule.chain.length < 2)
|
|
100
173
|
return '`chain` needs at least 2 hops';
|
|
@@ -132,17 +205,69 @@ export function validateWiringRule(rule) {
|
|
|
132
205
|
*
|
|
133
206
|
* The `resolve` callback supplies the files for a given rule-side (injected so
|
|
134
207
|
* the engine stays pure / testable — see `runWiring` for the fs-backed wiring).
|
|
208
|
+
*
|
|
209
|
+
* `unreadFor` supplies the files a side's globs matched that the one reader
|
|
210
|
+
* did NOT read. A rule with any in its scope has its coverage replaced by the
|
|
211
|
+
* file record (`readScopeCoverage`), so it is never a pass. When the unread
|
|
212
|
+
* file is on the source side and is why the rule compared nothing, the rule is
|
|
213
|
+
* PARTIAL rather than a (failOnEmpty) skip: it matched a file it could not read.
|
|
214
|
+
*
|
|
215
|
+
* `emptiedBy` names the negations that excluded EVERY file a source's
|
|
216
|
+
* inclusion globs matched (`undefined` when the list was not emptied that
|
|
217
|
+
* way). A source side emptied by its own `!` entries is skipped with THE
|
|
218
|
+
* negation-aware reason (`matched nothing after its own negations: …`), never
|
|
219
|
+
* "0 files matched the source globs" — a file matched, then was excluded.
|
|
135
220
|
*/
|
|
136
|
-
export function evaluateWiring(rules, resolve, context = {}
|
|
221
|
+
export function evaluateWiring(rules, resolve, context = {}, unreadFor, emptiedBy,
|
|
222
|
+
/**
|
|
223
|
+
* Every source's glob units settled with their `expectEmpty` markers
|
|
224
|
+
* (round 13) — `runWiring` supplies it (`sourceLivenessRequest` over the
|
|
225
|
+
* engine's side labels, `settleGlobLists`). A rule whose source side matched
|
|
226
|
+
* nothing is decided from it by THE rule-emptiness settle
|
|
227
|
+
* (`settleRuleEmptiness`): every inclusion glob of the source side
|
|
228
|
+
* intended-empty and no file matched → accepted, never a skip. Its
|
|
229
|
+
* acceptance and non-live units ride on the rule result. Absent: no unit is
|
|
230
|
+
* marked, so an empty rule is a loud skip exactly as before.
|
|
231
|
+
*/
|
|
232
|
+
ruleLivenessOf) {
|
|
137
233
|
const ruleResults = [];
|
|
138
234
|
const all = [];
|
|
139
235
|
const diagnostics = [];
|
|
140
236
|
const skipped = [];
|
|
141
237
|
let evaluated = 0;
|
|
238
|
+
let acceptedEmpty = 0;
|
|
142
239
|
let misconfigError = false;
|
|
143
240
|
let misconfigWarn = false;
|
|
144
|
-
for (const
|
|
145
|
-
const severity =
|
|
241
|
+
for (const authored of rules) {
|
|
242
|
+
const severity = authored.severity ?? 'error';
|
|
243
|
+
// The engine entry normalises idempotently (round 13): a loaded rule comes
|
|
244
|
+
// back equal; a hand-built `{ pattern, expectEmpty }` entry is a glob plus
|
|
245
|
+
// a marker; a malformed entry is a misconfigured rule — never a crash.
|
|
246
|
+
const normalized = normalizeWiringRule(authored);
|
|
247
|
+
if (!normalized.ok) {
|
|
248
|
+
const msg = `rule "${authored.id}": ${normalized.error.message}`;
|
|
249
|
+
diagnostics.push(msg);
|
|
250
|
+
if (severity === 'error')
|
|
251
|
+
misconfigError = true;
|
|
252
|
+
else
|
|
253
|
+
misconfigWarn = true;
|
|
254
|
+
ruleResults.push({
|
|
255
|
+
ruleId: authored.id,
|
|
256
|
+
...(authored.description ? { description: authored.description } : {}),
|
|
257
|
+
severity,
|
|
258
|
+
status: 'error',
|
|
259
|
+
declaredCount: 0,
|
|
260
|
+
registeredCount: 0,
|
|
261
|
+
declaredFiles: 0,
|
|
262
|
+
registeredFiles: 0,
|
|
263
|
+
violations: [],
|
|
264
|
+
error: msg,
|
|
265
|
+
coverage: wiringCoverage(authored, { kind: 'error' }),
|
|
266
|
+
});
|
|
267
|
+
evaluated += 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const rule = normalized.value;
|
|
146
271
|
const groupBy = rule.groupBy;
|
|
147
272
|
// Validate defensively — a misconfigured rule becomes a diagnostic, never a
|
|
148
273
|
// thrown exception that would crash the gate.
|
|
@@ -165,12 +290,26 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
165
290
|
registeredFiles: 0,
|
|
166
291
|
violations: [],
|
|
167
292
|
error: msg,
|
|
293
|
+
coverage: wiringCoverage(rule, { kind: 'error' }),
|
|
168
294
|
});
|
|
169
295
|
// A misconfigured rule attempted to run — count it so its error isn't
|
|
170
296
|
// swallowed by the gate's `evaluated === 0` skip path.
|
|
171
297
|
evaluated += 1;
|
|
172
298
|
continue;
|
|
173
299
|
}
|
|
300
|
+
// The rule's glob units and their `expectEmpty` markers (round 13): the
|
|
301
|
+
// acceptance, the printed unit lines and the non-live units ride on every
|
|
302
|
+
// result below, so `check wiring` accepts a planned glob the way `gates
|
|
303
|
+
// coverage` does.
|
|
304
|
+
const liveness = ruleLivenessOf?.(rule);
|
|
305
|
+
const nonLive = liveness?.units.filter((u) => u.state !== UnitLivenessState.Live) ?? [];
|
|
306
|
+
const unitFields = {
|
|
307
|
+
...(liveness?.acceptance !== undefined ? { unitAcceptance: liveness.acceptance } : {}),
|
|
308
|
+
...(liveness !== undefined && liveness.dead.length + liveness.intendedEmpty.length + liveness.wentLive.length > 0
|
|
309
|
+
? { units: unitStateLists(liveness) }
|
|
310
|
+
: {}),
|
|
311
|
+
...(nonLive.length > 0 ? { unitLiveness: nonLive } : {}),
|
|
312
|
+
};
|
|
174
313
|
const pairs = hopPairs(rule);
|
|
175
314
|
const violations = [];
|
|
176
315
|
const hops = [];
|
|
@@ -180,15 +319,26 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
180
319
|
let sinkCount = 0;
|
|
181
320
|
let emptySink = false;
|
|
182
321
|
let sinkHint;
|
|
322
|
+
// Registered sites no declared site produced — classic (one-hop) rules only.
|
|
323
|
+
let registeredOnly;
|
|
324
|
+
// Every file any side READ (for the read scope), and the last hop's sinks'
|
|
325
|
+
// unread files (to explain an empty sink that was simply never read).
|
|
326
|
+
const readPaths = new Set();
|
|
327
|
+
let sinkUnread = [];
|
|
183
328
|
for (const [hopIndex, pair] of pairs.entries()) {
|
|
184
329
|
const fromFiles = resolve(pair.from);
|
|
330
|
+
for (const f of fromFiles)
|
|
331
|
+
readPaths.add(f.path);
|
|
185
332
|
const fromSites = extractTokens(pair.from, fromFiles, context).sites;
|
|
186
333
|
const fromKeys = firstSites(fromSites, groupBy);
|
|
187
334
|
// Each sink kept separate so `intersection` can require membership in ALL.
|
|
188
335
|
const sinkKeySets = [];
|
|
189
336
|
let hopSinkFiles = 0;
|
|
337
|
+
sinkUnread = unreadFor ? pair.to.flatMap((s) => unreadFor(s)) : [];
|
|
190
338
|
for (const sink of pair.to) {
|
|
191
339
|
const files = resolve(sink);
|
|
340
|
+
for (const f of files)
|
|
341
|
+
readPaths.add(f.path);
|
|
192
342
|
hopSinkFiles += files.length;
|
|
193
343
|
const sinkRes = extractTokens(sink, files, context);
|
|
194
344
|
if (sinkRes.hint && sinkHint === undefined)
|
|
@@ -252,12 +402,16 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
252
402
|
...(declaredHint ? { hint: declaredHint } : {}),
|
|
253
403
|
});
|
|
254
404
|
}
|
|
405
|
+
// ONE set difference, two renderings: parity FAILS on each registered
|
|
406
|
+
// token no declared site produced; subset records them as unexamined
|
|
407
|
+
// (its coverage shortfall). Only a classic one-hop rule carries the
|
|
408
|
+
// set — a chain hop keeps the declared-token view.
|
|
409
|
+
const regOnly = registeredNotDeclared(fromKeys, unionKeys);
|
|
410
|
+
if (pairs.length === 1)
|
|
411
|
+
registeredOnly = regOnly;
|
|
255
412
|
if (rule.mode === 'parity') {
|
|
256
413
|
const registeredHint = rule.hintRegisteredMissing ?? rule.hint;
|
|
257
|
-
for (const
|
|
258
|
-
if (fromKeys.has(k))
|
|
259
|
-
continue;
|
|
260
|
-
const site = unionKeys.get(k);
|
|
414
|
+
for (const site of regOnly) {
|
|
261
415
|
violations.push({
|
|
262
416
|
ruleId: rule.id,
|
|
263
417
|
token: site.token,
|
|
@@ -273,16 +427,104 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
273
427
|
}
|
|
274
428
|
hops.push({ index: hopIndex, fromCount: fromKeys.size, toCount: unionKeys.size, missing });
|
|
275
429
|
}
|
|
430
|
+
// What the rule's walk covered: every file any side read, and every file
|
|
431
|
+
// any side's globs matched that the reader did not read.
|
|
432
|
+
const scope = unreadFor
|
|
433
|
+
? mergeReadScopes(readPaths.size, wiringSourcesOf(rule).map((s) => unreadFor(s)))
|
|
434
|
+
: undefined;
|
|
435
|
+
const unreadField = scope !== undefined && scope.unread.length > 0 ? { unread: scope.unread } : {};
|
|
436
|
+
// An empty sink whose files were never READ is not a stale glob — say so,
|
|
437
|
+
// or the reader is sent to fix a selector that is fine.
|
|
438
|
+
if (emptySink && sinkUnread.length > 0) {
|
|
439
|
+
sinkHint = `the registered side ${describeUnread(sinkUnread)} was never read, so a token registered there cannot be seen`;
|
|
440
|
+
}
|
|
276
441
|
// Loud skip: the SOURCE side checked nothing. Deliberately NOT triggered by
|
|
277
442
|
// an empty sink — that is a real failure, flagged via `emptySink` instead.
|
|
443
|
+
// A source its OWN negations emptied is worded as such (round 12 review,
|
|
444
|
+
// R12-DOC-2), exactly as `gates coverage` and `policy-lint` word it.
|
|
445
|
+
const firstSource = pairs[0]?.from;
|
|
446
|
+
const emptiedByOwn = sourceFiles === 0 && firstSource ? emptiedBy?.(firstSource) : undefined;
|
|
278
447
|
const skipReason = sourceFiles === 0
|
|
279
|
-
?
|
|
448
|
+
? emptiedByOwn !== undefined && emptiedByOwn.length > 0
|
|
449
|
+
? emptiedByNegationsReason(emptiedByOwn)
|
|
450
|
+
: '0 files matched the source globs'
|
|
280
451
|
: sourceCount === 0
|
|
281
452
|
? '0 ids extracted from the source side'
|
|
282
453
|
: undefined;
|
|
454
|
+
const sourceSide = pairs[0]?.from;
|
|
455
|
+
const sourceUnread = sourceSide && unreadFor ? unreadFor(sourceSide) : [];
|
|
456
|
+
if (skipReason !== undefined && readScopeHasUnread({ read: sourceFiles, unread: sourceUnread })) {
|
|
457
|
+
// The source side's zero comes from a file the reader could not read,
|
|
458
|
+
// not from a stale selector: PARTIAL (its coverage names the file),
|
|
459
|
+
// never failOnEmpty's failure and never a pass. Nothing was compared,
|
|
460
|
+
// so no violation derived from the incomplete side is reported.
|
|
461
|
+
ruleResults.push({
|
|
462
|
+
ruleId: rule.id,
|
|
463
|
+
...(rule.description ? { description: rule.description } : {}),
|
|
464
|
+
severity,
|
|
465
|
+
status: 'passed',
|
|
466
|
+
declaredCount: sourceCount,
|
|
467
|
+
registeredCount: sinkCount,
|
|
468
|
+
declaredFiles: sourceFiles,
|
|
469
|
+
registeredFiles: sinkFiles,
|
|
470
|
+
violations: [],
|
|
471
|
+
...(pairs.length > 1 ? { hops } : {}),
|
|
472
|
+
...unreadField,
|
|
473
|
+
...unitFields,
|
|
474
|
+
coverage: readScopeCoverage(wiringCoverage(rule, { kind: 'skipped', reason: skipReason }), scope),
|
|
475
|
+
});
|
|
476
|
+
evaluated += 1;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
283
479
|
if (skipReason !== undefined) {
|
|
284
|
-
|
|
285
|
-
|
|
480
|
+
// THE rule-emptiness settle (round 13, `settleRuleEmptiness`): every
|
|
481
|
+
// inclusion glob of the SOURCE side marked `expectEmpty` and no file
|
|
482
|
+
// matched → the empty result is the intended one, accepted and printed.
|
|
483
|
+
// Anything else is the loud skip it always was (failOnEmpty's 1, else 2):
|
|
484
|
+
// `0 ids extracted` from live files is never assertable.
|
|
485
|
+
const primaryLabel = (rule.chain?.length ?? 0) > 0 ? 'chain[0]' : 'declared';
|
|
486
|
+
const emptied = emptiedByOwn !== undefined && emptiedByOwn.length > 0;
|
|
487
|
+
const emptiness = settleRuleEmptiness({
|
|
488
|
+
subject: rule.id,
|
|
489
|
+
unitLabel: 'declared tokens',
|
|
490
|
+
filesMatched: sourceFiles,
|
|
491
|
+
unitsMatched: sourceCount,
|
|
492
|
+
unread: false,
|
|
493
|
+
emptiedByNegations: emptied,
|
|
494
|
+
...(emptied ? { emptiedReason: skipReason } : {}),
|
|
495
|
+
liveness: liveness ?? settleGlobLists({ subject: rule.id, lists: [], marks: [] }),
|
|
496
|
+
primaryLists: [`${primaryLabel}.files`],
|
|
497
|
+
failOnEmpty: failsWhenEmpty(rule),
|
|
498
|
+
noFilesReason: '0 files matched the source globs',
|
|
499
|
+
noUnitsReason: '0 ids extracted from the source side',
|
|
500
|
+
});
|
|
501
|
+
if (emptiness.state === RuleEmptiness.IntendedEmpty && emptiness.coverage !== undefined) {
|
|
502
|
+
ruleResults.push({
|
|
503
|
+
ruleId: rule.id,
|
|
504
|
+
...(rule.description ? { description: rule.description } : {}),
|
|
505
|
+
severity,
|
|
506
|
+
status: 'passed',
|
|
507
|
+
declaredCount: sourceCount,
|
|
508
|
+
registeredCount: sinkCount,
|
|
509
|
+
declaredFiles: sourceFiles,
|
|
510
|
+
registeredFiles: sinkFiles,
|
|
511
|
+
violations: [],
|
|
512
|
+
...(pairs.length > 1 ? { hops } : {}),
|
|
513
|
+
...unreadField,
|
|
514
|
+
...unitFields,
|
|
515
|
+
// The acceptance IS the rule's coverage (the same record, folded once).
|
|
516
|
+
coverage: emptiness.coverage,
|
|
517
|
+
});
|
|
518
|
+
// Counted in `evaluated` (the verdict path's "nothing ran" guard must
|
|
519
|
+
// never read an accepted plan as a skip) AND in `acceptedEmpty`, which
|
|
520
|
+
// a renderer subtracts: `N evaluated, M accepted as intended-empty`.
|
|
521
|
+
evaluated += 1;
|
|
522
|
+
acceptedEmpty += 1;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const reason = emptiness.skipReason ?? skipReason;
|
|
526
|
+
const failed = emptiness.fails;
|
|
527
|
+
skipped.push({ ruleId: rule.id, reason, failed, severity });
|
|
286
528
|
if (failed) {
|
|
287
529
|
if (severity === 'error')
|
|
288
530
|
misconfigError = true;
|
|
@@ -300,6 +542,9 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
300
542
|
registeredFiles: sinkFiles,
|
|
301
543
|
violations: [],
|
|
302
544
|
...(pairs.length > 1 ? { hops } : {}),
|
|
545
|
+
...unreadField,
|
|
546
|
+
...unitFields,
|
|
547
|
+
coverage: readScopeCoverage(wiringCoverage(rule, { kind: 'skipped', reason }), scope),
|
|
303
548
|
});
|
|
304
549
|
continue;
|
|
305
550
|
}
|
|
@@ -316,6 +561,15 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
316
561
|
...(emptySink ? { emptySink: true } : {}),
|
|
317
562
|
...(emptySink && sinkHint !== undefined ? { sinkHint } : {}),
|
|
318
563
|
...(pairs.length > 1 ? { hops } : {}),
|
|
564
|
+
...(registeredOnly !== undefined ? { registeredOnly } : {}),
|
|
565
|
+
...unreadField,
|
|
566
|
+
...unitFields,
|
|
567
|
+
coverage: readScopeCoverage(wiringCoverage(rule, {
|
|
568
|
+
kind: 'ran',
|
|
569
|
+
declaredCount: sourceCount,
|
|
570
|
+
registeredCount: sinkCount,
|
|
571
|
+
...(registeredOnly !== undefined ? { registeredOnly } : {}),
|
|
572
|
+
}), scope),
|
|
319
573
|
});
|
|
320
574
|
all.push(...violations);
|
|
321
575
|
evaluated += 1;
|
|
@@ -331,6 +585,7 @@ export function evaluateWiring(rules, resolve, context = {}) {
|
|
|
331
585
|
diagnostics,
|
|
332
586
|
skipped,
|
|
333
587
|
evaluated,
|
|
588
|
+
acceptedEmpty,
|
|
334
589
|
verdict: hasError ? 'errors' : hasWarn ? 'warnings' : 'pass',
|
|
335
590
|
};
|
|
336
591
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IUnitLiveness, type IUnitStateLists, type IVerdictCoverage, type IWiringRule, type ScanZone } from '@shrkcrft/core';
|
|
2
2
|
import { type IWiringHopResult, type IWiringTokenSite } from './evaluate-wiring.js';
|
|
3
3
|
export declare const WIRING_EXPLAIN_SCHEMA: "sharkcraft.wiring-explain/v1";
|
|
4
4
|
/** One side (declared or registered) of a wiring rule, as extracted from the tree. */
|
|
@@ -18,6 +18,17 @@ export interface IWiringSideExplain {
|
|
|
18
18
|
* guaranteed to stay in step with the other planes.
|
|
19
19
|
*/
|
|
20
20
|
readonly viaExtractor?: string;
|
|
21
|
+
/** The lexical zone this side extracted from, when it is not the default `all`. */
|
|
22
|
+
readonly scan?: ScanZone;
|
|
23
|
+
/**
|
|
24
|
+
* Characters a non-`all` `scan` blanked before extraction.
|
|
25
|
+
*
|
|
26
|
+
* A zone is the one setting that legitimately makes a rule match LESS while
|
|
27
|
+
* still reading green, so the amount removed is shown rather than assumed: a
|
|
28
|
+
* suspicious drop separates "the pattern was reading prose" from "the glob
|
|
29
|
+
* went stale".
|
|
30
|
+
*/
|
|
31
|
+
readonly blankedChars?: number;
|
|
21
32
|
}
|
|
22
33
|
/**
|
|
23
34
|
* The full intermediate output of evaluating ONE wiring rule against the live
|
|
@@ -43,11 +54,44 @@ export interface IWiringExplain {
|
|
|
43
54
|
readonly overlap: readonly IWiringTokenSite[];
|
|
44
55
|
/** Per-hop breakdown when the rule is a multi-hop `chain`. */
|
|
45
56
|
readonly hops?: readonly IWiringHopResult[];
|
|
46
|
-
/**
|
|
47
|
-
|
|
57
|
+
/**
|
|
58
|
+
* `passed` / `partial` / `failed` / `skipped` / `error` — skipped is never a
|
|
59
|
+
* pass, and neither is `partial`: a rule the engine reported `passed` whose
|
|
60
|
+
* {@link coverage} has a shortfall. Derived by core's `settleRuleStatus`, the
|
|
61
|
+
* same function the gate envelope uses, so `gates explain` and `gates check`
|
|
62
|
+
* report the same status for the same rule.
|
|
63
|
+
*/
|
|
64
|
+
readonly status: 'passed' | 'partial' | 'failed' | 'skipped' | 'error';
|
|
48
65
|
/** Why the rule checked nothing, when it was skipped. */
|
|
49
66
|
readonly skipReason?: string;
|
|
50
|
-
|
|
67
|
+
/**
|
|
68
|
+
* The engine's verdict, settled against {@link coverage}: a `pass` or
|
|
69
|
+
* `warnings` verdict over a coverage shortfall is `not-verified` — exactly the
|
|
70
|
+
* `0 → 2` the gate envelope applies. `errors` is never changed.
|
|
71
|
+
*/
|
|
72
|
+
readonly verdict: 'pass' | 'errors' | 'warnings' | 'not-verified';
|
|
73
|
+
/** What the rule examined against what it was asked to (the engine's own coverage). */
|
|
74
|
+
readonly coverage: IVerdictCoverage;
|
|
75
|
+
/** The coverage gap that vetoes a clean verdict, when there is one (`coverageShortfall`). */
|
|
76
|
+
readonly shortfall?: string;
|
|
77
|
+
/** A gap the rule's own config waived (`registeredExtras`), printed — never silent. */
|
|
78
|
+
readonly acceptance?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The rule's `expectEmpty` acceptance (round 13) — the engine's settle record
|
|
81
|
+
* B, read off the rule result exactly as `check wiring` carries it — so a
|
|
82
|
+
* verdict settled over this explain (`settleWiringExplain`, `gates try`)
|
|
83
|
+
* folds it through `ruleVerdictRecords` and prints it. When the rule is
|
|
84
|
+
* intended-empty it IS {@link coverage}.
|
|
85
|
+
*/
|
|
86
|
+
readonly unitAcceptance?: IVerdictCoverage;
|
|
87
|
+
/** The rule's dead / intended-empty / went-live glob units as printed lines. */
|
|
88
|
+
readonly units?: IUnitStateLists;
|
|
89
|
+
/**
|
|
90
|
+
* The rule's non-live glob units as the engine settled them (round 13
|
|
91
|
+
* review, additive) — what the explain renderers' went-live / dead block
|
|
92
|
+
* reads, so explain says what `check wiring` and `gates coverage` say.
|
|
93
|
+
*/
|
|
94
|
+
readonly unitLiveness?: readonly IUnitLiveness[];
|
|
51
95
|
/** Rule-level misconfiguration messages (engine degrades gracefully). */
|
|
52
96
|
readonly diagnostics: readonly string[];
|
|
53
97
|
}
|
|
@@ -55,5 +99,5 @@ export interface IExplainWiringOptions {
|
|
|
55
99
|
/** Project-relative directories to prune from the walk. */
|
|
56
100
|
readonly excludeDirs?: readonly string[];
|
|
57
101
|
}
|
|
58
|
-
export declare function explainWiring(projectRoot: string,
|
|
102
|
+
export declare function explainWiring(projectRoot: string, authored: IWiringRule, options?: IExplainWiringOptions): IWiringExplain;
|
|
59
103
|
//# sourceMappingURL=explain-wiring.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/explain-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"explain-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/explain-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAEhB,KAAK,QAAQ,EACd,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EAOL,KAAK,gBAAgB,EAErB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,qBAAqB,EAAG,8BAAuC,CAAC;AAE7E,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,cAAc,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,oFAAoF;IACpF,QAAQ,CAAC,qBAAqB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5D,yFAAyF;IACzF,QAAQ,CAAC,qBAAqB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IACvE,yDAAyD;IACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,cAAc,CAAC;IAClE,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,6FAA6F;IAC7F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAC3C,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACjD,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AA6CD,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,qBAA0B,GAClC,cAAc,CA0IhB"}
|