@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.
Files changed (240) hide show
  1. package/assets/gep/genes.jsonl +5 -5
  2. package/dist/algo/bans.d.ts +10 -1
  3. package/dist/algo/bans.js +56 -6
  4. package/dist/algo/candidateAssembly.d.ts +25 -2
  5. package/dist/algo/candidateAssembly.js +171 -26
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/conversationSniffer.js +25 -8
  8. package/dist/algo/cycleEngine.d.ts +63 -5
  9. package/dist/algo/cycleEngine.js +320 -42
  10. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  11. package/dist/algo/evolutionEvent.d.ts +26 -0
  12. package/dist/algo/evolutionEvent.js +32 -0
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +16 -3
  15. package/dist/algo/geneHealth.d.ts +36 -3
  16. package/dist/algo/geneHealth.js +47 -4
  17. package/dist/algo/geneIntake.d.ts +46 -1
  18. package/dist/algo/geneIntake.js +121 -10
  19. package/dist/algo/geneSelection.d.ts +126 -6
  20. package/dist/algo/geneSelection.js +479 -36
  21. package/dist/algo/index.d.ts +6 -1
  22. package/dist/algo/index.js +6 -1
  23. package/dist/algo/kautoProjection.d.ts +41 -0
  24. package/dist/algo/kautoProjection.js +95 -0
  25. package/dist/algo/kautoValidator.d.ts +68 -0
  26. package/dist/algo/kautoValidator.js +256 -0
  27. package/dist/algo/memoryGraph.d.ts +62 -0
  28. package/dist/algo/memoryGraph.js +86 -0
  29. package/dist/algo/orchestrator.d.ts +17 -1
  30. package/dist/algo/orchestrator.js +30 -4
  31. package/dist/algo/publishEligibility.d.ts +34 -0
  32. package/dist/algo/publishEligibility.js +52 -0
  33. package/dist/algo/solidify.d.ts +11 -2
  34. package/dist/algo/solidify.js +37 -7
  35. package/dist/algo/ucb1.d.ts +53 -0
  36. package/dist/algo/ucb1.js +156 -0
  37. package/dist/assetrepair/hubRejection.d.ts +12 -0
  38. package/dist/assetrepair/hubRejection.js +109 -0
  39. package/dist/assetrepair/index.d.ts +2 -0
  40. package/dist/assetrepair/index.js +2 -0
  41. package/dist/assetrepair/repair.d.ts +33 -0
  42. package/dist/assetrepair/repair.js +155 -0
  43. package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
  44. package/dist/assetstore/assetSidecarRecords.js +384 -0
  45. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  46. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  47. package/dist/assetstore/assetStoreHealth.d.ts +81 -0
  48. package/dist/assetstore/assetStoreHealth.js +319 -0
  49. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  50. package/dist/assetstore/assetStoreLayout.js +6 -0
  51. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  52. package/dist/assetstore/assetStoreStorage.js +336 -0
  53. package/dist/assetstore/assetSyncLedger.d.ts +91 -1
  54. package/dist/assetstore/assetSyncLedger.js +718 -59
  55. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  56. package/dist/assetstore/foreignJsonlSource.js +150 -0
  57. package/dist/assetstore/index.d.ts +5 -0
  58. package/dist/assetstore/index.js +5 -0
  59. package/dist/assetstore/learningHistory.js +3 -3
  60. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  61. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  62. package/dist/assetstore/localJsonl.d.ts +12 -1
  63. package/dist/assetstore/localJsonl.js +251 -42
  64. package/dist/assetstore/provenance.d.ts +93 -4
  65. package/dist/assetstore/provenance.js +368 -84
  66. package/dist/assetstore/provider.d.ts +63 -0
  67. package/dist/assetstore/provider.js +97 -6
  68. package/dist/assetstore/reviewFilter.d.ts +19 -1
  69. package/dist/assetstore/reviewFilter.js +39 -1
  70. package/dist/assetstore/reviewLedger.d.ts +8 -2
  71. package/dist/assetstore/reviewLedger.js +71 -45
  72. package/dist/assetstore/unionReadStore.d.ts +25 -0
  73. package/dist/assetstore/unionReadStore.js +119 -0
  74. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  75. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  76. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  77. package/dist/benchmark/antiGeneRollout.js +4 -3
  78. package/dist/benchmark/index.d.ts +3 -1
  79. package/dist/benchmark/index.js +3 -1
  80. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  81. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  82. package/dist/benchmark/triggerShift.d.ts +62 -0
  83. package/dist/benchmark/triggerShift.js +106 -0
  84. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  85. package/dist/bootstrap/envFingerprint.js +5 -0
  86. package/dist/bootstrap/index.d.ts +3 -1
  87. package/dist/bootstrap/index.js +3 -1
  88. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  89. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  90. package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
  91. package/dist/bootstrap/v1EnvCompat.js +303 -0
  92. package/dist/events/eventArchive.d.ts +2 -0
  93. package/dist/events/eventArchive.js +13 -3
  94. package/dist/events/eventSchema.d.ts +7 -7
  95. package/dist/events/eventStore.d.ts +2 -0
  96. package/dist/events/eventStore.js +5 -1
  97. package/dist/events/ingest.d.ts +2 -1
  98. package/dist/events/ingest.js +15 -0
  99. package/dist/events/paths.d.ts +12 -10
  100. package/dist/events/paths.js +24 -20
  101. package/dist/events/public.d.ts +2 -2
  102. package/dist/events/public.js +2 -2
  103. package/dist/events/reports.d.ts +2 -0
  104. package/dist/events/reports.js +4 -0
  105. package/dist/exec/autoExec.d.ts +68 -4
  106. package/dist/exec/autoExec.js +392 -33
  107. package/dist/exec/autonomousCycle.d.ts +31 -4
  108. package/dist/exec/autonomousCycle.js +71 -13
  109. package/dist/exec/claudeBridge.d.ts +80 -15
  110. package/dist/exec/claudeBridge.js +845 -76
  111. package/dist/exec/executionBinding.d.ts +414 -0
  112. package/dist/exec/executionBinding.js +588 -0
  113. package/dist/exec/index.d.ts +1 -0
  114. package/dist/exec/index.js +1 -0
  115. package/dist/exec/openPrRegistry.d.ts +8 -2
  116. package/dist/exec/openPrRegistry.js +32 -22
  117. package/dist/exec/prompt.js +14 -1
  118. package/dist/exec/proofOfWork.d.ts +1 -1
  119. package/dist/exec/proofOfWork.js +2 -2
  120. package/dist/exec/runnerRegistry.d.ts +153 -36
  121. package/dist/exec/runnerRegistry.js +848 -65
  122. package/dist/exec/selfPr.js +1 -7
  123. package/dist/feedback/envelope.d.ts +61 -0
  124. package/dist/feedback/envelope.js +168 -0
  125. package/dist/feedback/index.d.ts +1 -0
  126. package/dist/feedback/index.js +1 -0
  127. package/dist/hooks/hooks.js +1 -0
  128. package/dist/hub/assetCallLog.d.ts +35 -1
  129. package/dist/hub/assetCallLog.js +124 -1
  130. package/dist/hub/bindings.d.ts +8 -1
  131. package/dist/hub/bindings.js +29 -8
  132. package/dist/hub/capability.d.ts +130 -4
  133. package/dist/hub/conversationDistiller.d.ts +19 -0
  134. package/dist/hub/conversationDistiller.js +115 -37
  135. package/dist/hub/fake.d.ts +3 -2
  136. package/dist/hub/fake.js +2 -1
  137. package/dist/hub/index.d.ts +1 -0
  138. package/dist/hub/index.js +1 -0
  139. package/dist/hub/questionGenerator.d.ts +5 -1
  140. package/dist/hub/questionGenerator.js +8 -6
  141. package/dist/hub/recipeCompose.d.ts +27 -0
  142. package/dist/hub/recipeCompose.js +90 -0
  143. package/dist/hub/sanitize.js +122 -7
  144. package/dist/index.d.ts +6 -1
  145. package/dist/index.js +7 -1
  146. package/dist/issueReporter/index.d.ts +156 -0
  147. package/dist/issueReporter/index.js +1688 -0
  148. package/dist/mailbox/dispatch.d.ts +1 -1
  149. package/dist/mailbox/dispatch.js +22 -6
  150. package/dist/mailbox/envelope.d.ts +7 -1
  151. package/dist/mailbox/envelope.js +9 -2
  152. package/dist/mailbox/ipcServer.d.ts +12 -2
  153. package/dist/mailbox/ipcServer.js +183 -13
  154. package/dist/mailbox/store.d.ts +89 -3
  155. package/dist/mailbox/store.js +895 -41
  156. package/dist/modelCompatibility.d.ts +164 -0
  157. package/dist/modelCompatibility.js +309 -0
  158. package/dist/observers/valueDigestObserver.d.ts +9 -0
  159. package/dist/observers/valueDigestObserver.js +35 -2
  160. package/dist/ops/cleanup.js +1 -1
  161. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  162. package/dist/ops/evolutionGraphProjection.js +315 -0
  163. package/dist/ops/index.d.ts +2 -1
  164. package/dist/ops/index.js +2 -1
  165. package/dist/ops/savingsCore.js +1 -2
  166. package/dist/ops/selfUpdate.d.ts +18 -1
  167. package/dist/ops/selfUpdate.js +88 -23
  168. package/dist/ops/valueOutreach.d.ts +3 -1
  169. package/dist/ops/valueOutreach.js +5 -1
  170. package/dist/personality/schema.d.ts +24 -24
  171. package/dist/schema/evolutionGraph.d.ts +784 -0
  172. package/dist/schema/evolutionGraph.js +187 -0
  173. package/dist/schema/index.d.ts +1 -0
  174. package/dist/schema/index.js +1 -0
  175. package/dist/schema/proofOfWork.d.ts +125 -6
  176. package/dist/schema/proofOfWork.js +102 -4
  177. package/dist/schema/signal.d.ts +3 -3
  178. package/dist/schema/signal.js +1 -1
  179. package/dist/shadow/shadowHub.js +1 -0
  180. package/dist/signals/curriculum.d.ts +55 -0
  181. package/dist/signals/curriculum.js +202 -0
  182. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  183. package/dist/signals/expand.d.ts +15 -1
  184. package/dist/signals/expand.js +169 -1
  185. package/dist/signals/extractor.d.ts +2 -2
  186. package/dist/signals/extractor.js +31 -6
  187. package/dist/signals/index.d.ts +4 -1
  188. package/dist/signals/index.js +4 -1
  189. package/dist/signals/metaSignals.d.ts +4 -0
  190. package/dist/signals/metaSignals.js +42 -0
  191. package/dist/signals/scopeVocabulary.d.ts +75 -0
  192. package/dist/signals/scopeVocabulary.js +91 -0
  193. package/dist/signals/signalGate.js +1 -1
  194. package/dist/signals/taskDomain.d.ts +22 -0
  195. package/dist/signals/taskDomain.js +43 -0
  196. package/dist/strategy/constraintAblation.d.ts +64 -0
  197. package/dist/strategy/constraintAblation.js +2820 -0
  198. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  199. package/dist/strategy/constraintAblationPredicates.js +339 -0
  200. package/dist/strategy/index.d.ts +2 -1
  201. package/dist/strategy/index.js +2 -1
  202. package/dist/trace/index.d.ts +3 -1
  203. package/dist/trace/index.js +3 -1
  204. package/dist/trace/learningTrace.d.ts +216 -0
  205. package/dist/trace/learningTrace.js +298 -0
  206. package/dist/trace/proxyTurns.d.ts +31 -0
  207. package/dist/trace/proxyTurns.js +137 -0
  208. package/dist/trace/trajectory.d.ts +8 -0
  209. package/dist/trace/trajectory.js +14 -2
  210. package/dist/util/fetchPort.d.ts +1 -0
  211. package/dist/util/fetchPort.js +11 -0
  212. package/dist/util/fileLock.d.ts +113 -7
  213. package/dist/util/fileLock.js +1035 -80
  214. package/dist/util/index.d.ts +3 -1
  215. package/dist/util/index.js +2 -1
  216. package/dist/verify/index.d.ts +2 -1
  217. package/dist/verify/index.js +1 -1
  218. package/dist/verify/sandboxRunner.d.ts +30 -0
  219. package/dist/verify/sandboxRunner.js +370 -27
  220. package/dist/verify/sandboxedValidation.d.ts +16 -2
  221. package/dist/verify/sandboxedValidation.js +200 -22
  222. package/dist/verify/validation.d.ts +35 -4
  223. package/dist/verify/validation.js +164 -16
  224. package/dist/wire/geneHints.d.ts +88 -16
  225. package/dist/wire/geneHints.js +124 -15
  226. package/dist/wire/index.d.ts +8 -3
  227. package/dist/wire/index.js +2 -2
  228. package/dist/wire/schemaGate.d.ts +21 -0
  229. package/dist/wire/schemaGate.js +119 -10
  230. package/dist/workflow/dsl.d.ts +24 -3
  231. package/dist/workflow/dsl.js +4 -0
  232. package/dist/workflow/engine.d.ts +5 -1
  233. package/dist/workflow/engine.js +3 -0
  234. package/dist/workflow/index.d.ts +3 -1
  235. package/dist/workflow/index.js +3 -1
  236. package/dist/workflow/runtime.d.ts +110 -0
  237. package/dist/workflow/runtime.js +1298 -0
  238. package/dist/workflow/stateStore.d.ts +172 -0
  239. package/dist/workflow/stateStore.js +1044 -0
  240. package/package.json +12 -4
