@henols/vice-mcp 0.2.2 → 0.2.4
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/README.md +2 -2
- package/THIRD-PARTY-NOTICES.md +422 -1
- package/anno-bank.ts +171 -0
- package/anno-cli.ts +1736 -163
- package/anno-confidence.ts +2 -2
- package/anno-derive.ts +6 -6
- package/anno-details.ts +4 -4
- package/anno-enum-gen.ts +416 -30
- package/anno-export-asm.ts +1211 -126
- package/anno-graphics.ts +338 -0
- package/anno-hazard-report.ts +1367 -0
- package/anno-import.ts +495 -0
- package/anno-index.ts +8 -8
- package/anno-join.ts +480 -0
- package/anno-memmap-render.ts +22 -21
- package/anno-provenance-ledger.ts +472 -0
- package/anno-regbits-gen.ts +13 -13
- package/anno-register.ts +159 -0
- package/anno-store-export.ts +661 -0
- package/anno-store.ts +635 -124
- package/anno-symbols.ts +7 -7
- package/anno-tools.ts +1169 -16
- package/anno-types.ts +313 -40
- package/backend-detect.mts +124 -312
- package/build.ts +3 -1
- package/capture-predicate.ts +597 -0
- package/channel-lock.ts +349 -0
- package/evid-ingest.ts +217 -0
- package/evid-reconcile.ts +316 -0
- package/host-tool-client.ts +430 -0
- package/incident-record.ts +23 -12
- package/install-resources.ts +29 -13
- package/memmap-lookup.ts +285 -0
- package/package.json +27 -8
- package/prg-image.ts +1 -2
- package/repo-root.ts +87 -3
- package/resources/backend-detect.mjs +98 -236
- package/resources/broker-control.mjs +220 -54
- package/resources/broker-epoch.mjs +7 -8
- package/resources/broker-kill.mjs +36 -31
- package/resources/broker-launch.mjs +511 -374
- package/resources/broker-state.mjs +69 -24
- package/resources/container-guard.mjs +1 -1
- package/resources/ghidra-project.mjs +790 -0
- package/resources/host-tool.mjs +2533 -0
- package/resources/vice-broker.mjs +434 -290
- package/resources/vice-launcher.sh +127 -9
- package/stock-address.ts +1 -1
- package/stock-condition.ts +1 -1
- package/stock-connect.ts +9 -5
- package/stock-derived.ts +29 -37
- package/stock-diagnose.ts +200 -36
- package/stock-dispatch.ts +179 -77
- package/stock-handler.ts +1 -1
- package/stock-paths.ts +18 -14
- package/stock-petscii.ts +1 -1
- package/stock-protocol.ts +1 -1
- package/stock-recycle.ts +83 -2
- package/stock-reproducible-run.ts +811 -0
- package/stock-run-until.ts +100 -1
- package/stock-symbols.ts +4 -4
- package/stock-timing.ts +1 -1
- package/stop-oracle.ts +167 -0
- package/text-capability-probe.ts +660 -0
- package/text-connect.ts +157 -0
- package/text-protocol.ts +810 -0
- package/text-tools.ts +778 -0
- package/textmon-backtrace.ts +385 -0
- package/textmon-cpuhistory.ts +335 -0
- package/textmon-memmap.ts +494 -0
- package/textmon-profile.ts +458 -0
- package/textmon-registers.ts +748 -0
- package/tools-manifest.stock.json +864 -3
- package/vice-broker-client.ts +253 -108
- package/vice-errors.ts +268 -0
- package/vice-proxy.ts +339 -2144
- package/vsf-slice.ts +640 -0
- package/anno-d64.ts +0 -310
- package/capability-registry.ts +0 -390
- package/refresh-manifest.ts +0 -124
- package/tools-manifest.json +0 -1223
- package/vice-probe.ts +0 -278
- package/vice-sync.ts +0 -336
- package/vice.ts +0 -772
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// evid-reconcile.ts
|
|
3
|
+
//
|
|
4
|
+
// Plan 43-04 (EVID-03, EVID-04): the ONE place that joins the byte-derived
|
|
5
|
+
// block table (`block-class.ts`'s own vocabulary, read through `blockClassAt`)
|
|
6
|
+
// against the runtime evidence table (`anno_evid_exec`, plan 43-02's
|
|
7
|
+
// `EvidExecRow`) and reports where the two independent classifiers disagree.
|
|
8
|
+
// Shaped exactly like `dxa-proof01-compare.ts` (Phase 38, plan 38-01): two
|
|
9
|
+
// already-fetched classifications in, buckets with an explicit denominator
|
|
10
|
+
// and named third/fourth buckets out, nothing fetched and nothing mutated.
|
|
11
|
+
//
|
|
12
|
+
// WHY THIS FILE EXISTS: an address the byte-derived block table calls `data`
|
|
13
|
+
// AND the emulator was observed executing is the single highest-value output
|
|
14
|
+
// this whole evidence layer can produce -- proof that a byte-derived guess
|
|
15
|
+
// was wrong, from a source (real execution) that never saw the guess. That
|
|
16
|
+
// value only exists because the two classifiers are independent; the moment
|
|
17
|
+
// this module compares a block-type string itself, or folds absence into a
|
|
18
|
+
// classification, both requirements this plan settles are void even though
|
|
19
|
+
// every existing test keeps passing.
|
|
20
|
+
//
|
|
21
|
+
// THE FOUR BUCKETS, AND WHY THE THIRD AND FOURTH ARE NOT OPTIONAL (EVID-03):
|
|
22
|
+
// 1. disagreement -- block class is `data`, evidence holds an observed
|
|
23
|
+
// execute. Reported FIRST, as rows. This is the whole point of the query.
|
|
24
|
+
// 2. agreement -- block class is `code`, evidence holds an observed
|
|
25
|
+
// execute. Reported as a COUNT ONLY. A wall of agreeing rows here would
|
|
26
|
+
// bury bucket 1.
|
|
27
|
+
// 3. block-covered, never observed -- the block table classifies the
|
|
28
|
+
// address and NO run ever observed it executing. Neither agreement nor
|
|
29
|
+
// disagreement: absence proves nothing (EVID-04), and folding this
|
|
30
|
+
// population into either bucket would be exactly the soundness
|
|
31
|
+
// violation EVID-04 forbids.
|
|
32
|
+
// 4. observed outside any block / observed at an explicitly `undefined`
|
|
33
|
+
// block -- evidence about an address the block table does not classify
|
|
34
|
+
// as code or data at all. Neither agreement nor disagreement either;
|
|
35
|
+
// named and counted so the denominator can never quietly drop real
|
|
36
|
+
// evidence and lie about what it accounts for.
|
|
37
|
+
//
|
|
38
|
+
// UNION-ACROSS-RUNS PROHIBITION, STATED HERE ONCE FOR THE WHOLE MODULE
|
|
39
|
+
// (EVID-04): however many runs contribute observations to `observations`,
|
|
40
|
+
// their union is NEVER exhaustive and NEVER complete coverage of the image.
|
|
41
|
+
// No field this module returns may be read as "the rest is data" -- the four
|
|
42
|
+
// buckets are the structural reason: summing them tells a reader what the
|
|
43
|
+
// block table covers, never what the program actually is. An address absent
|
|
44
|
+
// from every run's observations is `blockCoveredNeverObservedCount` (if the
|
|
45
|
+
// block table covers it) or simply outside `denominator` altogether (if it
|
|
46
|
+
// does not) -- in neither case is it, or does it become, `data`.
|
|
47
|
+
//
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// WHAT NOT TO DO -- each of these is a specific, named trap
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// 1. NEVER fetch either side here. `blocks` and `observations` arrive as
|
|
52
|
+
// plain data the caller already fetched (`listRanges()` and
|
|
53
|
+
// `listExecObservations()` respectively); this module never opens a
|
|
54
|
+
// store, reaches a transport, or names a filesystem/child-process
|
|
55
|
+
// specifier. A structural source assertion in this module's own test
|
|
56
|
+
// file bars exactly that.
|
|
57
|
+
// 2. NEVER compare a store block-type string here. That is `block-class.ts`'s
|
|
58
|
+
// job and its own trap 2 ("never compare a store vocabulary string
|
|
59
|
+
// outside this module") -- this module borrows `blockClassAt`'s answer
|
|
60
|
+
// through the injectable `classifier` parameter and never reads
|
|
61
|
+
// `BlockEntry.type` itself.
|
|
62
|
+
// 3. NEVER return a percentage, rate, ratio or score. Every count this
|
|
63
|
+
// module returns carries `denominator` as the fraction it is relative
|
|
64
|
+
// to; a consumer that wants a rate forms it from the two. There is no
|
|
65
|
+
// rounding site in this module for exactly that reason.
|
|
66
|
+
// 4. NEVER let "no row" and "observed not executing" collide. A row in
|
|
67
|
+
// `observations` exists only for an observed execute (plan 43-05's own
|
|
68
|
+
// discipline) -- there is no "observed but not executing" state this
|
|
69
|
+
// module's input can even represent, so this module never invents one.
|
|
70
|
+
// 5. NEVER derive `data` from absence. `blockCoveredNeverObservedCount` is
|
|
71
|
+
// the ONLY thing this module may say about the never-observed
|
|
72
|
+
// population: a count against a named denominator, never a class. See
|
|
73
|
+
// that field's own doc comment below.
|
|
74
|
+
import { blockClassAt, type BlockClass, type BlockEntry, type BlockClassifier } from "./block-class.ts";
|
|
75
|
+
import type { EvidExecRow, EvidSourceBank, RuntimeExecClass } from "./anno-types.ts";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Plain data the caller already fetched -- this module never fetches either
|
|
79
|
+
* side itself (trap 1 above). `blocks` is `listRanges()`'s own byte-derived
|
|
80
|
+
* listing; `observations` is `listExecObservations()`'s own runtime-evidence
|
|
81
|
+
* rows. `classifier` defaults to the one production interpreter of a store
|
|
82
|
+
* block-type string, `blockClassAt` -- the exact
|
|
83
|
+
* `typeof opts.x === "function" ? opts.x : blockClassAt` guard
|
|
84
|
+
* `anno-coverage.ts` already uses for the identical substitutability
|
|
85
|
+
* discipline, so a test can drive a substituted vocabulary through the real
|
|
86
|
+
* code path rather than a copy of it.
|
|
87
|
+
*/
|
|
88
|
+
export interface EvidReconcileInput {
|
|
89
|
+
blocks: readonly BlockEntry[];
|
|
90
|
+
observations: readonly EvidExecRow[];
|
|
91
|
+
classifier?: BlockClassifier;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* One disagreement row: the byte-derived tier said `data`, the runtime
|
|
96
|
+
* evidence layer holds at least one observed execute at that address.
|
|
97
|
+
* `byteDerived` and `runtime` are fixed to `"data"`/`"code"` for every member
|
|
98
|
+
* of this bucket by construction (a row only ever lands here on that exact
|
|
99
|
+
* pairing) -- carried as fields anyway so a renderer never has to re-derive
|
|
100
|
+
* what bucket it is looking at from context.
|
|
101
|
+
*/
|
|
102
|
+
export interface EvidDisagreement {
|
|
103
|
+
address: number;
|
|
104
|
+
/** Always `"data"` for a member of this bucket. */
|
|
105
|
+
byteDerived: BlockClass;
|
|
106
|
+
/** Always `"code"` for a member of this bucket. */
|
|
107
|
+
runtime: RuntimeExecClass;
|
|
108
|
+
/** Sorted ascending and de-duplicated, so a reader can see at a glance
|
|
109
|
+
* whether the execute bit came from RAM, ROM or IO space. */
|
|
110
|
+
sourceBanks: readonly EvidSourceBank[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The join's result. `disagreements` is declared and returned FIRST --
|
|
115
|
+
* EVID-03's own requirement that disagreement is reported before agreement,
|
|
116
|
+
* never buried under it. `agreementCount` is a NUMBER; there is no
|
|
117
|
+
* agreement-row array anywhere in this shape, on purpose (a wall of agreeing
|
|
118
|
+
* rows would bury the one output this whole query exists to surface).
|
|
119
|
+
*
|
|
120
|
+
* Every count field here is a fraction of `denominator`, and this type has
|
|
121
|
+
* no field that is, or that could be mistaken for, a percentage, rate, ratio
|
|
122
|
+
* or score (trap 3 above).
|
|
123
|
+
*/
|
|
124
|
+
export interface EvidReconciliation {
|
|
125
|
+
/** The disagreements. FIRST key, both declared and returned -- EVID-03. */
|
|
126
|
+
disagreements: EvidDisagreement[];
|
|
127
|
+
/** `disagreements.length`, exposed as its own field so a caller never has
|
|
128
|
+
* to re-derive a count from an array it may not want to hold onto. */
|
|
129
|
+
disagreementCount: number;
|
|
130
|
+
/** Count only -- see this interface's own doc comment for why there is no
|
|
131
|
+
* corresponding row array. */
|
|
132
|
+
agreementCount: number;
|
|
133
|
+
/**
|
|
134
|
+
* The block table classifies this address (either `code` or `data` or
|
|
135
|
+
* `undefined`) and NO run's observations ever recorded it executing. This
|
|
136
|
+
* count is the ONLY thing this module may say about that population -- a
|
|
137
|
+
* count against `denominator`, never a class -- because an address never
|
|
138
|
+
* observed executing proves nothing about whether it is code or data
|
|
139
|
+
* (EVID-04, and the same discipline `textmon-memmap.ts` states for itself:
|
|
140
|
+
* "never classify an address as data on the strength of never having been
|
|
141
|
+
* observed"). Neither agreement nor disagreement folds this population in.
|
|
142
|
+
*/
|
|
143
|
+
blockCoveredNeverObservedCount: number;
|
|
144
|
+
/**
|
|
145
|
+
* An observed execute at an address NO block covers at all
|
|
146
|
+
* (`classifier` returned `null`). This exists so the denominator cannot
|
|
147
|
+
* lie: an observation the block table does not cover is real evidence
|
|
148
|
+
* about an unclassified address, and silently dropping it would leave a
|
|
149
|
+
* reader believing every observation this module saw was accounted for by
|
|
150
|
+
* `denominator` when it was not.
|
|
151
|
+
*/
|
|
152
|
+
observedOutsideAnyBlockCount: number;
|
|
153
|
+
/**
|
|
154
|
+
* An observed execute at an address the block table explicitly marks
|
|
155
|
+
* `undefined`. An explicitly-undefined block claimed nothing about that
|
|
156
|
+
* address, so an observation there is neither agreement nor disagreement
|
|
157
|
+
* -- it joins `blockCoveredNeverObservedCount`'s sibling count under its
|
|
158
|
+
* own name rather than being silently absorbed into either bucket.
|
|
159
|
+
*/
|
|
160
|
+
observedAtUndefinedBlockCount: number;
|
|
161
|
+
/**
|
|
162
|
+
* The count of distinct addresses the block table covers -- what every
|
|
163
|
+
* other count on this object is a fraction of. Computed here from the
|
|
164
|
+
* blocks' own inclusive ranges, never copied from an input or a literal.
|
|
165
|
+
* No rate is ever formed from it in this module (trap 3 above), so a zero
|
|
166
|
+
* denominator (an empty `blocks` array) is a legal input, never a refusal.
|
|
167
|
+
*/
|
|
168
|
+
denominator: number;
|
|
169
|
+
/** Always `"code"` -- the runtime evidence layer's only positive class. */
|
|
170
|
+
positiveClass: "code";
|
|
171
|
+
/** Always `"runtime-observed"` -- this reconciler only ever joins against
|
|
172
|
+
* the runtime-observed tier. */
|
|
173
|
+
tier: "runtime-observed";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** De-duplicates and sorts a source-bank list ascending. Never insertion
|
|
177
|
+
* order, which would depend on the order observation rows happened to
|
|
178
|
+
* arrive in and could differ across two logically-identical inputs built by
|
|
179
|
+
* different code paths. */
|
|
180
|
+
function sortSourceBanksAscending(banks: readonly EvidSourceBank[]): EvidSourceBank[] {
|
|
181
|
+
return [...new Set(banks)].sort();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Sorts disagreement rows by address ascending. Same determinism
|
|
185
|
+
* discipline as `sortSourceBanksAscending`, applied to the one row array
|
|
186
|
+
* this module returns. */
|
|
187
|
+
function sortDisagreementsAscending(disagreements: readonly EvidDisagreement[]): EvidDisagreement[] {
|
|
188
|
+
return [...disagreements].sort((a, b) => a.address - b.address);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The join. For every address the block table covers (derived from the
|
|
193
|
+
* blocks' own inclusive ranges, both ends, never from a hardcoded 65536):
|
|
194
|
+
* `disagreements` when the classifier says `data` and an observation exists;
|
|
195
|
+
* `agreementCount` when it says `code` and an observation exists;
|
|
196
|
+
* `blockCoveredNeverObservedCount` when NO observation exists, regardless of
|
|
197
|
+
* class. A covered address whose class is `undefined` (or, defensively, an
|
|
198
|
+
* address a substituted classifier fails to classify at all) that DOES carry
|
|
199
|
+
* an observation falls through this first walk untouched, and is picked up
|
|
200
|
+
* by the second walk below as `observedAtUndefinedBlockCount` -- never here,
|
|
201
|
+
* so it can never be miscounted as "never observed".
|
|
202
|
+
*
|
|
203
|
+
* A second walk, over the observation side alone, then counts every observed
|
|
204
|
+
* address the first walk did not already account for: `classifier` returning
|
|
205
|
+
* `null` (no block covers it at all) becomes `observedOutsideAnyBlockCount`;
|
|
206
|
+
* `classifier` returning `"undefined"` becomes `observedAtUndefinedBlockCount`.
|
|
207
|
+
* An observed address the classifier calls `code` or `data` is skipped here
|
|
208
|
+
* -- it was already counted in the first walk, and counting it twice would
|
|
209
|
+
* break the identity that every block-covered address falls into EXACTLY one
|
|
210
|
+
* of `disagreementCount` / `agreementCount` / `blockCoveredNeverObservedCount`
|
|
211
|
+
* / `observedAtUndefinedBlockCount`.
|
|
212
|
+
*
|
|
213
|
+
* Never mutates `input.blocks`, `input.observations`, or any of their
|
|
214
|
+
* elements -- both are read only, and no `BlockEntry` is ever constructed
|
|
215
|
+
* from an `EvidExecRow` or vice versa.
|
|
216
|
+
*/
|
|
217
|
+
export function reconcileObservedExecution(input: EvidReconcileInput): EvidReconciliation {
|
|
218
|
+
const classifier: BlockClassifier = typeof input.classifier === "function" ? input.classifier : blockClassAt;
|
|
219
|
+
const blocks: readonly BlockEntry[] = Array.isArray(input.blocks) ? input.blocks : [];
|
|
220
|
+
const observations: readonly EvidExecRow[] = Array.isArray(input.observations) ? input.observations : [];
|
|
221
|
+
|
|
222
|
+
// One pass over the observations: address -> every source bank an
|
|
223
|
+
// observation named at that address, in whatever order the rows arrived.
|
|
224
|
+
// De-duplicated and sorted only where a caller actually receives the list
|
|
225
|
+
// (`EvidDisagreement.sourceBanks`), never here.
|
|
226
|
+
const observationBanks = new Map<number, EvidSourceBank[]>();
|
|
227
|
+
for (const row of observations) {
|
|
228
|
+
if (!row) continue;
|
|
229
|
+
const existing = observationBanks.get(row.address);
|
|
230
|
+
if (existing) {
|
|
231
|
+
existing.push(row.sourceBank);
|
|
232
|
+
} else {
|
|
233
|
+
observationBanks.set(row.address, [row.sourceBank]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// The addresses the block table covers -- a `null`/`undefined` hole in
|
|
238
|
+
// `blocks` is skipped, exactly as `blockClassAt` itself skips one, so this
|
|
239
|
+
// module's own defences match the classifier's documented ones (trap 2's
|
|
240
|
+
// boundary is the vocabulary comparison, not this geometric coverage
|
|
241
|
+
// check, which every well-behaved `BlockClassifier` -- production or
|
|
242
|
+
// substituted -- agrees on: both ends inclusive, first-match-wins never
|
|
243
|
+
// changes which addresses are covered, only which class they map to).
|
|
244
|
+
const coveredAddresses = new Set<number>();
|
|
245
|
+
for (const block of blocks) {
|
|
246
|
+
if (!block) continue;
|
|
247
|
+
const start = block.start_address;
|
|
248
|
+
const end = block.end_address;
|
|
249
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) continue;
|
|
250
|
+
for (let address = start; address <= end; address++) {
|
|
251
|
+
coveredAddresses.add(address);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const disagreements: EvidDisagreement[] = [];
|
|
256
|
+
let disagreementCount = 0;
|
|
257
|
+
let agreementCount = 0;
|
|
258
|
+
let blockCoveredNeverObservedCount = 0;
|
|
259
|
+
|
|
260
|
+
for (const address of coveredAddresses) {
|
|
261
|
+
const byteDerived = classifier(blocks, address);
|
|
262
|
+
const banks = observationBanks.get(address);
|
|
263
|
+
const hasObservation = banks !== undefined && banks.length > 0;
|
|
264
|
+
|
|
265
|
+
if (byteDerived === "data") {
|
|
266
|
+
if (hasObservation) {
|
|
267
|
+
disagreements.push({
|
|
268
|
+
address,
|
|
269
|
+
byteDerived: "data",
|
|
270
|
+
runtime: "code",
|
|
271
|
+
sourceBanks: sortSourceBanksAscending(banks!),
|
|
272
|
+
});
|
|
273
|
+
disagreementCount++;
|
|
274
|
+
} else {
|
|
275
|
+
blockCoveredNeverObservedCount++;
|
|
276
|
+
}
|
|
277
|
+
} else if (byteDerived === "code") {
|
|
278
|
+
if (hasObservation) {
|
|
279
|
+
agreementCount++;
|
|
280
|
+
} else {
|
|
281
|
+
blockCoveredNeverObservedCount++;
|
|
282
|
+
}
|
|
283
|
+
} else if (!hasObservation) {
|
|
284
|
+
// byteDerived is "undefined", or (defensively) a substituted classifier
|
|
285
|
+
// failed to classify a geometrically-covered address at all. Neither
|
|
286
|
+
// case was ever claimed as code or data, so an UNOBSERVED address here
|
|
287
|
+
// is exactly the never-observed population -- and an OBSERVED one is
|
|
288
|
+
// deliberately left uncounted here; the walk below names it instead.
|
|
289
|
+
blockCoveredNeverObservedCount++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let observedOutsideAnyBlockCount = 0;
|
|
294
|
+
let observedAtUndefinedBlockCount = 0;
|
|
295
|
+
for (const address of observationBanks.keys()) {
|
|
296
|
+
const byteDerived = classifier(blocks, address);
|
|
297
|
+
if (byteDerived === null) {
|
|
298
|
+
observedOutsideAnyBlockCount++;
|
|
299
|
+
} else if (byteDerived === "undefined") {
|
|
300
|
+
observedAtUndefinedBlockCount++;
|
|
301
|
+
}
|
|
302
|
+
// "code" and "data" were already counted in the walk above.
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
disagreements: sortDisagreementsAscending(disagreements),
|
|
307
|
+
disagreementCount,
|
|
308
|
+
agreementCount,
|
|
309
|
+
blockCoveredNeverObservedCount,
|
|
310
|
+
observedOutsideAnyBlockCount,
|
|
311
|
+
observedAtUndefinedBlockCount,
|
|
312
|
+
denominator: coveredAddresses.size,
|
|
313
|
+
positiveClass: "code",
|
|
314
|
+
tier: "runtime-observed",
|
|
315
|
+
};
|
|
316
|
+
}
|