@evomap/evolver-core 2.0.0-beta.9 → 2.0.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/dist/algo/candidateAssembly.d.ts +7 -2
- package/dist/algo/candidateAssembly.js +81 -13
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/cycleEngine.d.ts +23 -2
- package/dist/algo/cycleEngine.js +236 -36
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +2 -2
- package/dist/algo/geneHealth.js +5 -4
- package/dist/algo/geneSelection.d.ts +63 -5
- package/dist/algo/geneSelection.js +330 -30
- package/dist/algo/index.d.ts +2 -0
- package/dist/algo/index.js +2 -0
- package/dist/algo/orchestrator.d.ts +8 -1
- package/dist/algo/orchestrator.js +15 -3
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
- package/dist/assetstore/assetSidecarRecords.js +238 -0
- package/dist/assetstore/assetSidecarRecovery.js +2 -2
- package/dist/assetstore/assetStoreHealth.d.ts +6 -0
- package/dist/assetstore/assetStoreHealth.js +56 -14
- package/dist/assetstore/assetStoreStorage.d.ts +1 -1
- package/dist/assetstore/assetStoreStorage.js +27 -9
- package/dist/assetstore/assetSyncLedger.d.ts +86 -0
- package/dist/assetstore/assetSyncLedger.js +683 -4
- package/dist/assetstore/index.d.ts +1 -0
- package/dist/assetstore/index.js +1 -0
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +4 -2
- package/dist/assetstore/localJsonl.js +89 -9
- package/dist/assetstore/provenance.d.ts +80 -4
- package/dist/assetstore/provenance.js +313 -5
- package/dist/assetstore/provider.d.ts +58 -1
- package/dist/assetstore/provider.js +97 -6
- 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 +2 -1
- package/dist/benchmark/index.js +2 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
- package/dist/bootstrap/v1EnvCompat.js +280 -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 +1 -0
- package/dist/events/ingest.js +1 -0
- package/dist/events/paths.d.ts +3 -1
- package/dist/events/paths.js +4 -0
- 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 +43 -2
- package/dist/exec/autoExec.js +76 -9
- package/dist/exec/autonomousCycle.d.ts +22 -4
- package/dist/exec/autonomousCycle.js +64 -13
- package/dist/exec/claudeBridge.d.ts +32 -7
- package/dist/exec/claudeBridge.js +281 -29
- package/dist/exec/prompt.js +5 -1
- package/dist/exec/runnerRegistry.d.ts +68 -26
- package/dist/exec/runnerRegistry.js +307 -72
- 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 +17 -6
- package/dist/hub/capability.d.ts +90 -3
- package/dist/hub/fake.d.ts +2 -2
- package/dist/hub/fake.js +1 -1
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- 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 +10 -2
- package/dist/mailbox/ipcServer.js +163 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- 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/selfUpdate.d.ts +8 -0
- package/dist/ops/selfUpdate.js +24 -8
- package/dist/personality/schema.d.ts +16 -16
- 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/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- 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 +3 -1
- package/dist/signals/index.js +3 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -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.js +115 -369
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +209 -0
- package/dist/trace/learningTrace.js +276 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/verify/sandboxRunner.d.ts +28 -0
- package/dist/verify/sandboxRunner.js +218 -19
- package/dist/verify/sandboxedValidation.d.ts +9 -0
- package/dist/verify/sandboxedValidation.js +113 -13
- package/dist/verify/validation.d.ts +11 -1
- package/dist/verify/validation.js +31 -0
- package/package.json +5 -2
|
@@ -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
|
+
}
|
|
@@ -15,6 +15,12 @@ export interface EnvFingerprint {
|
|
|
15
15
|
* as its own field (e.g. so the Hub can index assets by model / feed the by-model leaderboard).
|
|
16
16
|
*/
|
|
17
17
|
model: string;
|
|
18
|
+
/**
|
|
19
|
+
* Reported Evolver client version for Hub anti-abuse trust (#555). The owning runtime resolves this from its
|
|
20
|
+
* own package/build metadata and supplies it explicitly, so a host application's npm environment cannot be
|
|
21
|
+
* mistaken for the Evolver version. Optional so older callers keep working.
|
|
22
|
+
*/
|
|
23
|
+
evolver_version?: string;
|
|
18
24
|
}
|
|
19
25
|
export interface EnvCaptureDeps {
|
|
20
26
|
platform?: string;
|
|
@@ -24,7 +30,10 @@ export interface EnvCaptureDeps {
|
|
|
24
30
|
hostname?: string;
|
|
25
31
|
env?: Record<string, string | undefined>;
|
|
26
32
|
isContainer?: () => boolean;
|
|
33
|
+
/** Explicit Evolver version for fingerprint (Refs #555). */
|
|
34
|
+
evolverVersion?: string;
|
|
27
35
|
}
|
|
36
|
+
export declare function normalizeEvolverVersion(value: string | undefined): string | undefined;
|
|
28
37
|
/**
|
|
29
38
|
* Resolve the underlying LLM model name powering this evolver node (ported from v1 PR #174). Until now the
|
|
30
39
|
* only source was the explicit EVOLVER_MODEL_NAME, so nodes that never set it published assets with no model
|
|
@@ -6,6 +6,9 @@ import { hostname as osHostname, release as osRelease } from 'node:os';
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
8
|
const sha12 = (s) => createHash('sha256').update(s).digest('hex').slice(0, 12);
|
|
9
|
+
export function normalizeEvolverVersion(value) {
|
|
10
|
+
return value?.trim().slice(0, 64) || undefined;
|
|
11
|
+
}
|
|
9
12
|
/** Heuristic container detection (Docker / Kubernetes). */
|
|
10
13
|
function detectContainer(env) {
|
|
11
14
|
if (env['KUBERNETES_SERVICE_HOST'])
|
|
@@ -50,6 +53,7 @@ export function detectModelName(env = (typeof process !== 'undefined' ? process.
|
|
|
50
53
|
export function captureEnvFingerprint(deps = {}) {
|
|
51
54
|
const env = deps.env ?? (typeof process !== 'undefined' ? process.env : {});
|
|
52
55
|
const region = (env['EVOLVER_REGION'] ?? '').trim().toLowerCase().slice(0, 5) || undefined;
|
|
56
|
+
const evolverVersion = normalizeEvolverVersion(deps.evolverVersion);
|
|
53
57
|
return {
|
|
54
58
|
device: sha12(deps.hostname ?? osHostname()),
|
|
55
59
|
node_version: deps.nodeVersion ?? process.version,
|
|
@@ -59,6 +63,7 @@ export function captureEnvFingerprint(deps = {}) {
|
|
|
59
63
|
...(region ? { region } : {}),
|
|
60
64
|
container: deps.isContainer ? deps.isContainer() : detectContainer(env),
|
|
61
65
|
model: detectModelName(env),
|
|
66
|
+
...(evolverVersion ? { evolver_version: evolverVersion } : {}),
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
69
|
/**
|
package/dist/bootstrap/index.js
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V1 → V2 environment variable compatibility layer (#698).
|
|
3
|
+
*
|
|
4
|
+
* Evolver V1 used several env var names that are no longer recognized by V2
|
|
5
|
+
* resolvers. When operators upgrade from V1 to V2 without updating their env
|
|
6
|
+
* files, these legacy names silently do nothing. This module detects the
|
|
7
|
+
* presence of deprecated V1 env vars and emits structured warnings so
|
|
8
|
+
* operators know which knobs to migrate.
|
|
9
|
+
*
|
|
10
|
+
* ## Migration map
|
|
11
|
+
*
|
|
12
|
+
* | V1 name | V2 equivalent | Notes |
|
|
13
|
+
* |---|---|---|
|
|
14
|
+
* | `OPENCLAW_WORKSPACE` | *(manual)* | Partial V2 support does not preserve V1 workspace and bridge semantics |
|
|
15
|
+
* | `EVOLVER_NO_PARENT_GIT` | *(none)* | V2 uses `EVOLVER_REPO_ROOT` or nearest Git root |
|
|
16
|
+
* | `EVOLVER_VERBOSE` | *(none)* | V2 has no global switch; opt in to feature-specific diagnostics manually |
|
|
17
|
+
* | `EVOLVER_AUTO_ISSUE` | *(none)* | V2 creates local drafts; submit requires explicit approval-gated flow |
|
|
18
|
+
* | `EVOLVER_ROLLBACK_MODE` | *(none)* | V2 uses worktree/snapshot/recovery policy |
|
|
19
|
+
* | `WORKER_ENABLED` | *(none)* | No merchant-worker resolver in V2 |
|
|
20
|
+
* | `WORKER_DOMAINS` | *(none)* | No merchant-worker resolver in V2 |
|
|
21
|
+
* | `WORKER_MAX_LOAD` | *(none)* | No merchant-worker resolver in V2 |
|
|
22
|
+
* | `EVOLVER_MEMORY_GRAPH_AUTO_ROTATE` | *(none)* | V2 LocalMemoryGraph always performs bounded maintenance |
|
|
23
|
+
* | `EVOLVER_MEMORY_GRAPH_MAX_SIZE_MB` | *(none)* | V2 LocalMemoryGraph uses a fixed 4 MiB active-file limit |
|
|
24
|
+
* | `EVOLVER_MEMORY_GRAPH_RETENTION_COUNT` | *(none)* | V2 LocalMemoryGraph retains three archives by default |
|
|
25
|
+
* | `GITHUB_TOKEN` | `GITHUB_TOKEN` | Already supported by V2 issue reporter and PR tooling |
|
|
26
|
+
*
|
|
27
|
+
* GITHUB_TOKEN is NOT deprecated — it's actively used. We only note its
|
|
28
|
+
* presence for observability.
|
|
29
|
+
*/
|
|
30
|
+
export type V1EnvMigrationAction = 'map' | 'manual' | 'remove';
|
|
31
|
+
/** Metadata for a single V1 env var deprecation entry. */
|
|
32
|
+
export interface V1EnvDeprecation {
|
|
33
|
+
/** The deprecated V1 env var name. */
|
|
34
|
+
readonly v1Name: string;
|
|
35
|
+
/** The V2 equivalent, or `null` if no equivalent exists. */
|
|
36
|
+
readonly v2Equivalent: string | null;
|
|
37
|
+
/** Whether migration is an exact map, requires operator review, or only removes the V1 key. */
|
|
38
|
+
readonly migrationAction?: V1EnvMigrationAction;
|
|
39
|
+
/** Human-readable migration guidance. */
|
|
40
|
+
readonly guidance: string;
|
|
41
|
+
}
|
|
42
|
+
/** Result of scanning for deprecated V1 env vars. */
|
|
43
|
+
export interface V1EnvCompatResult {
|
|
44
|
+
/** Deprecated V1 vars that were found set (non-empty) in the environment. */
|
|
45
|
+
readonly detected: V1EnvDeprecation[];
|
|
46
|
+
/** `GITHUB_TOKEN` or `GH_TOKEN` presence flag (not deprecated, just noted). */
|
|
47
|
+
readonly githubTokenPresent: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The canonical table of V1 → V2 env var deprecations.
|
|
51
|
+
*
|
|
52
|
+
* Order matters for deterministic output; the scan iterates this list
|
|
53
|
+
* and checks each entry against the provided `env` object.
|
|
54
|
+
*/
|
|
55
|
+
export declare const V1_DEPRECATION_TABLE: readonly V1EnvDeprecation[];
|
|
56
|
+
/**
|
|
57
|
+
* Scan the provided environment for deprecated V1 env vars and return
|
|
58
|
+
* structured results. This function is purely read-only and never mutates
|
|
59
|
+
* the `env` object.
|
|
60
|
+
*
|
|
61
|
+
* @param env The environment to scan (defaults to `process.env`).
|
|
62
|
+
* @returns Structured scan results including detected deprecations.
|
|
63
|
+
*/
|
|
64
|
+
export declare function scanV1EnvCompat(env?: Record<string, string | undefined>): V1EnvCompatResult;
|
|
65
|
+
/** Resolve the migration action while preserving compatibility with pre-action table entries. */
|
|
66
|
+
export declare function resolveV1EnvMigrationAction(entry: Pick<V1EnvDeprecation, 'migrationAction' | 'v2Equivalent'>): V1EnvMigrationAction;
|
|
67
|
+
/**
|
|
68
|
+
* Emit deprecation warnings to the provided logger for all detected
|
|
69
|
+
* deprecated V1 env vars. This is the primary integration point for
|
|
70
|
+
* entrypoints that want human-readable console output.
|
|
71
|
+
*
|
|
72
|
+
* @param result The scan result from `scanV1EnvCompat`.
|
|
73
|
+
* @param warn Logger function (defaults to `console.warn`).
|
|
74
|
+
*/
|
|
75
|
+
export declare function emitV1DeprecationWarnings(result: V1EnvCompatResult, warn?: (msg: string) => void): void;
|
|
76
|
+
/**
|
|
77
|
+
* Convenience function: scan + emit in one call. Intended for early
|
|
78
|
+
* bootstrap in CLI/proxy/MCP entrypoints.
|
|
79
|
+
*
|
|
80
|
+
* @param env The environment to scan.
|
|
81
|
+
* @param warn Logger function.
|
|
82
|
+
* @returns The scan result (useful for programmatic inspection).
|
|
83
|
+
*/
|
|
84
|
+
export declare function checkV1EnvCompat(env?: Record<string, string | undefined>, warn?: (msg: string) => void): V1EnvCompatResult;
|
|
85
|
+
/** One suggested V2 assignment produced from a V1 env map. */
|
|
86
|
+
export interface V1EnvTranslationSuggestion {
|
|
87
|
+
readonly v1Name: string;
|
|
88
|
+
readonly action: V1EnvMigrationAction | 'keep';
|
|
89
|
+
readonly v2Name?: string;
|
|
90
|
+
readonly v2Value?: string;
|
|
91
|
+
readonly guidance: string;
|
|
92
|
+
}
|
|
93
|
+
/** Full offline translation report for migrate env / doctor. */
|
|
94
|
+
export interface V1EnvTranslationReport {
|
|
95
|
+
readonly suggestions: V1EnvTranslationSuggestion[];
|
|
96
|
+
readonly detectedCount: number;
|
|
97
|
+
readonly mappableCount: number;
|
|
98
|
+
readonly manualCount: number;
|
|
99
|
+
readonly removableCount: number;
|
|
100
|
+
readonly githubTokenPresent: boolean;
|
|
101
|
+
readonly caveats?: readonly string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Build an offline V1→V2 env translation report.
|
|
105
|
+
* Does not write files; callers decide how to present or apply suggestions.
|
|
106
|
+
* Raw values are retained only as v2Value for mappable non-secret keys.
|
|
107
|
+
* GITHUB_TOKEN and GH_TOKEN are reported as keep without retaining their values.
|
|
108
|
+
*/
|
|
109
|
+
export declare function translateV1Env(env?: Record<string, string | undefined>): V1EnvTranslationReport;
|
|
110
|
+
/** Human-readable report (never prints secret-looking values; only key names + actions). */
|
|
111
|
+
export declare function formatV1EnvTranslationReport(report: V1EnvTranslationReport): string;
|