@evomap/evolver-core 2.0.0-beta.18 → 2.0.0-beta.19

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.
Files changed (68) hide show
  1. package/dist/algo/candidateAssembly.js +21 -2
  2. package/dist/algo/cycleEngine.d.ts +12 -0
  3. package/dist/algo/cycleEngine.js +36 -4
  4. package/dist/algo/geneHealth.d.ts +2 -2
  5. package/dist/algo/geneHealth.js +5 -4
  6. package/dist/algo/geneSelection.d.ts +1 -1
  7. package/dist/algo/orchestrator.js +9 -2
  8. package/dist/assetstore/assetSidecarRecords.js +4 -0
  9. package/dist/assetstore/assetStoreHealth.js +41 -24
  10. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  11. package/dist/assetstore/assetStoreStorage.js +16 -7
  12. package/dist/assetstore/localJsonl.d.ts +2 -1
  13. package/dist/assetstore/localJsonl.js +54 -10
  14. package/dist/assetstore/provenance.d.ts +24 -0
  15. package/dist/assetstore/provenance.js +219 -12
  16. package/dist/assetstore/provider.d.ts +20 -1
  17. package/dist/assetstore/provider.js +34 -1
  18. package/dist/bootstrap/index.d.ts +2 -1
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
  21. package/dist/bootstrap/v1EnvCompat.js +256 -0
  22. package/dist/events/public.d.ts +1 -1
  23. package/dist/events/public.js +1 -1
  24. package/dist/events/reports.d.ts +2 -0
  25. package/dist/events/reports.js +4 -0
  26. package/dist/exec/autoExec.d.ts +1 -1
  27. package/dist/exec/autoExec.js +8 -10
  28. package/dist/exec/autonomousCycle.d.ts +19 -4
  29. package/dist/exec/autonomousCycle.js +63 -13
  30. package/dist/exec/claudeBridge.d.ts +25 -7
  31. package/dist/exec/claudeBridge.js +264 -29
  32. package/dist/exec/prompt.js +5 -1
  33. package/dist/exec/runnerRegistry.d.ts +68 -26
  34. package/dist/exec/runnerRegistry.js +307 -72
  35. package/dist/exec/selfPr.js +1 -7
  36. package/dist/feedback/envelope.d.ts +61 -0
  37. package/dist/feedback/envelope.js +168 -0
  38. package/dist/feedback/index.d.ts +1 -0
  39. package/dist/feedback/index.js +1 -0
  40. package/dist/hub/assetCallLog.d.ts +35 -1
  41. package/dist/hub/assetCallLog.js +124 -1
  42. package/dist/hub/bindings.d.ts +8 -1
  43. package/dist/hub/bindings.js +17 -6
  44. package/dist/hub/capability.d.ts +11 -1
  45. package/dist/hub/fake.d.ts +2 -2
  46. package/dist/hub/fake.js +1 -1
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +4 -1
  49. package/dist/mailbox/dispatch.d.ts +1 -1
  50. package/dist/mailbox/dispatch.js +22 -6
  51. package/dist/mailbox/envelope.d.ts +7 -1
  52. package/dist/mailbox/envelope.js +9 -2
  53. package/dist/mailbox/ipcServer.d.ts +10 -2
  54. package/dist/mailbox/ipcServer.js +163 -13
  55. package/dist/mailbox/store.d.ts +38 -2
  56. package/dist/mailbox/store.js +416 -27
  57. package/dist/signals/curriculum.d.ts +55 -0
  58. package/dist/signals/curriculum.js +202 -0
  59. package/dist/signals/expand.js +17 -6
  60. package/dist/signals/index.d.ts +2 -1
  61. package/dist/signals/index.js +2 -1
  62. package/dist/strategy/constraintAblation.js +115 -369
  63. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  64. package/dist/strategy/constraintAblationPredicates.js +339 -0
  65. package/dist/trace/proxyTurns.js +15 -7
  66. package/dist/verify/validation.d.ts +11 -1
  67. package/dist/verify/validation.js +31 -0
  68. package/package.json +4 -1
@@ -4,9 +4,19 @@
4
4
  // must not enter the content hash (#30.2), or it would break content-addressing. Trust-first by construction:
5
5
  // selection defaults to trusted-only; an untrusted asset is promoted to trusted only by an explicit, logged act.