@@ -0,0 +1,164 @@
1
+ export type CompatibilityDecision = 'compatible' | 'quarantine' | 'revalidate' | 'inconclusive' | 'unrecorded';
2
+ export type ReplayMode = 'baseline' | 'asset-enabled';
3
+ export type RunnerTrust = 'claude-cli' | 'fixture';
4
+ export type LedgerTransition = 'observation' | 'quarantine' | 'revalidate' | 'release';
5
+ export interface RequestedModelIdentity {
6
+ id: string;
7
+ }
8
+ export interface ServedModelIdentity {
9
+ id: string;
10
+ source: 'claude-cli-envelope';
11
+ }
12
+ export interface AssetIdentity {
13
+ type: string;
14
+ id: string;
15
+ revision: string;
16
+ }
17
+ export interface ReplayBudget {
18
+ maxUsd: number;
19
+ timeoutMs: number;
20
+ }
21
+ export interface ReplayTaskIdentity {
22
+ family: string;
23
+ inputDigest: string;
24
+ budget: ReplayBudget;
25
+ environmentFingerprint: string;
26
+ }
27
+ export interface CompatibilityKey extends AssetIdentity, ReplayTaskIdentity {
28
+ requestedModelId: string;
29
+ }
30
+ export interface ValidationStepTrace {
31
+ command: string;
32
+ exitCode: number | null;
33
+ passed: boolean;
34
+ executed: boolean;
35
+ termination: 'exit' | 'timeout' | 'cancelled' | 'not-started';
36
+ }
37
+ export interface ValidationTrace {
38
+ schemaVersion: 1;
39
+ isolated: boolean;
40
+ complete: boolean;
41
+ steps: readonly ValidationStepTrace[];
42
+ digest: string;
43
+ }
44
+ export interface RunnerUsage {
45
+ inputTokens: number;
46
+ outputTokens: number;
47
+ cacheCreationInputTokens: number;
48
+ cacheReadInputTokens: number;
49
+ costUsd: number;
50
+ }
51
+ export interface RunnerObservation {
52
+ trust: RunnerTrust;
53
+ mode: ReplayMode;
54
+ ok: boolean;
55
+ exitCode: number | null;
56
+ termination: 'exit' | 'timeout' | 'cancelled' | 'spawn-error';
57
+ requestedModel: RequestedModelIdentity;
58
+ servedModel: ServedModelIdentity | null;
59
+ sessionId: string | null;
60
+ usage: RunnerUsage | null;
61
+ structuredResult: unknown;
62
+ validation: ValidationTrace;
63
+ stdoutTruncated: boolean;
64
+ stderrTruncated: boolean;
65
+ }
66
+ export interface ReplayCase {
67
+ asset: AssetIdentity;
68
+ requestedModelId: string;
69
+ taskFamily: string;
70
+ input: unknown;
71
+ environment: unknown;
72
+ budget: ReplayBudget;
73
+ validation: readonly string[];
74
+ }
75
+ export interface ReplayExecutorInput {
76
+ mode: ReplayMode;
77
+ test: ReplayCase;
78
+ attempt: number;
79
+ }
80
+ export type ReplayExecutor = (input: ReplayExecutorInput) => Promise<RunnerObservation>;
81
+ export interface ReplayEvidence {
82
+ schemaVersion: 4;
83
+ requestId: string;
84
+ key: CompatibilityKey;
85
+ baseline: RunnerObservation;
86
+ enabled: RunnerObservation;
87
+ observedAt: string;
88
+ staleAfterMs: number;
89
+ }
90
+ export interface ReplayRun {
91
+ schemaVersion: 4;
92
+ requestId: string;
93
+ startedAt: string;
94
+ completedAt: string;
95
+ evidence: readonly ReplayEvidence[];
96
+ partial: boolean;
97
+ }
98
+ export interface LedgerEvent {
99
+ schemaVersion: 4;
100
+ sequence: number;
101
+ eventId: string;
102
+ requestId: string;
103
+ transition: LedgerTransition;
104
+ key: CompatibilityKey;
105
+ at: string;
106
+ evidence?: ReplayEvidence;
107
+ reason?: string;
108
+ legacyPayload?: unknown;
109
+ }
110
+ export interface ResolvedCompatibility {
111
+ decision: CompatibilityDecision;
112
+ key: CompatibilityKey;
113
+ evidence?: ReplayEvidence;
114
+ sequence?: number;
115
+ reason: string;
116
+ }
117
+ export declare function canonicalDigest(value: unknown): string;
118
+ export declare function normalizeEnvironmentFingerprint(environment: unknown): string;
119
+ export declare function compatibilityKey(test: ReplayCase): CompatibilityKey;
120
+ export declare function compatibilityKeyString(key: CompatibilityKey): string;
121
+ export declare function validationTrace(isolated: boolean, steps: readonly ValidationStepTrace[]): ValidationTrace;
122
+ export declare function validationTracesComparable(baseline: ValidationTrace, enabled: ValidationTrace): boolean;
123
+ export declare function isStrictRunnerSuccess(observation: RunnerObservation): boolean;
124
+ export declare function evaluateEvidence(evidence: ReplayEvidence): CompatibilityDecision;
125
+ export declare function replayCorpus(corpus: readonly ReplayCase[], execute: ReplayExecutor, options?: {
126
+ requestId?: string;
127
+ retries?: number;
128
+ staleAfterMs?: number;
129
+ now?: () => number;
130
+ }): Promise<ReplayRun>;
131
+ export declare class CompatibilityLedger {
132
+ readonly path: string;
133
+ readonly lockPath: string;
134
+ constructor(path: string);
135
+ private state;
136
+ read(): {
137
+ events: LedgerEvent[];
138
+ malformed: number;
139
+ legacy: number;
140
+ };
141
+ private mutate;
142
+ appendRun(run: ReplayRun): boolean;
143
+ transition(key: CompatibilityKey, transition: Exclude<LedgerTransition, 'observation'>, requestId: string, reason: string, now?: number): boolean;
144
+ resolve(key: CompatibilityKey, now?: number): ResolvedCompatibility;
145
+ }
146
+ export interface CompatibilityCandidateIdentity {
147
+ assetType: string;
148
+ assetId: string;
149
+ revision: string;
150
+ }
151
+ export interface CompatibilityCandidate<T> {
152
+ candidate: T;
153
+ identity: CompatibilityCandidateIdentity;
154
+ }
155
+ export interface CompatibilityEvidenceIndex {
156
+ decisionFor(identity: CompatibilityCandidateIdentity): ResolvedCompatibility;
157
+ }
158
+ export declare function makeCompatibilityEvidenceIndex(ledger: CompatibilityLedger, context: Omit<ReplayCase, 'asset' | 'validation'>, now?: () => number): CompatibilityEvidenceIndex;
159
+ export declare function checkAssetApplicability(identity: CompatibilityCandidateIdentity, evidence?: CompatibilityEvidenceIndex): {
160
+ selectable: boolean;
161
+ decision: CompatibilityDecision;
162
+ reason: string;
163
+ };
164
+ export declare function isCompatibilityBlocked(identity: CompatibilityCandidateIdentity, evidence?: CompatibilityEvidenceIndex): boolean;
@@ -0,0 +1,309 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import { acquireLock, releaseLock } from './util/fileLock.js';
5
+ const DEFAULT_STALE_MS = 30 * 24 * 60 * 60 * 1000;
6
+ const MAX_TEXT = 512;
7
+ const MAX_HISTORY = 20_000;
8
+ const STABLE_ENVIRONMENT_KEYS = new Set([
9
+ 'adapterVersion', 'arch', 'capabilities', 'features', 'node', 'nodeVersion', 'os', 'platform',
10
+ 'provider', 'providerVersion', 'runtime', 'schemaVersion', 'toolchain', 'validatorVersion',
11
+ ]);
12
+ const SECRET_OR_VOLATILE_KEY = /(?:token|secret|password|authorization|api[_-]?key|credential|captured|timestamp|hostname|host|cwd|device|machine|serial|uuid|path)/i;
13
+ function bounded(value, limit = MAX_TEXT) {
14
+ return typeof value === 'string' ? value.slice(0, limit) : '';
15
+ }
16
+ function canonical(value, depth = 0) {
17
+ if (depth > 20)
18
+ return '[depth-limit]';
19
+ if (Array.isArray(value))
20
+ return value.map((item) => canonical(item, depth + 1));
21
+ if (value && typeof value === 'object') {
22
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [
23
+ key, canonical(value[key], depth + 1),
24
+ ]));
25
+ }
26
+ if (typeof value === 'number' && !Number.isFinite(value))
27
+ return String(value);
28
+ return value;
29
+ }
30
+ export function canonicalDigest(value) {
31
+ return createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex');
32
+ }
33
+ export function normalizeEnvironmentFingerprint(environment) {
34
+ if (!environment || typeof environment !== 'object' || Array.isArray(environment))
35
+ return canonicalDigest({});
36
+ const source = environment;
37
+ const selected = Object.fromEntries(Object.keys(source)
38
+ .filter((key) => STABLE_ENVIRONMENT_KEYS.has(key) && !SECRET_OR_VOLATILE_KEY.test(key))
39
+ .sort()
40
+ .map((key) => [key, canonical(source[key])]));
41
+ return canonicalDigest(selected);
42
+ }
43
+ function normalizedBudget(budget) {
44
+ if (!Number.isFinite(budget.maxUsd) || budget.maxUsd <= 0)
45
+ throw new Error('budget.maxUsd must be positive');
46
+ if (!Number.isSafeInteger(budget.timeoutMs) || budget.timeoutMs < 1 || budget.timeoutMs > 120_000) {
47
+ throw new Error('budget.timeoutMs must be an integer from 1 to 120000');
48
+ }
49
+ return { maxUsd: budget.maxUsd, timeoutMs: budget.timeoutMs };
50
+ }
51
+ export function compatibilityKey(test) {
52
+ const budget = normalizedBudget(test.budget);
53
+ const family = bounded(test.taskFamily, 256);
54
+ if (!family)
55
+ throw new Error('taskFamily is required');
56
+ const requestedModelId = bounded(test.requestedModelId);
57
+ if (!requestedModelId)
58
+ throw new Error('requestedModelId is required');
59
+ return Object.freeze({
60
+ type: bounded(test.asset.type, 256),
61
+ id: bounded(test.asset.id),
62
+ revision: bounded(test.asset.revision, 256),
63
+ requestedModelId,
64
+ family,
65
+ inputDigest: canonicalDigest(test.input),
66
+ budget,
67
+ environmentFingerprint: normalizeEnvironmentFingerprint(test.environment),
68
+ });
69
+ }
70
+ export function compatibilityKeyString(key) {
71
+ return JSON.stringify(canonical(key));
72
+ }
73
+ export function validationTrace(isolated, steps) {
74
+ const normalized = steps.map((step) => ({
75
+ command: bounded(step.command, 2048),
76
+ exitCode: typeof step.exitCode === 'number' ? step.exitCode : null,
77
+ passed: step.passed === true,
78
+ executed: step.executed === true,
79
+ termination: step.termination,
80
+ }));
81
+ const complete = isolated && normalized.length > 0 && normalized.every((step) => (step.executed && step.exitCode !== null && step.termination === 'exit'));
82
+ return { schemaVersion: 1, isolated, complete, steps: normalized, digest: canonicalDigest(normalized) };
83
+ }
84
+ export function validationTracesComparable(baseline, enabled) {
85
+ return baseline.complete && enabled.complete
86
+ && baseline.isolated && enabled.isolated
87
+ && baseline.digest === enabled.digest;
88
+ }
89
+ export function isStrictRunnerSuccess(observation) {
90
+ return observation.trust === 'claude-cli'
91
+ && observation.ok
92
+ && observation.exitCode === 0
93
+ && observation.termination === 'exit'
94
+ && observation.requestedModel.id.length > 0
95
+ && observation.servedModel?.source === 'claude-cli-envelope'
96
+ && observation.servedModel.id === observation.requestedModel.id
97
+ && typeof observation.sessionId === 'string' && observation.sessionId.length > 0
98
+ && observation.usage !== null
99
+ && observation.usage.inputTokens >= 0
100
+ && observation.usage.outputTokens >= 0
101
+ && observation.usage.costUsd >= 0
102
+ && observation.structuredResult !== null
103
+ && observation.structuredResult !== undefined
104
+ && observation.validation.complete
105
+ && !observation.stdoutTruncated
106
+ && !observation.stderrTruncated;
107
+ }
108
+ export function evaluateEvidence(evidence) {
109
+ if (!isStrictRunnerSuccess(evidence.baseline))
110
+ return 'inconclusive';
111
+ if (!validationTracesComparable(evidence.baseline.validation, evidence.enabled.validation))
112
+ return 'inconclusive';
113
+ if (!isStrictRunnerSuccess(evidence.enabled))
114
+ return 'quarantine';
115
+ return 'compatible';
116
+ }
117
+ function failedObservation(mode, test) {
118
+ return {
119
+ trust: 'fixture', mode, ok: false, exitCode: null, termination: 'spawn-error',
120
+ requestedModel: { id: test.requestedModelId }, servedModel: null, sessionId: null, usage: null,
121
+ structuredResult: null, validation: validationTrace(false, []), stdoutTruncated: false, stderrTruncated: false,
122
+ };
123
+ }
124
+ export async function replayCorpus(corpus, execute, options = {}) {
125
+ const now = options.now ?? Date.now;
126
+ const requestId = bounded(options.requestId ?? randomUUID(), 256) || randomUUID();
127
+ const retries = Math.max(0, Math.min(options.retries ?? 1, 5));
128
+ const startedAt = new Date(now()).toISOString();
129
+ const evidence = [];
130
+ let partial = false;
131
+ const sorted = [...corpus].sort((a, b) => compatibilityKeyString(compatibilityKey(a)).localeCompare(compatibilityKeyString(compatibilityKey(b))));
132
+ for (const test of sorted) {
133
+ const outcomes = new Map();
134
+ for (const mode of ['baseline', 'asset-enabled']) {
135
+ let observation = failedObservation(mode, test);
136
+ for (let attempt = 1; attempt <= retries + 1; attempt += 1) {
137
+ try {
138
+ observation = await execute({ mode, test, attempt });
139
+ }
140
+ catch {
141
+ observation = failedObservation(mode, test);
142
+ }
143
+ if (observation.termination === 'exit')
144
+ break;
145
+ }
146
+ outcomes.set(mode, observation);
147
+ }
148
+ const item = {
149
+ schemaVersion: 4,
150
+ requestId,
151
+ key: compatibilityKey(test),
152
+ baseline: outcomes.get('baseline'),
153
+ enabled: outcomes.get('asset-enabled'),
154
+ observedAt: new Date(now()).toISOString(),
155
+ staleAfterMs: options.staleAfterMs ?? DEFAULT_STALE_MS,
156
+ };
157
+ if (evaluateEvidence(item) === 'inconclusive')
158
+ partial = true;
159
+ evidence.push(item);
160
+ }
161
+ return { schemaVersion: 4, requestId, startedAt, completedAt: new Date(now()).toISOString(), evidence, partial };
162
+ }
163
+ export class CompatibilityLedger {
164
+ path;
165
+ lockPath;
166
+ constructor(path) {
167
+ this.path = path;
168
+ this.lockPath = `${path}.lock`;
169
+ mkdirSync(dirname(path), { recursive: true });
170
+ }
171
+ state() {
172
+ if (!existsSync(this.path))
173
+ return { state: { schemaVersion: 4, nextSequence: 1, events: [] }, malformed: 0, legacy: 0 };
174
+ try {
175
+ const parsed = JSON.parse(readFileSync(this.path, 'utf8'));
176
+ if (parsed.schemaVersion !== 4 || !Array.isArray(parsed.events)) {
177
+ const legacyKey = canonicalDigest(parsed);
178
+ const legacyEvent = {
179
+ schemaVersion: 4, sequence: 1, eventId: `legacy-${legacyKey}`, requestId: `legacy-${legacyKey}`,
180
+ transition: 'revalidate',
181
+ key: {
182
+ type: 'legacy', id: legacyKey, revision: String(parsed.schemaVersion ?? 'unknown'),
183
+ requestedModelId: 'legacy-unknown', family: 'legacy-migration', inputDigest: legacyKey,
184
+ budget: { maxUsd: 1, timeoutMs: 1 }, environmentFingerprint: canonicalDigest({}),
185
+ },
186
+ at: new Date(0).toISOString(), reason: 'legacy ledger requires explicit revalidation', legacyPayload: canonical(parsed),
187
+ };
188
+ return { state: { schemaVersion: 4, nextSequence: 2, events: [legacyEvent] }, malformed: 0, legacy: 1 };
189
+ }
190
+ const events = parsed.events.filter((event) => event?.schemaVersion === 4 && Number.isSafeInteger(event.sequence));
191
+ const nextSequence = Math.max(Number(parsed.nextSequence) || 1, ...events.map((event) => event.sequence + 1));
192
+ return { state: { schemaVersion: 4, nextSequence, events: events.slice(-MAX_HISTORY) }, malformed: 0, legacy: 0 };
193
+ }
194
+ catch {
195
+ return { state: { schemaVersion: 4, nextSequence: 1, events: [] }, malformed: 1, legacy: 0 };
196
+ }
197
+ }
198
+ read() {
199
+ const current = this.state();
200
+ return { events: current.state.events, malformed: current.malformed, legacy: current.legacy };
201
+ }
202
+ mutate(requestId, apply) {
203
+ acquireLock(this.lockPath);
204
+ const temporary = `${this.path}.${randomUUID()}.tmp`;
205
+ try {
206
+ const { state } = this.state();
207
+ if (state.events.some((event) => event.requestId === requestId))
208
+ return false;
209
+ apply(state);
210
+ state.events = state.events.slice(-MAX_HISTORY);
211
+ writeFileSync(temporary, `${JSON.stringify(state)}\n`, { mode: 0o600 });
212
+ renameSync(temporary, this.path);
213
+ return true;
214
+ }
215
+ finally {
216
+ rmSync(temporary, { force: true });
217
+ releaseLock(this.lockPath);
218
+ }
219
+ }
220
+ appendRun(run) {
221
+ return this.mutate(run.requestId, (state) => {
222
+ for (const evidence of run.evidence) {
223
+ state.events.push({
224
+ schemaVersion: 4, sequence: state.nextSequence++, eventId: randomUUID(), requestId: run.requestId,
225
+ transition: 'observation', key: evidence.key, at: evidence.observedAt, evidence,
226
+ });
227
+ }
228
+ });
229
+ }
230
+ transition(key, transition, requestId, reason, now = Date.now()) {
231
+ const boundedRequest = bounded(requestId, 256);
232
+ if (!boundedRequest)
233
+ throw new Error('requestId is required');
234
+ if (!bounded(reason, 2048))
235
+ throw new Error('transition reason is required');
236
+ return this.mutate(boundedRequest, (state) => {
237
+ const selected = compatibilityKeyString(key);
238
+ const history = state.events
239
+ .filter((event) => compatibilityKeyString(event.key) === selected)
240
+ .sort((a, b) => a.sequence - b.sequence);
241
+ if (transition === 'quarantine' && !history.some((event) => event.transition === 'observation')) {
242
+ throw new Error('quarantine requires recorded evidence');
243
+ }
244
+ if (transition === 'release') {
245
+ const lastQuarantine = history.findLastIndex((event) => event.transition === 'quarantine');
246
+ const lastRevalidate = history.findLastIndex((event) => event.transition === 'revalidate');
247
+ const successfulRevalidation = history.slice(lastRevalidate + 1).find((event) => (event.transition === 'observation' && event.evidence && evaluateEvidence(event.evidence) === 'compatible'));
248
+ if (lastQuarantine < 0 || lastRevalidate <= lastQuarantine || !successfulRevalidation) {
249
+ throw new Error('release requires quarantine, explicit revalidate, and newer compatible evidence');
250
+ }
251
+ }
252
+ state.events.push({
253
+ schemaVersion: 4, sequence: state.nextSequence++, eventId: randomUUID(), requestId: boundedRequest,
254
+ transition, key, at: new Date(now).toISOString(), reason: bounded(reason, 2048),
255
+ });
256
+ });
257
+ }
258
+ resolve(key, now = Date.now()) {
259
+ const selected = compatibilityKeyString(key);
260
+ const events = this.state().state.events.filter((event) => compatibilityKeyString(event.key) === selected);
261
+ if (events.length === 0)
262
+ return { decision: 'unrecorded', key, reason: 'no-evidence' };
263
+ let quarantined = false;
264
+ let needsRevalidation = false;
265
+ let latestEvidence;
266
+ let sequence;
267
+ for (const event of events.sort((a, b) => a.sequence - b.sequence)) {
268
+ sequence = event.sequence;
269
+ if (event.transition === 'quarantine')
270
+ quarantined = true;
271
+ if (event.transition === 'release') {
272
+ quarantined = false;
273
+ needsRevalidation = false;
274
+ }
275
+ if (event.transition === 'revalidate')
276
+ needsRevalidation = true;
277
+ if (event.transition === 'observation' && event.evidence) {
278
+ latestEvidence = event.evidence;
279
+ needsRevalidation = false;
280
+ }
281
+ }
282
+ if (quarantined)
283
+ return { decision: 'quarantine', key, evidence: latestEvidence, sequence, reason: 'sticky-quarantine' };
284
+ if (!latestEvidence)
285
+ return { decision: 'revalidate', key, sequence, reason: 'transition-without-evidence' };
286
+ if (needsRevalidation || now - Date.parse(latestEvidence.observedAt) > latestEvidence.staleAfterMs) {
287
+ return { decision: 'revalidate', key, evidence: latestEvidence, sequence, reason: needsRevalidation ? 'explicit-revalidate' : 'stale-at-resolution' };
288
+ }
289
+ return { decision: evaluateEvidence(latestEvidence), key, evidence: latestEvidence, sequence, reason: 'latest-evidence' };
290
+ }
291
+ }
292
+ export function makeCompatibilityEvidenceIndex(ledger, context, now) {
293
+ return {
294
+ decisionFor: (identity) => ledger.resolve(compatibilityKey({
295
+ ...context,
296
+ asset: { type: identity.assetType, id: identity.assetId, revision: identity.revision },
297
+ validation: [],
298
+ }), (now ?? Date.now)()),
299
+ };
300
+ }
301
+ export function checkAssetApplicability(identity, evidence) {
302
+ if (!evidence)
303
+ return { selectable: true, decision: 'unrecorded', reason: 'no-evidence-index' };
304
+ const resolved = evidence.decisionFor(identity);
305
+ return { selectable: resolved.decision !== 'quarantine', decision: resolved.decision, reason: resolved.reason };
306
+ }
307
+ export function isCompatibilityBlocked(identity, evidence) {
308
+ return !checkAssetApplicability(identity, evidence).selectable;
309
+ }
@@ -23,6 +23,7 @@ export declare class InMemoryDigestState implements DigestStateStore {
23
23
  markDelivered(at: number): void;
24
24
  }
