@tiphys/kernel 0.0.0 → 0.2.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/AGENTS.md +663 -0
- package/LICENSE +202 -0
- package/assurance-modes.yaml +299 -0
- package/checklists/clean-room.yaml +325 -0
- package/checklists/env-failure-diagnosis.yaml +68 -0
- package/checklists/flake-playbook.yaml +68 -0
- package/checklists/hazard-review.yaml +144 -0
- package/checklists/plan-review.yaml +103 -0
- package/dist/bin/tiphys.d.ts +2 -0
- package/dist/bin/tiphys.js +92 -0
- package/dist/src/adapters/load.d.ts +202 -0
- package/dist/src/adapters/load.js +440 -0
- package/dist/src/brief.d.ts +32 -0
- package/dist/src/brief.js +66 -0
- package/dist/src/checklists.d.ts +169 -0
- package/dist/src/checklists.js +310 -0
- package/dist/src/checks.d.ts +1539 -0
- package/dist/src/checks.js +5025 -0
- package/dist/src/cli.d.ts +9 -0
- package/dist/src/cli.js +68 -0
- package/dist/src/commands/brief.d.ts +92 -0
- package/dist/src/commands/brief.js +343 -0
- package/dist/src/commands/checklist.d.ts +42 -0
- package/dist/src/commands/checklist.js +168 -0
- package/dist/src/commands/cutover.d.ts +35 -0
- package/dist/src/commands/cutover.js +448 -0
- package/dist/src/commands/doctor.d.ts +264 -0
- package/dist/src/commands/doctor.js +1546 -0
- package/dist/src/commands/gates.d.ts +9 -0
- package/dist/src/commands/gates.js +360 -0
- package/dist/src/commands/init.d.ts +30 -0
- package/dist/src/commands/init.js +177 -0
- package/dist/src/commands/lock.d.ts +34 -0
- package/dist/src/commands/lock.js +340 -0
- package/dist/src/commands/mode.d.ts +40 -0
- package/dist/src/commands/mode.js +134 -0
- package/dist/src/commands/next.d.ts +130 -0
- package/dist/src/commands/next.js +597 -0
- package/dist/src/commands/plan.d.ts +20 -0
- package/dist/src/commands/plan.js +105 -0
- package/dist/src/commands/pool.d.ts +1 -0
- package/dist/src/commands/pool.js +139 -0
- package/dist/src/commands/resume.d.ts +1 -0
- package/dist/src/commands/resume.js +88 -0
- package/dist/src/commands/spawn.d.ts +1 -0
- package/dist/src/commands/spawn.js +195 -0
- package/dist/src/commands/status.d.ts +21 -0
- package/dist/src/commands/status.js +122 -0
- package/dist/src/commands/sync.d.ts +47 -0
- package/dist/src/commands/sync.js +341 -0
- package/dist/src/commands/teardown.d.ts +1 -0
- package/dist/src/commands/teardown.js +87 -0
- package/dist/src/commands/tuition.d.ts +21 -0
- package/dist/src/commands/tuition.js +218 -0
- package/dist/src/commands/validate.d.ts +78 -0
- package/dist/src/commands/validate.js +430 -0
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +172 -0
- package/dist/src/cutover.d.ts +584 -0
- package/dist/src/cutover.js +1444 -0
- package/dist/src/exclusion.d.ts +389 -0
- package/dist/src/exclusion.js +843 -0
- package/dist/src/exec/env.d.ts +278 -0
- package/dist/src/exec/env.js +334 -0
- package/dist/src/fleet.d.ts +223 -0
- package/dist/src/fleet.js +298 -0
- package/dist/src/gates/adapters/http-json.d.ts +5 -0
- package/dist/src/gates/adapters/http-json.js +283 -0
- package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
- package/dist/src/gates/adapters/migrations-command.js +373 -0
- package/dist/src/gates/citations.d.ts +408 -0
- package/dist/src/gates/citations.js +1169 -0
- package/dist/src/gates/coverage.d.ts +375 -0
- package/dist/src/gates/coverage.js +836 -0
- package/dist/src/gates/credentials.d.ts +233 -0
- package/dist/src/gates/credentials.js +752 -0
- package/dist/src/gates/deploy.d.ts +1 -0
- package/dist/src/gates/deploy.js +33 -0
- package/dist/src/gates/gate-classes.d.ts +56 -0
- package/dist/src/gates/gate-classes.js +633 -0
- package/dist/src/gates/manifest.d.ts +99 -0
- package/dist/src/gates/manifest.js +208 -0
- package/dist/src/gates/merge-preconditions.d.ts +319 -0
- package/dist/src/gates/merge-preconditions.js +932 -0
- package/dist/src/gates/migrations.d.ts +1 -0
- package/dist/src/gates/migrations.js +36 -0
- package/dist/src/gates/pin.d.ts +114 -0
- package/dist/src/gates/pin.js +154 -0
- package/dist/src/gates/red-witness.d.ts +22 -0
- package/dist/src/gates/red-witness.js +482 -0
- package/dist/src/gates/release.d.ts +283 -0
- package/dist/src/gates/release.js +820 -0
- package/dist/src/gates/result.d.ts +116 -0
- package/dist/src/gates/result.js +91 -0
- package/dist/src/gates/run.d.ts +614 -0
- package/dist/src/gates/run.js +1614 -0
- package/dist/src/gates/schemas/citation-config.schema.json +59 -0
- package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
- package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
- package/dist/src/gates/schemas/gate-result.schema.json +160 -0
- package/dist/src/gates/schemas/phase-declaration.schema.json +87 -0
- package/dist/src/gates/schemas/release-record.schema.json +119 -0
- package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
- package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
- package/dist/src/gates/scope.d.ts +131 -0
- package/dist/src/gates/scope.js +1018 -0
- package/dist/src/gates/suite.d.ts +217 -0
- package/dist/src/gates/suite.js +968 -0
- package/dist/src/gates/validate.d.ts +121 -0
- package/dist/src/gates/validate.js +414 -0
- package/dist/src/hooks.d.ts +84 -0
- package/dist/src/hooks.js +125 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +30 -0
- package/dist/src/liveness.d.ts +321 -0
- package/dist/src/liveness.js +396 -0
- package/dist/src/lock.d.ts +256 -0
- package/dist/src/lock.js +792 -0
- package/dist/src/model-resolution.d.ts +159 -0
- package/dist/src/model-resolution.js +307 -0
- package/dist/src/modes.d.ts +149 -0
- package/dist/src/modes.js +258 -0
- package/dist/src/path-identity.d.ts +34 -0
- package/dist/src/path-identity.js +48 -0
- package/dist/src/plan.d.ts +73 -0
- package/dist/src/plan.js +153 -0
- package/dist/src/pool.d.ts +326 -0
- package/dist/src/pool.js +988 -0
- package/dist/src/roles.d.ts +461 -0
- package/dist/src/roles.js +776 -0
- package/dist/src/spawn.d.ts +482 -0
- package/dist/src/spawn.js +1003 -0
- package/dist/src/status.d.ts +116 -0
- package/dist/src/status.js +148 -0
- package/dist/src/task.d.ts +504 -0
- package/dist/src/task.js +307 -0
- package/dist/src/teardown.d.ts +39 -0
- package/dist/src/teardown.js +422 -0
- package/dist/src/tuition.d.ts +159 -0
- package/dist/src/tuition.js +311 -0
- package/dist/src/validate.d.ts +263 -0
- package/dist/src/validate.js +742 -0
- package/dist/src/version.d.ts +3 -0
- package/dist/src/version.js +38 -0
- package/dist/src/watcher.d.ts +275 -0
- package/dist/src/watcher.js +849 -0
- package/dist/src/witness/run.d.ts +299 -0
- package/dist/src/witness/run.js +1373 -0
- package/dist/src/witness/spec.d.ts +270 -0
- package/dist/src/witness/spec.js +475 -0
- package/dist/tsconfig.src.tsbuildinfo +1 -0
- package/gate-registry.yaml +526 -0
- package/gates.manifest.json +257 -0
- package/package.json +72 -3
- package/role-model-config.yaml +88 -0
- package/roles/README.md +128 -0
- package/roles/_shared-dispatch-contract.md +87 -0
- package/roles/adversarial-plan-reviewer.md +80 -0
- package/roles/clean-room-reviewer.md +140 -0
- package/roles/implementer.md +463 -0
- package/roles/investigator.md +138 -0
- package/roles/plan-writer.md +95 -0
- package/schemas/README.md +82 -0
- package/schemas/assurance-modes.schema.json +264 -0
- package/schemas/charter.schema.json +185 -0
- package/schemas/checklist.schema.json +114 -0
- package/schemas/cutover-state.schema.json +64 -0
- package/schemas/decision-record.schema.json +88 -0
- package/schemas/executor-record.schema.json +36 -0
- package/schemas/final-report.schema.json +90 -0
- package/schemas/finding.schema.json +106 -0
- package/schemas/gate-registry.schema.json +260 -0
- package/schemas/mechanism-index.schema.json +94 -0
- package/schemas/model-resolution.schema.json +362 -0
- package/schemas/plan.schema.json +300 -0
- package/schemas/report.schema.json +579 -0
- package/schemas/role-brief.schema.json +105 -0
- package/schemas/role-model-config.schema.json +90 -0
- package/schemas/status-line.schema.json +40 -0
- package/schemas/tuition.schema.json +191 -0
- package/schemas/verdict.schema.json +295 -0
- package/schemas/work-history.schema.json +183 -0
- package/schemas/write-bypass.schema.json +69 -0
- package/templates/charter.example.yaml +54 -0
- package/templates/decision-record.example.yaml +27 -0
- package/templates/final-report.example.yaml +80 -0
- package/templates/plan.example.yaml +87 -0
- package/templates/report.example.yaml +236 -0
- package/templates/warnings.md +74 -0
- package/templates/work-history.example.yaml +185 -0
- package/tuition/README.md +76 -0
- package/tuition/T-001.yaml +48 -0
- package/tuition/T-002.yaml +51 -0
- package/tuition/T-003.yaml +100 -0
- package/tuition/T-004.yaml +52 -0
- package/tuition/T-005.yaml +72 -0
- package/tuition/T-006.yaml +81 -0
- package/tuition/T-007.yaml +56 -0
- package/tuition/T-008.yaml +111 -0
- package/tuition/T-009.yaml +50 -0
- package/tuition/T-015.yaml +36 -0
- package/tuition/T-016.yaml +36 -0
- package/tuition/T-017.yaml +46 -0
- package/tuition/T-018.yaml +84 -0
- package/tuition/T-021.yaml +40 -0
- package/tuition/T-022.yaml +36 -0
- package/tuition/mechanism-index.yaml +256 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE COVERAGE CHECKER (kernel plan M2, M2-P6).
|
|
3
|
+
*
|
|
4
|
+
* "No orphans" as a check with an exit code: every id in an INVENTORY
|
|
5
|
+
* document lands in exactly one row of a COVERAGE-TABLE document, a bucket
|
|
6
|
+
* value that does not match a declared kind is named rather than silently
|
|
7
|
+
* uncounted, a bucket kind that requires a note (`parked`) is red when the
|
|
8
|
+
* note is empty, and a coverage-table row whose id is absent from the
|
|
9
|
+
* inventory (a renumbering) is named as phantom coverage.
|
|
10
|
+
*
|
|
11
|
+
* HAZARD CLASS (T-007, M2-D-18): arithmetic over two documents that can
|
|
12
|
+
* drift, where a total can add up while a row is lost. Named defenses,
|
|
13
|
+
* each tied to a criterion:
|
|
14
|
+
*
|
|
15
|
+
* - a renumbering that leaves a bucket row pointing at a dead id: the
|
|
16
|
+
* PHANTOM check (every coverage-table row's id is required to be in
|
|
17
|
+
* the inventory), independent of the orphan check (every inventory id
|
|
18
|
+
* is required to have a coverage-table row).
|
|
19
|
+
* - a duplicated id INSIDE the inventory document itself (fix round 1,
|
|
20
|
+
* CR-985): counted once as a unit is not the same claim as "appears
|
|
21
|
+
* once in the text", so `units` is the count of DISTINCT ids and a
|
|
22
|
+
* repeated id is its own named finding (`duplicate-inventory-id`),
|
|
23
|
+
* symmetric with the coverage side's `double-bucketed` check.
|
|
24
|
+
* **CORRECTED CLAIM (fix round 1): an earlier revision of this comment
|
|
25
|
+
* said the orphan and phantom checks "scan in opposite directions on
|
|
26
|
+
* purpose, so neither can compensate for the other's blind spot." That
|
|
27
|
+
* was false for cardinality: a duplicated inventory id defeated BOTH
|
|
28
|
+
* of them (it is not an orphan, because a coverage row exists; it is
|
|
29
|
+
* not phantom, because the id is genuinely in the inventory), and
|
|
30
|
+
* inflated every count while staying green. The true property is
|
|
31
|
+
* narrower: orphan and phantom together catch every EXISTENCE
|
|
32
|
+
* mismatch (an id present on one side and not the other); cardinality
|
|
33
|
+
* mismatches (an id present more than once on one side) need the
|
|
34
|
+
* separate check named above, and a row lost from BOTH documents
|
|
35
|
+
* needs the `expectedUnits` check below, because the two documents
|
|
36
|
+
* still agree with each other in that case and existence/cardinality
|
|
37
|
+
* checks over agreeing documents find nothing.
|
|
38
|
+
* - a row lost from BOTH documents (fix round 1, CR-986): the previous
|
|
39
|
+
* bullet's residue. An optional `expectedUnits` config field states
|
|
40
|
+
* the anchor no arithmetic over the two documents alone can produce;
|
|
41
|
+
* a computed unit count that does not equal it is the
|
|
42
|
+
* `expected-units-mismatch` finding. This repository's real config
|
|
43
|
+
* sets it to 115 (kernel plan v1 Appendix A's stated total).
|
|
44
|
+
* - a bucket value matching no declared kind, silently uncounted: every
|
|
45
|
+
* bucket-kind pattern is compiled ANCHORED (`^(?:pattern)$`) and tested
|
|
46
|
+
* against the whole cell; a value none of them match is the
|
|
47
|
+
* `unknown-kind` finding, named by id and value, never absorbed into a
|
|
48
|
+
* nearby kind by a partial match.
|
|
49
|
+
* - a bucket value matching MORE than one declared kind (fix round 1,
|
|
50
|
+
* CR-992): resolved by silent first-match-wins until this round; now
|
|
51
|
+
* the `ambiguous-kind` finding, naming every kind that matched, rather
|
|
52
|
+
* than one kind's `requiresNote` silently never being consulted.
|
|
53
|
+
* - an empty inventory producing a green with nothing examined: `units`
|
|
54
|
+
* is always the number of DISTINCT ids the inventory actually
|
|
55
|
+
* produced, and this module never constructs a `GateResult` except
|
|
56
|
+
* through `makeGateResult`, whose M2-C-2 rewrite turns a
|
|
57
|
+
* green-with-zero-units record into `error` with `vacuous: true`.
|
|
58
|
+
* This gate does not duplicate that rule; it relies on the shared
|
|
59
|
+
* constructor the way M2-C-6 requires reuse of `classifyEntry` rather
|
|
60
|
+
* than a second implementation of "may this path be read".
|
|
61
|
+
* - a milestone extraction that cannot produce the totals the plan
|
|
62
|
+
* states: the milestone view and the kind view are two INDEPENDENT
|
|
63
|
+
* tallies computed from the same classification pass (`perKind` keyed
|
|
64
|
+
* by the bucket kind's name, `perMilestone` keyed by the pattern's
|
|
65
|
+
* first capture group when present, else by the kind's own name), so
|
|
66
|
+
* the two views can be compared against each other and against the
|
|
67
|
+
* plan's stated totals rather than one being asserted to imply the
|
|
68
|
+
* other.
|
|
69
|
+
* - a finding whose outcome cell is present but empty:
|
|
70
|
+
* `checkFindingOutcomeParity` treats an empty outcome as a named
|
|
71
|
+
* failure distinct from a missing row, so "the row exists" and "the
|
|
72
|
+
* row says something" are not conflated. Fix round 1, CR-987: "empty"
|
|
73
|
+
* is ONE shared predicate (`isEmptyCell`, below), applied wherever a
|
|
74
|
+
* note or an outcome is tested, so whitespace-only and zero-width-only
|
|
75
|
+
* content (U+200B, which `String.prototype.trim` does not strip) is
|
|
76
|
+
* empty on both sides rather than only where a trim happened to run.
|
|
77
|
+
* - a finding-to-outcome report carrying a PHANTOM outcome (fix round 1,
|
|
78
|
+
* CR-988): `checkFindingOutcomeParity` used to scan inventory ids
|
|
79
|
+
* only, so a report row for an id absent from the inventory (the
|
|
80
|
+
* renumbering shape the coverage side's phantom check exists for) was
|
|
81
|
+
* silently accepted. Now symmetric: a phantom outcome row is named.
|
|
82
|
+
*
|
|
83
|
+
* M2-C-6: the inventory path, the coverage-table path and an optional
|
|
84
|
+
* `--config` document are all supplied by configuration and none of them
|
|
85
|
+
* is a path this module created, so every read goes through
|
|
86
|
+
* `readRegularFileIfPresent` (which itself routes through `classifyEntry`),
|
|
87
|
+
* never a bare `readFileSync`. A named pipe at any of the three paths is
|
|
88
|
+
* `error` naming the path and the observed type, and this module never
|
|
89
|
+
* blocks on one. Fix round 1, CR-989: the same discipline now applies to
|
|
90
|
+
* the WRITE side inside the evidence directory (`counts.json`): a refused
|
|
91
|
+
* or failed write there used to be computed and discarded, leaving a
|
|
92
|
+
* silent green with empty evidence; it is now loud and makes the gate
|
|
93
|
+
* `error`, the same way a refused result-path write already did.
|
|
94
|
+
*
|
|
95
|
+
* Fix round 1 also closes the REGEX MECHANISM (CR-990/991/992): every
|
|
96
|
+
* config-supplied pattern (both `idPattern`s and every `bucketKinds[].pattern`)
|
|
97
|
+
* is VALIDATED (compiles, and is rejected if it matches a known
|
|
98
|
+
* catastrophic-backtracking shape) before it is ever executed, and every
|
|
99
|
+
* EXECUTION of a config-supplied pattern is BOUNDED (`boundedExec`, below),
|
|
100
|
+
* so neither a malformed pattern (CR-990, used to
|
|
101
|
+
* throw with no result record written) nor a ReDoS pattern (CR-991, used
|
|
102
|
+
* to hang indefinitely) can defeat this gate. M4-P28 changed WHAT that
|
|
103
|
+
* bound measures, from elapsed wall clock to CPU work, because the first
|
|
104
|
+
* one reddened for well-behaved patterns whenever the machine was busy.
|
|
105
|
+
* CR-992 (overlapping kinds)
|
|
106
|
+
* is folded into the same fix because it is the same "a config string is
|
|
107
|
+
* trusted further than its syntax justifies" mechanism one property over.
|
|
108
|
+
*/
|
|
109
|
+
/** One row of the `bucketKinds` config. */
|
|
110
|
+
export interface BucketKindConfig {
|
|
111
|
+
kind: string;
|
|
112
|
+
/** Regex source, compiled anchored `^(?:pattern)$` against a bucket value. */
|
|
113
|
+
pattern: string;
|
|
114
|
+
requiresNote: boolean;
|
|
115
|
+
}
|
|
116
|
+
export interface DocumentConfig {
|
|
117
|
+
path: string;
|
|
118
|
+
/** Regex source, compiled anchored, tested against a row's first cell. */
|
|
119
|
+
idPattern: string;
|
|
120
|
+
}
|
|
121
|
+
export interface CoverageTableConfig extends DocumentConfig {
|
|
122
|
+
/** 0-based cell index (cell 0 is the id) holding the bucket value. */
|
|
123
|
+
bucketColumn: number;
|
|
124
|
+
/** 0-based cell index holding the note. */
|
|
125
|
+
noteColumn: number;
|
|
126
|
+
}
|
|
127
|
+
export interface CoverageConfig {
|
|
128
|
+
inventory: DocumentConfig;
|
|
129
|
+
coverageTable: CoverageTableConfig;
|
|
130
|
+
bucketKinds: BucketKindConfig[];
|
|
131
|
+
/**
|
|
132
|
+
* CR-986: the exact expected count of DISTINCT inventory ids. Optional;
|
|
133
|
+
* when absent, no floor is enforced. A row deleted from BOTH documents
|
|
134
|
+
* leaves the two documents agreeing with each other, which every other
|
|
135
|
+
* check in this module is powerless against, so this is a config-stated
|
|
136
|
+
* anchor rather than a derived one.
|
|
137
|
+
*/
|
|
138
|
+
expectedUnits?: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* THE KERNEL CONFIG (kernel plan M2-P6 step 2): this repository's real
|
|
142
|
+
* pair and the four bucket kinds the plan states verbatim. It is a plain
|
|
143
|
+
* exported constant, not a checked-in data file, because M2-C-1's
|
|
144
|
+
* verification-first reading of the plan's files-to-touch list for this
|
|
145
|
+
* phase names the module, its schema and its test, and no committed
|
|
146
|
+
* configuration document alongside them; a `--config` flag (validated
|
|
147
|
+
* against `coverage-config.schema.json`) exists for a caller that wants a
|
|
148
|
+
* different pair, and every fixture test in `test/coverage-gate.test.ts`
|
|
149
|
+
* uses it rather than editing this constant.
|
|
150
|
+
*/
|
|
151
|
+
export declare const KERNEL_COVERAGE_CONFIG: CoverageConfig;
|
|
152
|
+
/**
|
|
153
|
+
* THE SHARED EMPTINESS PREDICATE (fix round 1, CR-987).
|
|
154
|
+
*
|
|
155
|
+
* Before this round, `checkCoverage`'s note check compared a value already
|
|
156
|
+
* passed through `String.prototype.trim` (applied once, at extraction) to
|
|
157
|
+
* `""`, and `checkFindingOutcomeParity`'s outcome check compared a value
|
|
158
|
+
* that was never trimmed at all to `""`. Two definitions, only one of
|
|
159
|
+
* which trimmed, and NEITHER strips a zero-width character: `trim()`
|
|
160
|
+
* removes Unicode whitespace (which includes U+00A0 and U+FEFF) but not
|
|
161
|
+
* U+200B ZERO WIDTH SPACE, U+200C ZERO WIDTH NON-JOINER or U+200D ZERO
|
|
162
|
+
* WIDTH JOINER, so a cell or an outcome containing only one of those reads
|
|
163
|
+
* as non-empty to a bare `=== ""` comparison, trimmed or not. One
|
|
164
|
+
* predicate, called at every point this module asks "is this empty",
|
|
165
|
+
* closes both instances at once.
|
|
166
|
+
*/
|
|
167
|
+
export declare function isEmptyCell(value: string): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* THE VERDICT-PRODUCING BOUND IS CPU WORK, NOT ELAPSED TIME (M4-P28).
|
|
170
|
+
*
|
|
171
|
+
* What this constant replaces, and why the replacement is not a bigger
|
|
172
|
+
* number. Until this phase the bound was `REGEX_EXEC_TIMEOUT_MS = 250`,
|
|
173
|
+
* handed to `vm` as a WALL-CLOCK timeout, and exceeding it was reported as
|
|
174
|
+
* "did not complete within 250ms ... (possible catastrophic
|
|
175
|
+
* backtracking)". Elapsed wall time is complexity DIVIDED BY available
|
|
176
|
+
* CPU, so that condition did not test the property it named: it reddened
|
|
177
|
+
* whenever the machine was busy. Measured and recorded in
|
|
178
|
+
* delivery/verification/wall-clock-budgets-are-load-dependent.md:1, five
|
|
179
|
+
* independent witnesses hit it on six structurally different patterns,
|
|
180
|
+
* including `^(?:parked)$`, a doubly anchored literal with no quantifier,
|
|
181
|
+
* no character class and no alternation, which has nothing to backtrack
|
|
182
|
+
* over at any input length. One million executions of
|
|
183
|
+
* `^(?:R-[0-9]+[a-z]?)$` against `R-094a` take 154.2ms in total, so ONE is
|
|
184
|
+
* roughly 1.6 million times under the old budget; for one of them to
|
|
185
|
+
* exceed it the thread must be descheduled for a quarter of a second,
|
|
186
|
+
* which is a fact about the machine.
|
|
187
|
+
*
|
|
188
|
+
* Raising 250 to a larger number was considered and is REJECTED by the
|
|
189
|
+
* plan (delivery/plan/kernel-plan-m4.md:3784): it keeps the same
|
|
190
|
+
* instrument and only moves the load at which it lies.
|
|
191
|
+
*
|
|
192
|
+
* WHAT IS MEASURED NOW. `process.threadCpuUsage()` reports the CPU time
|
|
193
|
+
* this thread has consumed, in microseconds. Another process being busy
|
|
194
|
+
* does not add to it, because a descheduled thread consumes no CPU while
|
|
195
|
+
* it is not running. The regex runs on THIS thread inside
|
|
196
|
+
* `runInContext`, so the delta across that call is the work the regex
|
|
197
|
+
* did, and nothing else. On an interpreter without
|
|
198
|
+
* `process.threadCpuUsage` (added in Node 22.15; the package floor is 26)
|
|
199
|
+
* the fallback is `process.cpuUsage()`, which is process-wide and
|
|
200
|
+
* therefore an over-estimate, never an under-estimate, so the fallback
|
|
201
|
+
* cannot let a catastrophic pattern through.
|
|
202
|
+
*/
|
|
203
|
+
export declare const REGEX_EXEC_CPU_BUDGET_MS = 250;
|
|
204
|
+
/**
|
|
205
|
+
* HOW LONG ONE ATTEMPT IS WILLING TO WAIT. This is PATIENCE, not a
|
|
206
|
+
* verdict: nothing is ever reported about a pattern because this elapsed.
|
|
207
|
+
* A wall-clock interrupt is still needed, because a catastrophic match
|
|
208
|
+
* never returns on its own and `vm`'s `timeout` is the only mechanism
|
|
209
|
+
* here that can stop one (v8 checks for the termination request during a
|
|
210
|
+
* regex match, not only between statements).
|
|
211
|
+
*
|
|
212
|
+
* It is set to TWICE the CPU budget so that a thread receiving at least
|
|
213
|
+
* half of one CPU reaches the budget inside a single attempt; measured on
|
|
214
|
+
* an unloaded box, `(a+)+b` against 30 a's consumes 230.6ms of CPU in a
|
|
215
|
+
* 252ms wall window, so a backstop equal to the budget would have needed
|
|
216
|
+
* a second attempt to reach a verdict it had nearly earned.
|
|
217
|
+
*/
|
|
218
|
+
export declare const REGEX_EXEC_WALL_BACKSTOP_MS = 500;
|
|
219
|
+
/**
|
|
220
|
+
* How many interrupted attempts before this module admits it cannot
|
|
221
|
+
* reach a verdict. Each attempt doubles its own patience, and the CPU
|
|
222
|
+
* consumed ACCUMULATES across attempts, so a genuinely spinning pattern
|
|
223
|
+
* is still caught on a loaded machine (it burns CPU whenever it runs)
|
|
224
|
+
* while a benign one accumulates microseconds however often it is
|
|
225
|
+
* interrupted.
|
|
226
|
+
*/
|
|
227
|
+
export declare const REGEX_EXEC_MAX_ATTEMPTS = 4;
|
|
228
|
+
/** The pattern did too much WORK. A verdict about the pattern. */
|
|
229
|
+
export declare class RegexBoundExceededError extends Error {
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* No verdict was reached: every attempt was interrupted before the
|
|
233
|
+
* pattern finished, and the CPU it consumed never came near the budget,
|
|
234
|
+
* which is the signature of a thread that is not being scheduled rather
|
|
235
|
+
* than one that is spinning. Under M2-C-3 a check that cannot reach a
|
|
236
|
+
* verdict reports ERROR, never a verdict it did not earn, so this is
|
|
237
|
+
* deliberately NOT a subclass of `RegexBoundExceededError`: the two must
|
|
238
|
+
* not be confusable by a `catch` or an `instanceof`.
|
|
239
|
+
*/
|
|
240
|
+
export declare class RegexBudgetUndeterminedError extends Error {
|
|
241
|
+
}
|
|
242
|
+
/** Overrides for one call, used by the witness tests to stand in for a
|
|
243
|
+
* machine slower or busier than this one. Only the PATIENCE is varied
|
|
244
|
+
* there; the verdict instrument stays the CPU budget. */
|
|
245
|
+
export interface RegexExecBounds {
|
|
246
|
+
cpuBudgetMs?: number;
|
|
247
|
+
wallBackstopMs?: number;
|
|
248
|
+
maxAttempts?: number;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Execute `compiled.exec(value)` inside a v8 context, bounded by the CPU
|
|
252
|
+
* WORK it does rather than by the time it takes.
|
|
253
|
+
*
|
|
254
|
+
* One attempt runs the match with a wall-clock interrupt. Whatever
|
|
255
|
+
* happens, the CPU consumed by this thread during that call is added to a
|
|
256
|
+
* running total:
|
|
257
|
+
*
|
|
258
|
+
* - total CPU at or above the budget: the thread really did burn that
|
|
259
|
+
* much CPU on this one match, which no anchored non-backtracking
|
|
260
|
+
* pattern can do, so `RegexBoundExceededError` is thrown. A busy
|
|
261
|
+
* machine cannot cause this, because a descheduled thread accumulates
|
|
262
|
+
* no CPU.
|
|
263
|
+
* - the match finished: return its result, which is the only path that
|
|
264
|
+
* produces a match.
|
|
265
|
+
* - interrupted with the total still far below the budget: the machine
|
|
266
|
+
* was busy, not the pattern. Double the patience and try again.
|
|
267
|
+
*
|
|
268
|
+
* After `maxAttempts` interruptions with the CPU total still under
|
|
269
|
+
* budget, `RegexBudgetUndeterminedError` is thrown, naming the CPU
|
|
270
|
+
* consumed, the wall clock spent and the load average, so the record says
|
|
271
|
+
* what it observed instead of asserting something about the pattern.
|
|
272
|
+
*
|
|
273
|
+
* An error from `runInContext` that is NOT the timeout is also
|
|
274
|
+
* undetermined rather than a finding: the old code funnelled every throw
|
|
275
|
+
* into the catastrophic-backtracking message, which is the same
|
|
276
|
+
* substitution one cause over.
|
|
277
|
+
*/
|
|
278
|
+
export declare function boundedExec(compiled: RegExp, value: string, bounds?: RegexExecBounds): RegExpExecArray | null;
|
|
279
|
+
/**
|
|
280
|
+
* A STATIC heuristic for the single most common catastrophic-backtracking
|
|
281
|
+
* shape (fix round 1, CR-991): a parenthesised group containing an
|
|
282
|
+
* unbounded quantifier (`+` or `*`), itself immediately followed by
|
|
283
|
+
* another unbounded quantifier, e.g. `(a+)+`, `(a*)+`, `(a+)*`. This is
|
|
284
|
+
* exactly the shape the round's own red witness constructs
|
|
285
|
+
* (`(a+)+b`). It is a heuristic, not a proof: it does not see every
|
|
286
|
+
* catastrophic shape (a documented residue below), which is why every
|
|
287
|
+
* EXECUTION is also bounded by `boundedExec` regardless of whether a
|
|
288
|
+
* pattern passes this check.
|
|
289
|
+
*/
|
|
290
|
+
export declare function catastrophicShapeReason(pattern: string): string | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* Validate one config-supplied pattern SOURCE before it is ever compiled
|
|
293
|
+
* into a live regex the gate will execute (fix round 1, CR-990/991):
|
|
294
|
+
* compiles cleanly, and does not match the static catastrophic-shape
|
|
295
|
+
* heuristic. `label` identifies which config field failed, so a config
|
|
296
|
+
* error names the field, not just "a pattern".
|
|
297
|
+
*/
|
|
298
|
+
export declare function validatePatternSource(label: string, pattern: string): string | undefined;
|
|
299
|
+
/**
|
|
300
|
+
* Validate every config-supplied pattern this module will ever compile
|
|
301
|
+
* and execute: both `idPattern`s and every `bucketKinds[].pattern`. Run
|
|
302
|
+
* once per config, before the config is used, so a malformed or dangerous
|
|
303
|
+
* pattern is a named CONFIG error (a clean result record) rather than an
|
|
304
|
+
* uncaught exception with no record (CR-990) or an unbounded hang
|
|
305
|
+
* (CR-991).
|
|
306
|
+
*/
|
|
307
|
+
export declare function validateConfigPatterns(config: CoverageConfig): string | undefined;
|
|
308
|
+
interface TableRow {
|
|
309
|
+
id: string;
|
|
310
|
+
cells: string[];
|
|
311
|
+
line: number;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Walk a document's lines and return every markdown-table row whose first
|
|
315
|
+
* cell matches `idPattern` (compiled anchored). Separator rows
|
|
316
|
+
* (`|---|:--:|---|`) are skipped by construction: every cell in one matches
|
|
317
|
+
* `/^:?-+:?$/`, which cannot also match a realistic id pattern, but the
|
|
318
|
+
* separator test is applied explicitly rather than relied on implicitly, so
|
|
319
|
+
* a future id pattern that could coincide does not silently absorb one.
|
|
320
|
+
* Every match against a config-supplied pattern is BOUNDED (fix round 1,
|
|
321
|
+
* `boundedExec`), so a hostile `idPattern` cannot hang this walk.
|
|
322
|
+
*/
|
|
323
|
+
export declare function extractIdRows(text: string, idPattern: string): TableRow[];
|
|
324
|
+
export type CoverageFindingKind = "orphan" | "double-bucketed" | "phantom" | "unknown-kind" | "ambiguous-kind" | "missing-note" | "duplicate-inventory-id" | "expected-units-mismatch";
|
|
325
|
+
export interface CoverageFinding {
|
|
326
|
+
kind: CoverageFindingKind;
|
|
327
|
+
id: string;
|
|
328
|
+
detail: string;
|
|
329
|
+
}
|
|
330
|
+
export interface CoverageReport {
|
|
331
|
+
totalInventoryIds: number;
|
|
332
|
+
perKind: Record<string, number>;
|
|
333
|
+
perMilestone: Record<string, number>;
|
|
334
|
+
findings: CoverageFinding[];
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* The core check. Pure: takes the two documents' TEXT (already read through
|
|
338
|
+
* M2-C-6's guarded read at the call site) and the config, and returns a
|
|
339
|
+
* report with nothing hidden inside a status string.
|
|
340
|
+
*/
|
|
341
|
+
export declare function checkCoverage(config: CoverageConfig, inventoryText: string, coverageTableText: string): CoverageReport;
|
|
342
|
+
/**
|
|
343
|
+
* R-089b, THE FINDING-TO-OUTCOME PARITY CONTRACT (M2-P6 step 4).
|
|
344
|
+
*
|
|
345
|
+
* Section 2 item 2 of the M2 plan states that the report contract does not
|
|
346
|
+
* exist yet, so this module defines its own input shape and M3's report
|
|
347
|
+
* schema must emit it or supersede it. The shape declared here:
|
|
348
|
+
*
|
|
349
|
+
* inventoryIds: string[] every id that must be resolved
|
|
350
|
+
* findings: { id: string; outcome: string }[] the report's rows
|
|
351
|
+
*
|
|
352
|
+
* A CONFORMING future report is a list of `{id, outcome}` pairs, one row
|
|
353
|
+
* per id, `outcome` a non-empty string. This function does not care what
|
|
354
|
+
* the outcome STRING says (that is a judgement M3 owns); it only checks
|
|
355
|
+
* that every id got exactly one non-empty say. Fix round 1 adds the
|
|
356
|
+
* direction the original version lacked: a report row whose id is not in
|
|
357
|
+
* the inventory at all (CR-988, a PHANTOM outcome, symmetric with
|
|
358
|
+
* `checkCoverage`'s phantom finding) is also named, not silently accepted.
|
|
359
|
+
*/
|
|
360
|
+
export interface FindingOutcomeRow {
|
|
361
|
+
id: string;
|
|
362
|
+
outcome: string;
|
|
363
|
+
}
|
|
364
|
+
export interface FindingParityResult {
|
|
365
|
+
ok: boolean;
|
|
366
|
+
checked: number;
|
|
367
|
+
missing: string[];
|
|
368
|
+
duplicated: string[];
|
|
369
|
+
empty: string[];
|
|
370
|
+
/** CR-988: report rows whose id is absent from `inventoryIds`. */
|
|
371
|
+
phantom: string[];
|
|
372
|
+
}
|
|
373
|
+
export declare function checkFindingOutcomeParity(inventoryIds: string[], findings: FindingOutcomeRow[]): FindingParityResult;
|
|
374
|
+
export declare function main(argv: string[]): number;
|
|
375
|
+
export {};
|