6
6
  import { join, dirname } from 'node:path';
7
- import { assertCapsuleGeneBinding, normalizeForPut, supportsAtomicConditionalPut, validateConditionalPutResult, } from './provider.js';
7
+ import { assertCapsuleGeneBinding, FrozenAssetIdCollisionError, InvalidFrozenPutResultError, frozenAssetRecordsEqual, normalizeForPut, supportsAtomicConditionalPut, supportsAtomicFrozenConditionalPut, validateConditionalPutResult, validateFrozenPutResult, } from './provider.js';
8
8
  import { appendUtf8Durable, assertAssetStoreDirectory, ensureAssetStoreDirectory, readUtf8Regular, regularFileFingerprint, truncateUtf8SuffixDurable, withAssetStoreLock, } from './assetStoreStorage.js';
9
9
  import { assertTrustSidecarHealthy, parseProvenanceRecord, parseSidecarJsonl, } from './assetSidecarRecords.js';
10
+ import { computeAssetId } from '../wire/index.js';
11
+ export class ProvenanceWritePendingError extends Error {
12
+ assetId;
13
+ code = 'PROVENANCE_WRITE_PENDING';
14
+ constructor(assetId) {
15
+ super('asset provenance write is pending');
16
+ this.assetId = assetId;
17
+ this.name = 'ProvenanceWritePendingError';
18
+ }
19
+ }
10
20
  function immutableRecord(record) {
11
21
  return Object.freeze({ ...record });
12
22
  }
@@ -69,6 +79,111 @@ export class ProvenanceStore {
69
79
  return this.appendUnderLock(full);
70
80
  });
71
81
  }