25
25
  export declare const DEFAULT_DIGEST_CADENCE_MS: number;
26
+ export declare const DEFAULT_DIGEST_EXTRAS_TIMEOUT_MS = 1000;
26
27
  export interface ValueDigestObserverDeps {
27
28
  /** Aggregate the value summary for a window (composition wires this to loadValueSummary over trace+events). */
28
29
  summaryProvider(window: SummaryWindow): ValueSummary;
@@ -36,6 +37,14 @@ export interface ValueDigestObserverDeps {
36
37
  cadenceMs?: number;
37
38
  /** Observer handle timeout (the bus quarantines on timeout). Default 5s — plenty for format + a local sink. */
38
39
  timeoutMs?: number;
40
+ /** Optional extra digest fields. Pending reviews never open the send-gate by themselves. */
41
+ digestExtras?: () => {
42
+ pendingReviewCount?: number;
43
+ } | Promise<{
44
+ pendingReviewCount?: number;
45
+ }>;
46
+ /** 可选 enrichment 的尽力超时;超时只省略 extras,不判定 digest 失败。 */
47
+ digestExtrasTimeoutMs?: number;
39
48
  }
40
49
  /**
41
50
  * Build the value-digest observer. Subscribes to ALL events (it is cadence-driven, not event-type-driven: any
@@ -19,6 +19,27 @@ export class InMemoryDigestState {
19
19
  markDelivered(at) { this.last = at; }
20
20
  }
21
21
  export const DEFAULT_DIGEST_CADENCE_MS = 7 * 24 * 60 * 60 * 1000; // weekly
22
+ export const DEFAULT_DIGEST_EXTRAS_TIMEOUT_MS = 1_000;
23
+ const DIGEST_EXTRAS_MAX_OBSERVER_BUDGET = 0.5;
24
+ /** 可选 enrichment 不得抑制有实测价值的 digest,也不得导致 observer 被隔离。 */
25
+ async function readDigestExtras(load, timeoutMs) {
26
+ if (!load || timeoutMs <= 0)
27
+ return {};
28
+ return new Promise((resolve) => {
29
+ let settled = false;
30
+ const finish = (extras) => {
31
+ if (settled)
32
+ return;
33
+ settled = true;
34
+ clearTimeout(timer);
35
+ resolve(extras);
36
+ };
37
+ const timer = setTimeout(() => finish({}), timeoutMs);
38
+ Promise.resolve()
39
+ .then(() => load())
40
+ .then((extras) => finish(extras), () => finish({}));
41
+ });
42
+ }
22
43
  /** Period label "YYYY-MM-DD..YYYY-MM-DD" for the window [since, now). Deterministic given the two epochs. */
23
44
  function periodLabel(sinceMs, nowMs) {
24
45
  const d = (ms) => new Date(ms).toISOString().slice(0, 10);
@@ -38,14 +59,20 @@ export function valueDigestObserver(deps) {
38
59
  const state = deps.state ?? new InMemoryDigestState();
39
60
  const now = deps.now ?? (() => Date.now());
40
61
  const cadenceMs = deps.cadenceMs ?? DEFAULT_DIGEST_CADENCE_MS;
62
+ const observerTimeoutMs = deps.timeoutMs ?? 5_000;
63
+ const configuredExtrasTimeout = deps.digestExtrasTimeoutMs ?? DEFAULT_DIGEST_EXTRAS_TIMEOUT_MS;
64
+ const validExtrasTimeout = Number.isFinite(configuredExtrasTimeout) && configuredExtrasTimeout > 0
65
+ ? Math.floor(configuredExtrasTimeout)
66
+ : DEFAULT_DIGEST_EXTRAS_TIMEOUT_MS;
41
67
  const meta = {
42
68
  name: 'value-digest',
43
69
  idempotent: false,
44
- timeoutMs: deps.timeoutMs ?? 5_000,
70
+ timeoutMs: observerTimeoutMs,
45
71
  };
46
72
  return {
47
73
  meta,
48
74
  async handle() {
75
+ const observerStartedAt = Date.now();
49
76
  const t = now();
50
77
  const last = state.lastDeliveredAt();
51
78
  // 1. cadence gate.
@@ -58,7 +85,13 @@ export function valueDigestObserver(deps) {
58
85
  if (!digestShouldSend(summary))
59
86
  return; // zero-measured-value week → produce nothing
60
87
  const period = periodLabel(since, t);
61
- const markdown = buildValueDigest(summary, period);
88
+ // 只允许可选 enrichment 使用剩余 observer 预算的一半,给摘要格式化和 sink 留出确定的余量。
89
+ const elapsedMs = Math.max(0, Date.now() - observerStartedAt);
90
+ const remainingObserverBudgetMs = Math.max(0, observerTimeoutMs - elapsedMs);
91
+ const extrasBudget = Math.floor(remainingObserverBudgetMs * DIGEST_EXTRAS_MAX_OBSERVER_BUDGET);
92
+ const digestExtrasTimeoutMs = Math.min(validExtrasTimeout, Math.max(0, extrasBudget));
93
+ const extras = await readDigestExtras(deps.digestExtras, digestExtrasTimeoutMs);
94
+ const markdown = buildValueDigest(summary, period, extras);
62
95
  if (markdown === null)
63
96
  return; // belt-and-suspenders: gate already passed, but never deliver a null
64
97
  // 3. deliver, then record the delivery time so the cadence advances. If the sink throws, the bus isolates
@@ -1,5 +1,5 @@
1
1
  // Artifact GC (ported + generalized from v1 src/ops/cleanup.js). A resident daemon accumulates artifacts —
2
- // exec patch files (proof.gitDiff.patchRef under tmpdir), old event logs, scratch prompts — that nothing ever
2
+ // exec patch files (proof.git_diff.patch_ref under tmpdir), old event logs, scratch prompts — that nothing ever
3
3
  // reclaims. This applies a two-phase retention policy to a directory: (1) age-based, always keeping the N
4
4
  // newest regardless of age; (2) a hard count cap as a safety backstop. Generic over a filename pattern so the
5
5
  // daemon can GC each artifact kind. Best-effort + graceful: a missing dir or an undeletable file never throws.
@@ -0,0 +1,20 @@
1
+ import { type EvolutionGraph } from '../schema/evolutionGraph.js';
2
+ /** The minimal root_event view this projector reads (structurally matches events' ReportEvent). */
3
+ export interface EvolutionGraphSourceEvent {
4
+ type: string;
5
+ seq: number;
6
+ ts: string;
7
+ payload?: Record<string, unknown> | undefined;
8
+ human?: {
9
+ title?: string | undefined;
10
+ } | undefined;
11
+ }
12
+ export interface ProjectEvolutionGraphOptions {
13
+ /** Stable identifier for the produced snapshot. */
14
+ graphId?: string;
15
+ /** Snapshot timestamp; defaults to the newest projected event so the projection stays deterministic. */
16
+ generatedAt?: string;
17
+ /** Bound on scanned events (newest window wins) so a long history cannot make the projection unbounded. */
18
+ maxEvents?: number;
19
+ }
20
+ export declare function projectEvolutionGraph(events: readonly EvolutionGraphSourceEvent[], options?: ProjectEvolutionGraphOptions): EvolutionGraph;