@manyoars/oars 0.1.1 → 0.3.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/README.md +134 -26
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +6 -3
- package/dist/cli.js +370 -72
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -5
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +35 -0
- package/dist/gate/hook-process.d.ts +14 -0
- package/dist/gate/hook-process.js +64 -0
- package/dist/gate/hook.d.ts +46 -0
- package/dist/gate/hook.js +256 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +257 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +25 -0
- package/dist/issuer.d.ts +16 -0
- package/dist/issuer.js +55 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +64 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +351 -0
- package/dist/sessions/allow.d.ts +14 -0
- package/dist/sessions/allow.js +51 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +188 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +43 -0
- package/dist/sessions/cell-increments.js +209 -0
- package/dist/sessions/cells.d.ts +79 -0
- package/dist/sessions/cells.js +181 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +679 -0
- package/dist/sessions/command.d.ts +117 -0
- package/dist/sessions/command.js +373 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +320 -0
- package/dist/sessions/exclusion.d.ts +81 -0
- package/dist/sessions/exclusion.js +353 -0
- package/dist/sessions/extract.d.ts +179 -0
- package/dist/sessions/extract.js +1774 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +53 -0
- package/dist/sessions/hook-index.js +157 -0
- package/dist/sessions/hooks.d.ts +13 -0
- package/dist/sessions/hooks.js +170 -0
- package/dist/sessions/identity.d.ts +22 -0
- package/dist/sessions/identity.js +101 -0
- package/dist/sessions/install.d.ts +54 -0
- package/dist/sessions/install.js +201 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +6 -0
- package/dist/sessions/mcp.js +153 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +43 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/package-resolve.d.ts +30 -0
- package/dist/sessions/package-resolve.js +183 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pool-command.d.ts +44 -0
- package/dist/sessions/pool-command.js +542 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-outbox.d.ts +63 -0
- package/dist/sessions/pool-outbox.js +260 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-wire.d.ts +225 -0
- package/dist/sessions/pool-wire.js +620 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +73 -0
- package/dist/sessions/public-vocabulary.js +302 -0
- package/dist/sessions/query.d.ts +111 -0
- package/dist/sessions/query.js +246 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +37 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +79 -0
- package/dist/sessions/read.js +84 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-object.d.ts +137 -0
- package/dist/sessions/recall-object.js +77 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +95 -0
- package/dist/sessions/recall-reads.d.ts +117 -0
- package/dist/sessions/recall-reads.js +410 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/redact.d.ts +6 -0
- package/dist/sessions/redact.js +54 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +644 -0
- package/dist/sessions/schema.js +148 -0
- package/dist/sessions/session-shape.d.ts +73 -0
- package/dist/sessions/session-shape.js +342 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +159 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +297 -0
- package/dist/sessions/store.js +1478 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +25 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +477 -0
- package/package.json +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -35
- package/dist/commands/deploy-input.js +0 -107
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -120
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Cell, type Grain, type Pool } from './cells.js';
|
|
2
|
+
import type { Motif } from './motif.js';
|
|
3
|
+
import { type Family, type ReadDraft, type ReadInterval, type ReadSlice, type ReadValue } from './read.js';
|
|
4
|
+
import type { Store } from './store.js';
|
|
5
|
+
export interface FamilyInput {
|
|
6
|
+
pool: Pool;
|
|
7
|
+
/** Only recovery reads this: an error's signature is not in a cell. */
|
|
8
|
+
store: Store;
|
|
9
|
+
}
|
|
10
|
+
/** A read as a family states it; the pool's snapshot is added to all of them in one place. */
|
|
11
|
+
export type ReadBody = Omit<ReadDraft, 'asOf'>;
|
|
12
|
+
export type DeriveFamily = (input: FamilyInput) => ReadBody[];
|
|
13
|
+
export declare const round: (value: number) => number;
|
|
14
|
+
/** A rate to four places, so a value reads alike wherever it is computed; null with nothing to divide by. */
|
|
15
|
+
export declare const rate: (part: number, whole: number) => number | null;
|
|
16
|
+
/** The normal quantile for a 95 percent interval, which every read's interval and the screen's scaling share. */
|
|
17
|
+
export declare const Z_95 = 1.96;
|
|
18
|
+
/** The Wilson interval: it stays inside 0 and 1, and it is honest about the small cells one operator's store is mostly made of. */
|
|
19
|
+
export declare const wilson: (part: number, whole: number) => ReadInterval | null;
|
|
20
|
+
/** Windows with a known result: a step with no result is in neither count, so it is in no rate. */
|
|
21
|
+
export declare const settled: (cell: Cell) => number;
|
|
22
|
+
export declare const total: (cells: readonly Cell[], count: (cell: Cell) => number) => number;
|
|
23
|
+
export declare const groupBy: <K, T>(items: Iterable<T>, keyOf: (item: T) => K) => Map<K, T[]>;
|
|
24
|
+
/**
|
|
25
|
+
* The pool's cells at one grain (rule 6). Weekly and day cells share a table and count the same steps, so a read that sums over both counts a step twice and a read that
|
|
26
|
+
* names a day from a week answers from the wrong grain: every read takes the grain it answers from here, and no read iterates `pool.cells` itself.
|
|
27
|
+
*/
|
|
28
|
+
export declare const atGrain: (pool: Pool, grain: Grain) => Cell[];
|
|
29
|
+
/** The (name, `platform`, `shell`) cells, which sit beside the version cells and count the same calls: a read that summed them with the version cells would count each call twice, so only the read of these asks. */
|
|
30
|
+
export declare const environmentCells: (pool: Pool) => Cell[];
|
|
31
|
+
/** The cells of one-step motifs at one grain: every action, and every identity capture has named. */
|
|
32
|
+
export declare const unitCells: (pool: Pool, grain: Grain) => {
|
|
33
|
+
cell: Cell;
|
|
34
|
+
motif: Motif;
|
|
35
|
+
}[];
|
|
36
|
+
/** Counts over every one-step `shape` cell, for shares: a named motif's share is of all steps, like its action's. */
|
|
37
|
+
export declare const shapeUnits: (pool: Pool, grain: Grain) => Cell[];
|
|
38
|
+
export declare const body: (family: Family, cells: readonly Cell[], fields: {
|
|
39
|
+
slice: ReadSlice;
|
|
40
|
+
value: ReadValue;
|
|
41
|
+
n: number;
|
|
42
|
+
interval: ReadInterval | null;
|
|
43
|
+
}, method?: {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly version: string;
|
|
46
|
+
}) => ReadBody;
|
|
47
|
+
export declare const firstDayOf: (days: readonly string[]) => string | null;
|
|
48
|
+
export declare const lastDayOf: (days: readonly string[]) => string | null;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// What the family modules share: their input, a read's common fields, and the arithmetic of a rate over cells.
|
|
2
|
+
import { cellIdOf, isEnvironmentCell } from './cells.js';
|
|
3
|
+
import { METHODS } from './read.js';
|
|
4
|
+
export const round = (value) => Math.round(value * 10_000) / 10_000;
|
|
5
|
+
/** A rate to four places, so a value reads alike wherever it is computed; null with nothing to divide by. */
|
|
6
|
+
export const rate = (part, whole) => (whole === 0 ? null : round(part / whole));
|
|
7
|
+
/** The normal quantile for a 95 percent interval, which every read's interval and the screen's scaling share. */
|
|
8
|
+
export const Z_95 = 1.96;
|
|
9
|
+
/** The Wilson interval: it stays inside 0 and 1, and it is honest about the small cells one operator's store is mostly made of. */
|
|
10
|
+
export const wilson = (part, whole) => {
|
|
11
|
+
if (whole === 0)
|
|
12
|
+
return null;
|
|
13
|
+
const share = part / whole;
|
|
14
|
+
const z2 = Z_95 * Z_95;
|
|
15
|
+
const denominator = 1 + z2 / whole;
|
|
16
|
+
const centre = share + z2 / (2 * whole);
|
|
17
|
+
const margin = Z_95 * Math.sqrt((share * (1 - share)) / whole + z2 / (4 * whole * whole));
|
|
18
|
+
return { low: round(Math.max(0, (centre - margin) / denominator)), high: round(Math.min(1, (centre + margin) / denominator)), level: 0.95 };
|
|
19
|
+
};
|
|
20
|
+
/** Windows with a known result: a step with no result is in neither count, so it is in no rate. */
|
|
21
|
+
export const settled = (cell) => cell.ok + cell.failed;
|
|
22
|
+
export const total = (cells, count) => {
|
|
23
|
+
let sum = 0;
|
|
24
|
+
for (const cell of cells)
|
|
25
|
+
sum += count(cell);
|
|
26
|
+
return sum;
|
|
27
|
+
};
|
|
28
|
+
export const groupBy = (items, keyOf) => {
|
|
29
|
+
const groups = new Map();
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
const key = keyOf(item);
|
|
32
|
+
const group = groups.get(key);
|
|
33
|
+
if (group === undefined)
|
|
34
|
+
groups.set(key, [item]);
|
|
35
|
+
else
|
|
36
|
+
group.push(item);
|
|
37
|
+
}
|
|
38
|
+
return groups;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The pool's cells at one grain (rule 6). Weekly and day cells share a table and count the same steps, so a read that sums over both counts a step twice and a read that
|
|
42
|
+
* names a day from a week answers from the wrong grain: every read takes the grain it answers from here, and no read iterates `pool.cells` itself.
|
|
43
|
+
*/
|
|
44
|
+
export const atGrain = (pool, grain) => pool.cells.filter((cell) => cell.grain === grain && !isEnvironmentCell(cell));
|
|
45
|
+
/** The (name, `platform`, `shell`) cells, which sit beside the version cells and count the same calls: a read that summed them with the version cells would count each call twice, so only the read of these asks. */
|
|
46
|
+
export const environmentCells = (pool) => pool.cells.filter(isEnvironmentCell);
|
|
47
|
+
/** The cells of one-step motifs at one grain: every action, and every identity capture has named. */
|
|
48
|
+
export const unitCells = (pool, grain) => {
|
|
49
|
+
const units = [];
|
|
50
|
+
for (const cell of atGrain(pool, grain)) {
|
|
51
|
+
const motif = pool.motifs.get(cell.motifId);
|
|
52
|
+
if (motif === undefined || motif.steps.length !== 1)
|
|
53
|
+
continue;
|
|
54
|
+
if (motif.level === 'named' && motif.steps[0]?.identity === null)
|
|
55
|
+
continue;
|
|
56
|
+
units.push({ cell, motif });
|
|
57
|
+
}
|
|
58
|
+
return units;
|
|
59
|
+
};
|
|
60
|
+
/** Counts over every one-step `shape` cell, for shares: a named motif's share is of all steps, like its action's. */
|
|
61
|
+
export const shapeUnits = (pool, grain) => unitCells(pool, grain).filter(({ motif }) => motif.level === 'shape').map(({ cell }) => cell);
|
|
62
|
+
export const body = (family, cells, fields, method = METHODS[family]) => ({
|
|
63
|
+
family,
|
|
64
|
+
method: method.name,
|
|
65
|
+
methodVersion: method.version,
|
|
66
|
+
inputs: cells.map(cellIdOf),
|
|
67
|
+
// Cell counts cannot give distinct accounts across cells, so this is the floor they do prove, which never overstates what a served floor may rely on.
|
|
68
|
+
clusters: cells.reduce((highest, cell) => Math.max(highest, cell.clusters), 0),
|
|
69
|
+
basis: 'all',
|
|
70
|
+
...fields,
|
|
71
|
+
});
|
|
72
|
+
export const firstDayOf = (days) => days.reduce((first, day) => (first === null || day < first ? day : first), null);
|
|
73
|
+
export const lastDayOf = (days) => days.reduce((last, day) => (last === null || day > last ? day : last), null);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// A tool on a day, at order 1: its failure rate, which the interval is on, and its share of that day's steps. The reads above it need both on a time axis, because this store
|
|
2
|
+
// has too few version boundaries to cut in time. Shape level only, so a day's steps are counted once. Confounders: the day's mix of work, effort, and rule commits.
|
|
3
|
+
import { motifName } from './motif.js';
|
|
4
|
+
import { body, groupBy, rate, settled, total, unitCells, wilson } from './read-support.js';
|
|
5
|
+
export const deriveToolDay = ({ pool }) => {
|
|
6
|
+
const known = unitCells(pool, 'day').filter(({ motif }) => motif.level === 'shape');
|
|
7
|
+
const steps = new Map([...groupBy(known, ({ cell }) => cell.day)].map(([day, group]) => [day, total(group.map(({ cell }) => cell), settled)]));
|
|
8
|
+
return [...groupBy(known, ({ cell }) => `${cell.motifId}/${cell.day}`).values()].flatMap((group) => {
|
|
9
|
+
const [first] = group;
|
|
10
|
+
const cells = group.map(({ cell }) => cell);
|
|
11
|
+
const n = total(cells, settled);
|
|
12
|
+
if (first === undefined || n === 0)
|
|
13
|
+
return [];
|
|
14
|
+
const failed = total(cells, (cell) => cell.failed);
|
|
15
|
+
const of = steps.get(first.cell.day) ?? 0;
|
|
16
|
+
return [
|
|
17
|
+
body('tool-day', cells, {
|
|
18
|
+
slice: { motif: motifName(first.motif), level: first.motif.level, day: first.cell.day },
|
|
19
|
+
// `steps` and not `of`: this read's interval rests on the tool's own calls, and `of` would hold it to the floor by the day's.
|
|
20
|
+
value: { failureRate: rate(failed, n), failed, share: rate(n, of), steps: of },
|
|
21
|
+
n,
|
|
22
|
+
interval: wilson(failed, n),
|
|
23
|
+
}),
|
|
24
|
+
];
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Candidate, type DeriveRelation, type Estimate } from './read-order.js';
|
|
2
|
+
import type { Read } from './read.js';
|
|
3
|
+
/** One version bucket of one harness, from its change reads. */
|
|
4
|
+
export interface Bucket {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Settled steps in the bucket. */
|
|
7
|
+
of: number;
|
|
8
|
+
firstWeek: string | null;
|
|
9
|
+
lastWeek: string | null;
|
|
10
|
+
reads: ReadonlyMap<string, Read>;
|
|
11
|
+
/** A read that states `of`, cited when the tool being compared has no read here. */
|
|
12
|
+
witness: Read;
|
|
13
|
+
}
|
|
14
|
+
export interface Boundary {
|
|
15
|
+
harness: string;
|
|
16
|
+
from: Bucket;
|
|
17
|
+
to: Bucket;
|
|
18
|
+
}
|
|
19
|
+
export declare const toolKey: (read: Read) => string;
|
|
20
|
+
/** Each harness's neighboring version buckets, in version order: `2.1.9x` comes before `2.1.10x`. An unknown version has no place in that order. */
|
|
21
|
+
export declare const boundariesOf: (reads: readonly Read[]) => Boundary[];
|
|
22
|
+
/** A tool's share in a bucket, and what to cite for it. With no read of the tool there, its share is zero of the bucket's steps, and that is a measurement, not a gap. */
|
|
23
|
+
export declare const shareIn: (bucket: Bucket, key: string) => {
|
|
24
|
+
read: Read;
|
|
25
|
+
share: Estimate;
|
|
26
|
+
} | undefined;
|
|
27
|
+
/** Every tool on either side of a boundary. */
|
|
28
|
+
export declare const toolsAt: ({ from, to }: Boundary) => string[];
|
|
29
|
+
/** The comparisons a store's change reads allow, above the floor, whether or not any of them moved. */
|
|
30
|
+
export declare const versionBoundaryCandidates: (reads: readonly Read[]) => Candidate[];
|
|
31
|
+
export declare const deriveVersionBoundary: DeriveRelation;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Version boundary (spec Section 6, order 2): what moved between two neighboring harness version buckets, from order 1 change reads. Only moves past the screen exist, each
|
|
2
|
+
// with how many comparisons it was screened among. A step counts as independent in the inputs and steps cluster in sessions, so the intervals run narrow, which is one reason
|
|
3
|
+
// the screen runs wide. Confounders it does not remove: date (a later bucket is mostly later days, and `fromLastWeek` and `toFirstWeek` show any overlap), effort, role by brief,
|
|
4
|
+
// rule commits, and class drift. A model version boundary is not readable here: a cell holds a model's name and no version of it (`read-readable.ts`).
|
|
5
|
+
import { UNKNOWN } from './cells.js';
|
|
6
|
+
import { candidateOf, differenceOf, estimateOf, screen, trialsOf, TRIALS_FLOOR } from './read-order.js';
|
|
7
|
+
import { firstDayOf, groupBy, lastDayOf, wilson } from './read-support.js';
|
|
8
|
+
const weekOf = (read, field) => {
|
|
9
|
+
const week = read.value[field];
|
|
10
|
+
return typeof week === 'string' ? [week] : [];
|
|
11
|
+
};
|
|
12
|
+
export const toolKey = (read) => `${read.slice.level ?? UNKNOWN}\n${read.slice.motif ?? UNKNOWN}`;
|
|
13
|
+
const bucketOf = (name, reads) => ({
|
|
14
|
+
name,
|
|
15
|
+
of: trialsOf(reads[0]),
|
|
16
|
+
firstWeek: firstDayOf(reads.flatMap((read) => weekOf(read, 'firstWeek'))),
|
|
17
|
+
lastWeek: lastDayOf(reads.flatMap((read) => weekOf(read, 'lastWeek'))),
|
|
18
|
+
reads: new Map(reads.map((read) => [toolKey(read), read])),
|
|
19
|
+
witness: reads[0],
|
|
20
|
+
});
|
|
21
|
+
const isNonEmpty = (reads) => reads.length > 0;
|
|
22
|
+
/** Each harness's neighboring version buckets, in version order: `2.1.9x` comes before `2.1.10x`. An unknown version has no place in that order. */
|
|
23
|
+
export const boundariesOf = (reads) => {
|
|
24
|
+
const change = reads.filter((read) => read.family === 'change' && read.slice.harnessBucket !== UNKNOWN);
|
|
25
|
+
return [...groupBy(change, (read) => read.slice.harness ?? UNKNOWN)].flatMap(([harness, group]) => {
|
|
26
|
+
const buckets = [...groupBy(group, (read) => read.slice.harnessBucket ?? UNKNOWN)]
|
|
27
|
+
.flatMap(([name, inBucket]) => (isNonEmpty(inBucket) ? [bucketOf(name, inBucket)] : []))
|
|
28
|
+
.sort((a, b) => a.name.localeCompare(b.name, 'en', { numeric: true }));
|
|
29
|
+
return buckets.slice(1).flatMap((to, index) => {
|
|
30
|
+
const from = buckets[index];
|
|
31
|
+
return from === undefined ? [] : [{ harness, from, to }];
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
/** A tool's share in a bucket, and what to cite for it. With no read of the tool there, its share is zero of the bucket's steps, and that is a measurement, not a gap. */
|
|
36
|
+
export const shareIn = (bucket, key) => {
|
|
37
|
+
const read = bucket.reads.get(key);
|
|
38
|
+
if (read !== undefined) {
|
|
39
|
+
const share = estimateOf(read, 'share');
|
|
40
|
+
return share === undefined ? undefined : { read, share };
|
|
41
|
+
}
|
|
42
|
+
const interval = wilson(0, bucket.of);
|
|
43
|
+
return interval === null ? undefined : { read: bucket.witness, share: { value: 0, interval } };
|
|
44
|
+
};
|
|
45
|
+
/** Every tool on either side of a boundary. */
|
|
46
|
+
export const toolsAt = ({ from, to }) => [...new Set([...from.reads.keys(), ...to.reads.keys()])];
|
|
47
|
+
const slicesOf = (boundary, key, metric) => {
|
|
48
|
+
const [level = UNKNOWN, motif = UNKNOWN] = key.split('\n');
|
|
49
|
+
return { motif, level, harness: boundary.harness, from: boundary.from.name, to: boundary.to.name, metric };
|
|
50
|
+
};
|
|
51
|
+
const movement = (boundary, move, before, after) => ({
|
|
52
|
+
before,
|
|
53
|
+
after,
|
|
54
|
+
move: move.value,
|
|
55
|
+
fromLastWeek: boundary.from.lastWeek,
|
|
56
|
+
toFirstWeek: boundary.to.firstWeek,
|
|
57
|
+
});
|
|
58
|
+
const shareCandidate = (boundary, key) => {
|
|
59
|
+
const earlier = shareIn(boundary.from, key);
|
|
60
|
+
const later = shareIn(boundary.to, key);
|
|
61
|
+
if (earlier === undefined || later === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const move = differenceOf(later.share, earlier.share);
|
|
64
|
+
return candidateOf('version-boundary', [earlier.read, later.read], [move], {
|
|
65
|
+
slice: slicesOf(boundary, key, 'share'),
|
|
66
|
+
value: movement(boundary, move, earlier.share.value, later.share.value),
|
|
67
|
+
interval: move.interval,
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
/** The change read's interval is on its share, so a failure rate's comes from the same read's count and rate. */
|
|
71
|
+
const failureOf = (read) => {
|
|
72
|
+
const value = read?.value.failureRate;
|
|
73
|
+
if (read === undefined || typeof value !== 'number' || read.n < TRIALS_FLOOR)
|
|
74
|
+
return undefined;
|
|
75
|
+
const interval = wilson(value * read.n, read.n);
|
|
76
|
+
return interval === null ? undefined : { value, interval };
|
|
77
|
+
};
|
|
78
|
+
const failureCandidate = (boundary, key) => {
|
|
79
|
+
const earlierRead = boundary.from.reads.get(key);
|
|
80
|
+
const laterRead = boundary.to.reads.get(key);
|
|
81
|
+
const earlier = failureOf(earlierRead);
|
|
82
|
+
const later = failureOf(laterRead);
|
|
83
|
+
if (earlierRead === undefined || laterRead === undefined || earlier === undefined || later === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
const move = differenceOf(later, earlier);
|
|
86
|
+
return candidateOf('version-boundary', [earlierRead, laterRead], [move], {
|
|
87
|
+
slice: slicesOf(boundary, key, 'failureRate'),
|
|
88
|
+
value: movement(boundary, move, earlier.value, later.value),
|
|
89
|
+
interval: move.interval,
|
|
90
|
+
n: Math.min(earlierRead.n, laterRead.n),
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
/** The comparisons a store's change reads allow, above the floor, whether or not any of them moved. */
|
|
94
|
+
export const versionBoundaryCandidates = (reads) => boundariesOf(reads).flatMap((boundary) => toolsAt(boundary).flatMap((key) => [shareCandidate(boundary, key), failureCandidate(boundary, key)].filter((candidate) => candidate !== undefined)));
|
|
95
|
+
export const deriveVersionBoundary = (reads) => screen(versionBoundaryCandidates(reads));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** SHA-256 of canonical JSON, 16 hex characters, unsalted: one content reads alike on any account (D1's `hP`). */
|
|
2
|
+
export declare const contentHash: (value: unknown) => string;
|
|
3
|
+
/** The eight families, in rank order, recovery first: the only one that changes a next call today. Then `tool-day`, which the reads above order 1 stand on. */
|
|
4
|
+
export declare const FIRST_ORDER_FAMILIES: readonly ["recovery", "fit", "cost", "change", "live", "adoption", "method", "next-step", "tool-day"];
|
|
5
|
+
export type FirstOrderFamily = (typeof FIRST_ORDER_FAMILIES)[number];
|
|
6
|
+
/** Families that read other reads, lowest order first. `lead-lag` is the labeled experiment (spec Section 6). */
|
|
7
|
+
export declare const FAMILIES: readonly ["recovery", "fit", "cost", "change", "live", "adoption", "method", "next-step", "tool-day", "version-boundary", "substitution", "interaction", "day-move", "lead-lag"];
|
|
8
|
+
export type Family = (typeof FAMILIES)[number];
|
|
9
|
+
/**
|
|
10
|
+
* Each family's method and its version. A rule change bumps its version here, and the pool's version hashes this table, so a stored file of the old rule is not served.
|
|
11
|
+
* It lives here and not beside each family because the pool hash needs it, and a family module imports the cells the hash is in.
|
|
12
|
+
*/
|
|
13
|
+
export declare const METHODS: {
|
|
14
|
+
readonly [family in Family]: {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly version: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/** The fit family's other method (`read-call-shape.ts`). The pool's version hashes it beside the table, for the same reason. */
|
|
20
|
+
export declare const CALL_SHAPE_METHOD: {
|
|
21
|
+
readonly name: "call-shape";
|
|
22
|
+
readonly version: "2";
|
|
23
|
+
};
|
|
24
|
+
/** `all`, or `unexposed`: only steps outside any recall's consequence window, so a read cannot mistake the pool's influence for the world. */
|
|
25
|
+
export type ReadBasis = 'all' | 'unexposed';
|
|
26
|
+
/** The coordinates a read covers, as public names. */
|
|
27
|
+
export interface ReadSlice {
|
|
28
|
+
[axis: string]: string;
|
|
29
|
+
}
|
|
30
|
+
/** What a read says. Closed vocabulary and numbers only: a read never holds a path, a prompt, or an error's text. */
|
|
31
|
+
export interface ReadValue {
|
|
32
|
+
[metric: string]: number | string | null | {
|
|
33
|
+
[name: string]: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** A 95 percent interval on a read's headline number: a rate at order 1, propagated from the inputs above it (`read-order.ts`), so it can run below zero. Null when the value is not a rate. */
|
|
37
|
+
export interface ReadInterval {
|
|
38
|
+
low: number;
|
|
39
|
+
high: number;
|
|
40
|
+
level: number;
|
|
41
|
+
}
|
|
42
|
+
/** The pool a read was computed from: a later version is a new read, even when the value comes out the same. */
|
|
43
|
+
export interface ReadAsOf {
|
|
44
|
+
pool: string;
|
|
45
|
+
/** The newest day in that pool; null when no cell has a known day. */
|
|
46
|
+
day: string | null;
|
|
47
|
+
}
|
|
48
|
+
export interface Read {
|
|
49
|
+
/** `read:<contentHash>` of every other field. */
|
|
50
|
+
readId: string;
|
|
51
|
+
family: Family;
|
|
52
|
+
method: string;
|
|
53
|
+
methodVersion: string;
|
|
54
|
+
/** The IDs this read cites, sorted, each `cell:<hash>` or `read:<hash>`. Never empty. A rate's denominator is a total of the pool `asOf` pins, so it is not listed. */
|
|
55
|
+
inputs: string[];
|
|
56
|
+
/** 1 + the highest order among `inputs`. */
|
|
57
|
+
order: number;
|
|
58
|
+
slice: ReadSlice;
|
|
59
|
+
value: ReadValue;
|
|
60
|
+
/** The observations behind the value. At order 1 its own count; above order 1 the trials of its weakest input, or the observations a relation names, such as pairs. */
|
|
61
|
+
n: number;
|
|
62
|
+
/** The accounts behind the value, as its inputs state it. One operator's store says 1. */
|
|
63
|
+
clusters: number;
|
|
64
|
+
interval: ReadInterval | null;
|
|
65
|
+
asOf: ReadAsOf;
|
|
66
|
+
basis: ReadBasis;
|
|
67
|
+
}
|
|
68
|
+
/** A read before its inputs are checked and its ID and order are computed. */
|
|
69
|
+
export type ReadDraft = Omit<Read, 'readId' | 'order'>;
|
|
70
|
+
export declare const cellRef: (hash: string) => string;
|
|
71
|
+
/** Looks up an input's order; undefined when this input is not a cell ID and no read known here has it. */
|
|
72
|
+
export type OrderResolver = (input: string) => number | undefined;
|
|
73
|
+
/** Orders from the IDs themselves for cells, and from these reads for a read. */
|
|
74
|
+
export declare const orderResolver: (reads: readonly Read[]) => OrderResolver;
|
|
75
|
+
export declare const readIdOf: (read: Omit<Read, "readId">) => string;
|
|
76
|
+
/** A read's ID pins every field down to the last count, so none of them may change below the top either. */
|
|
77
|
+
export declare const frozenRead: (read: Read) => Read;
|
|
78
|
+
/** The one way a read comes to exist. It refuses a read that cites nothing or cites what it cannot place, since either would have no lineage. */
|
|
79
|
+
export declare const makeRead: (draft: ReadDraft, orderOf: OrderResolver) => Read;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// A live read's baseline is not traceable through lineage: the read cites only the sliced day's cells, and `value.baseline` comes from cells it does not cite.
|
|
2
|
+
// The read (recall spec Section 2.6): any derived value, citing its inputs. A read's ID is the hash of its content and a read never changes, so
|
|
3
|
+
// a recall can cite one and lineage can walk back from it. Its order is computed from its inputs and has no ceiling: a cell is 0 and a read is one more
|
|
4
|
+
// than the highest order it cites, so a read over reads needs nothing here that a read over cells does not.
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
/** A value, read by content: object keys are sorted, so two writers of one value hash alike. */
|
|
7
|
+
const canonical = (value) => {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.map(canonical);
|
|
10
|
+
if (typeof value !== 'object' || value === null)
|
|
11
|
+
return value;
|
|
12
|
+
return Object.fromEntries(Object.entries(value)
|
|
13
|
+
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
14
|
+
.map(([key, entry]) => [key, canonical(entry)]));
|
|
15
|
+
};
|
|
16
|
+
/** SHA-256 of canonical JSON, 16 hex characters, unsalted: one content reads alike on any account (D1's `hP`). */
|
|
17
|
+
export const contentHash = (value) => createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex').slice(0, 16);
|
|
18
|
+
/** The eight families, in rank order, recovery first: the only one that changes a next call today. Then `tool-day`, which the reads above order 1 stand on. */
|
|
19
|
+
export const FIRST_ORDER_FAMILIES = ['recovery', 'fit', 'cost', 'change', 'live', 'adoption', 'method', 'next-step', 'tool-day'];
|
|
20
|
+
/** Families that read other reads, lowest order first. `lead-lag` is the labeled experiment (spec Section 6). */
|
|
21
|
+
export const FAMILIES = [...FIRST_ORDER_FAMILIES, 'version-boundary', 'substitution', 'interaction', 'day-move', 'lead-lag'];
|
|
22
|
+
/**
|
|
23
|
+
* Each family's method and its version. A rule change bumps its version here, and the pool's version hashes this table, so a stored file of the old rule is not served.
|
|
24
|
+
* It lives here and not beside each family because the pool hash needs it, and a family module imports the cells the hash is in.
|
|
25
|
+
*/
|
|
26
|
+
export const METHODS = {
|
|
27
|
+
recovery: { name: 'signature-record', version: '2' },
|
|
28
|
+
fit: { name: 'failure-rate', version: '2' },
|
|
29
|
+
cost: { name: 'per-prompt-by-class', version: '1' },
|
|
30
|
+
change: { name: 'share-in-version-bucket', version: '2' },
|
|
31
|
+
live: { name: 'day-against-trailing-week', version: '2' },
|
|
32
|
+
adoption: { name: 'share-and-span', version: '2' },
|
|
33
|
+
method: { name: 'shape-share-by-model', version: '1' },
|
|
34
|
+
'next-step': { name: 'next-action-counts', version: '1' },
|
|
35
|
+
'tool-day': { name: 'rates-on-day', version: '1' },
|
|
36
|
+
'version-boundary': { name: 'move-between-buckets', version: '2' },
|
|
37
|
+
substitution: { name: 'rise-against-fall', version: '1' },
|
|
38
|
+
interaction: { name: 'failure-gap-across-tier-assigned', version: '3' },
|
|
39
|
+
'day-move': { name: 'move-to-next-day', version: '2' },
|
|
40
|
+
'lead-lag': { name: 'failure-move-leads-share-move', version: '1' },
|
|
41
|
+
};
|
|
42
|
+
/** The fit family's other method (`read-call-shape.ts`). The pool's version hashes it beside the table, for the same reason. */
|
|
43
|
+
export const CALL_SHAPE_METHOD = { name: 'call-shape', version: '2' };
|
|
44
|
+
const ORDER_OF_CELL = 0;
|
|
45
|
+
const CELL_PREFIX = 'cell:';
|
|
46
|
+
const READ_PREFIX = 'read:';
|
|
47
|
+
export const cellRef = (hash) => `${CELL_PREFIX}${hash}`;
|
|
48
|
+
/** Orders from the IDs themselves for cells, and from these reads for a read. */
|
|
49
|
+
export const orderResolver = (reads) => {
|
|
50
|
+
const orders = new Map(reads.map((read) => [read.readId, read.order]));
|
|
51
|
+
return (input) => (input.startsWith(CELL_PREFIX) ? ORDER_OF_CELL : orders.get(input));
|
|
52
|
+
};
|
|
53
|
+
export const readIdOf = (read) => {
|
|
54
|
+
const { family, method, methodVersion, inputs, order, slice, value, n, clusters, interval, asOf, basis } = read;
|
|
55
|
+
return `${READ_PREFIX}${contentHash({ family, method, methodVersion, inputs, order, slice, value, n, clusters, interval, asOf, basis })}`;
|
|
56
|
+
};
|
|
57
|
+
const deepFreeze = (value) => {
|
|
58
|
+
if (typeof value !== 'object' || value === null)
|
|
59
|
+
return;
|
|
60
|
+
Object.values(value).forEach((entry) => {
|
|
61
|
+
deepFreeze(entry);
|
|
62
|
+
});
|
|
63
|
+
Object.freeze(value);
|
|
64
|
+
};
|
|
65
|
+
/** A read's ID pins every field down to the last count, so none of them may change below the top either. */
|
|
66
|
+
export const frozenRead = (read) => {
|
|
67
|
+
deepFreeze(read);
|
|
68
|
+
return read;
|
|
69
|
+
};
|
|
70
|
+
/** The one way a read comes to exist. It refuses a read that cites nothing or cites what it cannot place, since either would have no lineage. */
|
|
71
|
+
export const makeRead = (draft, orderOf) => {
|
|
72
|
+
const inputs = [...new Set(draft.inputs)].sort();
|
|
73
|
+
if (inputs.length === 0)
|
|
74
|
+
throw new Error(`A ${draft.family} read cites no input.`);
|
|
75
|
+
let highest = -1;
|
|
76
|
+
for (const input of inputs) {
|
|
77
|
+
const order = orderOf(input);
|
|
78
|
+
if (order === undefined)
|
|
79
|
+
throw new Error(`A ${draft.family} read cites ${input}, whose order is not known.`);
|
|
80
|
+
highest = Math.max(highest, order);
|
|
81
|
+
}
|
|
82
|
+
const body = { ...draft, inputs, order: 1 + highest };
|
|
83
|
+
return frozenRead({ readId: readIdOf(body), ...body });
|
|
84
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type CountedStep } from './motif.js';
|
|
2
|
+
import type { Recall } from './recall-object.js';
|
|
3
|
+
import { type ToolObservation } from './schema.js';
|
|
4
|
+
import type { Store } from './store.js';
|
|
5
|
+
/** A consequence window ends at the prompt's end or after this many calls (spec Section 2.7). */
|
|
6
|
+
export declare const WINDOW_CALLS = 20;
|
|
7
|
+
/** Fills `n` and `consequence`. A recall with no session, or from a session this store does not hold, has nothing to read and comes back as it was. */
|
|
8
|
+
export declare const deriveConsequences: (store: Store, recalls: readonly Recall[]) => Recall[];
|
|
9
|
+
/** A step as a cell counts it, and whether it sits inside a shown recall's window. */
|
|
10
|
+
export interface PlacedStep {
|
|
11
|
+
step: CountedStep;
|
|
12
|
+
exposed: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** What recalls add to the graph: the one seam `deriveCells` reads for `recall.*` steps and `exposed`. */
|
|
15
|
+
export interface RecallGraph {
|
|
16
|
+
place: (tool: ToolObservation, steps: readonly CountedStep[]) => PlacedStep[];
|
|
17
|
+
exposedPrompt: (session: string, index: number) => boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const NO_RECALLS: RecallGraph;
|
|
20
|
+
/**
|
|
21
|
+
* Takes recalls that have been through `deriveConsequences`. An MCP pull has no session id, so the only step it has is the one in its transcript; a pull that names its session
|
|
22
|
+
* (`--session`) has no MCP call, so its step is this one.
|
|
23
|
+
*/
|
|
24
|
+
export declare const recallGraphOf: (store: Store, recalls: readonly Recall[]) => RecallGraph;
|
|
25
|
+
/** One row per mode, arm, and answer kind. Counts only. */
|
|
26
|
+
export interface RecallCountRow {
|
|
27
|
+
mode: Recall['mode'];
|
|
28
|
+
arm: Recall['arm'];
|
|
29
|
+
kind: Recall['answer']['kind'];
|
|
30
|
+
recalls: number;
|
|
31
|
+
sessions: number;
|
|
32
|
+
/** The size of what was injected, or for a held recall would have been. */
|
|
33
|
+
tokens: number;
|
|
34
|
+
/** Recalls with a consequence window, and of those how many followed. */
|
|
35
|
+
windows: number;
|
|
36
|
+
followed: number;
|
|
37
|
+
windowCalls: number;
|
|
38
|
+
/** Null when no window in the row measured any tokens. */
|
|
39
|
+
windowTokens: number | null;
|
|
40
|
+
/** Held windows that share a call with a shown recall's window. The arm is drawn per session and shape, so one session can hold both, and such a window is a contaminated baseline. */
|
|
41
|
+
overlapped: number;
|
|
42
|
+
}
|
|
43
|
+
/** Takes recalls that have been through `deriveConsequences`; `day` is a UTC day, so one day of hooks reads as one set of rows. A shown window from any day can overlap a held one. */
|
|
44
|
+
export declare const recallCounts: (recalls: readonly Recall[], day?: string) => RecallCountRow[];
|
|
45
|
+
export declare const formatRecallCounts: (rows: readonly RecallCountRow[]) => string;
|