@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
@@ -1,12 +1,15 @@
1
1
  export * from './geneHealth.js';
2
2
  export * from './exploration.js';
3
+ export * from './ucb1.js';
3
4
  export * from './epigenetics.js';
4
5
  export * from './confidence.js';
6
+ export * from './memoryGraph.js';
5
7
  export * from './capabilityCandidates.js';
6
8
  export * from './conversationSniffer.js';
7
9
  export * from './geneIntake.js';
8
10
  export * from './candidateAssembly.js';
9
11
  export * from './genePromotion.js';
12
+ export * from './publishEligibility.js';
10
13
  export * from './bans.js';
11
14
  export * from './antiDistill.js';
12
15
  export * from './orchestrator.js';
@@ -16,4 +19,6 @@ export * from './solidify.js';
16
19
  export * from './evolutionEvent.js';
17
20
  export * from './cycleEngine.js';
18
21
  export * from './strategyPresets.js';
19
- export * from './cycleFailureClassifier.js';
22
+ export * from './cycleFailureClassifier.js';
23
+ export * from './kautoValidator.js';
24
+ export * from './kautoProjection.js';
@@ -0,0 +1,41 @@
1
+ import type { KautoVerdict } from './kautoValidator.js';
2
+ /** The five coordinates of the K_auto projection key. Order is fixed for stable key encoding. */
3
+ export declare const PROJECTION_COORDINATES: readonly ["version", "claim", "scope", "runtime", "verifier"];
4
+ export type ProjectionCoordinate = (typeof PROJECTION_COORDINATES)[number];
5
+ /** The append-only event type a revocation is recorded under on the authoritative root-event stream. */
6
+ export declare const PROJECTION_REVOKED_EVENT_TYPE = "governance.projection.revoked";
7
+ /** A reinstatement withdraws a prior revocation of the SAME (coordinate, value) pair (reversibility). */
8
+ export declare const PROJECTION_REINSTATED_EVENT_TYPE = "governance.projection.reinstated";
9
+ export interface ProjectionRevocationPayload {
10
+ /** Which coordinate axis the distrust applies to. */
11
+ coordinate: ProjectionCoordinate;
12
+ /** The resolved coordinate value being distrusted (e.g. a verifier profile id or a runtime name). */
13
+ value: string;
14
+ /** Auditable reason; recorded, never interpreted. */
15
+ reason?: string;
16
+ }
17
+ /** Minimal shape of a root event this reducer reads. Compatible with events/eventSchema RootEvent. */
18
+ export interface ProjectionSourceEvent {
19
+ type: string;
20
+ payload?: unknown;
21
+ }
22
+ /** Canonical revoked-set key. A revocation is identified by its (coordinate, value) pair, never by asset id. */
23
+ export declare function revocationKey(coordinate: ProjectionCoordinate, value: string): string;
24
+ /**
25
+ * Reduce the authoritative root-event history to the CURRENT set of revoked (coordinate, value) pairs. Pure and
26
+ * order-preserving: a revocation adds its key; a matching reinstatement removes it; later events win. Recomputing
27
+ * this from the same events yields a byte-identical set (replay invariant), because the root-event stream — not a
28
+ * mutable table — is the single source of truth (mirrors ops/evolutionGraphProjection.ts).
29
+ */
30
+ export declare function projectRevocations(events: readonly ProjectionSourceEvent[]): Set<string>;
31
+ /**
32
+ * Whether a record's current projection touches any revoked coordinate value. Only records whose five coordinates
33
+ * are all machine-decidable (`verdict.inKauto`) HAVE a projection under T1, so an undecidable record is never
34
+ * governed by projection revocation here (it routes through the deployed advisory/exclusion paths instead) — and
35
+ * this function returns false for it. For a decidable record, revocation is coordinate-LOCAL: it is revoked iff
36
+ * some coordinate's resolved value equals a distrusted (coordinate, value) pair; a sibling that differs on that
37
+ * coordinate is not.
38
+ */
39
+ export declare function isProjectionRevoked(verdict: KautoVerdict, revoked: ReadonlySet<string>): boolean;
40
+ /** The specific revoked coordinate(s) a record's projection matched — for auditing WHY a projection was dropped. */
41
+ export declare function revokedCoordinatesFor(verdict: KautoVerdict, revoked: ReadonlySet<string>): ProjectionCoordinate[];
@@ -0,0 +1,95 @@
1
+ // K_auto EvidenceProjection — coordinate-level partial revocation over the five-coordinate key.
2
+ //
3
+ // WHY THIS EXISTS. The paper's headline abstraction is that an experience asset's validity is a projection
4
+ // over five coordinates — (asset version, claim, scope, runtime, verifier) — and that revocation is PARTIAL:
5
+ // distrusting one verifier profile quarantines every projection whose verifier coordinate names it, while
6
+ // leaving projections that differ on that coordinate (a sibling verifier, or a different runtime/scope)
7
+ // untouched. `algo/kautoValidator.ts` decides five-coordinate MEMBERSHIP (is each coordinate machine-decidable),
8
+ // but nothing consumed that verdict as a revocable projection: assembly collapsed it to a boolean `kautoMember`
9
+ // used only as a soft +λ selection preference (`algo/geneSelection.ts`), and the only real exclusion path
10
+ // (`algo/bans.ts`) keys on gene id × signal overlap, not on coordinates. This module realizes the missing
11
+ // consumer: an append-only revocation event names a (coordinate, value) pair, and a pure reducer projects the
12
+ // current revoked set from the authoritative root-event history so a selection guard can drop exactly the
13
+ // records whose projection touches a revoked coordinate value — coordinate-locally, recomputably, reversibly.
14
+ //
15
+ // DEPLOYMENT BOUNDARY. This is the T1 (specified) projection-revocation mechanism, exercised by a controlled
16
+ // experiment and an optional, default-OFF selection guard (`candidateAssembly` `opts.kautoProjection`). It does
17
+ // not change the deployed T2 path (signal-scoped gene-level `bannedGenesFromFailures` + soft λ preference), which
18
+ // stays byte-for-byte unchanged when the guard is absent.
19
+ /** The five coordinates of the K_auto projection key. Order is fixed for stable key encoding. */
20
+ export const PROJECTION_COORDINATES = ['version', 'claim', 'scope', 'runtime', 'verifier'];
21
+ /** The append-only event type a revocation is recorded under on the authoritative root-event stream. */
22
+ export const PROJECTION_REVOKED_EVENT_TYPE = 'governance.projection.revoked';
23
+ /** A reinstatement withdraws a prior revocation of the SAME (coordinate, value) pair (reversibility). */
24
+ export const PROJECTION_REINSTATED_EVENT_TYPE = 'governance.projection.reinstated';
25
+ const isCoordinate = (v) => typeof v === 'string' && PROJECTION_COORDINATES.includes(v);
26
+ /** Canonical revoked-set key. A revocation is identified by its (coordinate, value) pair, never by asset id. */
27
+ export function revocationKey(coordinate, value) {
28
+ return `${coordinate}:${value}`;
29
+ }
30
+ function readRevocationPayload(payload) {
31
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
32
+ return null;
33
+ const p = payload;
34
+ if (!isCoordinate(p['coordinate']))
35
+ return null;
36
+ const value = typeof p['value'] === 'string' ? p['value'].trim() : '';
37
+ if (!value)
38
+ return null;
39
+ const out = { coordinate: p['coordinate'], value };
40
+ if (typeof p['reason'] === 'string')
41
+ out.reason = p['reason'];
42
+ return out;
43
+ }
44
+ /**
45
+ * Reduce the authoritative root-event history to the CURRENT set of revoked (coordinate, value) pairs. Pure and
46
+ * order-preserving: a revocation adds its key; a matching reinstatement removes it; later events win. Recomputing
47
+ * this from the same events yields a byte-identical set (replay invariant), because the root-event stream — not a
48
+ * mutable table — is the single source of truth (mirrors ops/evolutionGraphProjection.ts).
49
+ */
50
+ export function projectRevocations(events) {
51
+ const revoked = new Set();
52
+ for (const evt of events) {
53
+ if (evt.type === PROJECTION_REVOKED_EVENT_TYPE) {
54
+ const p = readRevocationPayload(evt.payload);
55
+ if (p)
56
+ revoked.add(revocationKey(p.coordinate, p.value));
57
+ }
58
+ else if (evt.type === PROJECTION_REINSTATED_EVENT_TYPE) {
59
+ const p = readRevocationPayload(evt.payload);
60
+ if (p)
61
+ revoked.delete(revocationKey(p.coordinate, p.value));
62
+ }
63
+ }
64
+ return revoked;
65
+ }
66
+ /**
67
+ * Whether a record's current projection touches any revoked coordinate value. Only records whose five coordinates
68
+ * are all machine-decidable (`verdict.inKauto`) HAVE a projection under T1, so an undecidable record is never
69
+ * governed by projection revocation here (it routes through the deployed advisory/exclusion paths instead) — and
70
+ * this function returns false for it. For a decidable record, revocation is coordinate-LOCAL: it is revoked iff
71
+ * some coordinate's resolved value equals a distrusted (coordinate, value) pair; a sibling that differs on that
72
+ * coordinate is not.
73
+ */
74
+ export function isProjectionRevoked(verdict, revoked) {
75
+ if (!verdict.inKauto || revoked.size === 0)
76
+ return false;
77
+ for (const coordinate of PROJECTION_COORDINATES) {
78
+ const resolved = verdict.coordinates[coordinate].resolved;
79
+ if (resolved !== undefined && revoked.has(revocationKey(coordinate, resolved)))
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ /** The specific revoked coordinate(s) a record's projection matched — for auditing WHY a projection was dropped. */
85
+ export function revokedCoordinatesFor(verdict, revoked) {
86
+ if (!verdict.inKauto || revoked.size === 0)
87
+ return [];
88
+ const hits = [];
89
+ for (const coordinate of PROJECTION_COORDINATES) {
90
+ const resolved = verdict.coordinates[coordinate].resolved;
91
+ if (resolved !== undefined && revoked.has(revocationKey(coordinate, resolved)))
92
+ hits.push(coordinate);
93
+ }
94
+ return hits;
95
+ }
@@ -0,0 +1,68 @@
1
+ import type { AssetRecord } from '../assetstore/provider.js';
2
+ /** Why a coordinate could not be decided. `null` means it was decided. */
3
+ export type UndecidableReason = string | null;
4
+ export interface CoordinateVerdict {
5
+ decidable: boolean;
6
+ /** The resolved value when decidable, for auditing what the predicate actually accepted. */
7
+ resolved?: string;
8
+ reason: UndecidableReason;
9
+ }
10
+ export interface KautoVerdict {
11
+ inKauto: boolean;
12
+ coordinates: {
13
+ version: CoordinateVerdict;
14
+ claim: CoordinateVerdict;
15
+ scope: CoordinateVerdict;
16
+ runtime: CoordinateVerdict;
17
+ verifier: CoordinateVerdict;
18
+ };
19
+ /** Coordinate names that failed, most useful for aggregate reporting. */
20
+ blockedBy: string[];
21
+ }
22
+ export declare function decideVersion(rec: Readonly<Record<string, unknown>>): CoordinateVerdict;
23
+ export declare const CLAIM_PREDICATES: readonly ["source_of_truth", "requires_service", "requires_tool", "forbids_action", "output_contract", "ordering_constraint", "environment_precondition"];
24
+ export declare function decideClaim(rec: Readonly<Record<string, unknown>>): CoordinateVerdict;
25
+ export declare function decideScope(rec: Readonly<Record<string, unknown>>): CoordinateVerdict;
26
+ export interface RuntimeRegistry {
27
+ has(id: string): boolean;
28
+ }
29
+ export declare function decideRuntime(rec: Readonly<Record<string, unknown>>, registry?: RuntimeRegistry): CoordinateVerdict;
30
+ export declare function decideVerifier(rec: Readonly<Record<string, unknown>>): CoordinateVerdict;
31
+ /**
32
+ * Decide K_auto membership for one record. All five coordinates must be decidable; we evaluate every
33
+ * coordinate rather than short-circuiting, so aggregate reporting can attribute the shortfall.
34
+ */
35
+ export declare function decideKauto(record: Readonly<Record<string, unknown>> | AssetRecord, opts?: {
36
+ runtimeRegistry?: RuntimeRegistry;
37
+ }): KautoVerdict;
38
+ /**
39
+ * The PRESENCE proxy the original production query implemented, kept so the two can be reported side by
40
+ * side and the gap between them quantified rather than asserted.
41
+ */
42
+ export declare function decideKautoPresenceProxy(record: Readonly<Record<string, unknown>> | AssetRecord): boolean;
43
+ export interface KautoStructuralVerdict {
44
+ /** True when the record STRUCTURALLY carries all five dedicated coordinates in canonical shape (regardless of
45
+ * whether their CONTENT clears the strict decidability bar). */
46
+ structurallyComplete: boolean;
47
+ coordinates: {
48
+ version: boolean;
49
+ claim: boolean;
50
+ scope: boolean;
51
+ runtime: boolean;
52
+ verifier: boolean;
53
+ };
54
+ /** Dedicated-coordinate names the writer did not emit in canonical shape. */
55
+ missing: string[];
56
+ }
57
+ /**
58
+ * The SECOND track of the dual-track report (user decision: 双轨并报). Where {@link decideKauto} answers "is each
59
+ * coordinate machine-DECIDABLE" (strict, closed-vocabulary bar), this answers the strictly weaker, writer-side
60
+ * question "did the producer EMIT the dedicated coordinate field at all, in its canonical minimal shape". The two
61
+ * are reported side by side so the paper can separate two failure modes that a single number conflates:
62
+ * - the dedicated field is ABSENT (writer never emitted it) — the 0%-adoption problem this batch attacks; vs
63
+ * - the dedicated field is PRESENT but its content is not yet strict (e.g. a scope signal that is not namespaced,
64
+ * a claim predicate outside the closed vocabulary) — a content-quality gap, not an adoption gap.
65
+ * Structural presence NEVER implies decidability; it is deliberately permissive so the adoption-vs-quality gap is
66
+ * visible rather than hidden inside the single strict figure.
67
+ */
68
+ export declare function decideKautoStructural(record: Readonly<Record<string, unknown>> | AssetRecord): KautoStructuralVerdict;
@@ -0,0 +1,256 @@
1
+ const str = (v) => (typeof v === 'string' ? v.trim() : '');
2
+ const arr = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : []);
3
+ // ---------------------------------------------------------------------------------------------------
4
+ // Version. Needs an identifier that pins THIS asset's immutable version. A record-format version
5
+ // (schema_version) is shared across the whole catalogue and cannot distinguish two versions of one asset,
6
+ // so it does not satisfy the coordinate even though it is always present.
7
+ // ---------------------------------------------------------------------------------------------------
8
+ const VERSION_FIELDS = ['asset_version', 'assetVersion', 'version', 'content_hash', 'contentHash'];
9
+ /** Content-addressed asset ids are immutable version pins (sha256 of canonical gene bytes). */
10
+ const CONTENT_ADDRESSED_ASSET_ID = /^sha256:[a-f0-9]{64}$/i;
11
+ export function decideVersion(rec) {
12
+ for (const f of VERSION_FIELDS) {
13
+ const v = str(rec[f]);
14
+ if (v)
15
+ return { decidable: true, resolved: `${f}=${v}`, reason: null };
16
+ }
17
+ // asset_id is the production content-address: intake always writes it, and it changes when any
18
+ // coordinate-bearing field changes. Accepting it closes the version coordinate without inventing a
19
+ // parallel asset_version field the gep-sdk schema does not yet declare.
20
+ const assetId = str(rec['asset_id']);
21
+ if (CONTENT_ADDRESSED_ASSET_ID.test(assetId)) {
22
+ return { decidable: true, resolved: `asset_id=${assetId}`, reason: null };
23
+ }
24
+ const schemaVersion = str(rec['schema_version']);
25
+ if (schemaVersion) {
26
+ return {
27
+ decidable: false,
28
+ reason: `only schema_version=${schemaVersion} is present; that is the record format version, shared across assets, and does not identify this asset's immutable version`,
29
+ };
30
+ }
31
+ return { decidable: false, reason: 'no asset version identifier present' };
32
+ }
33
+ // ---------------------------------------------------------------------------------------------------
34
+ // Claim. The closed claim vocabulary is a set of typed claim PREDICATES about what an asset asserts,
35
+ // distinct from `category`, which records the kind of intervention. A record carrying only `category` has
36
+ // an intervention type but no machine-checkable claim, so claim-level conflict detection has nothing to
37
+ // compare.
38
+ // ---------------------------------------------------------------------------------------------------
39
+ export const CLAIM_PREDICATES = Object.freeze([
40
+ 'source_of_truth',
41
+ 'requires_service',
42
+ 'requires_tool',
43
+ 'forbids_action',
44
+ 'output_contract',
45
+ 'ordering_constraint',
46
+ 'environment_precondition',
47
+ ]);
48
+ const CLAIM_FIELDS = ['claim', 'claims', 'claim_predicate', 'claimPredicate'];
49
+ /** Extract candidate predicate STRINGS from a claim field value, accepting both the legacy string/string[] form
50
+ * and the gep-sdk 1.13.0 object form `{predicate, kind}` (single or array). Non-conforming entries are dropped. */
51
+ function claimPredicateStrings(raw) {
52
+ const fromOne = (v) => {
53
+ if (typeof v === 'string')
54
+ return v;
55
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
56
+ const p = v['predicate'];
57
+ return typeof p === 'string' ? p : null;
58
+ }
59
+ return null;
60
+ };
61
+ const items = Array.isArray(raw) ? raw : [raw];
62
+ return items.map(fromOne).filter((s) => typeof s === 'string' && s.trim().length > 0);
63
+ }
64
+ export function decideClaim(rec) {
65
+ const known = new Set(CLAIM_PREDICATES);
66
+ for (const f of CLAIM_FIELDS) {
67
+ const raw = rec[f];
68
+ if (raw === undefined || raw === null)
69
+ continue;
70
+ // A claim predicate names a closed-vocabulary term as its leading token — legacy `source_of_truth:...`
71
+ // strings and the gep-sdk `{predicate:'source_of_truth(...)'}` object form both reduce to the same token.
72
+ const candidates = claimPredicateStrings(raw);
73
+ const named = candidates.map((c) => str(c).split(/[:=(]/, 1)[0] ?? '').filter(Boolean);
74
+ const hit = named.find((n) => known.has(n));
75
+ if (hit)
76
+ return { decidable: true, resolved: hit, reason: null };
77
+ if (named.length > 0) {
78
+ return { decidable: false, reason: `claim predicate(s) ${named.join(', ')} are not in the closed claim vocabulary` };
79
+ }
80
+ }
81
+ const category = str(rec['category']);
82
+ if (category) {
83
+ return {
84
+ decidable: false,
85
+ reason: `only category=${category} is present; category is the intervention kind, not a claim predicate from the closed vocabulary`,
86
+ };
87
+ }
88
+ return { decidable: false, reason: 'no claim predicate present' };
89
+ }
90
+ // ---------------------------------------------------------------------------------------------------
91
+ // Scope. Must parse in the closed constraint language, because scope containment has to be decidable for
92
+ // set-inclusion precedence to work. We accept the forms the paper enumerates: identity sets, version
93
+ // intervals, repository-lineage prefixes, capability-set containment, time intervals, enumerated sets, and
94
+ // registered compatibility relations. A bare tag is an identity-set element ONLY when it is namespaced,
95
+ // so that two tags from different producers cannot silently collide; an unqualified free-text tag is not
96
+ // decidable against another producer's tag of the same name.
97
+ // ---------------------------------------------------------------------------------------------------
98
+ const SCOPE_TERM = /^(?:repo|lineage|pkg|lang|framework|capability|tool|model|time|env):[A-Za-z0-9._@/:-]+$/;
99
+ const VERSION_INTERVAL = /^[A-Za-z0-9._-]+@(?:[><=^~]{1,2})?\d[\w.*-]*(?:\s*-\s*\d[\w.*-]*)?$/;
100
+ /** Do all of `terms` parse in the closed constraint language? Shared by the dedicated `scope` field and the
101
+ * legacy `signals_match` proxy so the SAME containment-decidability bar governs both. */
102
+ function decideScopeTerms(terms) {
103
+ if (terms.length === 0)
104
+ return { decidable: false, reason: 'signals_match is empty; no scope predicate' };
105
+ const parses = (t) => SCOPE_TERM.test(t) || VERSION_INTERVAL.test(t);
106
+ const undecidable = terms.filter((t) => {
107
+ // A hard facet gates on a term; the term itself must parse, so strip the marker and test what remains.
108
+ const bare = t.startsWith('required:') ? t.slice('required:'.length) : t;
109
+ return !parses(bare);
110
+ });
111
+ if (undecidable.length === 0) {
112
+ return { decidable: true, resolved: terms.join(','), reason: null };
113
+ }
114
+ return {
115
+ decidable: false,
116
+ reason: `${undecidable.length} of ${terms.length} scope term(s) do not parse in the closed constraint language (e.g. ${undecidable.slice(0, 3).join(', ')}); containment against another scope is therefore undecidable`,
117
+ };
118
+ }
119
+ export function decideScope(rec) {
120
+ // Dedicated coordinate first (gep-sdk 1.13.0 `scope: {signals, predicate?}`). Its `signals` list carries the
121
+ // scope terms; a `predicate` is metadata for the reader, not a substitute for parseable signals. When the
122
+ // dedicated field is present but its signals are empty we fall through to the proxy, so a malformed dedicated
123
+ // field never DEMOTES a record that the legacy path would have admitted.
124
+ const scope = rec['scope'];
125
+ if (scope && typeof scope === 'object' && !Array.isArray(scope)) {
126
+ const signals = arr(scope['signals']).map((t) => str(t)).filter(Boolean);
127
+ if (signals.length > 0)
128
+ return decideScopeTerms(signals);
129
+ }
130
+ const terms = arr(rec['signals_match']).map((t) => str(t)).filter(Boolean);
131
+ return decideScopeTerms(terms);
132
+ }
133
+ /** Apply the runtime bar to one resolved identity string, `src` naming where it came from for the reason text. */
134
+ function decideRuntimeName(name, src, registry) {
135
+ if (!name)
136
+ return { decidable: false, reason: `no ${src} recorded` };
137
+ if (name.toLowerCase() === 'unknown') {
138
+ return { decidable: false, reason: `${src} is the \`unknown\` sentinel, which denotes a runtime that could not be detected` };
139
+ }
140
+ if (registry && !registry.has(name)) {
141
+ return { decidable: false, reason: `${src}=${name} does not resolve to a registered runtime profile` };
142
+ }
143
+ return { decidable: true, resolved: name, reason: null };
144
+ }
145
+ export function decideRuntime(rec, registry) {
146
+ // Dedicated coordinate first (gep-sdk 1.13.0 `runtime_profile: {runtime, env_class?}`). Same registry bar as
147
+ // the proxy. A present-but-empty runtime_profile falls through so it never demotes a record model_name admits.
148
+ const rp = rec['runtime_profile'];
149
+ if (rp && typeof rp === 'object' && !Array.isArray(rp)) {
150
+ const runtime = str(rp['runtime']);
151
+ if (runtime)
152
+ return decideRuntimeName(runtime, 'runtime_profile.runtime', registry);
153
+ }
154
+ return decideRuntimeName(str(rec['model_name']), 'model_name', registry);
155
+ }
156
+ // ---------------------------------------------------------------------------------------------------
157
+ // Verifier. Must clear a minimum semantic bar: it has to exercise the asset's own claim, not merely prove
158
+ // that an interpreter exists. We reject the known placeholder shapes rather than counting any non-empty
159
+ // validation array. This is the coordinate where a permissive predicate would be most misleading, because
160
+ // a nominal verifier is worse than an absent one: absence is honest.
161
+ // ---------------------------------------------------------------------------------------------------
162
+ const PLACEHOLDER_VERIFIER = /^(?:node|python3?|ruby|deno|bun|go|java|npm|pnpm|yarn)\s+(?:--?v(?:ersion)?|-V)\s*$/i;
163
+ const TRIVIAL_VERIFIER = /^(?:true|:|echo\b.*|exit\s+0)\s*$/i;
164
+ const isSubstantiveVerifier = (c) => !PLACEHOLDER_VERIFIER.test(c) && !TRIVIAL_VERIFIER.test(c);
165
+ export function decideVerifier(rec) {
166
+ // Dedicated coordinate first (gep-sdk 1.13.0 `verifier_profile: {verifier, decision?}`). The verifier IDENTITY
167
+ // string must clear the SAME minimum semantic bar as a legacy validation command — a `node --version` identity
168
+ // is as hollow here as it is there. A present-but-empty verifier_profile falls through to the proxy.
169
+ const vp = rec['verifier_profile'];
170
+ if (vp && typeof vp === 'object' && !Array.isArray(vp)) {
171
+ const verifier = str(vp['verifier']);
172
+ if (verifier) {
173
+ if (isSubstantiveVerifier(verifier))
174
+ return { decidable: true, resolved: verifier, reason: null };
175
+ return {
176
+ decidable: false,
177
+ reason: `verifier_profile.verifier=${verifier} is a placeholder; it establishes that an interpreter exists, not that the asset's claim holds`,
178
+ };
179
+ }
180
+ }
181
+ const cmds = arr(rec['validation']).map((c) => str(c)).filter(Boolean);
182
+ if (cmds.length === 0)
183
+ return { decidable: false, reason: 'validation is empty; no verifier profile' };
184
+ const substantive = cmds.filter(isSubstantiveVerifier);
185
+ if (substantive.length === 0) {
186
+ return {
187
+ decidable: false,
188
+ reason: `all ${cmds.length} validation entr${cmds.length === 1 ? 'y is' : 'ies are'} placeholders (e.g. ${cmds[0]}); they establish that an interpreter exists, not that the asset's claim holds`,
189
+ };
190
+ }
191
+ return { decidable: true, resolved: substantive[0], reason: null };
192
+ }
193
+ /**
194
+ * Decide K_auto membership for one record. All five coordinates must be decidable; we evaluate every
195
+ * coordinate rather than short-circuiting, so aggregate reporting can attribute the shortfall.
196
+ */
197
+ export function decideKauto(record, opts) {
198
+ const rec = record;
199
+ const coordinates = {
200
+ version: decideVersion(rec),
201
+ claim: decideClaim(rec),
202
+ scope: decideScope(rec),
203
+ runtime: decideRuntime(rec, opts?.runtimeRegistry),
204
+ verifier: decideVerifier(rec),
205
+ };
206
+ const blockedBy = Object.entries(coordinates).filter(([, v]) => !v.decidable).map(([k]) => k);
207
+ return { inKauto: blockedBy.length === 0, coordinates, blockedBy };
208
+ }
209
+ /**
210
+ * The PRESENCE proxy the original production query implemented, kept so the two can be reported side by
211
+ * side and the gap between them quantified rather than asserted.
212
+ */
213
+ export function decideKautoPresenceProxy(record) {
214
+ const rec = record;
215
+ const CATEGORIES = new Set(['repair', 'optimize', 'innovate', 'explore']);
216
+ return Boolean(str(rec['schema_version'])
217
+ && CATEGORIES.has(str(rec['category']))
218
+ && arr(rec['signals_match']).length > 0
219
+ && str(rec['model_name'])
220
+ && arr(rec['validation']).length > 0);
221
+ }
222
+ /**
223
+ * The SECOND track of the dual-track report (user decision: 双轨并报). Where {@link decideKauto} answers "is each
224
+ * coordinate machine-DECIDABLE" (strict, closed-vocabulary bar), this answers the strictly weaker, writer-side
225
+ * question "did the producer EMIT the dedicated coordinate field at all, in its canonical minimal shape". The two
226
+ * are reported side by side so the paper can separate two failure modes that a single number conflates:
227
+ * - the dedicated field is ABSENT (writer never emitted it) — the 0%-adoption problem this batch attacks; vs
228
+ * - the dedicated field is PRESENT but its content is not yet strict (e.g. a scope signal that is not namespaced,
229
+ * a claim predicate outside the closed vocabulary) — a content-quality gap, not an adoption gap.
230
+ * Structural presence NEVER implies decidability; it is deliberately permissive so the adoption-vs-quality gap is
231
+ * visible rather than hidden inside the single strict figure.
232
+ */
233
+ export function decideKautoStructural(record) {
234
+ const rec = record;
235
+ // version has no separate dedicated shape — asset_version/content_hash IS the coordinate, so structural
236
+ // presence coincides with strict decidability here (there is no semantic bar beyond "an immutable id exists").
237
+ const version = decideVersion(rec).decidable;
238
+ // claim: `claims: [{predicate}]` with at least one non-empty predicate string (closed-vocabulary check is the
239
+ // strict track's job, not this one).
240
+ const claim = claimPredicateStrings(rec['claims']).length > 0;
241
+ // scope: `scope: {signals: [...]}` with at least one non-empty signal (parseability is the strict track's job).
242
+ const scopeRaw = rec['scope'];
243
+ const scope = Boolean(scopeRaw && typeof scopeRaw === 'object' && !Array.isArray(scopeRaw)
244
+ && arr(scopeRaw['signals']).map((t) => str(t)).filter(Boolean).length > 0);
245
+ // runtime: `runtime_profile: {runtime}` with a non-empty runtime string (registry/sentinel is the strict track).
246
+ const rpRaw = rec['runtime_profile'];
247
+ const runtime = Boolean(rpRaw && typeof rpRaw === 'object' && !Array.isArray(rpRaw)
248
+ && str(rpRaw['runtime']));
249
+ // verifier: `verifier_profile: {verifier}` with a non-empty verifier string (placeholder bar is the strict track).
250
+ const vpRaw = rec['verifier_profile'];
251
+ const verifier = Boolean(vpRaw && typeof vpRaw === 'object' && !Array.isArray(vpRaw)
252
+ && str(vpRaw['verifier']));
253
+ const coordinates = { version, claim, scope, runtime, verifier };
254
+ const missing = Object.entries(coordinates).filter(([, present]) => !present).map(([k]) => k);
255
+ return { structurallyComplete: missing.length === 0, coordinates, missing };
256
+ }
@@ -0,0 +1,62 @@
1
+ export type MemoryGraphOutcomeStatus = 'success' | 'failed';
2
+ export interface MemoryGraphOutcomeRecord {
3
+ version: 2;
4
+ kind: 'outcome';
5
+ provenance: 'v2_local' | 'v1_import';
6
+ workspaceScope: string;
7
+ userScope: string;
8
+ signalFingerprint: string;
9
+ signals: readonly string[];
10
+ geneId: string;
11
+ status: MemoryGraphOutcomeStatus;
12
+ score: number;
13
+ at: string;
14
+ /** Stable local-source identity used to make one-time imports crash-retry safe. */
15
+ sourceFingerprint?: string;
16
+ successCount?: number;
17
+ failCount?: number;
18
+ }
19
+ export interface MemoryGraphGeneEvidence {
20
+ geneId: string;
21
+ boost: number;
22
+ expectedSuccess: number;
23
+ successCount: number;
24
+ failCount: number;
25
+ attempts: number;
26
+ similarity: number;
27
+ lastAt: string;
28
+ }
29
+ export interface MemoryGraphDiagnostics {
30
+ bytesRead: number;
31
+ recordsRead: number;
32
+ corruptLines: number;
33
+ oversizedLines: number;
34
+ scopeRejected: number;
35
+ provenanceRejected: number;
36
+ truncated: boolean;
37
+ recovery: 'healthy' | 'degraded' | 'recovered' | 'empty';
38
+ /** The graph was healthy enough to address, but another process held its maintenance lock. */
39
+ busy?: boolean;
40
+ }
41
+ export interface MemoryGraphAdvice {
42
+ genes: readonly MemoryGraphGeneEvidence[];
43
+ diagnostics: MemoryGraphDiagnostics;
44
+ }
45
+ export interface MemoryGraphQueryInput {
46
+ workspace: string;
47
+ signals: readonly string[];
48
+ }
49
+ export interface MemoryGraphRecordInput extends MemoryGraphQueryInput {
50
+ geneId: string;
51
+ status: MemoryGraphOutcomeStatus;
52
+ score: number;
53
+ at: string;
54
+ }
55
+ export interface MemoryGraphProvider {
56
+ query(input: MemoryGraphQueryInput): Promise<MemoryGraphAdvice> | MemoryGraphAdvice;
57
+ recordOutcome(input: MemoryGraphRecordInput): Promise<void> | void;
58
+ }
59
+ export declare function normalizeMemorySignals(signals: readonly string[]): string[];
60
+ export declare function memorySignalFingerprint(signals: readonly string[]): string;
61
+ export declare function safeMemoryGeneId(value: string): string;
62
+ export declare function deriveMemoryGraphAdvice(records: readonly MemoryGraphOutcomeRecord[], signals: readonly string[], nowMs: number, diagnostics: MemoryGraphDiagnostics): MemoryGraphAdvice;
@@ -0,0 +1,86 @@
1
+ const MAX_SIGNALS = 32;
2
+ const MAX_SIGNAL_CHARS = 120;
3
+ const MAX_GENE_CHARS = 240;
4
+ const MIN_SIMILARITY = 0.34;
5
+ const HALF_LIFE_DAYS = 30;
6
+ export function normalizeMemorySignals(signals) {
7
+ return [...new Set(signals
8
+ .filter((signal) => typeof signal === 'string')
9
+ .map((signal) => signal.trim().toLowerCase().slice(0, MAX_SIGNAL_CHARS))
10
+ .filter(Boolean))]
11
+ .sort()
12
+ .slice(0, MAX_SIGNALS);
13
+ }
14
+ export function memorySignalFingerprint(signals) {
15
+ return normalizeMemorySignals(signals).join('|') || '(none)';
16
+ }
17
+ export function safeMemoryGeneId(value) {
18
+ return value.trim().slice(0, MAX_GENE_CHARS);
19
+ }
20
+ export function deriveMemoryGraphAdvice(records, signals, nowMs, diagnostics) {
21
+ const currentSignals = normalizeMemorySignals(signals);
22
+ const aggregates = new Map();
23
+ for (const record of records) {
24
+ const similarity = jaccard(currentSignals, record.signals);
25
+ if (similarity < MIN_SIMILARITY)
26
+ continue;
27
+ const successCount = boundedCount(record.successCount ?? (record.status === 'success' ? 1 : 0));
28
+ const failCount = boundedCount(record.failCount ?? (record.status === 'failed' ? 1 : 0));
29
+ const total = successCount + failCount;
30
+ if (total === 0)
31
+ continue;
32
+ const decay = decayWeight(record.at, nowMs);
33
+ const weight = similarity * decay;
34
+ const current = aggregates.get(record.geneId) ?? { success: 0, fail: 0, weightedSimilarity: 0, weight: 0, lastAt: record.at };
35
+ current.success += successCount * weight;
36
+ current.fail += failCount * weight;
37
+ current.weightedSimilarity += similarity * total;
38
+ current.weight += total;
39
+ if (Date.parse(record.at) > Date.parse(current.lastAt))
40
+ current.lastAt = record.at;
41
+ aggregates.set(record.geneId, current);
42
+ }
43
+ const genes = [];
44
+ for (const [geneId, aggregate] of aggregates) {
45
+ const successCount = Math.round(aggregate.success);
46
+ const failCount = Math.round(aggregate.fail);
47
+ const attempts = successCount + failCount;
48
+ if (attempts === 0)
49
+ continue;
50
+ const expectedSuccess = (aggregate.success + 1) / (aggregate.success + aggregate.fail + 2);
51
+ const similarity = aggregate.weight > 0 ? Math.min(1, aggregate.weightedSimilarity / aggregate.weight) : 0;
52
+ const confidence = Math.min(1, Math.log2(attempts + 1) / 3);
53
+ const boost = clamp((expectedSuccess - 0.5) * 2 * similarity * confidence, -1, 1);
54
+ genes.push({ geneId, boost, expectedSuccess, successCount, failCount, attempts, similarity, lastAt: aggregate.lastAt });
55
+ }
56
+ genes.sort((left, right) => Math.abs(right.boost) - Math.abs(left.boost) || right.attempts - left.attempts || left.geneId.localeCompare(right.geneId));
57
+ return { genes: genes.slice(0, 64), diagnostics };
58
+ }
59
+ function boundedCount(value) {
60
+ if (!Number.isFinite(value) || value <= 0)
61
+ return 0;
62
+ return Math.min(1_000_000, Math.floor(value));
63
+ }
64
+ function jaccard(left, right) {
65
+ const a = new Set(normalizeMemorySignals(left));
66
+ const b = new Set(normalizeMemorySignals(right));
67
+ if (a.size === 0 && b.size === 0)
68
+ return 1;
69
+ if (a.size === 0 || b.size === 0)
70
+ return 0;
71
+ let intersection = 0;
72
+ for (const value of a)
73
+ if (b.has(value))
74
+ intersection += 1;
75
+ return intersection / (a.size + b.size - intersection);
76
+ }
77
+ function decayWeight(at, nowMs) {
78
+ const timestamp = Date.parse(at);
79
+ if (!Number.isFinite(timestamp))
80
+ return 0;
81
+ const ageDays = Math.max(0, nowMs - timestamp) / 86_400_000;
82
+ return Math.pow(0.5, ageDays / HALF_LIFE_DAYS);
83
+ }
84
+ function clamp(value, min, max) {
85
+ return Math.max(min, Math.min(max, value));
86
+ }