82
+ /** Stage a verified Hub write without replacing an in-flight conservative marker. */
83
+ stageUntrustedWriteTracked(assetId, source) {
84
+ assertAssetStoreDirectory(dirname(this.path));
85
+ return withAssetStoreLock(this.lockPath, () => {
86
+ this.refreshUnderLock();
87
+ const current = this.index.get(assetId);
88
+ if (current && (current.trusted === true || current.decision !== undefined)) {
89
+ return { record: current, appended: false };
90
+ }
91
+ if (current?.trusted === false) {
92
+ return { record: current, appended: false };
93
+ }
94
+ return {
95
+ record: this.appendUnderLock({
96
+ assetId,
97
+ source,
98
+ trusted: false,
99
+ at: new Date(this.now()).toISOString(),
100
+ }),
101
+ appended: true,
102
+ };
103
+ });
104
+ }
105
+ /** Finalize a verified write unless an operator made an explicit decision during I/O. */
106
+ finalizeUntrustedWrite(assetId, source) {
107
+ assertAssetStoreDirectory(dirname(this.path));
108
+ return withAssetStoreLock(this.lockPath, () => {
109
+ this.refreshUnderLock();
110
+ const current = this.index.get(assetId);
111
+ if (current && (current.trusted === true || current.decision !== undefined))
112
+ return current;
113
+ return this.appendUnderLock({
114
+ assetId,
115
+ source,
116
+ trusted: false,
117
+ at: new Date(this.now()).toISOString(),
118
+ });
119
+ });
120
+ }
121
+ /** Stage an unverified write without overwriting an explicit trust decision or another conservative marker. */
122
+ stageUnverifiedWrite(assetId, source, frozenContentId) {
123
+ return this.stageUnverifiedWriteTracked(assetId, source, frozenContentId).record;
124
+ }
125
+ stageUnverifiedWriteTracked(assetId, source, frozenContentId) {
126
+ assertAssetStoreDirectory(dirname(this.path));
127
+ return withAssetStoreLock(this.lockPath, () => {
128
+ this.refreshUnderLock();
129
+ const current = this.index.get(assetId);
130
+ const isUndecidedUntrusted = current?.trusted === false
131
+ && current.decision === undefined
132
+ && current.decidedBy === undefined
133
+ && current.promotedBy === undefined;
134
+ const isWaiver = isUndecidedUntrusted
135
+ && current.frozenContentId === frozenContentId
136
+ && ((current.source === 'hub'
137
+ && (current.reason === 'unverified_hub_rewrite' || current.reason === 'unverified_hub_synthesized'))
138
+ || (current.source === 'migrated' && current.reason === 'unverified_gepx_import'));
139
+ const isStaleIngestMarker = isUndecidedUntrusted
140
+ && current.source === source
141
+ && current.reason === undefined;
142
+ if (current && !isWaiver && !isStaleIngestMarker)
143
+ return { record: current, appended: false };
144
+ return {
145
+ record: this.appendUnderLock({
146
+ assetId,
147
+ source,
148
+ trusted: false,
149
+ reason: 'unverified_hub_write_pending',
150
+ frozenContentId,
151
+ at: new Date(this.now()).toISOString(),
152
+ }),
153
+ appended: true,
154
+ };
155
+ });
156
+ }
157
+ /** Atomically replace only a pending/no decision with the health-waiver reason after verified persistence. */
158
+ finalizeUnverifiedWrite(assetId, source, reason, frozenContentId) {
159
+ assertAssetStoreDirectory(dirname(this.path));
160
+ return withAssetStoreLock(this.lockPath, () => {
161
+ this.refreshUnderLock();
162
+ const current = this.index.get(assetId);
163
+ if (current) {
164
+ if (current.trusted === true
165
+ || current.decision !== undefined
166
+ || current.decidedBy !== undefined
167
+ || current.promotedBy !== undefined)
168
+ return current;
169
+ const sameWaiver = current.source === source && current.trusted === false && current.reason === reason;
170
+ if (sameWaiver && current.frozenContentId === frozenContentId)
171
+ return current;
172
+ if (!sameWaiver && (current.reason !== 'unverified_hub_write_pending'
173
+ || (current.frozenContentId !== undefined
174
+ && current.frozenContentId !== frozenContentId)))
175
+ return current;
176
+ }
177
+ return this.appendUnderLock({
178
+ assetId,
179
+ source,
180
+ trusted: false,
181
+ reason,
182
+ frozenContentId,
183
+ at: new Date(this.now()).toISOString(),
184
+ });
185
+ });
186
+ }
72
187
  rollbackLast(rec) {
73
188
  const line = `${JSON.stringify(rec)}\n`;
74
189
  try {
@@ -103,14 +218,22 @@ export class ProvenanceStore {
103
218
  return withAssetStoreLock(this.lockPath, () => {
104
219
  this.refreshUnderLock();
105
220
  const current = this.index.get(assetId) ?? null;
106
- if (current?.trusted === trusted)
221
+ if (trusted
222
+ && current?.trusted === false
223
+ && current.decision === undefined
224
+ && current.reason === 'unverified_hub_write_pending') {
225
+ throw new ProvenanceWritePendingError(assetId);
226
+ }
227
+ const decision = trusted ? 'promoted' : 'revoked';
228
+ if (current?.trusted === trusted && current.decision === decision) {
107
229
  return { changed: false, record: current };
230
+ }
108
231
  const full = {
109
232
  assetId,
110
233
  source: current?.source ?? 'local',
111
234
  trusted,
112
235
  at: new Date(this.now()).toISOString(),
113
- decision: trusted ? 'promoted' : 'revoked',
236
+ decision,
114
237
  decidedBy: by,
115
238
  ...(trusted ? { promotedBy: by } : {}),
116
239
  reason,
@@ -163,12 +286,82 @@ export async function ingestUnverified(store, prov, record, reason, source = 'hu
163
286
  // putFrozen bypasses normalizeForPut, so re-assert the M3-4 Capsule↔gene binding here — a hash-mismatched
164
287
  // Capsule with an empty gene must still fail closed on the frozen path, exactly as it does on the verified one.
165
288
  assertCapsuleGeneBinding(record);
166
- const mark = prov.mark({ assetId: record.asset_id, source, trusted: false, reason });
167
- const result = await store.putFrozen(record);
168
- // Mirror ingestUntrusted: only an explicit no-write result (dedup) is safe to roll the marker back; a
169
- // thrown write has an ambiguous on-disk outcome and must keep the untrusted marker.
170
- if (!result.stored)
171
- prov.rollbackLast(mark);
289
+ const frozenContentId = computeAssetId(record);
290
+ if (!frozenContentId)
291
+ throw new Error('failed to compute frozen content id');
292
+ const existing = await store.get(record.asset_id);
293
+ if (existing) {
294
+ if (!frozenAssetRecordsEqual(existing, record))
295
+ throw new FrozenAssetIdCollisionError(record.asset_id);
296
+ prov.finalizeUnverifiedWrite(record.asset_id, source, reason, frozenContentId);
297
+ return { asset_id: record.asset_id, stored: false, verified: false };
298
+ }
299
+ // Stage a non-waiver record before the body write. If the provider throws or lies about persistence,
300
+ // the asset stays untrusted and health still reports its hash mismatch instead of treating it as #570.
301
+ const staged = prov.stageUnverifiedWrite(record.asset_id, source, frozenContentId);
302
+ // A missing body with an explicit trust decision cannot be replaced without reopening a promotion race.
303
+ if (staged.source !== source
304
+ || staged.trusted
305
+ || staged.decision !== undefined
306
+ || staged.decidedBy !== undefined
307
+ || staged.promotedBy !== undefined
308
+ || staged.reason !== 'unverified_hub_write_pending'
309
+ || staged.frozenContentId !== frozenContentId) {
310
+ throw new FrozenAssetIdCollisionError(record.asset_id);
311
+ }
312
+ const result = validateFrozenPutResult(await store.putFrozen(record), record.asset_id);
313
+ const persisted = await store.get(record.asset_id);
314
+ if (!persisted || !frozenAssetRecordsEqual(persisted, record)) {
315
+ throw new FrozenAssetIdCollisionError(record.asset_id);
316
+ }
317
+ prov.finalizeUnverifiedWrite(record.asset_id, source, reason, frozenContentId);
318
+ return result;
319
+ }
320
+ /** Atomic frozen variant used by reuse so the logical-id check and append share one provider lock. */
321
+ export async function ingestUnverifiedConditional(store, prov, record, reason, options, source = 'hub') {
322
+ if (!supportsAtomicFrozenConditionalPut(store)) {
323
+ throw new Error('asset store does not support conditional frozen writes');
324
+ }
325
+ assertCapsuleGeneBinding(record);
326
+ const frozenContentId = computeAssetId(record);
327
+ if (!frozenContentId)
328
+ throw new Error('failed to compute frozen content id');
329
+ const existing = await store.get(record.asset_id);
330
+ if (existing) {
331
+ if (!frozenAssetRecordsEqual(existing, record))
332
+ throw new FrozenAssetIdCollisionError(record.asset_id);
333
+ prov.finalizeUnverifiedWrite(record.asset_id, source, reason, frozenContentId);
334
+ return { asset_id: record.asset_id, stored: false, verified: false, status: 'already_exists' };
335
+ }
336
+ const stage = prov.stageUnverifiedWriteTracked(record.asset_id, source, frozenContentId);
337
+ if (stage.record.source !== source
338
+ || stage.record.trusted
339
+ || stage.record.decision !== undefined
340
+ || stage.record.decidedBy !== undefined
341
+ || stage.record.promotedBy !== undefined
342
+ || stage.record.reason !== 'unverified_hub_write_pending'
343
+ || stage.record.frozenContentId !== frozenContentId) {
344
+ throw new FrozenAssetIdCollisionError(record.asset_id);
345
+ }
346
+ const result = validateConditionalPutResult(await store.putFrozenConditional(record, options), record.asset_id, options);
347
+ if (result.verified !== false)
348
+ throw new InvalidFrozenPutResultError();
349
+ if (!result.stored) {
350
+ // The pending row may be shared with a concurrent writer. Keep it until a persisted body is finalized.
351
+ if (result.status === 'already_exists') {
352
+ const persisted = await store.get(record.asset_id);
353
+ if (!persisted || !frozenAssetRecordsEqual(persisted, record)) {
354
+ throw new FrozenAssetIdCollisionError(record.asset_id);
355
+ }
356
+ prov.finalizeUnverifiedWrite(record.asset_id, source, reason, frozenContentId);
357
+ }
358
+ return result;
359
+ }
360
+ const persisted = await store.get(record.asset_id);
361
+ if (!persisted || !frozenAssetRecordsEqual(persisted, record)) {
362
+ throw new FrozenAssetIdCollisionError(record.asset_id);
363
+ }
364
+ prov.finalizeUnverifiedWrite(record.asset_id, source, reason, frozenContentId);
172
365
  return result;
173
366
  }
174
367
  /**
@@ -180,9 +373,23 @@ export async function ingestUntrustedConditional(store, prov, record, options, s
180
373
  throw new Error('asset store does not support conditional writes');
181
374
  }
182
375
  const normalized = normalizeForPut(record);
183
- const mark = prov.mark({ assetId: normalized.record.asset_id, source, trusted: false });
376
+ const stage = prov.stageUntrustedWriteTracked(normalized.record.asset_id, source);
184
377
  const result = validateConditionalPutResult(await store.putConditional(record, options), normalized.record.asset_id, options);
185
- if (!result.stored)
186
- prov.rollbackLast(mark);
378
+ if (result.status === 'logical_collision') {
379
+ if (stage.appended)
380
+ prov.rollbackLast(stage.record);
381
+ return result;
382
+ }
383
+ if (!result.stored) {
384
+ const persisted = await store.get(normalized.record.asset_id);
385
+ if (!persisted || !frozenAssetRecordsEqual(persisted, normalized.record)) {
386
+ throw new FrozenAssetIdCollisionError(normalized.record.asset_id);
387
+ }
388
+ if (stage.appended)
389
+ prov.rollbackLast(stage.record);
390
+ }
391
+ if (!stage.appended) {
392
+ prov.finalizeUntrustedWrite(normalized.record.asset_id, source);
393
+ }
187
394
  return result;
188
395
  }
@@ -10,6 +10,17 @@ export interface PutResult {
10
10
  stored: boolean;
11
11
  verified: boolean;
12
12
  }
13
+ export declare class FrozenAssetIdCollisionError extends Error {
14
+ readonly assetId: string;
15
+ readonly code = "FROZEN_ASSET_ID_COLLISION";
16
+ constructor(assetId: string);
17
+ }
18
+ export declare class InvalidFrozenPutResultError extends Error {
19
+ readonly code = "INVALID_FROZEN_PUT_RESULT";
20
+ constructor();
21
+ }
22
+ export declare function frozenAssetRecordsEqual(left: AssetRecord, right: AssetRecord): boolean;
23
+ export declare function validateFrozenPutResult(value: unknown, expectedAssetId: string): PutResult;
13
24
  export type ConditionalPutStatus = 'stored' | 'already_exists' | 'logical_collision';
14
25
  export interface ConditionalPutOptions {
15
26
  /** Only explicit force-like callers may keep multiple content versions for the same type + logical id. */
@@ -34,6 +45,8 @@ export interface SearchQuery {
34
45
  category?: string;
35
46
  gene?: string;
36
47
  text?: string;
48
+ /** Task-domain scope (hub taxonomy slug, e.g. "software_engineering"). Local providers may ignore it; the hub applies it as a recall fence. */
49
+ domain?: string;
37
50
  limit?: number;
38
51
  }
39
52
  /**
@@ -46,7 +59,7 @@ export interface AssetStoreProvider {
46
59
  putConditional?(asset: AssetRecord, options?: ConditionalPutOptions): Promise<ConditionalPutResult>;
47
60
  get(assetId: string): Promise<AssetRecord | null>;
48
61
  /** Optional direct lookup for non-content-addressed logical ids. Callers must handle 0, 1, or multiple matches. */
49
- findByLogicalId?(id: string, limit?: number): Promise<AssetRecord[]>;
62
+ findByLogicalId?(id: string, limit?: number, kind?: AssetKind): Promise<AssetRecord[]>;
50
63
  /**
51
64
  * Optional frozen write: store the record under its OWN declared asset_id without recomputing or
52
65
  * normalizing it (bypasses {@link normalizeForPut}'s self-consistency check). Only providers backing a
@@ -55,6 +68,8 @@ export interface AssetStoreProvider {
55
68
  * feature-detect it rather than assuming it exists.
56
69
  */
57
70
  putFrozen?(record: AssetRecord): Promise<PutResult>;
71
+ /** Atomic frozen variant that also enforces the type + logical-id condition under the provider write lock. */
72
+ putFrozenConditional?(record: AssetRecord, options?: ConditionalPutOptions): Promise<ConditionalPutResult>;
58
73
  search(query: SearchQuery): Promise<AssetRecord[]>;
59
74
  list(kind?: AssetKind, limit?: number): Promise<AssetRecord[]>;
60
75
  }
@@ -62,6 +77,10 @@ export type AtomicConditionalPutProvider = AssetStoreProvider & {
62
77
  putConditional(asset: AssetRecord, options?: ConditionalPutOptions): Promise<ConditionalPutResult>;
63
78
  };
64
79
  export declare function supportsAtomicConditionalPut(provider: AssetStoreProvider): provider is AtomicConditionalPutProvider;
80
+ export type AtomicFrozenConditionalPutProvider = AssetStoreProvider & {
81
+ putFrozenConditional(record: AssetRecord, options?: ConditionalPutOptions): Promise<ConditionalPutResult>;
82
+ };
83
+ export declare function supportsAtomicFrozenConditionalPut(provider: AssetStoreProvider): provider is AtomicFrozenConditionalPutProvider;
65
84
  export declare class AssetIdMismatchError extends Error {
66
85
  readonly claimed: string;
67
86
  readonly actual: string;
@@ -1,4 +1,34 @@
1
- import { computeAssetId, verifyAssetId } from '../wire/index.js';
1
+ import { canonicalize, computeAssetId, verifyAssetId } from '../wire/index.js';
2
+ export class FrozenAssetIdCollisionError extends Error {
3
+ assetId;
4
+ code = 'FROZEN_ASSET_ID_COLLISION';
5
+ constructor(assetId) {
6
+ super('frozen asset_id already exists with different content');
7
+ this.assetId = assetId;
8
+ this.name = 'FrozenAssetIdCollisionError';
9
+ }
10
+ }
11
+ export class InvalidFrozenPutResultError extends Error {
12
+ code = 'INVALID_FROZEN_PUT_RESULT';
13
+ constructor() {
14
+ super('invalid frozen put result');
15
+ this.name = 'InvalidFrozenPutResultError';
16
+ }
17
+ }
18
+ export function frozenAssetRecordsEqual(left, right) {
19
+ return canonicalize(left) === canonicalize(right);
20
+ }
21
+ export function validateFrozenPutResult(value, expectedAssetId) {
22
+ if (!value || typeof value !== 'object' || Array.isArray(value))
23
+ throw new InvalidFrozenPutResultError();
24
+ const result = value;
25
+ if (result['asset_id'] !== expectedAssetId
26
+ || typeof result['stored'] !== 'boolean'
27
+ || result['verified'] !== false) {
28
+ throw new InvalidFrozenPutResultError();
29
+ }
30
+ return value;
31
+ }
2
32
  export class InvalidConditionalPutResultError extends Error {
3
33
  reason;
4
34
  code = 'INVALID_CONDITIONAL_PUT_RESULT';
@@ -49,6 +79,9 @@ export function validateConditionalPutResult(value, expectedAssetId, options) {
49
79
  export function supportsAtomicConditionalPut(provider) {
50
80
  return typeof provider.putConditional === 'function';
51
81
  }
82
+ export function supportsAtomicFrozenConditionalPut(provider) {
83
+ return typeof provider.putFrozenConditional === 'function';
84
+ }
52
85
  export class AssetIdMismatchError extends Error {
53
86
  claimed;
54
87
  actual;
@@ -1,2 +1,3 @@
1
1
  export * from './selfEvolve.js';
2
- export * from './envFingerprint.js';
2
+ export * from './envFingerprint.js';
3
+ export * from './v1EnvCompat.js';
@@ -1,2 +1,3 @@
1
1
  export * from './selfEvolve.js';
2
- export * from './envFingerprint.js';
2
+ export * from './envFingerprint.js';
3
+ export * from './v1EnvCompat.js';
@@ -0,0 +1,110 @@
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
+ }
102
+ /**
103
+ * Build an offline V1→V2 env translation report.
104
+ * Does not write files; callers decide how to present or apply suggestions.
105
+ * Raw values are retained only as v2Value for mappable non-secret keys.
106
+ * GITHUB_TOKEN and GH_TOKEN are reported as keep without retaining their values.
107
+ */
108
+ export declare function translateV1Env(env?: Record<string, string | undefined>): V1EnvTranslationReport;
109
+ /** Human-readable report (never prints secret-looking values; only key names + actions). */
110
+ export declare function formatV1EnvTranslationReport(report: V1EnvTranslationReport): string;