@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22
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/assets/gep/genes.jsonl +5 -5
- package/dist/algo/bans.d.ts +10 -1
- package/dist/algo/bans.js +56 -6
- package/dist/algo/candidateAssembly.d.ts +25 -2
- package/dist/algo/candidateAssembly.js +171 -26
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/conversationSniffer.js +25 -8
- package/dist/algo/cycleEngine.d.ts +63 -5
- package/dist/algo/cycleEngine.js +320 -42
- package/dist/algo/cycleFailureClassifier.d.ts +1 -1
- package/dist/algo/evolutionEvent.d.ts +26 -0
- package/dist/algo/evolutionEvent.js +32 -0
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +36 -3
- package/dist/algo/geneHealth.js +47 -4
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +126 -6
- package/dist/algo/geneSelection.js +479 -36
- package/dist/algo/index.d.ts +6 -1
- package/dist/algo/index.js +6 -1
- package/dist/algo/kautoProjection.d.ts +41 -0
- package/dist/algo/kautoProjection.js +95 -0
- package/dist/algo/kautoValidator.d.ts +68 -0
- package/dist/algo/kautoValidator.js +256 -0
- package/dist/algo/memoryGraph.d.ts +62 -0
- package/dist/algo/memoryGraph.js +86 -0
- package/dist/algo/orchestrator.d.ts +17 -1
- package/dist/algo/orchestrator.js +30 -4
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/solidify.d.ts +11 -2
- package/dist/algo/solidify.js +37 -7
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetrepair/hubRejection.d.ts +12 -0
- package/dist/assetrepair/hubRejection.js +109 -0
- package/dist/assetrepair/index.d.ts +2 -0
- package/dist/assetrepair/index.js +2 -0
- package/dist/assetrepair/repair.d.ts +33 -0
- package/dist/assetrepair/repair.js +155 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
- package/dist/assetstore/assetSidecarRecords.js +384 -0
- package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
- package/dist/assetstore/assetSidecarRecovery.js +288 -0
- package/dist/assetstore/assetStoreHealth.d.ts +81 -0
- package/dist/assetstore/assetStoreHealth.js +319 -0
- package/dist/assetstore/assetStoreLayout.d.ts +2 -0
- package/dist/assetstore/assetStoreLayout.js +6 -0
- package/dist/assetstore/assetStoreStorage.d.ts +42 -0
- package/dist/assetstore/assetStoreStorage.js +336 -0
- package/dist/assetstore/assetSyncLedger.d.ts +91 -1
- package/dist/assetstore/assetSyncLedger.js +718 -59
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +5 -0
- package/dist/assetstore/index.js +5 -0
- package/dist/assetstore/learningHistory.js +3 -3
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +12 -1
- package/dist/assetstore/localJsonl.js +251 -42
- package/dist/assetstore/provenance.d.ts +93 -4
- package/dist/assetstore/provenance.js +368 -84
- package/dist/assetstore/provider.d.ts +63 -0
- package/dist/assetstore/provider.js +97 -6
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +39 -1
- package/dist/assetstore/reviewLedger.d.ts +8 -2
- package/dist/assetstore/reviewLedger.js +71 -45
- package/dist/assetstore/unionReadStore.d.ts +25 -0
- package/dist/assetstore/unionReadStore.js +119 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +3 -1
- package/dist/benchmark/index.js +3 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/benchmark/triggerShift.d.ts +62 -0
- package/dist/benchmark/triggerShift.js +106 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +3 -1
- package/dist/bootstrap/index.js +3 -1
- package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
- package/dist/bootstrap/v1EnvCompat.js +303 -0
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +2 -1
- package/dist/events/ingest.js +15 -0
- package/dist/events/paths.d.ts +12 -10
- package/dist/events/paths.js +24 -20
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +68 -4
- package/dist/exec/autoExec.js +392 -33
- package/dist/exec/autonomousCycle.d.ts +31 -4
- package/dist/exec/autonomousCycle.js +71 -13
- package/dist/exec/claudeBridge.d.ts +80 -15
- package/dist/exec/claudeBridge.js +845 -76
- package/dist/exec/executionBinding.d.ts +414 -0
- package/dist/exec/executionBinding.js +588 -0
- package/dist/exec/index.d.ts +1 -0
- package/dist/exec/index.js +1 -0
- package/dist/exec/openPrRegistry.d.ts +8 -2
- package/dist/exec/openPrRegistry.js +32 -22
- package/dist/exec/prompt.js +14 -1
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +153 -36
- package/dist/exec/runnerRegistry.js +848 -65
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hooks/hooks.js +1 -0
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +124 -1
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +29 -8
- package/dist/hub/capability.d.ts +130 -4
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +3 -2
- package/dist/hub/fake.js +2 -1
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +122 -7
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/issueReporter/index.d.ts +156 -0
- package/dist/issueReporter/index.js +1688 -0
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +12 -2
- package/dist/mailbox/ipcServer.js +183 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/modelCompatibility.d.ts +164 -0
- package/dist/modelCompatibility.js +309 -0
- package/dist/observers/valueDigestObserver.d.ts +9 -0
- package/dist/observers/valueDigestObserver.js +35 -2
- package/dist/ops/cleanup.js +1 -1
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/savingsCore.js +1 -2
- package/dist/ops/selfUpdate.d.ts +18 -1
- package/dist/ops/selfUpdate.js +88 -23
- package/dist/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +24 -24
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/proofOfWork.d.ts +125 -6
- package/dist/schema/proofOfWork.js +102 -4
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/shadow/shadowHub.js +1 -0
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/cycleHistoryFromEvents.js +17 -8
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +169 -1
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +31 -6
- package/dist/signals/index.d.ts +4 -1
- package/dist/signals/index.js +4 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -0
- package/dist/signals/signalGate.js +1 -1
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/strategy/constraintAblation.d.ts +64 -0
- package/dist/strategy/constraintAblation.js +2820 -0
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/strategy/index.d.ts +2 -1
- package/dist/strategy/index.js +2 -1
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +216 -0
- package/dist/trace/learningTrace.js +298 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fetchPort.d.ts +1 -0
- package/dist/util/fetchPort.js +11 -0
- package/dist/util/fileLock.d.ts +113 -7
- package/dist/util/fileLock.js +1035 -80
- package/dist/util/index.d.ts +3 -1
- package/dist/util/index.js +2 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +30 -0
- package/dist/verify/sandboxRunner.js +370 -27
- package/dist/verify/sandboxedValidation.d.ts +16 -2
- package/dist/verify/sandboxedValidation.js +200 -22
- package/dist/verify/validation.d.ts +35 -4
- package/dist/verify/validation.js +164 -16
- package/dist/wire/geneHints.d.ts +88 -16
- package/dist/wire/geneHints.js +124 -15
- package/dist/wire/index.d.ts +8 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/schemaGate.d.ts +21 -0
- package/dist/wire/schemaGate.js +119 -10
- package/dist/workflow/dsl.d.ts +24 -3
- package/dist/workflow/dsl.js +4 -0
- package/dist/workflow/engine.d.ts +5 -1
- package/dist/workflow/engine.js +3 -0
- package/dist/workflow/index.d.ts +3 -1
- package/dist/workflow/index.js +3 -1
- package/dist/workflow/runtime.d.ts +110 -0
- package/dist/workflow/runtime.js +1298 -0
- package/dist/workflow/stateStore.d.ts +172 -0
- package/dist/workflow/stateStore.js +1044 -0
- package/package.json +12 -4
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { type GeneDecision, type SelectionInput } from '../algo/geneSelection.js';
|
|
2
|
+
export declare const SELECTION_FLAT_ABSTENTION_BENCHMARK_VERSION = "global-flat-abstention-paired-v1";
|
|
3
|
+
export type SelectionFlatArm = 'current' | 'global-flat-abstain';
|
|
4
|
+
export type SelectionFlatSplit = 'calibration' | 'holdout';
|
|
5
|
+
export type SelectionFlatAction = {
|
|
6
|
+
kind: 'reuse';
|
|
7
|
+
geneId: string;
|
|
8
|
+
assetId?: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'innovate';
|
|
11
|
+
};
|
|
12
|
+
export interface SelectionFlatComparison {
|
|
13
|
+
targeted: boolean;
|
|
14
|
+
targetReason?: 'outside_plateau_flat_positive';
|
|
15
|
+
currentDecision: GeneDecision;
|
|
16
|
+
currentAction: SelectionFlatAction;
|
|
17
|
+
globalFlatAbstainAction: SelectionFlatAction;
|
|
18
|
+
}
|
|
19
|
+
export interface SelectionFlatCohort {
|
|
20
|
+
id: string;
|
|
21
|
+
version: string;
|
|
22
|
+
sourceCommit: string;
|
|
23
|
+
taskFingerprints: readonly string[];
|
|
24
|
+
verifier: {
|
|
25
|
+
id: string;
|
|
26
|
+
version: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export type SelectionFlatExpectedRole = 'target' | 'control';
|
|
30
|
+
export interface SelectionFlatExpectation {
|
|
31
|
+
role: SelectionFlatExpectedRole;
|
|
32
|
+
currentAction: SelectionFlatAction;
|
|
33
|
+
globalFlatAbstainAction: SelectionFlatAction;
|
|
34
|
+
}
|
|
35
|
+
export interface SelectionFlatTaskSnapshot<I> {
|
|
36
|
+
id: string;
|
|
37
|
+
workspaceSnapshotDigest: string;
|
|
38
|
+
split: SelectionFlatSplit;
|
|
39
|
+
stratum: string;
|
|
40
|
+
/** Verifier input is a private structured-clone-normalized copy (for example, Node Buffer becomes Uint8Array). */
|
|
41
|
+
payload: I;
|
|
42
|
+
selection: Omit<SelectionInput, 'selectionGuard'>;
|
|
43
|
+
expectation: SelectionFlatExpectation;
|
|
44
|
+
}
|
|
45
|
+
export interface SelectionFlatTask<I> extends SelectionFlatTaskSnapshot<I> {
|
|
46
|
+
taskFingerprint: string;
|
|
47
|
+
}
|
|
48
|
+
export interface SelectionFlatBenchmarkSuite<I> {
|
|
49
|
+
name: string;
|
|
50
|
+
cohort: SelectionFlatCohort;
|
|
51
|
+
tasks: readonly SelectionFlatTask<I>[];
|
|
52
|
+
}
|
|
53
|
+
export interface SelectionFlatVerifierProof {
|
|
54
|
+
id: string;
|
|
55
|
+
version: string;
|
|
56
|
+
proofSha256: string;
|
|
57
|
+
}
|
|
58
|
+
export interface SelectionFlatObjectiveOutcome {
|
|
59
|
+
passed: boolean;
|
|
60
|
+
producedValue: boolean;
|
|
61
|
+
unsafe: boolean;
|
|
62
|
+
cost: number;
|
|
63
|
+
verifier: SelectionFlatVerifierProof;
|
|
64
|
+
}
|
|
65
|
+
export interface SelectionFlatVerificationRequest<I> {
|
|
66
|
+
task: SelectionFlatTask<I>;
|
|
67
|
+
arm: SelectionFlatArm;
|
|
68
|
+
action: SelectionFlatAction;
|
|
69
|
+
isolationKey: string;
|
|
70
|
+
order: 0 | 1;
|
|
71
|
+
}
|
|
72
|
+
export type SelectionFlatVerifier<I> = (request: SelectionFlatVerificationRequest<I>) => Promise<SelectionFlatObjectiveOutcome> | SelectionFlatObjectiveOutcome;
|
|
73
|
+
export interface SelectionFlatBenchmarkRow {
|
|
74
|
+
taskFingerprint: string;
|
|
75
|
+
split: SelectionFlatSplit;
|
|
76
|
+
stratum: string;
|
|
77
|
+
armOrder: readonly [SelectionFlatArm, SelectionFlatArm];
|
|
78
|
+
current: {
|
|
79
|
+
action: SelectionFlatAction['kind'];
|
|
80
|
+
outcome: SelectionFlatObjectiveOutcome;
|
|
81
|
+
};
|
|
82
|
+
globalFlatAbstain: {
|
|
83
|
+
action: SelectionFlatAction['kind'];
|
|
84
|
+
outcome: SelectionFlatObjectiveOutcome;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export interface SelectionFlatPairedStats {
|
|
88
|
+
n: number;
|
|
89
|
+
currentPasses: number;
|
|
90
|
+
globalFlatAbstainPasses: number;
|
|
91
|
+
bothPass: number;
|
|
92
|
+
bothFail: number;
|
|
93
|
+
globalOnlyPass: number;
|
|
94
|
+
currentOnlyPass: number;
|
|
95
|
+
currentPassRate: number;
|
|
96
|
+
globalFlatAbstainPassRate: number;
|
|
97
|
+
pairedRiskDifference: number;
|
|
98
|
+
ci95Low: number;
|
|
99
|
+
ci95High: number;
|
|
100
|
+
mcnemarPValue: number;
|
|
101
|
+
currentProducedValueRate: number;
|
|
102
|
+
globalFlatAbstainProducedValueRate: number;
|
|
103
|
+
currentUnsafeRate: number;
|
|
104
|
+
globalFlatAbstainUnsafeRate: number;
|
|
105
|
+
unsafeRateDelta: number;
|
|
106
|
+
currentAverageCost: number;
|
|
107
|
+
globalFlatAbstainAverageCost: number;
|
|
108
|
+
averageCostDelta: number;
|
|
109
|
+
currentInnovateRate: number;
|
|
110
|
+
globalFlatAbstainInnovateRate: number;
|
|
111
|
+
}
|
|
112
|
+
export interface SelectionFlatBenchmarkReport {
|
|
113
|
+
benchmarkVersion: typeof SELECTION_FLAT_ABSTENTION_BENCHMARK_VERSION;
|
|
114
|
+
statisticalMethods: {
|
|
115
|
+
mcnemar: 'exact-two-sided-binomial';
|
|
116
|
+
riskDifferenceCi: 'paired-hoeffding-95';
|
|
117
|
+
};
|
|
118
|
+
suite: string;
|
|
119
|
+
cohort: Omit<SelectionFlatCohort, 'taskFingerprints'> & {
|
|
120
|
+
registeredTasks: number;
|
|
121
|
+
};
|
|
122
|
+
datasetDigest: string;
|
|
123
|
+
targetTasks: number;
|
|
124
|
+
controlTasks: number;
|
|
125
|
+
controlDivergences: 0;
|
|
126
|
+
rows: SelectionFlatBenchmarkRow[];
|
|
127
|
+
overall: SelectionFlatPairedStats;
|
|
128
|
+
calibration: SelectionFlatPairedStats;
|
|
129
|
+
holdout: SelectionFlatPairedStats;
|
|
130
|
+
strata: Record<string, SelectionFlatPairedStats>;
|
|
131
|
+
}
|
|
132
|
+
export interface SelectionFlatPassPair {
|
|
133
|
+
currentPassed: boolean;
|
|
134
|
+
globalFlatAbstainPassed: boolean;
|
|
135
|
+
}
|
|
136
|
+
/** Compare production with a benchmark-only global flat-positive abstention counterfactual. */
|
|
137
|
+
export declare function compareOutsidePlateauFlatSelection(input: Omit<SelectionInput, 'selectionGuard'>): SelectionFlatComparison;
|
|
138
|
+
/** Compute the digest that the cohort manifest must pre-register for this exact task snapshot. */
|
|
139
|
+
export declare function computeSelectionFlatTaskFingerprint<I>(task: SelectionFlatTaskSnapshot<I>): string;
|
|
140
|
+
/** Exact two-sided McNemar p-value over the discordant pairs. */
|
|
141
|
+
export declare function exactMcNemarTwoSided(globalOnlyPass: number, currentOnlyPass: number): number;
|
|
142
|
+
/** Distribution-free Hoeffding 95% CI for the mean paired success difference (global minus current). */
|
|
143
|
+
export declare function pairedRiskDifference95(pairs: readonly SelectionFlatPassPair[]): {
|
|
144
|
+
difference: number;
|
|
145
|
+
ci95Low: number;
|
|
146
|
+
ci95High: number;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Run a pre-registered, full-information paired benchmark. Controls are classification checks only and are never
|
|
150
|
+
* executed. Target tasks run both arms on separately cloned inputs and caller-owned isolated workspaces.
|
|
151
|
+
*/
|
|
152
|
+
export declare function runSelectionFlatAbstentionBenchmark<I>(suite: SelectionFlatBenchmarkSuite<I>, verifier: SelectionFlatVerifier<I>): Promise<SelectionFlatBenchmarkReport>;
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { assessSelectionGuard, engineHealthSelection, } from '../algo/geneSelection.js';
|
|
3
|
+
export const SELECTION_FLAT_ABSTENTION_BENCHMARK_VERSION = 'global-flat-abstention-paired-v1';
|
|
4
|
+
const FULL_COMMIT_RE = /^[0-9a-f]{40}$/;
|
|
5
|
+
const SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
6
|
+
const SAFE_METADATA_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
7
|
+
const CI95_ALPHA = 0.05;
|
|
8
|
+
function actionForDecision(decision) {
|
|
9
|
+
if (!decision.selectedGeneId)
|
|
10
|
+
return { kind: 'innovate' };
|
|
11
|
+
return {
|
|
12
|
+
kind: 'reuse',
|
|
13
|
+
geneId: decision.selectedGeneId,
|
|
14
|
+
...(decision.selectedAssetId ? { assetId: decision.selectedAssetId } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function actionsEqual(left, right) {
|
|
18
|
+
return left.kind === right.kind
|
|
19
|
+
&& (left.kind === 'innovate' || (right.kind === 'reuse'
|
|
20
|
+
&& left.geneId === right.geneId
|
|
21
|
+
&& left.assetId === right.assetId));
|
|
22
|
+
}
|
|
23
|
+
/** Compare production with a benchmark-only global flat-positive abstention counterfactual. */
|
|
24
|
+
export function compareOutsidePlateauFlatSelection(input) {
|
|
25
|
+
const currentDecision = engineHealthSelection.run({ ...input, selectionGuard: 'enforce' }, { now: 0, cycleId: 'benchmark-global-flat-abstention', rng: () => 0.5 });
|
|
26
|
+
const currentAction = actionForDecision(currentDecision);
|
|
27
|
+
const floor = input.floor ?? 0;
|
|
28
|
+
const eligible = currentDecision.candidates.filter((candidate) => candidate.score > floor);
|
|
29
|
+
const flatPoolAssessment = assessSelectionGuard(eligible, true);
|
|
30
|
+
const targeted = input.exploration?.plateau?.active !== true
|
|
31
|
+
&& input.exploration?.driftEnabled !== true
|
|
32
|
+
&& !input.forcedGeneId?.trim()
|
|
33
|
+
&& currentAction.kind === 'reuse'
|
|
34
|
+
&& currentDecision.selectionGuard?.status === 'allowed'
|
|
35
|
+
&& flatPoolAssessment.reason === 'plateau_flat_match';
|
|
36
|
+
return {
|
|
37
|
+
targeted,
|
|
38
|
+
...(targeted ? { targetReason: 'outside_plateau_flat_positive' } : {}),
|
|
39
|
+
currentDecision,
|
|
40
|
+
currentAction,
|
|
41
|
+
globalFlatAbstainAction: targeted ? { kind: 'innovate' } : currentAction,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function assertSafeMetadata(value, field) {
|
|
45
|
+
if (typeof value !== 'string' || !SAFE_METADATA_RE.test(value)) {
|
|
46
|
+
throw new Error(`${field} must be a non-sensitive ASCII slug`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function validateExpectedAction(action, field) {
|
|
50
|
+
if (action?.kind === 'innovate')
|
|
51
|
+
return;
|
|
52
|
+
if (action?.kind !== 'reuse' || typeof action.geneId !== 'string' || action.geneId.trim().length === 0) {
|
|
53
|
+
throw new Error(`${field} must be a valid selection action`);
|
|
54
|
+
}
|
|
55
|
+
if (action.assetId !== undefined && (typeof action.assetId !== 'string' || action.assetId.trim().length === 0)) {
|
|
56
|
+
throw new Error(`${field}.assetId must be non-empty when present`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function sha256(value) {
|
|
60
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
61
|
+
}
|
|
62
|
+
function encodeOwnProperties(value, context) {
|
|
63
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
64
|
+
throw new Error('task snapshot contains symbol keys');
|
|
65
|
+
}
|
|
66
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
67
|
+
return Object.getOwnPropertyNames(value).map((key) => {
|
|
68
|
+
const descriptor = descriptors[key];
|
|
69
|
+
if (descriptor.get || descriptor.set)
|
|
70
|
+
throw new Error('task snapshot contains accessor properties');
|
|
71
|
+
return `${JSON.stringify(key)}:${canonicalSnapshotValue(descriptor.value, context)}`;
|
|
72
|
+
}).join(',');
|
|
73
|
+
}
|
|
74
|
+
function canonicalSnapshotValue(value, context = { seen: new Map(), nextId: 0 }) {
|
|
75
|
+
if (value === null)
|
|
76
|
+
return 'null';
|
|
77
|
+
if (value === undefined)
|
|
78
|
+
return 'undefined';
|
|
79
|
+
if (typeof value === 'string')
|
|
80
|
+
return `string:${JSON.stringify(value)}`;
|
|
81
|
+
if (typeof value === 'boolean')
|
|
82
|
+
return value ? 'boolean:true' : 'boolean:false';
|
|
83
|
+
if (typeof value === 'number') {
|
|
84
|
+
if (!Number.isFinite(value))
|
|
85
|
+
throw new Error('task snapshot contains a non-finite number');
|
|
86
|
+
return `number:${Object.is(value, -0) ? '-0' : String(value)}`;
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === 'bigint')
|
|
89
|
+
return `bigint:${value.toString()}`;
|
|
90
|
+
if (typeof value !== 'object') {
|
|
91
|
+
throw new Error(`task snapshot contains unsupported ${typeof value} data`);
|
|
92
|
+
}
|
|
93
|
+
const previousId = context.seen.get(value);
|
|
94
|
+
if (previousId !== undefined)
|
|
95
|
+
return `reference:${previousId}`;
|
|
96
|
+
const id = context.nextId;
|
|
97
|
+
context.nextId += 1;
|
|
98
|
+
context.seen.set(value, id);
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
return `array#${id}:{${encodeOwnProperties(value, context)}}`;
|
|
101
|
+
}
|
|
102
|
+
if (value instanceof Date) {
|
|
103
|
+
if (Number.isNaN(value.getTime()))
|
|
104
|
+
throw new Error('task snapshot contains an invalid date');
|
|
105
|
+
return `date#${id}:${value.toISOString()}:{${encodeOwnProperties(value, context)}}`;
|
|
106
|
+
}
|
|
107
|
+
if (value instanceof Map) {
|
|
108
|
+
const entries = [...value.entries()].map(([key, item]) => (`${canonicalSnapshotValue(key, context)}=>${canonicalSnapshotValue(item, context)}`));
|
|
109
|
+
return `map#${id}:[${entries.join(',')}]:{${encodeOwnProperties(value, context)}}`;
|
|
110
|
+
}
|
|
111
|
+
if (value instanceof Set) {
|
|
112
|
+
const entries = [...value].map((item) => canonicalSnapshotValue(item, context));
|
|
113
|
+
return `set#${id}:[${entries.join(',')}]:{${encodeOwnProperties(value, context)}}`;
|
|
114
|
+
}
|
|
115
|
+
if (ArrayBuffer.isView(value)) {
|
|
116
|
+
if (typeof SharedArrayBuffer !== 'undefined' && value.buffer instanceof SharedArrayBuffer) {
|
|
117
|
+
throw new Error('task snapshot must not contain SharedArrayBuffer-backed views');
|
|
118
|
+
}
|
|
119
|
+
return `view#${id}:${value.constructor.name}:${value.byteOffset}:${value.byteLength}:${canonicalSnapshotValue(value.buffer, context)}:{${encodeOwnProperties(value, context)}}`;
|
|
120
|
+
}
|
|
121
|
+
if (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer) {
|
|
122
|
+
throw new Error('task snapshot must not contain SharedArrayBuffer');
|
|
123
|
+
}
|
|
124
|
+
if (value instanceof ArrayBuffer) {
|
|
125
|
+
return `array-buffer#${id}:${Buffer.from(value).toString('hex')}:{${encodeOwnProperties(value, context)}}`;
|
|
126
|
+
}
|
|
127
|
+
const prototype = Object.getPrototypeOf(value);
|
|
128
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
129
|
+
throw new Error('task snapshot contains unsupported non-plain object data');
|
|
130
|
+
}
|
|
131
|
+
return `object#${id}:{${encodeOwnProperties(value, context)}}`;
|
|
132
|
+
}
|
|
133
|
+
function cloneStructured(value, errorMessage) {
|
|
134
|
+
try {
|
|
135
|
+
return structuredClone(value);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
throw new Error(errorMessage);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Compute the digest that the cohort manifest must pre-register for this exact task snapshot. */
|
|
142
|
+
export function computeSelectionFlatTaskFingerprint(task) {
|
|
143
|
+
const snapshot = cloneStructured(task, 'task snapshot must be structured-cloneable for fingerprinting');
|
|
144
|
+
return sha256(canonicalSnapshotValue([
|
|
145
|
+
SELECTION_FLAT_ABSTENTION_BENCHMARK_VERSION,
|
|
146
|
+
snapshot.id,
|
|
147
|
+
snapshot.workspaceSnapshotDigest,
|
|
148
|
+
snapshot.split,
|
|
149
|
+
snapshot.stratum,
|
|
150
|
+
snapshot.payload,
|
|
151
|
+
snapshot.selection,
|
|
152
|
+
snapshot.expectation,
|
|
153
|
+
]));
|
|
154
|
+
}
|
|
155
|
+
function isolationKey(datasetDigest, executionNonce, fingerprint, arm) {
|
|
156
|
+
return sha256(`${datasetDigest}\0${executionNonce}\0${fingerprint}\0${arm}`);
|
|
157
|
+
}
|
|
158
|
+
function validateSuite(suite) {
|
|
159
|
+
assertSafeMetadata(suite.name, 'suite.name');
|
|
160
|
+
assertSafeMetadata(suite.cohort.id, 'cohort.id');
|
|
161
|
+
assertSafeMetadata(suite.cohort.version, 'cohort.version');
|
|
162
|
+
assertSafeMetadata(suite.cohort.verifier.id, 'cohort.verifier.id');
|
|
163
|
+
assertSafeMetadata(suite.cohort.verifier.version, 'cohort.verifier.version');
|
|
164
|
+
if (typeof suite.cohort.sourceCommit !== 'string'
|
|
165
|
+
|| !FULL_COMMIT_RE.test(suite.cohort.sourceCommit)) {
|
|
166
|
+
throw new Error('cohort.sourceCommit must be a full lowercase 40-hex commit');
|
|
167
|
+
}
|
|
168
|
+
if (suite.cohort.taskFingerprints.length !== suite.tasks.length) {
|
|
169
|
+
throw new Error('pre-registered cohort does not match the benchmark tasks');
|
|
170
|
+
}
|
|
171
|
+
const ids = new Set();
|
|
172
|
+
const fingerprints = new Set();
|
|
173
|
+
for (const [index, task] of suite.tasks.entries()) {
|
|
174
|
+
assertSafeMetadata(task.id, `tasks[${index}].id`);
|
|
175
|
+
assertSafeMetadata(task.stratum, `tasks[${index}].stratum`);
|
|
176
|
+
if (task.split !== 'calibration' && task.split !== 'holdout') {
|
|
177
|
+
throw new Error(`invalid split for task ${task.id}`);
|
|
178
|
+
}
|
|
179
|
+
if (typeof task.workspaceSnapshotDigest !== 'string'
|
|
180
|
+
|| !SHA256_RE.test(task.workspaceSnapshotDigest)) {
|
|
181
|
+
throw new Error(`invalid workspace snapshot digest for task ${task.id}`);
|
|
182
|
+
}
|
|
183
|
+
if (task.expectation?.role !== 'target' && task.expectation?.role !== 'control') {
|
|
184
|
+
throw new Error(`invalid expected role for task ${task.id}`);
|
|
185
|
+
}
|
|
186
|
+
validateExpectedAction(task.expectation.currentAction, `task ${task.id} current expectation`);
|
|
187
|
+
validateExpectedAction(task.expectation.globalFlatAbstainAction, `task ${task.id} global expectation`);
|
|
188
|
+
if (ids.has(task.id))
|
|
189
|
+
throw new Error(`duplicate task id: ${task.id}`);
|
|
190
|
+
if (fingerprints.has(task.taskFingerprint)) {
|
|
191
|
+
throw new Error(`duplicate task fingerprint: ${task.taskFingerprint}`);
|
|
192
|
+
}
|
|
193
|
+
if (typeof task.taskFingerprint !== 'string' || !SHA256_RE.test(task.taskFingerprint)) {
|
|
194
|
+
throw new Error(`invalid task fingerprint for ${task.id}`);
|
|
195
|
+
}
|
|
196
|
+
if (computeSelectionFlatTaskFingerprint(task) !== task.taskFingerprint) {
|
|
197
|
+
throw new Error(`task content fingerprint mismatch for ${task.id}`);
|
|
198
|
+
}
|
|
199
|
+
if (suite.cohort.taskFingerprints[index] !== task.taskFingerprint) {
|
|
200
|
+
throw new Error(`pre-registered cohort order mismatch at task ${task.id}`);
|
|
201
|
+
}
|
|
202
|
+
ids.add(task.id);
|
|
203
|
+
fingerprints.add(task.taskFingerprint);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function cleanOutcome(taskId, expectedVerifier, outcome) {
|
|
207
|
+
const passed = outcome?.passed;
|
|
208
|
+
const producedValue = outcome?.producedValue;
|
|
209
|
+
const unsafe = outcome?.unsafe;
|
|
210
|
+
const cost = outcome?.cost;
|
|
211
|
+
const verifier = outcome?.verifier;
|
|
212
|
+
const verifierId = verifier?.id;
|
|
213
|
+
const verifierVersion = verifier?.version;
|
|
214
|
+
const proofSha256 = verifier?.proofSha256;
|
|
215
|
+
if (typeof passed !== 'boolean'
|
|
216
|
+
|| typeof producedValue !== 'boolean'
|
|
217
|
+
|| typeof unsafe !== 'boolean') {
|
|
218
|
+
throw new Error(`verifier returned invalid boolean fields for task ${taskId}`);
|
|
219
|
+
}
|
|
220
|
+
if (!Number.isFinite(cost) || cost < 0) {
|
|
221
|
+
throw new Error(`verifier returned invalid cost for task ${taskId}`);
|
|
222
|
+
}
|
|
223
|
+
if (verifierId !== expectedVerifier.id || verifierVersion !== expectedVerifier.version) {
|
|
224
|
+
throw new Error(`verifier identity mismatch for task ${taskId}`);
|
|
225
|
+
}
|
|
226
|
+
if (typeof proofSha256 !== 'string' || !SHA256_RE.test(proofSha256)) {
|
|
227
|
+
throw new Error(`verifier returned invalid proof digest for task ${taskId}`);
|
|
228
|
+
}
|
|
229
|
+
// Rebuild the object so adapter-only logs or payloads cannot leak into the persisted report.
|
|
230
|
+
return {
|
|
231
|
+
passed,
|
|
232
|
+
producedValue,
|
|
233
|
+
unsafe,
|
|
234
|
+
cost,
|
|
235
|
+
verifier: {
|
|
236
|
+
id: verifierId,
|
|
237
|
+
version: verifierVersion,
|
|
238
|
+
proofSha256,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function logAdd(left, right) {
|
|
243
|
+
if (left === Number.NEGATIVE_INFINITY)
|
|
244
|
+
return right;
|
|
245
|
+
if (right === Number.NEGATIVE_INFINITY)
|
|
246
|
+
return left;
|
|
247
|
+
const high = Math.max(left, right);
|
|
248
|
+
return high + Math.log(Math.exp(left - high) + Math.exp(right - high));
|
|
249
|
+
}
|
|
250
|
+
/** Exact two-sided McNemar p-value over the discordant pairs. */
|
|
251
|
+
export function exactMcNemarTwoSided(globalOnlyPass, currentOnlyPass) {
|
|
252
|
+
if (!Number.isSafeInteger(globalOnlyPass) || globalOnlyPass < 0
|
|
253
|
+
|| !Number.isSafeInteger(currentOnlyPass) || currentOnlyPass < 0) {
|
|
254
|
+
throw new Error('McNemar counts must be non-negative safe integers');
|
|
255
|
+
}
|
|
256
|
+
const discordant = globalOnlyPass + currentOnlyPass;
|
|
257
|
+
if (!Number.isSafeInteger(discordant)) {
|
|
258
|
+
throw new Error('McNemar discordant total must be a safe integer');
|
|
259
|
+
}
|
|
260
|
+
if (discordant === 0)
|
|
261
|
+
return 1;
|
|
262
|
+
const tail = Math.min(globalOnlyPass, currentOnlyPass);
|
|
263
|
+
let logProbability = -discordant * Math.log(2);
|
|
264
|
+
let logCumulative = logProbability;
|
|
265
|
+
for (let k = 1; k <= tail; k += 1) {
|
|
266
|
+
logProbability += Math.log(discordant - k + 1) - Math.log(k);
|
|
267
|
+
logCumulative = logAdd(logCumulative, logProbability);
|
|
268
|
+
}
|
|
269
|
+
return Math.min(1, 2 * Math.exp(logCumulative));
|
|
270
|
+
}
|
|
271
|
+
/** Distribution-free Hoeffding 95% CI for the mean paired success difference (global minus current). */
|
|
272
|
+
export function pairedRiskDifference95(pairs) {
|
|
273
|
+
if (pairs.length === 0)
|
|
274
|
+
return { difference: 0, ci95Low: -1, ci95High: 1 };
|
|
275
|
+
const differences = pairs.map((pair) => Number(pair.globalFlatAbstainPassed) - Number(pair.currentPassed));
|
|
276
|
+
const difference = differences.reduce((sum, value) => sum + value, 0) / differences.length;
|
|
277
|
+
// Each paired difference lies in [-1, 1], so Hoeffding remains valid at small n and at all-win boundaries.
|
|
278
|
+
const margin = Math.sqrt((2 * Math.log(2 / CI95_ALPHA)) / differences.length);
|
|
279
|
+
return {
|
|
280
|
+
difference,
|
|
281
|
+
ci95Low: Math.max(-1, difference - margin),
|
|
282
|
+
ci95High: Math.min(1, difference + margin),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function rate(count, total) {
|
|
286
|
+
return total === 0 ? 0 : count / total;
|
|
287
|
+
}
|
|
288
|
+
function average(values) {
|
|
289
|
+
return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
290
|
+
}
|
|
291
|
+
function summarizeRows(rows) {
|
|
292
|
+
const n = rows.length;
|
|
293
|
+
let bothPass = 0;
|
|
294
|
+
let bothFail = 0;
|
|
295
|
+
let globalOnlyPass = 0;
|
|
296
|
+
let currentOnlyPass = 0;
|
|
297
|
+
for (const row of rows) {
|
|
298
|
+
const currentPassed = row.current.outcome.passed;
|
|
299
|
+
const globalPassed = row.globalFlatAbstain.outcome.passed;
|
|
300
|
+
if (currentPassed && globalPassed)
|
|
301
|
+
bothPass += 1;
|
|
302
|
+
else if (!currentPassed && !globalPassed)
|
|
303
|
+
bothFail += 1;
|
|
304
|
+
else if (globalPassed)
|
|
305
|
+
globalOnlyPass += 1;
|
|
306
|
+
else
|
|
307
|
+
currentOnlyPass += 1;
|
|
308
|
+
}
|
|
309
|
+
const currentPasses = bothPass + currentOnlyPass;
|
|
310
|
+
const globalFlatAbstainPasses = bothPass + globalOnlyPass;
|
|
311
|
+
const currentUnsafeRate = rate(rows.filter((row) => row.current.outcome.unsafe).length, n);
|
|
312
|
+
const globalUnsafeRate = rate(rows.filter((row) => row.globalFlatAbstain.outcome.unsafe).length, n);
|
|
313
|
+
const currentAverageCost = average(rows.map((row) => row.current.outcome.cost));
|
|
314
|
+
const globalAverageCost = average(rows.map((row) => row.globalFlatAbstain.outcome.cost));
|
|
315
|
+
const risk = pairedRiskDifference95(rows.map((row) => ({
|
|
316
|
+
currentPassed: row.current.outcome.passed,
|
|
317
|
+
globalFlatAbstainPassed: row.globalFlatAbstain.outcome.passed,
|
|
318
|
+
})));
|
|
319
|
+
return {
|
|
320
|
+
n,
|
|
321
|
+
currentPasses,
|
|
322
|
+
globalFlatAbstainPasses,
|
|
323
|
+
bothPass,
|
|
324
|
+
bothFail,
|
|
325
|
+
globalOnlyPass,
|
|
326
|
+
currentOnlyPass,
|
|
327
|
+
currentPassRate: rate(currentPasses, n),
|
|
328
|
+
globalFlatAbstainPassRate: rate(globalFlatAbstainPasses, n),
|
|
329
|
+
pairedRiskDifference: risk.difference,
|
|
330
|
+
ci95Low: risk.ci95Low,
|
|
331
|
+
ci95High: risk.ci95High,
|
|
332
|
+
mcnemarPValue: exactMcNemarTwoSided(globalOnlyPass, currentOnlyPass),
|
|
333
|
+
currentProducedValueRate: rate(rows.filter((row) => row.current.outcome.producedValue).length, n),
|
|
334
|
+
globalFlatAbstainProducedValueRate: rate(rows.filter((row) => row.globalFlatAbstain.outcome.producedValue).length, n),
|
|
335
|
+
currentUnsafeRate,
|
|
336
|
+
globalFlatAbstainUnsafeRate: globalUnsafeRate,
|
|
337
|
+
unsafeRateDelta: globalUnsafeRate - currentUnsafeRate,
|
|
338
|
+
currentAverageCost,
|
|
339
|
+
globalFlatAbstainAverageCost: globalAverageCost,
|
|
340
|
+
averageCostDelta: globalAverageCost - currentAverageCost,
|
|
341
|
+
currentInnovateRate: rate(rows.filter((row) => row.current.action === 'innovate').length, n),
|
|
342
|
+
globalFlatAbstainInnovateRate: rate(rows.filter((row) => row.globalFlatAbstain.action === 'innovate').length, n),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function cloneTaskForArm(task) {
|
|
346
|
+
return cloneStructured(task, `task ${task.id} must be structured-cloneable for isolated arm execution`);
|
|
347
|
+
}
|
|
348
|
+
function cloneSuiteSnapshot(suite) {
|
|
349
|
+
return cloneStructured(suite, 'benchmark suite must be structured-cloneable before validation');
|
|
350
|
+
}
|
|
351
|
+
function cloneAction(action) {
|
|
352
|
+
return action.kind === 'innovate'
|
|
353
|
+
? { kind: 'innovate' }
|
|
354
|
+
: {
|
|
355
|
+
kind: 'reuse',
|
|
356
|
+
geneId: action.geneId,
|
|
357
|
+
...(action.assetId ? { assetId: action.assetId } : {}),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function benchmarkDatasetDigest(suite, comparisons) {
|
|
361
|
+
const material = [
|
|
362
|
+
SELECTION_FLAT_ABSTENTION_BENCHMARK_VERSION,
|
|
363
|
+
suite.name,
|
|
364
|
+
suite.cohort.id,
|
|
365
|
+
suite.cohort.version,
|
|
366
|
+
suite.cohort.sourceCommit,
|
|
367
|
+
suite.cohort.verifier.id,
|
|
368
|
+
suite.cohort.verifier.version,
|
|
369
|
+
suite.tasks.map((task, index) => [
|
|
370
|
+
index,
|
|
371
|
+
task.taskFingerprint,
|
|
372
|
+
task.split,
|
|
373
|
+
task.stratum,
|
|
374
|
+
comparisons[index]?.targeted === true,
|
|
375
|
+
comparisons[index]?.currentAction.kind,
|
|
376
|
+
comparisons[index]?.globalFlatAbstainAction.kind,
|
|
377
|
+
]),
|
|
378
|
+
];
|
|
379
|
+
return sha256(JSON.stringify(material));
|
|
380
|
+
}
|
|
381
|
+
function validatePreRegisteredExpectation(task, comparison) {
|
|
382
|
+
const actualRole = comparison.targeted ? 'target' : 'control';
|
|
383
|
+
if (task.expectation.role !== actualRole) {
|
|
384
|
+
throw new Error(`pre-registered role mismatch for task ${task.id}`);
|
|
385
|
+
}
|
|
386
|
+
if (!actionsEqual(task.expectation.currentAction, comparison.currentAction)) {
|
|
387
|
+
throw new Error(`pre-registered current action mismatch for task ${task.id}`);
|
|
388
|
+
}
|
|
389
|
+
if (!actionsEqual(task.expectation.globalFlatAbstainAction, comparison.globalFlatAbstainAction)) {
|
|
390
|
+
throw new Error(`pre-registered global action mismatch for task ${task.id}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function rowsByStratum(rows) {
|
|
394
|
+
const result = Object.create(null);
|
|
395
|
+
const strata = [...new Set(rows.map((row) => row.stratum))].sort();
|
|
396
|
+
for (const stratum of strata) {
|
|
397
|
+
result[stratum] = summarizeRows(rows.filter((row) => row.stratum === stratum));
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Run a pre-registered, full-information paired benchmark. Controls are classification checks only and are never
|
|
403
|
+
* executed. Target tasks run both arms on separately cloned inputs and caller-owned isolated workspaces.
|
|
404
|
+
*/
|
|
405
|
+
export async function runSelectionFlatAbstentionBenchmark(suite, verifier) {
|
|
406
|
+
const snapshot = cloneSuiteSnapshot(suite);
|
|
407
|
+
validateSuite(snapshot);
|
|
408
|
+
const comparisons = snapshot.tasks.map((task) => compareOutsidePlateauFlatSelection(task.selection));
|
|
409
|
+
snapshot.tasks.forEach((task, index) => {
|
|
410
|
+
validatePreRegisteredExpectation(task, comparisons[index]);
|
|
411
|
+
});
|
|
412
|
+
const datasetDigest = benchmarkDatasetDigest(snapshot, comparisons);
|
|
413
|
+
const executionNonce = randomUUID();
|
|
414
|
+
const rows = [];
|
|
415
|
+
let targetIndex = 0;
|
|
416
|
+
for (const [taskIndex, task] of snapshot.tasks.entries()) {
|
|
417
|
+
const comparison = comparisons[taskIndex];
|
|
418
|
+
if (!comparison.targeted) {
|
|
419
|
+
if (!actionsEqual(comparison.currentAction, comparison.globalFlatAbstainAction)) {
|
|
420
|
+
throw new Error(`control action divergence for task ${task.id}`);
|
|
421
|
+
}
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const armOrder = targetIndex % 2 === 0
|
|
425
|
+
? ['current', 'global-flat-abstain']
|
|
426
|
+
: ['global-flat-abstain', 'current'];
|
|
427
|
+
targetIndex += 1;
|
|
428
|
+
const outcomes = new Map();
|
|
429
|
+
for (const [order, arm] of armOrder.entries()) {
|
|
430
|
+
const action = arm === 'current'
|
|
431
|
+
? comparison.currentAction
|
|
432
|
+
: comparison.globalFlatAbstainAction;
|
|
433
|
+
const rawOutcome = await verifier({
|
|
434
|
+
task: cloneTaskForArm(task),
|
|
435
|
+
arm,
|
|
436
|
+
action: cloneAction(action),
|
|
437
|
+
isolationKey: isolationKey(datasetDigest, executionNonce, task.taskFingerprint, arm),
|
|
438
|
+
order: order,
|
|
439
|
+
});
|
|
440
|
+
outcomes.set(arm, cleanOutcome(task.id, snapshot.cohort.verifier, rawOutcome));
|
|
441
|
+
}
|
|
442
|
+
rows.push({
|
|
443
|
+
taskFingerprint: task.taskFingerprint,
|
|
444
|
+
split: task.split,
|
|
445
|
+
stratum: task.stratum,
|
|
446
|
+
armOrder,
|
|
447
|
+
current: {
|
|
448
|
+
action: comparison.currentAction.kind,
|
|
449
|
+
outcome: outcomes.get('current'),
|
|
450
|
+
},
|
|
451
|
+
globalFlatAbstain: {
|
|
452
|
+
action: comparison.globalFlatAbstainAction.kind,
|
|
453
|
+
outcome: outcomes.get('global-flat-abstain'),
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
benchmarkVersion: SELECTION_FLAT_ABSTENTION_BENCHMARK_VERSION,
|
|
459
|
+
statisticalMethods: {
|
|
460
|
+
mcnemar: 'exact-two-sided-binomial',
|
|
461
|
+
riskDifferenceCi: 'paired-hoeffding-95',
|
|
462
|
+
},
|
|
463
|
+
suite: snapshot.name,
|
|
464
|
+
cohort: {
|
|
465
|
+
id: snapshot.cohort.id,
|
|
466
|
+
version: snapshot.cohort.version,
|
|
467
|
+
sourceCommit: snapshot.cohort.sourceCommit,
|
|
468
|
+
verifier: { ...snapshot.cohort.verifier },
|
|
469
|
+
registeredTasks: snapshot.cohort.taskFingerprints.length,
|
|
470
|
+
},
|
|
471
|
+
datasetDigest,
|
|
472
|
+
targetTasks: rows.length,
|
|
473
|
+
controlTasks: snapshot.tasks.length - rows.length,
|
|
474
|
+
controlDivergences: 0,
|
|
475
|
+
rows,
|
|
476
|
+
overall: summarizeRows(rows),
|
|
477
|
+
calibration: summarizeRows(rows.filter((row) => row.split === 'calibration')),
|
|
478
|
+
holdout: summarizeRows(rows.filter((row) => row.split === 'holdout')),
|
|
479
|
+
strata: rowsByStratum(rows),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare const TRIGGER_SHIFT_METHOD_VERSION = "trigger-shift-v1";
|
|
2
|
+
/** @experimental Shift dimension for a semantically paired replay task. */
|
|
3
|
+
export type TriggerShiftAxis = 'wrapper_trigger' | 'temporal_context' | 'instruction_phrasing';
|
|
4
|
+
/** @experimental One replay prompt variant in a trigger-shift pair. */
|
|
5
|
+
export interface TriggerShiftTask {
|
|
6
|
+
id: string;
|
|
7
|
+
prompt: string;
|
|
8
|
+
wrapperTrigger?: string;
|
|
9
|
+
temporalContext?: string;
|
|
10
|
+
instructionPhrasing?: string;
|
|
11
|
+
}
|
|
12
|
+
/** @experimental Same objective, paired across one trigger/context shift axis. */
|
|
13
|
+
export interface TriggerShiftPair {
|
|
14
|
+
id: string;
|
|
15
|
+
objectiveId: string;
|
|
16
|
+
axis: TriggerShiftAxis;
|
|
17
|
+
expectedDecision: string;
|
|
18
|
+
train: TriggerShiftTask;
|
|
19
|
+
shifted: TriggerShiftTask;
|
|
20
|
+
}
|
|
21
|
+
/** @experimental Policy output normalized for replay scoring. */
|
|
22
|
+
export interface TriggerShiftDecision {
|
|
23
|
+
label: string;
|
|
24
|
+
confidence?: number;
|
|
25
|
+
}
|
|
26
|
+
/** @experimental Replay-only policy seam; callers own any model/tool execution. */
|
|
27
|
+
export interface TriggerShiftPolicy {
|
|
28
|
+
id: string;
|
|
29
|
+
predict(task: TriggerShiftTask): TriggerShiftDecision;
|
|
30
|
+
}
|
|
31
|
+
/** @experimental One paired replay result with train/shifted rewards and gap. */
|
|
32
|
+
export interface TriggerShiftPairResult {
|
|
33
|
+
pairId: string;
|
|
34
|
+
objectiveId: string;
|
|
35
|
+
axis: TriggerShiftAxis;
|
|
36
|
+
trainTaskId: string;
|
|
37
|
+
shiftedTaskId: string;
|
|
38
|
+
expectedDecision: string;
|
|
39
|
+
trainDecision: string;
|
|
40
|
+
shiftedDecision: string;
|
|
41
|
+
trainReward: number;
|
|
42
|
+
shiftedReward: number;
|
|
43
|
+
gap: number;
|
|
44
|
+
}
|
|
45
|
+
/** @experimental Aggregate replay report; diagnostic only, not a selector input. */
|
|
46
|
+
export interface TriggerShiftReport {
|
|
47
|
+
methodVersion: string;
|
|
48
|
+
policyId: string;
|
|
49
|
+
pairs: number;
|
|
50
|
+
meanTrainReward: number;
|
|
51
|
+
meanShiftedReward: number;
|
|
52
|
+
meanGap: number;
|
|
53
|
+
maxGap: number;
|
|
54
|
+
rows: TriggerShiftPairResult[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* @experimental Offline trigger-shift replay evaluator. It returns inert report
|
|
58
|
+
* rows only: no trigger fires, no store writes, and no live selection updates.
|
|
59
|
+
*/
|
|
60
|
+
export declare function evaluateTriggerShift(policy: TriggerShiftPolicy, pairs: readonly TriggerShiftPair[]): TriggerShiftReport;
|
|
61
|
+
/** @experimental Tiny public calibration/demo suite; not a live threshold. */
|
|
62
|
+
export declare function smallTriggerShiftSuite(): TriggerShiftPair[];
|