@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,104 @@
|
|
|
1
|
+
// Layer 4, T0, local (spec 2.4): does the raw file still match the root capture wrote, and do the capture records agree with each other?
|
|
2
|
+
// Local only: a local match commits nobody, so no result here raises a tier. It reads `raw/` and `state.json`, and prints ids and counts, never content.
|
|
3
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { merkleRootOf, MIN_ROOT_LEAVES, rawLinesOf } from './merkle.js';
|
|
6
|
+
import { paths, readState, relativeOfSession } from './store.js';
|
|
7
|
+
/** The T0 result table of spec 2.4, one name per row. */
|
|
8
|
+
export const T0_RESULTS = ['root matches and the counts bind', 'changed since deposit', 'root mismatch', 'counts do not bind', 'not run'];
|
|
9
|
+
const statOrUndefined = (file) => stat(file, { bigint: true }).catch((error) => {
|
|
10
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
11
|
+
return undefined;
|
|
12
|
+
throw error;
|
|
13
|
+
});
|
|
14
|
+
/** The count bindings of spec 2.4: the skeleton's leaves, the session's lines, and the skeleton's lines plus the session's bad lines must all agree. No skeleton is zero leaves and zero lines. */
|
|
15
|
+
const bindingsOf = (session, skeleton) => {
|
|
16
|
+
const leaves = skeleton?.leaves ?? 0;
|
|
17
|
+
const skeletonLines = skeleton?.lines.length ?? 0;
|
|
18
|
+
return { leaves, sessionLines: session.lines, skeletonLines, badLines: session.badLines, bind: leaves === session.lines && session.lines === skeletonLines + session.badLines };
|
|
19
|
+
};
|
|
20
|
+
const unrun = (session, skeleton, bindings) => ({
|
|
21
|
+
session,
|
|
22
|
+
result: 'not run',
|
|
23
|
+
tier: 'skeleton',
|
|
24
|
+
mode: null,
|
|
25
|
+
leaves: skeleton?.leaves ?? 0,
|
|
26
|
+
belowMinLeaves: (skeleton?.leaves ?? 0) < MIN_ROOT_LEAVES,
|
|
27
|
+
changedSinceDeposit: false,
|
|
28
|
+
possibleTampering: false,
|
|
29
|
+
bindings,
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* One session's T0 result. The count bindings come first and need no raw file: a record set that disagrees with itself is `none` whatever `raw/`
|
|
33
|
+
* holds. `state` is `state.json`, read once by {@link auditStore}.
|
|
34
|
+
*/
|
|
35
|
+
export const auditSession = async (runtime, session, skeleton, state) => {
|
|
36
|
+
const bindings = bindingsOf(session, skeleton);
|
|
37
|
+
const base = { session: session.session, leaves: bindings.leaves, belowMinLeaves: bindings.leaves < MIN_ROOT_LEAVES, changedSinceDeposit: false, possibleTampering: false, bindings };
|
|
38
|
+
if (skeleton === undefined)
|
|
39
|
+
return { ...base, result: 'not run', tier: 'skeleton', mode: null };
|
|
40
|
+
if (!bindings.bind)
|
|
41
|
+
return { ...base, result: 'counts do not bind', tier: 'none', mode: null };
|
|
42
|
+
const relative = join(session.project, ...relativeOfSession(session.session));
|
|
43
|
+
const rawFile = join(paths(runtime.storeDir).raw, relative);
|
|
44
|
+
const raw = await statOrUndefined(rawFile);
|
|
45
|
+
if (raw === undefined)
|
|
46
|
+
return { ...base, result: 'not run', tier: 'skeleton', mode: null };
|
|
47
|
+
// The mode is read from the file: with the source's inode it is the link, otherwise a copy (or a link whose source is gone).
|
|
48
|
+
const source = await statOrUndefined(join(runtime.projectsRoot, relative));
|
|
49
|
+
const mode = source !== undefined && source.ino === raw.ino && source.dev === raw.dev ? 'link' : 'copy';
|
|
50
|
+
const held = (state ?? (await readState(runtime.storeDir))).files[relative.split('\\').join('/')];
|
|
51
|
+
// `state.json` holds float milliseconds from a plain `stat`; the match compares against the same reading.
|
|
52
|
+
const plain = await stat(rawFile);
|
|
53
|
+
const sizeHeld = held !== undefined && plain.size === held.size;
|
|
54
|
+
const matches = held !== undefined && sizeHeld && (mode === 'link' ? plain.mtimeMs === held.mtimeMs : plain.mtimeMs >= held.mtimeMs);
|
|
55
|
+
if (!matches)
|
|
56
|
+
return { ...base, result: 'changed since deposit', tier: 'skeleton', mode, changedSinceDeposit: true, possibleTampering: mode === 'copy' && held !== undefined && !sizeHeld };
|
|
57
|
+
const lines = rawLinesOf(await readFile(rawFile, 'utf8'));
|
|
58
|
+
const recomputed = lines.length < skeleton.leaves ? undefined : merkleRootOf(lines.slice(0, skeleton.leaves));
|
|
59
|
+
return recomputed === skeleton.merkleRoot ? { ...base, result: 'root matches and the counts bind', tier: 'skeleton', mode } : { ...base, result: 'root mismatch', tier: 'none', mode };
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Every session's T0 result, in the order given, then one `not run` for each skeleton with no capture record (a torn or removed observations line), so
|
|
63
|
+
* the table never shrinks without a signal. A session with no skeleton, or whose raw file cannot be read, is also `not run`.
|
|
64
|
+
*/
|
|
65
|
+
export const auditStore = async (runtime, sessions, skeletons) => {
|
|
66
|
+
const state = await readState(runtime.storeDir);
|
|
67
|
+
const bySession = new Map(skeletons.map((skeleton) => [skeleton.session, skeleton]));
|
|
68
|
+
const audits = [];
|
|
69
|
+
for (const session of sessions) {
|
|
70
|
+
const skeleton = bySession.get(session.session);
|
|
71
|
+
// One unreadable raw file (EACCES, EBUSY, too large for a string) costs that session its result, never the others'.
|
|
72
|
+
audits.push(await auditSession(runtime, session, skeleton, state).catch(() => unrun(session.session, skeleton, bindingsOf(session, skeleton))));
|
|
73
|
+
}
|
|
74
|
+
const recorded = new Set(sessions.map((session) => session.session));
|
|
75
|
+
for (const skeleton of skeletons) {
|
|
76
|
+
if (!recorded.has(skeleton.session))
|
|
77
|
+
audits.push(unrun(skeleton.session, skeleton, { leaves: skeleton.leaves, sessionLines: 0, skeletonLines: skeleton.lines.length, badLines: 0, bind: false }));
|
|
78
|
+
}
|
|
79
|
+
return audits;
|
|
80
|
+
};
|
|
81
|
+
export const auditCounts = (audits) => {
|
|
82
|
+
const count = (result) => audits.filter((audit) => audit.result === result).length;
|
|
83
|
+
return {
|
|
84
|
+
'root matches and the counts bind': count('root matches and the counts bind'),
|
|
85
|
+
'changed since deposit': count('changed since deposit'),
|
|
86
|
+
'root mismatch': count('root mismatch'),
|
|
87
|
+
'counts do not bind': count('counts do not bind'),
|
|
88
|
+
'not run': count('not run'),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/** A count line for every row of the T0 table, then one FAIL line per session at tier `none` and one note per session that changed. */
|
|
92
|
+
export const formatAudit = (audits) => {
|
|
93
|
+
const counts = auditCounts(audits);
|
|
94
|
+
const tampering = audits.filter((audit) => audit.possibleTampering).length;
|
|
95
|
+
const small = audits.filter((audit) => audit.belowMinLeaves).length;
|
|
96
|
+
return [
|
|
97
|
+
`${String(audits.length)} sessions audited against raw (T0, local: a local match commits nobody)`,
|
|
98
|
+
...T0_RESULTS.map((result) => ` ${result}: ${String(counts[result])}`),
|
|
99
|
+
...(tampering > 0 ? [` possible tampering (a copy of a different size): ${String(tampering)}`] : []),
|
|
100
|
+
...(small > 0 ? [` under ${String(MIN_ROOT_LEAVES)} leaves, so joined to no cluster and counted toward no n: ${String(small)}`] : []),
|
|
101
|
+
...audits.filter((audit) => audit.tier === 'none').map((audit) => `FAIL ${audit.session}: ${audit.result}; weight is 0 until a new capture writes a new root`),
|
|
102
|
+
...audits.filter((audit) => audit.changedSinceDeposit).map((audit) => ` ${audit.session}: changed since deposit (${audit.mode ?? 'no mode'}${audit.possibleTampering ? ', possible tampering' : ''})`),
|
|
103
|
+
].join('\n');
|
|
104
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Harness, type PromptObservation, type SessionObservation, type ToolObservation } from './schema.js';
|
|
2
|
+
/** A week needs this many tool calls to be a unit or a baseline week (spec 5.4 item 1). */
|
|
3
|
+
export declare const BASELINE_MIN_CALLS = 1000;
|
|
4
|
+
/** A baseline needs this many other qualifying weeks (spec 5.4 item 1). */
|
|
5
|
+
export declare const BASELINE_MIN_WEEKS = 4;
|
|
6
|
+
/** F's bounds: a median distance at or under the low is F 1, at or over the high is F 0. */
|
|
7
|
+
export declare const FIT_LOW: Fraction;
|
|
8
|
+
export declare const FIT_HIGH: Fraction;
|
|
9
|
+
/**
|
|
10
|
+
* The fixed bin edges of the numeric dimensions: a value goes in the first bin whose edge it is under, else the last, so a value exactly on an edge
|
|
11
|
+
* lands in the upper bin. The cache-read share's edges are tenths (k / 10) and compare as integers: `cacheRead * 10 < k * input`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DURATION_EDGES_MS: readonly number[];
|
|
14
|
+
export declare const WALL_EDGES_SEC: readonly number[];
|
|
15
|
+
export declare const CACHE_SHARE_TENTHS: readonly number[];
|
|
16
|
+
export declare const OUTPUT_PER_TURN_EDGES: readonly number[];
|
|
17
|
+
export declare const DIMENSIONS: readonly ["tool mix", "status", "durationMs", "wallSec", "cache-read share", "output per turn", "prompt class"];
|
|
18
|
+
export type Dimension = (typeof DIMENSIONS)[number];
|
|
19
|
+
/** The fields each record needs, so a caller passes the store's records and a test passes small ones. */
|
|
20
|
+
export type BaselineSession = Pick<SessionObservation, 'session' | 'tsFirst' | 'tools' | 'harness'>;
|
|
21
|
+
export type BaselineTool = Pick<ToolObservation, 'session' | 'status' | 'durationMs'>;
|
|
22
|
+
export type BaselinePrompt = Pick<PromptObservation, 'session' | 'wallSec' | 'cls' | 'turns' | 'usage' | 'usageObserved'>;
|
|
23
|
+
export interface DimensionDistance {
|
|
24
|
+
dimension: Dimension;
|
|
25
|
+
distance: number;
|
|
26
|
+
}
|
|
27
|
+
/** One session's layer-2 result: `scored` with `m` and `f`, or `not run` with the reason and nothing scored. */
|
|
28
|
+
export interface SelfBaselineResult {
|
|
29
|
+
session: string;
|
|
30
|
+
harness: Harness;
|
|
31
|
+
/** The UTC Monday of the session's `tsFirst` (YYYY-MM-DD); null when `tsFirst` is null. */
|
|
32
|
+
week: string | null;
|
|
33
|
+
status: 'scored' | 'not run';
|
|
34
|
+
reason: string | null;
|
|
35
|
+
/** Tool calls in the session's week on its harness. */
|
|
36
|
+
calls: number;
|
|
37
|
+
/** The other qualifying weeks the baseline pools. */
|
|
38
|
+
baselineWeeks: number;
|
|
39
|
+
distances: DimensionDistance[];
|
|
40
|
+
m: number | null;
|
|
41
|
+
f: number | null;
|
|
42
|
+
}
|
|
43
|
+
/** An exact rational with a positive denominator: the distances, `m`, and `F` stay in this form until the one rounding at the end (spec 2.2, no float). */
|
|
44
|
+
export interface Fraction {
|
|
45
|
+
n: bigint;
|
|
46
|
+
d: bigint;
|
|
47
|
+
}
|
|
48
|
+
/** The UTC Monday 00:00 (epoch ms) of the week holding `ts`; null for a null or unparsable timestamp. */
|
|
49
|
+
export declare const weekOf: (ts: string | null) => number | null;
|
|
50
|
+
/** The median; the mean of the middle two for an even count. Never called with an empty list. */
|
|
51
|
+
export declare const medianOf: (values: readonly Fraction[]) => Fraction;
|
|
52
|
+
/** F = 1 - clamp((m - 1/10) / (2/5 - 1/10)), exact, rounded half-even to 4 places. */
|
|
53
|
+
export declare const fitOf: (m: Fraction) => number;
|
|
54
|
+
/**
|
|
55
|
+
* Every session's result. A session belongs wholly to the UTC week of its `tsFirst` and takes the F of its harness and week. A week qualifies at
|
|
56
|
+
* {@link BASELINE_MIN_CALLS} tool calls with its last UTC day no later than `asOf`; the baseline is a harness's other qualifying weeks, their counts
|
|
57
|
+
* added per bin so each week weighs what its calls weigh. A dimension with no counts on one side or both is dropped; `m` is the median of the
|
|
58
|
+
* rest. Mechanics only: no signature, no record content.
|
|
59
|
+
*/
|
|
60
|
+
export declare const selfBaseline: (sessions: readonly BaselineSession[], tools: readonly BaselineTool[], prompts: readonly BaselinePrompt[], asOf: number) => SelfBaselineResult[];
|
|
61
|
+
/** A count line for the scored sessions, then one per reason a session was not run. Counts only. */
|
|
62
|
+
export declare const formatSelfBaseline: (results: readonly SelfBaselineResult[]) => string;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Layer 2's self-baseline (spec 2.2): a session's week against the operator's own other weeks on the same harness. Local only: no pooled field,
|
|
2
|
+
// no network, no account step. The peer baseline waits for T2.
|
|
3
|
+
import { harnessOf, observesUsage } from './schema.js';
|
|
4
|
+
/** A week needs this many tool calls to be a unit or a baseline week (spec 5.4 item 1). */
|
|
5
|
+
export const BASELINE_MIN_CALLS = 1000;
|
|
6
|
+
/** A baseline needs this many other qualifying weeks (spec 5.4 item 1). */
|
|
7
|
+
export const BASELINE_MIN_WEEKS = 4;
|
|
8
|
+
/** F's bounds: a median distance at or under the low is F 1, at or over the high is F 0. */
|
|
9
|
+
export const FIT_LOW = { n: 1n, d: 10n };
|
|
10
|
+
export const FIT_HIGH = { n: 2n, d: 5n };
|
|
11
|
+
/**
|
|
12
|
+
* The fixed bin edges of the numeric dimensions: a value goes in the first bin whose edge it is under, else the last, so a value exactly on an edge
|
|
13
|
+
* lands in the upper bin. The cache-read share's edges are tenths (k / 10) and compare as integers: `cacheRead * 10 < k * input`.
|
|
14
|
+
*/
|
|
15
|
+
export const DURATION_EDGES_MS = [100, 1000, 10_000, 60_000];
|
|
16
|
+
export const WALL_EDGES_SEC = [5, 30, 120, 600];
|
|
17
|
+
export const CACHE_SHARE_TENTHS = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
18
|
+
export const OUTPUT_PER_TURN_EDGES = [50, 200, 500, 1000, 2000];
|
|
19
|
+
const DAY_MS = 86_400_000;
|
|
20
|
+
const WEEK_MS = 7 * DAY_MS;
|
|
21
|
+
export const DIMENSIONS = ['tool mix', 'status', 'durationMs', 'wallSec', 'cache-read share', 'output per turn', 'prompt class'];
|
|
22
|
+
/** The UTC Monday 00:00 (epoch ms) of the week holding `ts`; null for a null or unparsable timestamp. */
|
|
23
|
+
export const weekOf = (ts) => {
|
|
24
|
+
const at = ts === null ? Number.NaN : Date.parse(ts);
|
|
25
|
+
if (Number.isNaN(at))
|
|
26
|
+
return null;
|
|
27
|
+
const day = Math.floor(at / DAY_MS) * DAY_MS;
|
|
28
|
+
// 1970-01-01 was a Thursday: three days after the Monday of its week.
|
|
29
|
+
return day - ((((day / DAY_MS) + 3) % 7) * DAY_MS);
|
|
30
|
+
};
|
|
31
|
+
const compare = (a, b) => {
|
|
32
|
+
const diff = a.n * b.d - b.n * a.d;
|
|
33
|
+
return diff < 0n ? -1 : diff > 0n ? 1 : 0;
|
|
34
|
+
};
|
|
35
|
+
const minus = (a, b) => ({ n: a.n * b.d - b.n * a.d, d: a.d * b.d });
|
|
36
|
+
/** Rounds half-even to 4 places, once, at the end (spec 3.2). A value is never negative here. */
|
|
37
|
+
const roundTo4 = (value) => {
|
|
38
|
+
const scaled = value.n * 10000n;
|
|
39
|
+
const quotient = scaled / value.d;
|
|
40
|
+
const twiceRest = 2n * (scaled - quotient * value.d);
|
|
41
|
+
const up = twiceRest > value.d || (twiceRest === value.d && quotient % 2n === 1n);
|
|
42
|
+
return Number(up ? quotient + 1n : quotient) / 10_000;
|
|
43
|
+
};
|
|
44
|
+
/** The median; the mean of the middle two for an even count. Never called with an empty list. */
|
|
45
|
+
export const medianOf = (values) => {
|
|
46
|
+
const sorted = [...values].sort(compare);
|
|
47
|
+
const mid = sorted.length >> 1;
|
|
48
|
+
const upper = sorted[mid] ?? { n: 0n, d: 1n };
|
|
49
|
+
if (sorted.length % 2 === 1)
|
|
50
|
+
return upper;
|
|
51
|
+
const lower = sorted[mid - 1] ?? { n: 0n, d: 1n };
|
|
52
|
+
return { n: lower.n * upper.d + upper.n * lower.d, d: 2n * lower.d * upper.d };
|
|
53
|
+
};
|
|
54
|
+
/** F = 1 - clamp((m - 1/10) / (2/5 - 1/10)), exact, rounded half-even to 4 places. */
|
|
55
|
+
export const fitOf = (m) => {
|
|
56
|
+
const span = minus(FIT_HIGH, FIT_LOW);
|
|
57
|
+
if (compare(m, FIT_LOW) <= 0)
|
|
58
|
+
return 1;
|
|
59
|
+
if (compare(m, FIT_HIGH) >= 0)
|
|
60
|
+
return 0;
|
|
61
|
+
const over = minus(m, FIT_LOW);
|
|
62
|
+
// 1 - (over.n / over.d) / (span.n / span.d) = (over.d * span.n - over.n * span.d) / (over.d * span.n)
|
|
63
|
+
return roundTo4({ n: over.d * span.n - over.n * span.d, d: over.d * span.n });
|
|
64
|
+
};
|
|
65
|
+
/** The first bin whose edge the value is under, else the last: `value < edge * scale` with integers, so the comparison is exact. */
|
|
66
|
+
const binOf = (value, edges, scale = 1) => {
|
|
67
|
+
const index = edges.findIndex((edge) => value < edge * scale);
|
|
68
|
+
return String(index === -1 ? edges.length : index);
|
|
69
|
+
};
|
|
70
|
+
const add = (histogram, key, count = 1) => {
|
|
71
|
+
histogram.set(key, (histogram.get(key) ?? 0) + count);
|
|
72
|
+
};
|
|
73
|
+
const emptyHistograms = () => new Map(DIMENSIONS.map((dimension) => [dimension, new Map()]));
|
|
74
|
+
const histogramOf = (histograms, dimension) => histograms.get(dimension) ?? new Map();
|
|
75
|
+
const callsOf = (session) => Object.values(session.tools).reduce((total, count) => total + count, 0);
|
|
76
|
+
const addInto = (into, from) => {
|
|
77
|
+
for (const dimension of DIMENSIONS)
|
|
78
|
+
for (const [key, count] of histogramOf(from, dimension))
|
|
79
|
+
add(histogramOf(into, dimension), key, count);
|
|
80
|
+
};
|
|
81
|
+
const total = (histogram) => [...histogram.values()].reduce((sum, count) => sum + count, 0);
|
|
82
|
+
/** Half the sum of absolute differences of the two normalized histograms, over integer counts: sum |a * sumB - b * sumA| / (2 * sumA * sumB). */
|
|
83
|
+
const distanceBetween = (a, b) => {
|
|
84
|
+
const sumA = BigInt(total(a));
|
|
85
|
+
const sumB = BigInt(total(b));
|
|
86
|
+
let sum = 0n;
|
|
87
|
+
for (const key of new Set([...a.keys(), ...b.keys()])) {
|
|
88
|
+
const diff = BigInt(a.get(key) ?? 0) * sumB - BigInt(b.get(key) ?? 0) * sumA;
|
|
89
|
+
sum += diff < 0n ? -diff : diff;
|
|
90
|
+
}
|
|
91
|
+
return { n: sum, d: 2n * sumA * sumB };
|
|
92
|
+
};
|
|
93
|
+
/** One pass, pushing into each session's list: a copy per row is quadratic in a session's rows. */
|
|
94
|
+
const bySession = (records) => {
|
|
95
|
+
const grouped = new Map();
|
|
96
|
+
for (const record of records) {
|
|
97
|
+
const list = grouped.get(record.session);
|
|
98
|
+
if (list === undefined)
|
|
99
|
+
grouped.set(record.session, [record]);
|
|
100
|
+
else
|
|
101
|
+
list.push(record);
|
|
102
|
+
}
|
|
103
|
+
return grouped;
|
|
104
|
+
};
|
|
105
|
+
const isoDay = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
106
|
+
/**
|
|
107
|
+
* Every session's result. A session belongs wholly to the UTC week of its `tsFirst` and takes the F of its harness and week. A week qualifies at
|
|
108
|
+
* {@link BASELINE_MIN_CALLS} tool calls with its last UTC day no later than `asOf`; the baseline is a harness's other qualifying weeks, their counts
|
|
109
|
+
* added per bin so each week weighs what its calls weigh. A dimension with no counts on one side or both is dropped; `m` is the median of the
|
|
110
|
+
* rest. Mechanics only: no signature, no record content.
|
|
111
|
+
*/
|
|
112
|
+
export const selfBaseline = (sessions, tools, prompts, asOf) => {
|
|
113
|
+
const toolsOf = bySession(tools);
|
|
114
|
+
const promptsOf = bySession(prompts);
|
|
115
|
+
const groups = new Map();
|
|
116
|
+
const keyOf = new Map();
|
|
117
|
+
for (const session of sessions) {
|
|
118
|
+
const start = weekOf(session.tsFirst);
|
|
119
|
+
if (start === null) {
|
|
120
|
+
keyOf.set(session.session, null);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const harness = harnessOf(session);
|
|
124
|
+
const key = `${harness}:${String(start)}`;
|
|
125
|
+
keyOf.set(session.session, key);
|
|
126
|
+
const group = groups.get(key) ?? { harness, start, calls: 0, histograms: emptyHistograms() };
|
|
127
|
+
groups.set(key, group);
|
|
128
|
+
group.calls += callsOf(session);
|
|
129
|
+
for (const [tool, count] of Object.entries(session.tools))
|
|
130
|
+
add(histogramOf(group.histograms, 'tool mix'), tool, count);
|
|
131
|
+
for (const tool of toolsOf.get(session.session) ?? []) {
|
|
132
|
+
add(histogramOf(group.histograms, 'status'), tool.status);
|
|
133
|
+
if (tool.durationMs !== null)
|
|
134
|
+
add(histogramOf(group.histograms, 'durationMs'), binOf(tool.durationMs, DURATION_EDGES_MS));
|
|
135
|
+
}
|
|
136
|
+
for (const prompt of promptsOf.get(session.session) ?? []) {
|
|
137
|
+
add(histogramOf(group.histograms, 'prompt class'), prompt.cls);
|
|
138
|
+
if (prompt.wallSec !== null)
|
|
139
|
+
add(histogramOf(group.histograms, 'wallSec'), binOf(prompt.wallSec, WALL_EDGES_SEC));
|
|
140
|
+
// A category the source never recorded is a placeholder zero, not a measurement: the share needs input and cacheRead, and counts only the input-side categories recorded;
|
|
141
|
+
// output per turn needs output. A prompt missing what a dimension needs adds no count to it, so its denominator holds only the prompts that recorded the category.
|
|
142
|
+
const input = ['input', 'cacheWrite', 'cacheRead'].reduce((sum, field) => sum + (observesUsage(prompt, field) ? prompt.usage[field] : 0), 0);
|
|
143
|
+
if (observesUsage(prompt, 'input') && observesUsage(prompt, 'cacheRead') && input > 0)
|
|
144
|
+
add(histogramOf(group.histograms, 'cache-read share'), binOf(prompt.usage.cacheRead * 10, CACHE_SHARE_TENTHS, input));
|
|
145
|
+
if (observesUsage(prompt, 'output') && prompt.turns > 0)
|
|
146
|
+
add(histogramOf(group.histograms, 'output per turn'), binOf(prompt.usage.output, OUTPUT_PER_TURN_EDGES, prompt.turns));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const scored = new Map();
|
|
150
|
+
for (const [key, group] of groups) {
|
|
151
|
+
const base = { calls: group.calls, baselineWeeks: 0, distances: [], m: null, f: null };
|
|
152
|
+
if (group.calls < BASELINE_MIN_CALLS) {
|
|
153
|
+
scored.set(key, { ...base, status: 'not run', reason: `week under ${String(BASELINE_MIN_CALLS)} tool calls` });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const others = [...groups.values()].filter((other) => other.harness === group.harness && other.start !== group.start && other.calls >= BASELINE_MIN_CALLS && other.start + WEEK_MS - DAY_MS <= Math.floor(asOf / DAY_MS) * DAY_MS);
|
|
157
|
+
if (others.length < BASELINE_MIN_WEEKS) {
|
|
158
|
+
scored.set(key, { ...base, baselineWeeks: others.length, status: 'not run', reason: `under ${String(BASELINE_MIN_WEEKS)} qualifying baseline weeks` });
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const pooled = emptyHistograms();
|
|
162
|
+
for (const other of others)
|
|
163
|
+
addInto(pooled, other.histograms);
|
|
164
|
+
const exact = DIMENSIONS.filter((dimension) => total(histogramOf(group.histograms, dimension)) > 0 && total(histogramOf(pooled, dimension)) > 0).map((dimension) => ({ dimension, distance: distanceBetween(histogramOf(group.histograms, dimension), histogramOf(pooled, dimension)) }));
|
|
165
|
+
// The tool mix has counts on both sides whenever the week and the baseline each hold 1,000 calls, so at least one dimension always remains.
|
|
166
|
+
const m = medianOf(exact.map((entry) => entry.distance));
|
|
167
|
+
const distances = exact.map((entry) => ({ dimension: entry.dimension, distance: roundTo4(entry.distance) }));
|
|
168
|
+
scored.set(key, { ...base, baselineWeeks: others.length, distances, m: roundTo4(m), f: fitOf(m), status: 'scored', reason: null });
|
|
169
|
+
}
|
|
170
|
+
return sessions.map((session) => {
|
|
171
|
+
const key = keyOf.get(session.session) ?? null;
|
|
172
|
+
const harness = harnessOf(session);
|
|
173
|
+
const found = key === null ? undefined : scored.get(key);
|
|
174
|
+
if (key === null || found === undefined)
|
|
175
|
+
return { session: session.session, harness, week: null, status: 'not run', reason: 'no first timestamp', calls: 0, baselineWeeks: 0, distances: [], m: null, f: null };
|
|
176
|
+
return { session: session.session, harness, week: isoDay(groups.get(key)?.start ?? 0), ...found };
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
/** A count line for the scored sessions, then one per reason a session was not run. Counts only. */
|
|
180
|
+
export const formatSelfBaseline = (results) => {
|
|
181
|
+
const reasons = new Map();
|
|
182
|
+
for (const entry of results)
|
|
183
|
+
if (entry.reason !== null)
|
|
184
|
+
add(reasons, entry.reason);
|
|
185
|
+
return [
|
|
186
|
+
`${String(results.length)} sessions against their own other weeks (layer 2, local; a low F lowers W and rejects nothing)`,
|
|
187
|
+
` scored: ${String(results.filter((entry) => entry.status === 'scored').length)}`,
|
|
188
|
+
...[...reasons].sort().map(([reason, count]) => ` not run, ${reason}: ${String(count)}`),
|
|
189
|
+
].join('\n');
|
|
190
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TargetBase } from './schema.js';
|
|
2
|
+
/** Where a path target sits in the project, as a closed list. `none` is a call with no path target (a shell call, a search, a fetch). */
|
|
3
|
+
export declare const TARGET_CLASSES: readonly ["source", "test", "config", "doc", "lockfile", "generated", "data", "none", "other"];
|
|
4
|
+
export type TargetClass = (typeof TARGET_CLASSES)[number];
|
|
5
|
+
/** What an argument holds, as a closed list: a call's arguments are the sorted set of these. `none` is a call with no arguments. */
|
|
6
|
+
export declare const ARG_CLASSES: readonly ["path", "url", "pattern", "command", "number", "flag", "text", "none"];
|
|
7
|
+
export type ArgClass = (typeof ARG_CLASSES)[number];
|
|
8
|
+
/**
|
|
9
|
+
* The class of a path target, from the base that hashed it and the path's own suffix. A call with no path target (`base` null) is `none`; the order below is the order a path is asked,
|
|
10
|
+
* so a lockfile is never `config` and a `tests/data.json` is `test`. The mapping is pinned in `call-shape.test.ts`. `path` is the key the target is hashed on (relative to the repository or the cwd, never the absolute path), so the folders above a repository do not decide the class; it is read here and goes no further.
|
|
11
|
+
*/
|
|
12
|
+
export declare const targetClassOf: (path: string | undefined, base: TargetBase | null) => TargetClass;
|
|
13
|
+
/**
|
|
14
|
+
* The classes of a call's arguments, as a sorted set: which of path, URL, pattern, command, number, flag, and text its input holds, or just `none` when it holds no value. A key name
|
|
15
|
+
* gives the class of a string (`file_path` is a path, `pattern` a pattern, `command` a command), a number or a boolean gives its own, and any other value is text. The values are read here and go no further.
|
|
16
|
+
*/
|
|
17
|
+
export declare const argClassOf: (input: {
|
|
18
|
+
readonly [key: string]: unknown;
|
|
19
|
+
}) => ArgClass[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Where a path target sits in the project, as a closed list. `none` is a call with no path target (a shell call, a search, a fetch). */
|
|
2
|
+
export const TARGET_CLASSES = ['source', 'test', 'config', 'doc', 'lockfile', 'generated', 'data', 'none', 'other'];
|
|
3
|
+
/** What an argument holds, as a closed list: a call's arguments are the sorted set of these. `none` is a call with no arguments. */
|
|
4
|
+
export const ARG_CLASSES = ['path', 'url', 'pattern', 'command', 'number', 'flag', 'text', 'none'];
|
|
5
|
+
const words = (list) => new Set(list.split(/\s+/).filter((word) => word !== ''));
|
|
6
|
+
const LOCKFILES = words('package-lock.json npm-shrinkwrap.json yarn.lock pnpm-lock.yaml bun.lockb bun.lock cargo.lock poetry.lock pipfile.lock uv.lock gemfile.lock composer.lock go.sum gradle.lockfile packages.lock.json podfile.lock pubspec.lock mix.lock flake.lock');
|
|
7
|
+
const GENERATED_DIRS = words('dist build out node_modules .next .nuxt .astro .svelte-kit coverage target __generated__ generated .turbo .cache __pycache__ vendor');
|
|
8
|
+
const TEST_DIRS = words('test tests __tests__ spec specs e2e __mocks__ fixtures testdata cypress');
|
|
9
|
+
const DOC_DIRS = words('docs doc documentation');
|
|
10
|
+
const SOURCE_EXTENSIONS = words('ts tsx mts cts js jsx mjs cjs py pyi java kt kts go rs rb php cs c h cc cpp hpp swift scala sh bash zsh ps1 psm1 sql astro vue svelte css scss sass less html htm lua dart ex exs erl clj hs ml r jl pl zig groovy gradle');
|
|
11
|
+
const DOC_EXTENSIONS = words('md mdx rst txt adoc org tex');
|
|
12
|
+
const CONFIG_EXTENSIONS = words('json jsonc json5 yaml yml toml ini cfg conf properties env plist editorconfig xml');
|
|
13
|
+
const DATA_EXTENSIONS = words('csv tsv ndjson jsonl parquet avro arrow db sqlite sqlite3 log xlsx xls dat bin pkl');
|
|
14
|
+
const CONFIG_BASENAMES = words('dockerfile makefile justfile procfile gemfile rakefile pom.xml build.gradle settings.gradle .gitignore .gitattributes .npmrc .nvmrc .prettierrc .eslintrc .dockerignore .editorconfig');
|
|
15
|
+
const DOC_BASENAMES = words('readme license licence changelog contributing notice authors codeowners');
|
|
16
|
+
const TEST_NAME = /(?:^|[._-])(?:test|tests|spec)(?:[._-]|$)|(?:_test|_spec|Test|Tests|Spec)\.[a-z]+$/;
|
|
17
|
+
const GENERATED_NAME = /\.(?:min\.(?:js|css)|d\.ts|map|generated\.[a-z]+|pb\.go|g\.dart)$/;
|
|
18
|
+
/**
|
|
19
|
+
* The class of a path target, from the base that hashed it and the path's own suffix. A call with no path target (`base` null) is `none`; the order below is the order a path is asked,
|
|
20
|
+
* so a lockfile is never `config` and a `tests/data.json` is `test`. The mapping is pinned in `call-shape.test.ts`. `path` is the key the target is hashed on (relative to the repository or the cwd, never the absolute path), so the folders above a repository do not decide the class; it is read here and goes no further.
|
|
21
|
+
*/
|
|
22
|
+
export const targetClassOf = (path, base) => {
|
|
23
|
+
if (base === null || path === undefined || path === '')
|
|
24
|
+
return 'none';
|
|
25
|
+
const parts = path.replace(/\\/g, '/').split('/').filter((part) => part !== '' && part !== '.');
|
|
26
|
+
const name = parts.at(-1) ?? '';
|
|
27
|
+
const lower = name.toLowerCase();
|
|
28
|
+
const dirs = parts.slice(0, -1).map((part) => part.toLowerCase());
|
|
29
|
+
const dot = lower.lastIndexOf('.');
|
|
30
|
+
const extension = dot > 0 ? lower.slice(dot + 1) : lower.startsWith('.') ? lower.slice(1) : '';
|
|
31
|
+
if (LOCKFILES.has(lower))
|
|
32
|
+
return 'lockfile';
|
|
33
|
+
if (dirs.some((dir) => GENERATED_DIRS.has(dir)) || GENERATED_NAME.test(lower))
|
|
34
|
+
return 'generated';
|
|
35
|
+
if (dirs.some((dir) => TEST_DIRS.has(dir)) || TEST_NAME.test(name))
|
|
36
|
+
return 'test';
|
|
37
|
+
const stem = dot > 0 ? lower.slice(0, dot) : lower;
|
|
38
|
+
if (DOC_EXTENSIONS.has(extension) || DOC_BASENAMES.has(stem) || dirs.some((dir) => DOC_DIRS.has(dir)))
|
|
39
|
+
return 'doc';
|
|
40
|
+
if (CONFIG_BASENAMES.has(lower) || /^\.[a-z0-9_.-]*(?:rc|ignore)(?:\.[a-z]+)?$/.test(lower) || /\.config\.[a-z]+$/.test(lower) || /^tsconfig[a-z0-9.-]*\.json$/.test(lower) || /^\.env(?:\.|$)/.test(lower))
|
|
41
|
+
return 'config';
|
|
42
|
+
if (SOURCE_EXTENSIONS.has(extension))
|
|
43
|
+
return 'source';
|
|
44
|
+
if (DATA_EXTENSIONS.has(extension))
|
|
45
|
+
return 'data';
|
|
46
|
+
if (CONFIG_EXTENSIONS.has(extension))
|
|
47
|
+
return 'config';
|
|
48
|
+
return 'other';
|
|
49
|
+
};
|
|
50
|
+
const PATH_ARGS = words('file_path notebook_path path cwd workdir directory dir filepath filename file');
|
|
51
|
+
const URL_ARGS = words('url uri href');
|
|
52
|
+
const PATTERN_ARGS = words('pattern regex regexp glob include exclude');
|
|
53
|
+
const COMMAND_ARGS = words('command cmd script');
|
|
54
|
+
const URL_VALUE = /^[a-z][a-z0-9+.-]*:\/\/\S+$/i;
|
|
55
|
+
const argClassOfEntry = (key, value) => {
|
|
56
|
+
if (value === null || value === undefined)
|
|
57
|
+
return null;
|
|
58
|
+
if (typeof value === 'number')
|
|
59
|
+
return 'number';
|
|
60
|
+
if (typeof value === 'boolean')
|
|
61
|
+
return 'flag';
|
|
62
|
+
const lower = key.toLowerCase();
|
|
63
|
+
if (PATH_ARGS.has(lower))
|
|
64
|
+
return 'path';
|
|
65
|
+
if (URL_ARGS.has(lower) || (typeof value === 'string' && URL_VALUE.test(value)))
|
|
66
|
+
return 'url';
|
|
67
|
+
if (PATTERN_ARGS.has(lower))
|
|
68
|
+
return 'pattern';
|
|
69
|
+
if (COMMAND_ARGS.has(lower))
|
|
70
|
+
return 'command';
|
|
71
|
+
return 'text';
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* The classes of a call's arguments, as a sorted set: which of path, URL, pattern, command, number, flag, and text its input holds, or just `none` when it holds no value. A key name
|
|
75
|
+
* gives the class of a string (`file_path` is a path, `pattern` a pattern, `command` a command), a number or a boolean gives its own, and any other value is text. The values are read here and go no further.
|
|
76
|
+
*/
|
|
77
|
+
export const argClassOf = (input) => {
|
|
78
|
+
const held = new Set();
|
|
79
|
+
for (const [key, value] of Object.entries(input)) {
|
|
80
|
+
const kind = argClassOfEntry(key, value);
|
|
81
|
+
if (kind !== null)
|
|
82
|
+
held.add(kind);
|
|
83
|
+
}
|
|
84
|
+
return held.size === 0 ? ['none'] : ARG_CLASSES.filter((kind) => held.has(kind)).sort();
|
|
85
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The `version` in a package.json text, or `0.0.0` when it has none. */
|
|
2
|
+
export declare const versionIn: (manifestText: string) => string;
|
|
3
|
+
/** This package's version: `package.json` sits two folders above both `src/sessions/` and `dist/sessions/`. */
|
|
4
|
+
export declare const oarsVersion: () => string;
|
|
5
|
+
export interface CaptureRuntime {
|
|
6
|
+
oars: string;
|
|
7
|
+
node: string;
|
|
8
|
+
os: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const captureRuntime: () => CaptureRuntime;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// What a capture records about the machine it ran on (`env` on the session record, class P): the oars package version, Node.js, and OS.
|
|
2
|
+
// Names and versions only; a library version is recorded only when a transcript states it, and none is read here.
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
/** The `version` in a package.json text, or `0.0.0` when it has none. */
|
|
5
|
+
export const versionIn = (manifestText) => {
|
|
6
|
+
const manifest = JSON.parse(manifestText);
|
|
7
|
+
const version = typeof manifest === 'object' && manifest !== null ? Reflect.get(manifest, 'version') : undefined;
|
|
8
|
+
return typeof version === 'string' ? version : '0.0.0';
|
|
9
|
+
};
|
|
10
|
+
/** This package's version: `package.json` sits two folders above both `src/sessions/` and `dist/sessions/`. */
|
|
11
|
+
export const oarsVersion = () => versionIn(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
12
|
+
export const captureRuntime = () => ({ oars: oarsVersion(), node: process.version, os: process.platform });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type CellSet, type Grain } from './cells.js';
|
|
2
|
+
import type { Motif } from './motif.js';
|
|
3
|
+
import { type SessionSkeleton, type ShellReader } from './pool-wire.js';
|
|
4
|
+
import { type RecallGraph } from './recall-consequence.js';
|
|
5
|
+
import type { Store } from './store.js';
|
|
6
|
+
export interface CellRow {
|
|
7
|
+
key: string;
|
|
8
|
+
harnessBucket: string;
|
|
9
|
+
motifId: string;
|
|
10
|
+
frameBucket: string;
|
|
11
|
+
grain: Grain;
|
|
12
|
+
day: string;
|
|
13
|
+
exposed: number;
|
|
14
|
+
sessions: number;
|
|
15
|
+
ok: number;
|
|
16
|
+
failed: number;
|
|
17
|
+
tokens: number;
|
|
18
|
+
/** How many of the batch's sessions recorded input, the coverage of `tokens`; the served column sums it. */
|
|
19
|
+
tokenSessions: number;
|
|
20
|
+
/** How many of the batch's prompts (or steps) `tokens` covers: the divisor of a per-prompt mean. */
|
|
21
|
+
tokenPrompts: number;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
}
|
|
24
|
+
export interface StratumRow {
|
|
25
|
+
key: string;
|
|
26
|
+
dimension: string;
|
|
27
|
+
value: string;
|
|
28
|
+
sessions: number;
|
|
29
|
+
}
|
|
30
|
+
/** What one batch adds to the index: its cells, the motifs they name, and rule 3's strata. */
|
|
31
|
+
export interface Increments {
|
|
32
|
+
cells: Map<string, CellRow>;
|
|
33
|
+
motifs: Motif[];
|
|
34
|
+
strata: Map<string, StratumRow>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Rule 5: exact tokens and durations never reach `index`. A cell counts a bucketed value as the bucket's lowest value, so a derivation reads only what a deposit carries, and a
|
|
38
|
+
* summed window is a sum of those. A value already at a bucket's floor stays there, which is why a skeleton's store and a local store of the same session derive the same cells.
|
|
39
|
+
* Rules 1 and 2: a window names a shell step by its program and subcommand alone, and each flag and each pull of it adds a one-step cell of its own.
|
|
40
|
+
*/
|
|
41
|
+
export declare const cellsOf: (store: Store, graph?: RecallGraph) => CellSet;
|
|
42
|
+
/** A local store with each shell step read as a deposit reads it (`projectedSteps`), so its named motifs are the ones the pool holds. It uses the open gate, which keeps the name shape and length but applies no redactor or exclusion term: a local read sends nothing. */
|
|
43
|
+
export declare const projectedStore: (store: Store, read: ShellReader) => Store;
|
|
44
|
+
/** One account's deposits as a store. A session's id is its skeleton's, which the client hashes per account, so it joins nothing across accounts. */
|
|
45
|
+
export declare const storeOf: (skeletons: readonly SessionSkeleton[]) => Store;
|
|
46
|
+
/** Whole cells and strata, summed over a batch's own sessions: the same cells a store of that batch alone derives. */
|
|
47
|
+
export declare const incrementsOf: (skeleton: SessionSkeleton) => Increments;
|