@manyoars/oars 0.2.0 → 0.4.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 +147 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +10 -4
- package/dist/cli.js +440 -91
- 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 -3
- 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 +47 -0
- package/dist/gate/hook-process.d.ts +24 -0
- package/dist/gate/hook-process.js +112 -0
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +48 -0
- package/dist/gate/hook.js +259 -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 +254 -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 +50 -0
- package/dist/issuer.d.ts +10 -6
- package/dist/issuer.js +19 -24
- package/dist/main.d.ts +1 -0
- package/dist/main.js +67 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +429 -0
- package/dist/sessions/allow.d.ts +23 -0
- package/dist/sessions/allow.js +71 -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 +190 -0
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -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 +47 -0
- package/dist/sessions/cell-increments.js +243 -0
- package/dist/sessions/cells.d.ts +109 -0
- package/dist/sessions/cells.js +261 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +745 -0
- package/dist/sessions/command.d.ts +127 -0
- package/dist/sessions/command.js +419 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -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 +386 -0
- package/dist/sessions/exclusion.d.ts +87 -0
- package/dist/sessions/exclusion.js +361 -0
- package/dist/sessions/extract.d.ts +181 -0
- package/dist/sessions/extract.js +1806 -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 +66 -0
- package/dist/sessions/hook-index.js +164 -0
- package/dist/sessions/hooks.d.ts +17 -0
- package/dist/sessions/hooks.js +281 -0
- package/dist/sessions/identity.d.ts +27 -0
- package/dist/sessions/identity.js +131 -0
- package/dist/sessions/install.d.ts +70 -0
- package/dist/sessions/install.js +246 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +7 -0
- package/dist/sessions/mcp.js +157 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +44 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/opencode.d.ts +33 -0
- package/dist/sessions/opencode.js +319 -0
- package/dist/sessions/otel.d.ts +60 -0
- package/dist/sessions/otel.js +494 -0
- package/dist/sessions/package-books.d.ts +48 -0
- package/dist/sessions/package-books.js +120 -0
- package/dist/sessions/package-resolve.d.ts +32 -0
- package/dist/sessions/package-resolve.js +185 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pi.d.ts +36 -0
- package/dist/sessions/pi.js +353 -0
- package/dist/sessions/pool-books.d.ts +21 -0
- package/dist/sessions/pool-books.js +75 -0
- package/dist/sessions/pool-choices.d.ts +5 -0
- package/dist/sessions/pool-choices.js +11 -0
- package/dist/sessions/pool-command.d.ts +115 -0
- package/dist/sessions/pool-command.js +669 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-nightly-deps.d.ts +3 -0
- package/dist/sessions/pool-nightly-deps.js +11 -0
- package/dist/sessions/pool-nightly.d.ts +10 -0
- package/dist/sessions/pool-nightly.js +85 -0
- package/dist/sessions/pool-outbox.d.ts +76 -0
- package/dist/sessions/pool-outbox.js +313 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-raw.d.ts +27 -0
- package/dist/sessions/pool-raw.js +68 -0
- package/dist/sessions/pool-replica.d.ts +35 -0
- package/dist/sessions/pool-replica.js +91 -0
- package/dist/sessions/pool-send-lock.d.ts +20 -0
- package/dist/sessions/pool-send-lock.js +87 -0
- package/dist/sessions/pool-sent.d.ts +4 -0
- package/dist/sessions/pool-sent.js +26 -0
- package/dist/sessions/pool-totals.d.ts +10 -0
- package/dist/sessions/pool-totals.js +21 -0
- package/dist/sessions/pool-wire.d.ts +435 -0
- package/dist/sessions/pool-wire.js +1100 -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 +72 -0
- package/dist/sessions/public-vocabulary.js +295 -0
- package/dist/sessions/query.d.ts +123 -0
- package/dist/sessions/query.js +268 -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 +48 -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-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -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 +86 -0
- package/dist/sessions/read.js +87 -0
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-frames.d.ts +18 -0
- package/dist/sessions/recall-frames.js +44 -0
- package/dist/sessions/recall-next.d.ts +145 -0
- package/dist/sessions/recall-next.js +241 -0
- package/dist/sessions/recall-object.d.ts +139 -0
- package/dist/sessions/recall-object.js +78 -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 +47 -0
- package/dist/sessions/recall-reads.d.ts +124 -0
- package/dist/sessions/recall-reads.js +417 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +17 -0
- package/dist/sessions/redact.js +113 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +694 -0
- package/dist/sessions/schema.js +167 -0
- package/dist/sessions/session-shape.d.ts +76 -0
- package/dist/sessions/session-shape.js +355 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +233 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +309 -0
- package/dist/sessions/store.js +1498 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +27 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -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 +494 -0
- package/dist/telemetry/command.d.ts +8 -0
- package/dist/telemetry/command.js +85 -0
- package/dist/telemetry/consent.d.ts +17 -0
- package/dist/telemetry/consent.js +63 -0
- package/dist/telemetry/crash.d.ts +8 -0
- package/dist/telemetry/crash.js +52 -0
- package/dist/telemetry/emit.d.ts +31 -0
- package/dist/telemetry/emit.js +42 -0
- package/dist/telemetry/fields.d.ts +16 -0
- package/dist/telemetry/fields.js +26 -0
- package/dist/telemetry/health.d.ts +21 -0
- package/dist/telemetry/health.js +108 -0
- package/dist/telemetry/hook-emit.d.ts +4 -0
- package/dist/telemetry/hook-emit.js +18 -0
- package/dist/telemetry/measures.d.ts +32 -0
- package/dist/telemetry/measures.js +128 -0
- package/dist/telemetry/observe.d.ts +13 -0
- package/dist/telemetry/observe.js +51 -0
- package/dist/telemetry/read-events.d.ts +9 -0
- package/dist/telemetry/read-events.js +17 -0
- package/dist/telemetry/record.d.ts +17 -0
- package/dist/telemetry/record.js +26 -0
- package/dist/telemetry/report.d.ts +77 -0
- package/dist/telemetry/report.js +188 -0
- package/dist/telemetry/schema.d.ts +40 -0
- package/dist/telemetry/schema.js +187 -0
- package/dist/telemetry/send-main.d.ts +1 -0
- package/dist/telemetry/send-main.js +11 -0
- package/dist/telemetry/send.d.ts +36 -0
- package/dist/telemetry/send.js +172 -0
- package/dist/telemetry/spool.d.ts +17 -0
- package/dist/telemetry/spool.js +70 -0
- package/dist/telemetry/wire.d.ts +25 -0
- package/dist/telemetry/wire.js +76 -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 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- 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,81 @@
|
|
|
1
|
+
// Lead-lag (spec Section 6, order 3), the labeled experiment: does a tool's failure rate moving come before its share moving? Chosen over a model release moving tool choice
|
|
2
|
+
// because each model has one version here with no release date (ruling mrd5591-08), and two version boundaries are too few to cut in time, while consecutive days over many tools
|
|
3
|
+
// can be cut. A search over pairs of tools was rejected: it manufactures the false patterns Section 6 warns about, and this is one question asked once.
|
|
4
|
+
//
|
|
5
|
+
// A pair shares no day between its sides. A failure move ending on day d is paired with the share move from d+1 to d+2, not d to d+1: a burst of failing calls on d lifts both the
|
|
6
|
+
// day's failure rate and its share, so a pair that read d twice would show a negative slope with no lead in it, and that slope would hold on later days and pass the check.
|
|
7
|
+
//
|
|
8
|
+
// Fit on earlier days and scored on later ones, with a pair dated by the day its outcome is known. It exists or not by its floor and states its score either way, labeled in
|
|
9
|
+
// every output. No path serves it to an agent: Section 6 requires this check before one, and this read is the check's first run, not a licensed read.
|
|
10
|
+
//
|
|
11
|
+
// What the check does not remove: shares sum to one, so tools on one day are not independent; steps cluster in sessions, so intervals run narrow; a failure move measured with
|
|
12
|
+
// error flattens the slope; and one operator's changes of habit move many tools at once.
|
|
13
|
+
import { UNKNOWN } from './cells.js';
|
|
14
|
+
import { estimateOf, relate } from './read-order.js';
|
|
15
|
+
import { nextDay } from './read-day-move.js';
|
|
16
|
+
import { rate, round, wilson, Z_95 } from './read-support.js';
|
|
17
|
+
/** The fewest pairs on each side of the cut. */
|
|
18
|
+
export const PAIRS_FLOOR = 30;
|
|
19
|
+
const FIT_SHARE = 0.7;
|
|
20
|
+
const SIGMA_FLOOR = 0.0001;
|
|
21
|
+
const CHANCE = 0.5;
|
|
22
|
+
const keyOf = (motif, metric, from) => `${motif ?? UNKNOWN}\n${metric}\n${from ?? UNKNOWN}`;
|
|
23
|
+
export const leadLagPairs = (reads) => {
|
|
24
|
+
const moves = reads.filter((read) => read.family === 'day-move');
|
|
25
|
+
const byStart = new Map(moves.map((read) => [keyOf(read.slice.motif, read.slice.metric ?? UNKNOWN, read.slice.from), read]));
|
|
26
|
+
return moves.flatMap((lead) => {
|
|
27
|
+
if (lead.slice.metric !== 'failureRate')
|
|
28
|
+
return [];
|
|
29
|
+
const lag = byStart.get(keyOf(lead.slice.motif, 'share', nextDay(lead.slice.to ?? UNKNOWN)));
|
|
30
|
+
const x = estimateOf(lead, 'move');
|
|
31
|
+
const y = lag === undefined ? undefined : estimateOf(lag, 'move');
|
|
32
|
+
if (lag === undefined || x === undefined || y === undefined)
|
|
33
|
+
return [];
|
|
34
|
+
return [{ x, y, day: lag.slice.to ?? UNKNOWN, reads: [lead, lag] }];
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
/** A share move's weight: a thin day's move says less. The floor keeps one pair with no measured spread from outweighing the rest. */
|
|
38
|
+
const weightOf = ({ y }) => {
|
|
39
|
+
const sigma = Math.max((y.interval.high - y.interval.low) / (2 * Z_95), SIGMA_FLOOR);
|
|
40
|
+
return 1 / (sigma * sigma);
|
|
41
|
+
};
|
|
42
|
+
const weightedSum = (pairs, term) => pairs.reduce((sum, pair) => sum + weightOf(pair) * term(pair), 0);
|
|
43
|
+
/** The later days' weighted error against predicting no move at all: above zero means the slope from earlier days beat that. With nothing to explain there is no skill. */
|
|
44
|
+
const skillOf = (pairs, slope) => {
|
|
45
|
+
const zero = weightedSum(pairs, ({ y }) => y.value * y.value);
|
|
46
|
+
if (zero === 0)
|
|
47
|
+
return 0;
|
|
48
|
+
return 1 - weightedSum(pairs, ({ x, y }) => (y.value - slope * x.value) ** 2) / zero;
|
|
49
|
+
};
|
|
50
|
+
/** How often a later day's share moved the way the slope said, among pairs where both moved. */
|
|
51
|
+
const signsOf = (pairs, slope) => {
|
|
52
|
+
const moved = pairs.filter(({ x, y }) => x.value !== 0 && y.value !== 0);
|
|
53
|
+
const agreed = moved.filter(({ x, y }) => Math.sign(slope * x.value) === Math.sign(y.value)).length;
|
|
54
|
+
return { agreement: rate(agreed, moved.length), low: wilson(agreed, moved.length)?.low ?? 0 };
|
|
55
|
+
};
|
|
56
|
+
export const deriveLeadLag = (reads) => {
|
|
57
|
+
const pairs = leadLagPairs(reads);
|
|
58
|
+
const days = [...new Set(pairs.map(({ day }) => day))].sort();
|
|
59
|
+
const cut = Math.floor(days.length * FIT_SHARE);
|
|
60
|
+
const fitThrough = days[cut - 1];
|
|
61
|
+
const scoredFrom = days[cut];
|
|
62
|
+
if (fitThrough === undefined || scoredFrom === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
const fit = pairs.filter(({ day }) => day <= fitThrough);
|
|
65
|
+
const scored = pairs.filter(({ day }) => day >= scoredFrom);
|
|
66
|
+
const leverage = weightedSum(fit, ({ x }) => x.value * x.value);
|
|
67
|
+
if (fit.length < PAIRS_FLOOR || scored.length < PAIRS_FLOOR || leverage === 0)
|
|
68
|
+
return [];
|
|
69
|
+
const slope = weightedSum(fit, ({ x, y }) => x.value * y.value) / leverage;
|
|
70
|
+
const margin = Z_95 * Math.sqrt(1 / leverage);
|
|
71
|
+
const skill = skillOf(scored, slope);
|
|
72
|
+
const signs = signsOf(scored, slope);
|
|
73
|
+
const passed = Math.abs(slope) > margin && skill > 0 && signs.low > CHANCE;
|
|
74
|
+
const read = relate('lead-lag', pairs.flatMap((pair) => pair.reads), {
|
|
75
|
+
slice: { lead: 'failureRate', lag: 'share', level: 'shape' },
|
|
76
|
+
value: { label: 'experimental', check: passed ? 'passed' : 'failed', slope: round(slope), skill: round(skill), signAgreement: signs.agreement, fitPairs: fit.length, scorePairs: scored.length, fitThrough, scoredFrom },
|
|
77
|
+
interval: { low: round(slope - margin), high: round(slope + margin), level: 0.95 },
|
|
78
|
+
n: pairs.length,
|
|
79
|
+
});
|
|
80
|
+
return read === undefined ? [] : [read];
|
|
81
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type DeriveFamily } from './read-support.js';
|
|
2
|
+
/** The baseline: the seven days before the day read. */
|
|
3
|
+
export declare const LIVE_BASELINE_DAYS = 7;
|
|
4
|
+
/** The days a read of `day` takes its baseline from, so a pool that serves the baseline separately names the same cells. */
|
|
5
|
+
export declare const baselineDaysOf: (day: string) => string[];
|
|
6
|
+
export declare const deriveLive: DeriveFamily;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { motifName } from './motif.js';
|
|
2
|
+
import { body, groupBy, rate, settled, total, unitCells, wilson } from './read-support.js';
|
|
3
|
+
/** The baseline: the seven days before the day read. */
|
|
4
|
+
export const LIVE_BASELINE_DAYS = 7;
|
|
5
|
+
const DAY_MS = 86_400_000;
|
|
6
|
+
const dayBefore = (day, back) => new Date(Date.parse(day) - back * DAY_MS).toISOString().slice(0, 10);
|
|
7
|
+
/** The days a read of `day` takes its baseline from, so a pool that serves the baseline separately names the same cells. */
|
|
8
|
+
export const baselineDaysOf = (day) => Array.from({ length: LIVE_BASELINE_DAYS }, (_, index) => dayBefore(day, index + 1));
|
|
9
|
+
export const deriveLive = ({ pool }) => {
|
|
10
|
+
const units = unitCells(pool, 'day').filter(({ motif }) => motif.level === 'shape');
|
|
11
|
+
const stepsOn = new Map([...groupBy(units, ({ cell }) => cell.day)].map(([day, group]) => [day, total(group.map(({ cell }) => cell), settled)]));
|
|
12
|
+
return [...groupBy(units, ({ cell }) => cell.motifId).values()].flatMap((group) => {
|
|
13
|
+
const motif = group[0]?.motif;
|
|
14
|
+
if (motif === undefined)
|
|
15
|
+
return [];
|
|
16
|
+
const byDay = groupBy(group.map(({ cell }) => cell), (cell) => cell.day);
|
|
17
|
+
return [...byDay].flatMap(([day, cells]) => {
|
|
18
|
+
const n = total(cells, settled);
|
|
19
|
+
if (n === 0)
|
|
20
|
+
return [];
|
|
21
|
+
const of = stepsOn.get(day) ?? 0;
|
|
22
|
+
const trailing = baselineDaysOf(day);
|
|
23
|
+
const baselineCells = trailing.flatMap((earlier) => byDay.get(earlier) ?? []);
|
|
24
|
+
const baselineOf = trailing.reduce((sum, earlier) => sum + (stepsOn.get(earlier) ?? 0), 0);
|
|
25
|
+
const share = rate(n, of);
|
|
26
|
+
const baseline = rate(total(baselineCells, settled), baselineOf);
|
|
27
|
+
return [
|
|
28
|
+
// The read cites the day's cells only: a pool counts accounts over a read's inputs, and the baseline must not lift the floor of a day it does not hold (Section 4 rule 1).
|
|
29
|
+
body('live', cells, {
|
|
30
|
+
slice: { motif: motifName(motif), day },
|
|
31
|
+
value: { share, baseline, of, ratio: share === null || baseline === null || baseline === 0 ? null : rate(share, baseline) },
|
|
32
|
+
n,
|
|
33
|
+
interval: wilson(n, of),
|
|
34
|
+
}),
|
|
35
|
+
];
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Method (rank 7): a shape's share of all windows of its length, per model. The family would also read this by class; a cell holds no class, because a step has none, so
|
|
2
|
+
// the class axis waits for a field on the cell. Confounders: task assignment by tier, effort, and no outcome label, so nothing here says a method is better.
|
|
3
|
+
import { bucketFields } from './cells.js';
|
|
4
|
+
import { motifName } from './motif.js';
|
|
5
|
+
import { body, atGrain, groupBy, rate, settled, total, wilson } from './read-support.js';
|
|
6
|
+
export const deriveMethod = ({ pool }) => {
|
|
7
|
+
const windows = atGrain(pool, 'week').flatMap((cell) => {
|
|
8
|
+
const motif = pool.motifs.get(cell.motifId);
|
|
9
|
+
return motif?.level === 'shape' && motif.steps.length >= 2 ? [{ cell, motif, model: bucketFields(cell.frameBucket).model }] : [];
|
|
10
|
+
});
|
|
11
|
+
const scopeTotals = new Map([...groupBy(windows, ({ motif, model }) => `${String(motif.steps.length)}/${model}`)].map(([scope, group]) => [scope, total(group.map(({ cell }) => cell), settled)]));
|
|
12
|
+
return [...groupBy(windows, ({ cell, model }) => `${cell.motifId}/${model}`).values()].flatMap((group) => {
|
|
13
|
+
const [first] = group;
|
|
14
|
+
const cells = group.map(({ cell }) => cell);
|
|
15
|
+
const n = total(cells, settled);
|
|
16
|
+
if (first === undefined || n === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const length = first.motif.steps.length;
|
|
19
|
+
const of = scopeTotals.get(`${String(length)}/${first.model}`) ?? 0;
|
|
20
|
+
return [
|
|
21
|
+
body('method', cells, {
|
|
22
|
+
slice: { motif: motifName(first.motif), model: first.model, length: String(length) },
|
|
23
|
+
value: { share: rate(n, of), of, failureRate: rate(total(cells, (cell) => cell.failed), n) },
|
|
24
|
+
n,
|
|
25
|
+
interval: wilson(n, of),
|
|
26
|
+
}),
|
|
27
|
+
];
|
|
28
|
+
});
|
|
29
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Next step (rank 8): from a prefix of one to three actions, which action came next, by count. Confounders: no outcome label, so the most common next step is
|
|
2
|
+
// not the right one; a scoring found this barely above repeating the last tool. Remaining cost is not read here, because a window's cost stops at the window.
|
|
3
|
+
import {} from './cells.js';
|
|
4
|
+
import { motifName, shapeMotif } from './motif.js';
|
|
5
|
+
import { body, atGrain, groupBy, rate, settled, total, wilson } from './read-support.js';
|
|
6
|
+
const countsOf = (group) => {
|
|
7
|
+
const counts = {};
|
|
8
|
+
for (const { cell, next } of group)
|
|
9
|
+
counts[next] = (counts[next] ?? 0) + settled(cell);
|
|
10
|
+
return counts;
|
|
11
|
+
};
|
|
12
|
+
export const deriveNextStep = ({ pool }) => {
|
|
13
|
+
const extensions = atGrain(pool, 'week').flatMap((cell) => {
|
|
14
|
+
const motif = pool.motifs.get(cell.motifId);
|
|
15
|
+
const last = motif?.steps.at(-1);
|
|
16
|
+
return motif?.level === 'shape' && motif.steps.length >= 2 && last !== undefined ? [{ cell, prefix: motif.steps.slice(0, -1), next: last.action }] : [];
|
|
17
|
+
});
|
|
18
|
+
return [...groupBy(extensions, ({ prefix }) => shapeMotif(prefix).motifId).values()].flatMap((group) => {
|
|
19
|
+
const [first] = group;
|
|
20
|
+
const cells = group.map(({ cell }) => cell);
|
|
21
|
+
const n = total(cells, settled);
|
|
22
|
+
if (first === undefined || n === 0)
|
|
23
|
+
return [];
|
|
24
|
+
const next = countsOf(group);
|
|
25
|
+
const [top, topCount] = Object.entries(next).sort(([a, one], [b, two]) => two - one || (a < b ? -1 : 1))[0] ?? ['', 0];
|
|
26
|
+
return [
|
|
27
|
+
body('next-step', cells, {
|
|
28
|
+
slice: { prefix: motifName(shapeMotif(first.prefix)), length: String(first.prefix.length) },
|
|
29
|
+
value: { next, top, topShare: rate(topCount, n) },
|
|
30
|
+
n,
|
|
31
|
+
interval: wilson(topCount, n),
|
|
32
|
+
}),
|
|
33
|
+
];
|
|
34
|
+
});
|
|
35
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Family, type Read, type ReadInterval, type ReadSlice, type ReadValue } from './read.js';
|
|
2
|
+
import { type ReadBody } from './read-support.js';
|
|
3
|
+
/** A family over reads: it reads every read of the stages before it. */
|
|
4
|
+
export type DeriveRelation = (reads: readonly Read[]) => ReadBody[];
|
|
5
|
+
/** The fewest trials a rate may rest on and still be compared. */
|
|
6
|
+
export declare const TRIALS_FLOOR = 30;
|
|
7
|
+
/** The smallest move worth stating. Large samples clear zero on moves no one would act on, so a move has to be large as well as unlikely. */
|
|
8
|
+
export declare const MIN_MOVE = 0.01;
|
|
9
|
+
/** A number and its interval, as an input states them. */
|
|
10
|
+
export interface Estimate {
|
|
11
|
+
value: number;
|
|
12
|
+
interval: ReadInterval;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `later - earlier`, by MOVER (Newcombe 1998, Zou and Donner 2008): each side of the result takes the sides of the inputs that could push it there, so a skewed Wilson
|
|
16
|
+
* interval stays skewed. It holds at any order, because it asks only for a value and an interval. It treats the two inputs as independent, which two periods nearly are.
|
|
17
|
+
*/
|
|
18
|
+
export declare const differenceOf: (later: Estimate, earlier: Estimate) => Estimate;
|
|
19
|
+
/** One named number of a read with the read's interval, or nothing to propagate. */
|
|
20
|
+
export declare const estimateOf: (read: Read, metric: string) => Estimate | undefined;
|
|
21
|
+
/** What a read's interval rests on: a share's denominator, or else its own count. A rare tool's share of many steps is well measured, so its count would be the wrong floor. */
|
|
22
|
+
export declare const trialsOf: (read: Read) => number;
|
|
23
|
+
/**
|
|
24
|
+
* Whether an estimate's interval clears zero once `tested` comparisons were made, the Bonferroni way: the half-width toward zero scales from 95 percent to the family's z.
|
|
25
|
+
* `sqrt(2 ln(2m / alpha))` is the Chernoff bound on that z. It runs a little wide and needs no quantile table, and a wide screen suits inputs that count a step as independent.
|
|
26
|
+
*/
|
|
27
|
+
export declare const clearsZero: (estimate: Estimate, tested: number) => boolean;
|
|
28
|
+
/** A move a reader should hear about: past the screen for `tested` comparisons, and large enough to matter. */
|
|
29
|
+
export declare const moved: (estimate: Estimate, tested: number) => boolean;
|
|
30
|
+
export interface Relation {
|
|
31
|
+
slice: ReadSlice;
|
|
32
|
+
value: ReadValue;
|
|
33
|
+
interval: ReadInterval;
|
|
34
|
+
/** The observations behind the value, when they are not the inputs' trials: a slope over pairs counts pairs. */
|
|
35
|
+
n?: number;
|
|
36
|
+
/** Reads this one cites but does not measure on: a witness for a total. They are not held to the floor. */
|
|
37
|
+
cites?: readonly Read[];
|
|
38
|
+
/** What the floor holds an input to, when the value rests on something other than `trialsOf`: a share read from a read whose interval is on another rate. */
|
|
39
|
+
trials?: (read: Read) => number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A read over reads, or nothing when any measured input sits below the floor or has no interval. `n` and `clusters` state the weakest side: a relation knows no more than its
|
|
43
|
+
* thinnest input, and summing trials up a chain would count one step many times.
|
|
44
|
+
*/
|
|
45
|
+
export declare const relate: (family: Family, inputs: readonly Read[], relation: Relation) => ReadBody | undefined;
|
|
46
|
+
/** A comparison made, before the screen: the read it would be, and every estimate that has to move for it to exist. */
|
|
47
|
+
export interface Candidate {
|
|
48
|
+
gates: readonly Estimate[];
|
|
49
|
+
body: ReadBody;
|
|
50
|
+
}
|
|
51
|
+
/** A comparison above the floor, which therefore counts toward what the screen allows for. */
|
|
52
|
+
export declare const candidateOf: (family: Family, inputs: readonly Read[], gates: readonly Estimate[], relation: Relation) => Candidate | undefined;
|
|
53
|
+
/** The reads that survive the screen over everything compared, each stating how many comparisons that was: `tested * 0.05` is what chance alone would offer an unscreened reader. */
|
|
54
|
+
export declare const screen: (candidates: readonly Candidate[], claims?: (candidate: Candidate) => boolean) => ReadBody[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Reads over reads (spec Section 6): noise compounds, so an interval comes from the inputs and a read below its floor does not exist. Every family above order 1 builds its
|
|
2
|
+
// read here, which is how the floor stays a property of the read and not a filter on a listing. Order 1 has no floor, because a cell is the operator's own; a relation
|
|
3
|
+
// between rates has one, because two thin rates differ by chance and read as news.
|
|
4
|
+
import { METHODS } from './read.js';
|
|
5
|
+
import { round, Z_95 } from './read-support.js';
|
|
6
|
+
/** The fewest trials a rate may rest on and still be compared. */
|
|
7
|
+
export const TRIALS_FLOOR = 30;
|
|
8
|
+
/** The smallest move worth stating. Large samples clear zero on moves no one would act on, so a move has to be large as well as unlikely. */
|
|
9
|
+
export const MIN_MOVE = 0.01;
|
|
10
|
+
const FAMILYWISE_ERROR = 0.05;
|
|
11
|
+
/**
|
|
12
|
+
* `later - earlier`, by MOVER (Newcombe 1998, Zou and Donner 2008): each side of the result takes the sides of the inputs that could push it there, so a skewed Wilson
|
|
13
|
+
* interval stays skewed. It holds at any order, because it asks only for a value and an interval. It treats the two inputs as independent, which two periods nearly are.
|
|
14
|
+
*/
|
|
15
|
+
export const differenceOf = (later, earlier) => {
|
|
16
|
+
const value = later.value - earlier.value;
|
|
17
|
+
const down = Math.hypot(later.value - later.interval.low, earlier.interval.high - earlier.value);
|
|
18
|
+
const up = Math.hypot(later.interval.high - later.value, earlier.value - earlier.interval.low);
|
|
19
|
+
return { value: round(value), interval: { low: round(value - down), high: round(value + up), level: later.interval.level } };
|
|
20
|
+
};
|
|
21
|
+
/** One named number of a read with the read's interval, or nothing to propagate. */
|
|
22
|
+
export const estimateOf = (read, metric) => {
|
|
23
|
+
const value = read.value[metric];
|
|
24
|
+
return typeof value === 'number' && read.interval !== null ? { value, interval: read.interval } : undefined;
|
|
25
|
+
};
|
|
26
|
+
/** What a read's interval rests on: a share's denominator, or else its own count. A rare tool's share of many steps is well measured, so its count would be the wrong floor. */
|
|
27
|
+
export const trialsOf = (read) => {
|
|
28
|
+
const { of } = read.value;
|
|
29
|
+
return typeof of === 'number' ? of : read.n;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether an estimate's interval clears zero once `tested` comparisons were made, the Bonferroni way: the half-width toward zero scales from 95 percent to the family's z.
|
|
33
|
+
* `sqrt(2 ln(2m / alpha))` is the Chernoff bound on that z. It runs a little wide and needs no quantile table, and a wide screen suits inputs that count a step as independent.
|
|
34
|
+
*/
|
|
35
|
+
export const clearsZero = (estimate, tested) => {
|
|
36
|
+
const { value, interval } = estimate;
|
|
37
|
+
const toZero = value > 0 ? value - interval.low : interval.high - value;
|
|
38
|
+
return value !== 0 && Math.abs(value) > toZero * (Math.sqrt(2 * Math.log((2 * tested) / FAMILYWISE_ERROR)) / Z_95);
|
|
39
|
+
};
|
|
40
|
+
/** A move a reader should hear about: past the screen for `tested` comparisons, and large enough to matter. */
|
|
41
|
+
export const moved = (estimate, tested) => Math.abs(estimate.value) >= MIN_MOVE && clearsZero(estimate, tested);
|
|
42
|
+
/**
|
|
43
|
+
* A read over reads, or nothing when any measured input sits below the floor or has no interval. `n` and `clusters` state the weakest side: a relation knows no more than its
|
|
44
|
+
* thinnest input, and summing trials up a chain would count one step many times.
|
|
45
|
+
*/
|
|
46
|
+
export const relate = (family, inputs, relation) => {
|
|
47
|
+
const { cites = [], trials = trialsOf, n = Math.min(...inputs.map(trials)), ...fields } = relation;
|
|
48
|
+
if (inputs.some((read) => read.interval === null || trials(read) < TRIALS_FLOOR))
|
|
49
|
+
return undefined;
|
|
50
|
+
const all = [...inputs, ...cites];
|
|
51
|
+
return { family, method: METHODS[family].name, methodVersion: METHODS[family].version, inputs: all.map((read) => read.readId), clusters: Math.min(...all.map((read) => read.clusters)), basis: 'all', n, ...fields };
|
|
52
|
+
};
|
|
53
|
+
/** A comparison above the floor, which therefore counts toward what the screen allows for. */
|
|
54
|
+
export const candidateOf = (family, inputs, gates, relation) => {
|
|
55
|
+
const body = relate(family, inputs, relation);
|
|
56
|
+
return body === undefined ? undefined : { gates, body };
|
|
57
|
+
};
|
|
58
|
+
/** The reads that survive the screen over everything compared, each stating how many comparisons that was: `tested * 0.05` is what chance alone would offer an unscreened reader. */
|
|
59
|
+
export const screen = (candidates, claims = () => true) => {
|
|
60
|
+
const tested = candidates.length;
|
|
61
|
+
return candidates.filter((candidate) => claims(candidate) && candidate.gates.every((gate) => moved(gate, tested))).map(({ body }) => ({ ...body, value: { ...body.value, tested } }));
|
|
62
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Family, Read } from './read.js';
|
|
2
|
+
export interface NotReadable {
|
|
3
|
+
family: Family;
|
|
4
|
+
what: string;
|
|
5
|
+
why: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const notReadable: (reads: readonly Read[]) => NotReadable[];
|
|
8
|
+
export declare const formatNotReadable: (rows: readonly NotReadable[]) => string[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// What this store cannot read, stated rather than forced (ruling mrd5591-08). A family that finds nothing has made no claim about why, so a reader of an empty family could take
|
|
2
|
+
// "no reads" for "nothing moved". These rows separate the two: a question the store cannot ask from one it asked and got no answer to.
|
|
3
|
+
import { interactionCandidates } from './read-interaction.js';
|
|
4
|
+
import { leadLagPairs, PAIRS_FLOOR } from './read-lead-lag.js';
|
|
5
|
+
import { substitutionCandidates } from './read-substitution.js';
|
|
6
|
+
import { boundariesOf, versionBoundaryCandidates } from './read-version-boundary.js';
|
|
7
|
+
export const notReadable = (reads) => {
|
|
8
|
+
const rows = [{ family: 'version-boundary', what: 'a model version boundary', why: 'a cell holds a model name and no version of it' }];
|
|
9
|
+
const noBoundary = boundariesOf(reads).length === 0;
|
|
10
|
+
const whyNot = (candidates) => (noBoundary ? 'no harness has two version buckets' : candidates.length === 0 ? 'no comparison clears the floor' : undefined);
|
|
11
|
+
const boundaryWhy = whyNot(versionBoundaryCandidates(reads));
|
|
12
|
+
if (boundaryWhy !== undefined)
|
|
13
|
+
rows.push({ family: 'version-boundary', what: 'a harness version boundary', why: boundaryWhy });
|
|
14
|
+
const substitutionWhy = whyNot(substitutionCandidates(reads));
|
|
15
|
+
if (substitutionWhy !== undefined)
|
|
16
|
+
rows.push({ family: 'substitution', what: 'one tool replacing another at a boundary', why: substitutionWhy });
|
|
17
|
+
if (interactionCandidates(reads).length === 0)
|
|
18
|
+
rows.push({ family: 'interaction', what: 'a tool across the tiers assigned', why: 'no tool clears the floor on two tiers in one frame' });
|
|
19
|
+
if (!reads.some((read) => read.family === 'lead-lag')) {
|
|
20
|
+
rows.push({ family: 'lead-lag', what: 'a failure move leading a share move (experimental)', why: `${String(leadLagPairs(reads).length)} pairs, and it needs ${String(PAIRS_FLOOR)} on each side of the cut` });
|
|
21
|
+
}
|
|
22
|
+
return rows;
|
|
23
|
+
};
|
|
24
|
+
export const formatNotReadable = (rows) => (rows.length === 0 ? [] : ['Not readable on this store:', ...rows.map(({ family, what, why }) => ` ${family}: ${what}: ${why}`)]);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Recovery from cells (rank 1, t868): what others did next after a failed call, by (failed action, `errorCode`, next action). One read for each edge, citing its own week cell only, so a count under the floor in
|
|
2
|
+
// another edge is not lifted by this one. It states counts, never a fix: there is no fix field, `none` means nothing followed in that prompt, and an edit that followed is not that the failure was resolved.
|
|
3
|
+
// Confounders: the work the failure interrupted, the user's refusals and policy denials (their own codes), and that a call's next action is one of many a prompt may take.
|
|
4
|
+
import { parseRecoveryClass } from './cells.js';
|
|
5
|
+
import { RECOVERY_EDGE_METHOD } from './read.js';
|
|
6
|
+
import { atGrain, body, groupBy, rate, total, wilson } from './read-support.js';
|
|
7
|
+
export const deriveRecoveryEdges = ({ pool }) => {
|
|
8
|
+
const edges = atGrain(pool, 'week').flatMap((cell) => {
|
|
9
|
+
const cls = pool.motifs.get(cell.motifId)?.cls;
|
|
10
|
+
const parts = cls === undefined || cls === null ? null : parseRecoveryClass(cls);
|
|
11
|
+
return parts === null ? [] : [{ cell, ...parts }];
|
|
12
|
+
});
|
|
13
|
+
// The denominator is the failures of the same action and code in the same frame bucket and week, over the cells this pool holds. A bundle's pool holds served cells only (`bundleOf`), so no `of` sums a cell under the floor and subtraction reveals none; the local read has no floor and sums what it holds.
|
|
14
|
+
const keyOf = ({ cell, code, failed }) => `${cell.frameBucket}/${cell.day}/${code}/${failed}`;
|
|
15
|
+
const failures = new Map([...groupBy(edges, keyOf)].map(([key, group]) => [key, total(group.map(({ cell }) => cell), (cell) => cell.failed)]));
|
|
16
|
+
return edges.flatMap((edge) => {
|
|
17
|
+
const { cell, code, failed, next } = edge;
|
|
18
|
+
const followed = cell.failed;
|
|
19
|
+
const of = failures.get(keyOf(edge)) ?? 0;
|
|
20
|
+
return followed === 0
|
|
21
|
+
? []
|
|
22
|
+
: [
|
|
23
|
+
body('recovery', [cell], { slice: { action: failed, errorCode: code, next, week: cell.day }, value: { followed, of, share: rate(followed, of) }, n: followed, interval: wilson(followed, of) }, RECOVERY_EDGE_METHOD),
|
|
24
|
+
];
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DeriveFamily, type FamilyInput, type ReadBody } from './read-support.js';
|
|
2
|
+
/** Why a signature record has no read. Each is a record with nothing to cite, and a read that cites nothing is refused. */
|
|
3
|
+
export type RecoveryDrop =
|
|
4
|
+
/** No call of this action in any cell, such as one a version-2 deposit left with no tool record. */
|
|
5
|
+
'no-cell-of-action'
|
|
6
|
+
/** Calls of this action in cells, none of them failed: the error line's call did not settle as an error. */
|
|
7
|
+
| 'no-failed-cell-of-action'
|
|
8
|
+
/** Failed calls of this action in cells, none on a day this signature occurred. */
|
|
9
|
+
| 'no-failed-cell-on-day'
|
|
10
|
+
/** Every day the signature's errors and their frames can name is unknown. A day cell exists for a known day only (rule 6), so there is no cell to cite however many failed calls the action has. */
|
|
11
|
+
| 'frame-has-no-day';
|
|
12
|
+
export interface RecoveryOutcome {
|
|
13
|
+
reads: ReadBody[];
|
|
14
|
+
/** Every signature record is a read or is counted here, so none leaves without a reason. */
|
|
15
|
+
dropped: {
|
|
16
|
+
[reason in RecoveryDrop]: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare const recoveryOf: ({ pool, store }: FamilyInput) => RecoveryOutcome;
|
|
20
|
+
export declare const deriveRecovery: DeriveFamily;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Recovery (rank 1; spec Section 7): each signature record, as a read. A signature's text is class A, so the read holds its hash (the same value on any account,
|
|
2
|
+
// computed by the asker's client) and public names only; `recall()` is still how an operator reads the text. Confounders: `editFollowed` is not success, and refusals and
|
|
3
|
+
// hook denials are local policy. Its evidence is the cells holding the failed call and the step after it, on the days the signature occurred and the days their frames were written.
|
|
4
|
+
import { ACTION_TABLES, actionOf } from './actions.js';
|
|
5
|
+
import { UNKNOWN, weekStartOf } from './cells.js';
|
|
6
|
+
import { utcDay } from './frame.js';
|
|
7
|
+
import { shapeMotif } from './motif.js';
|
|
8
|
+
import { contentHash } from './read.js';
|
|
9
|
+
import { atGrain, body, groupBy } from './read-support.js';
|
|
10
|
+
import { harnessOf } from './schema.js';
|
|
11
|
+
import { signatureRecord } from './store.js';
|
|
12
|
+
const shapeOf = (actions) => shapeMotif(actions.map((action) => ({ action, identity: null }))).motifId;
|
|
13
|
+
/** Earliest first; a tie falls to what the rows say, so the copy that stands never depends on record order. */
|
|
14
|
+
const standsBefore = (a, b) => {
|
|
15
|
+
if (a.ts !== b.ts)
|
|
16
|
+
return a.ts !== null && (b.ts === null || a.ts < b.ts);
|
|
17
|
+
return JSON.stringify([a.sig, a.next, a.editFollowed]) < JSON.stringify([b.sig, b.next, b.editFollowed]);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* One row per session and tool-use id. The tool fold leaves every error line in place, so a store not yet captured again keeps a replayed result's line beside the
|
|
21
|
+
* first, and fit's folded `failed` would disagree with these counts. The earliest copy stands for the call, whatever order they came in; a row with no id is its own call.
|
|
22
|
+
*/
|
|
23
|
+
const oneRowPerCall = (errors) => {
|
|
24
|
+
const calls = new Map();
|
|
25
|
+
const rows = [];
|
|
26
|
+
for (const error of errors) {
|
|
27
|
+
if (error.toolUseId === undefined || error.toolUseId === null) {
|
|
28
|
+
rows.push(error);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const key = `${error.agent ?? error.session}\n${error.toolUseId}`;
|
|
32
|
+
const held = calls.get(key);
|
|
33
|
+
if (held === undefined || standsBefore(error, held))
|
|
34
|
+
calls.set(key, error);
|
|
35
|
+
}
|
|
36
|
+
return [...rows, ...calls.values()];
|
|
37
|
+
};
|
|
38
|
+
export const recoveryOf = ({ pool, store }) => {
|
|
39
|
+
const sessions = new Map([...store.sessions, ...store.subagents].map((session) => [session.session, session]));
|
|
40
|
+
const prompts = new Map(store.prompts.map((prompt) => [`${prompt.session}#${String(prompt.index)}`, prompt]));
|
|
41
|
+
const actionIn = (session, tool) => actionOf(ACTION_TABLES[harnessOf(sessions.get(session) ?? {})], tool).action;
|
|
42
|
+
// The failed call is a one-step shape, which has a live day cell. The step after it is a two-step window, which has a week cell only (rule 6), so it is found on the week its day falls in.
|
|
43
|
+
const cellsAt = groupBy(atGrain(pool, 'day'), (cell) => `${cell.motifId}/${cell.day}`);
|
|
44
|
+
const weeksAt = groupBy(atGrain(pool, 'week'), (cell) => `${cell.motifId}/${cell.day}`);
|
|
45
|
+
const cellsOf = groupBy(atGrain(pool, 'day'), (cell) => cell.motifId);
|
|
46
|
+
/** The day a cell files this error's call under: its frame's, which is its prompt's, or its session's for a subagent or a call before the first prompt. An error can run past that midnight. */
|
|
47
|
+
const frameDayOf = (error) => {
|
|
48
|
+
const prompt = error.agent === undefined ? prompts.get(`${error.session}#${String(error.prompt)}`) : undefined;
|
|
49
|
+
if (prompt !== undefined)
|
|
50
|
+
return prompt.frame?.day ?? utcDay(prompt.ts) ?? UNKNOWN;
|
|
51
|
+
const session = sessions.get(error.agent ?? error.session);
|
|
52
|
+
return session?.frame?.day ?? utcDay(session?.tsFirst) ?? UNKNOWN;
|
|
53
|
+
};
|
|
54
|
+
const dropped = { 'no-cell-of-action': 0, 'no-failed-cell-of-action': 0, 'no-failed-cell-on-day': 0, 'frame-has-no-day': 0 };
|
|
55
|
+
const reads = [...groupBy(oneRowPerCall(store.errors), (error) => error.sig)].flatMap(([sig, list]) => {
|
|
56
|
+
const inputs = new Set();
|
|
57
|
+
const following = {};
|
|
58
|
+
let failedAction = 'other';
|
|
59
|
+
let dated = false;
|
|
60
|
+
const session = (error) => error.agent ?? error.session;
|
|
61
|
+
for (const error of list) {
|
|
62
|
+
failedAction = actionIn(session(error), error.tool);
|
|
63
|
+
const next = error.next[0] === undefined ? undefined : actionIn(session(error), error.next[0]);
|
|
64
|
+
for (const day of new Set([utcDay(error.ts) ?? UNKNOWN, frameDayOf(error)])) {
|
|
65
|
+
dated ||= day !== UNKNOWN;
|
|
66
|
+
for (const cell of cellsAt.get(`${shapeOf([failedAction])}/${day}`) ?? [])
|
|
67
|
+
if (cell.failed > 0)
|
|
68
|
+
inputs.add(cell);
|
|
69
|
+
// A frame of no day names no week, though a week cell sits at the unknown week.
|
|
70
|
+
if (next !== undefined && day !== UNKNOWN)
|
|
71
|
+
for (const cell of weeksAt.get(`${shapeOf([failedAction, next])}/${weekStartOf(day)}`) ?? [])
|
|
72
|
+
inputs.add(cell);
|
|
73
|
+
}
|
|
74
|
+
for (const tool of error.next) {
|
|
75
|
+
const action = actionIn(session(error), tool);
|
|
76
|
+
following[action] = (following[action] ?? 0) + 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (inputs.size === 0) {
|
|
80
|
+
const ofAction = cellsOf.get(shapeOf([failedAction])) ?? [];
|
|
81
|
+
const reason = !dated ? 'frame-has-no-day' : ofAction.length === 0 ? 'no-cell-of-action' : ofAction.some((cell) => cell.failed > 0) ? 'no-failed-cell-on-day' : 'no-failed-cell-of-action';
|
|
82
|
+
dropped[reason] += 1;
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const record = signatureRecord(sig, list);
|
|
86
|
+
return [
|
|
87
|
+
body('recovery', [...inputs], {
|
|
88
|
+
slice: { sig: contentHash(sig), action: failedAction },
|
|
89
|
+
value: { sessions: record.sessions, errors: record.count, firstSeen: utcDay(record.firstSeen), lastSeen: utcDay(record.lastSeen), following, editFollowed: record.editFollowed },
|
|
90
|
+
n: record.count,
|
|
91
|
+
interval: null,
|
|
92
|
+
}),
|
|
93
|
+
];
|
|
94
|
+
});
|
|
95
|
+
return { reads, dropped };
|
|
96
|
+
};
|
|
97
|
+
export const deriveRecovery = (input) => recoveryOf(input).reads;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Family, type Read, type ReadSlice } from './read.js';
|
|
2
|
+
export interface ReadFilter {
|
|
3
|
+
family: Family | undefined;
|
|
4
|
+
/** Every axis named must hold exactly this value. */
|
|
5
|
+
slice: ReadSlice;
|
|
6
|
+
}
|
|
7
|
+
export declare const isFamily: (value: string) => value is Family;
|
|
8
|
+
/** `axis=value`, repeated; undefined when one has no axis or no value, for the caller to refuse. A value may hold `=`, as a named motif does. */
|
|
9
|
+
export declare const parseSlice: (pairs: readonly string[]) => ReadSlice | undefined;
|
|
10
|
+
/** In family order, then largest sample first; a read's ID settles a tie, so a rerun lists the same rows in the same order. */
|
|
11
|
+
export declare const selectReads: (reads: readonly Read[], filter: ReadFilter) => Read[];
|
|
12
|
+
/** Rows under a heading per family, in the order `selectReads` put them. */
|
|
13
|
+
export declare const formatReads: (reads: readonly Read[]) => string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// What `oars sessions read` serves: reads filtered by family and slice, largest sample first, one row each. A row states its clusters. This file adds no floor: an order 1 read is
|
|
2
|
+
// the operator's own cell, so a floor on it would protect no one. Reads above order 1 that fall below their floor do not exist, so there is none of them to list.
|
|
3
|
+
import { FAMILIES } from './read.js';
|
|
4
|
+
export const isFamily = (value) => FAMILIES.some((family) => family === value);
|
|
5
|
+
/** `axis=value`, repeated; undefined when one has no axis or no value, for the caller to refuse. A value may hold `=`, as a named motif does. */
|
|
6
|
+
export const parseSlice = (pairs) => {
|
|
7
|
+
const slice = {};
|
|
8
|
+
for (const pair of pairs) {
|
|
9
|
+
const at = pair.indexOf('=');
|
|
10
|
+
if (at <= 0 || at === pair.length - 1)
|
|
11
|
+
return undefined;
|
|
12
|
+
slice[pair.slice(0, at)] = pair.slice(at + 1);
|
|
13
|
+
}
|
|
14
|
+
return slice;
|
|
15
|
+
};
|
|
16
|
+
const compare = (a, b) => (a === b ? 0 : a < b ? -1 : 1);
|
|
17
|
+
/** In family order, then largest sample first; a read's ID settles a tie, so a rerun lists the same rows in the same order. */
|
|
18
|
+
export const selectReads = (reads, filter) => reads
|
|
19
|
+
.filter((read) => (filter.family === undefined || read.family === filter.family) && Object.entries(filter.slice).every(([axis, value]) => read.slice[axis] === value))
|
|
20
|
+
.sort((a, b) => FAMILIES.indexOf(a.family) - FAMILIES.indexOf(b.family) || b.n - a.n || compare(a.readId, b.readId));
|
|
21
|
+
const pairsOf = (fields) => Object.entries(fields)
|
|
22
|
+
.map(([name, value]) => {
|
|
23
|
+
if (typeof value !== 'object' || value === null)
|
|
24
|
+
return `${name}=${String(value)}`;
|
|
25
|
+
return `${name}=${Object.entries(value).sort(([a, one], [b, two]) => two - one || compare(a, b)).map(([key, number]) => `${key}:${String(number)}`).join(',')}`;
|
|
26
|
+
})
|
|
27
|
+
.join(' ');
|
|
28
|
+
const formatRead = (read) => {
|
|
29
|
+
const interval = read.interval === null ? '' : ` ci=${String(read.interval.low)}..${String(read.interval.high)}`;
|
|
30
|
+
return ` n=${String(read.n)} clusters: ${String(read.clusters)} order=${String(read.order)} ${pairsOf(read.slice)} | ${pairsOf(read.value)}${interval}`;
|
|
31
|
+
};
|
|
32
|
+
const EXPERIMENT = ', experimental: fit on earlier days and scored on later ones; not served to an agent';
|
|
33
|
+
/** Rows under a heading per family, in the order `selectReads` put them. */
|
|
34
|
+
export const formatReads = (reads) => {
|
|
35
|
+
if (reads.length === 0)
|
|
36
|
+
return 'No reads match.';
|
|
37
|
+
const lines = [];
|
|
38
|
+
let family;
|
|
39
|
+
for (const read of reads) {
|
|
40
|
+
if (read.family !== family) {
|
|
41
|
+
family = read.family;
|
|
42
|
+
lines.push(`${family} (${String(reads.filter((one) => one.family === family).length)} reads${read.value.label === 'experimental' ? EXPERIMENT : ''})`);
|
|
43
|
+
}
|
|
44
|
+
lines.push(formatRead(read));
|
|
45
|
+
}
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Candidate, type DeriveRelation } from './read-order.js';
|
|
2
|
+
import type { Read } from './read.js';
|
|
3
|
+
/** The pairs a store's change reads allow, above the floor, whether or not any of them moved. */
|
|
4
|
+
export declare const substitutionCandidates: (reads: readonly Read[]) => Candidate[];
|
|
5
|
+
export declare const deriveSubstitution: DeriveRelation;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Substitution (spec Section 6, order 2): at one version boundary, one tool's share rose as another's fell. Shares sum to one, so any rise pushes every other share down; a pair
|
|
2
|
+
// counts only when the falling tool also fell among the steps that are not the rising tool, which arithmetic alone cannot do. Confounders it does not remove: date, class
|
|
3
|
+
// drift (the work changed, not the choice of tool), and rule commits. It says two shares moved oppositely, not that one call replaced another.
|
|
4
|
+
import { UNKNOWN } from './cells.js';
|
|
5
|
+
import { candidateOf, differenceOf, screen, TRIALS_FLOOR } from './read-order.js';
|
|
6
|
+
import { rate, wilson } from './read-support.js';
|
|
7
|
+
import { boundariesOf, shareIn, toolsAt } from './read-version-boundary.js';
|
|
8
|
+
const countIn = (bucket, key) => bucket.reads.get(key)?.n ?? 0;
|
|
9
|
+
/** A tool's share of the steps that are not the other tool's, floored like any rate. */
|
|
10
|
+
const shareOfRest = (bucket, key, without) => {
|
|
11
|
+
const trials = bucket.of - countIn(bucket, without);
|
|
12
|
+
const count = countIn(bucket, key);
|
|
13
|
+
const value = rate(count, trials);
|
|
14
|
+
const interval = wilson(count, trials);
|
|
15
|
+
return trials < TRIALS_FLOOR || value === null || interval === null ? undefined : { value, interval };
|
|
16
|
+
};
|
|
17
|
+
const levelOf = (key) => key.split('\n')[0] ?? UNKNOWN;
|
|
18
|
+
const motifOf = (key) => key.split('\n')[1] ?? UNKNOWN;
|
|
19
|
+
const pairCandidate = (boundary, rising, falling) => {
|
|
20
|
+
const { from, to } = boundary;
|
|
21
|
+
const risingBefore = shareIn(from, rising);
|
|
22
|
+
const risingAfter = shareIn(to, rising);
|
|
23
|
+
const fallingBefore = shareIn(from, falling);
|
|
24
|
+
const fallingAfter = shareIn(to, falling);
|
|
25
|
+
const restBefore = shareOfRest(from, falling, rising);
|
|
26
|
+
const restAfter = shareOfRest(to, falling, rising);
|
|
27
|
+
if (risingBefore === undefined || risingAfter === undefined || fallingBefore === undefined || fallingAfter === undefined || restBefore === undefined || restAfter === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
const rise = differenceOf(risingAfter.share, risingBefore.share);
|
|
30
|
+
const fall = differenceOf(fallingBefore.share, fallingAfter.share);
|
|
31
|
+
const restFall = differenceOf(restBefore, restAfter);
|
|
32
|
+
// How much of the move the two share: the smaller of them, whose interval is bounded by the smaller of each side.
|
|
33
|
+
const offset = Math.min(rise.value, fall.value);
|
|
34
|
+
const interval = { low: Math.min(rise.interval.low, fall.interval.low), high: Math.min(rise.interval.high, fall.interval.high), level: rise.interval.level };
|
|
35
|
+
return candidateOf('substitution', [risingBefore.read, risingAfter.read, fallingBefore.read, fallingAfter.read], [rise, fall, restFall], {
|
|
36
|
+
slice: { rising: motifOf(rising), falling: motifOf(falling), level: levelOf(rising), harness: boundary.harness, from: from.name, to: to.name },
|
|
37
|
+
value: { rise: rise.value, fall: fall.value, restFall: restFall.value, offset },
|
|
38
|
+
interval,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
/** Both moves point the way the read claims: `moved` asks only that each is large, not its sign. */
|
|
42
|
+
const opposed = ({ gates }) => gates.every((gate) => gate.value > 0);
|
|
43
|
+
/** The pairs a store's change reads allow, above the floor, whether or not any of them moved. */
|
|
44
|
+
export const substitutionCandidates = (reads) => boundariesOf(reads).flatMap((boundary) => {
|
|
45
|
+
const keys = toolsAt(boundary);
|
|
46
|
+
return keys.flatMap((rising) => keys.filter((falling) => falling !== rising && levelOf(falling) === levelOf(rising)).flatMap((falling) => {
|
|
47
|
+
const candidate = pairCandidate(boundary, rising, falling);
|
|
48
|
+
return candidate === undefined ? [] : [candidate];
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
export const deriveSubstitution = (reads) => screen(substitutionCandidates(reads), opposed);
|