@evomap/evolver-core 2.0.0-beta.19 → 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 (160) 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 +143 -20
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/cycleEngine.d.ts +40 -5
  8. package/dist/algo/cycleEngine.js +277 -37
  9. package/dist/algo/evolutionEvent.d.ts +26 -0
  10. package/dist/algo/evolutionEvent.js +32 -0
  11. package/dist/algo/exploration.d.ts +7 -0
  12. package/dist/algo/exploration.js +16 -3
  13. package/dist/algo/geneHealth.d.ts +34 -1
  14. package/dist/algo/geneHealth.js +42 -0
  15. package/dist/algo/geneIntake.d.ts +46 -1
  16. package/dist/algo/geneIntake.js +121 -10
  17. package/dist/algo/geneSelection.d.ts +115 -6
  18. package/dist/algo/geneSelection.js +459 -32
  19. package/dist/algo/index.d.ts +4 -1
  20. package/dist/algo/index.js +4 -1
  21. package/dist/algo/kautoProjection.d.ts +41 -0
  22. package/dist/algo/kautoProjection.js +95 -0
  23. package/dist/algo/kautoValidator.d.ts +68 -0
  24. package/dist/algo/kautoValidator.js +256 -0
  25. package/dist/algo/orchestrator.d.ts +14 -1
  26. package/dist/algo/orchestrator.js +9 -2
  27. package/dist/algo/solidify.d.ts +11 -2
  28. package/dist/algo/solidify.js +37 -7
  29. package/dist/algo/ucb1.d.ts +53 -0
  30. package/dist/algo/ucb1.js +156 -0
  31. package/dist/assetrepair/hubRejection.d.ts +12 -0
  32. package/dist/assetrepair/hubRejection.js +109 -0
  33. package/dist/assetrepair/index.d.ts +2 -0
  34. package/dist/assetrepair/index.js +2 -0
  35. package/dist/assetrepair/repair.d.ts +33 -0
  36. package/dist/assetrepair/repair.js +155 -0
  37. package/dist/assetstore/assetSidecarRecords.js +5 -1
  38. package/dist/assetstore/assetStoreHealth.d.ts +2 -3
  39. package/dist/assetstore/assetStoreHealth.js +4 -10
  40. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  41. package/dist/assetstore/foreignJsonlSource.js +150 -0
  42. package/dist/assetstore/index.d.ts +3 -0
  43. package/dist/assetstore/index.js +3 -0
  44. package/dist/assetstore/learningHistory.js +3 -3
  45. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  46. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  47. package/dist/assetstore/localJsonl.d.ts +8 -0
  48. package/dist/assetstore/localJsonl.js +128 -3
  49. package/dist/assetstore/provenance.d.ts +38 -3
  50. package/dist/assetstore/provenance.js +103 -45
  51. package/dist/assetstore/provider.d.ts +4 -0
  52. package/dist/assetstore/reviewFilter.d.ts +19 -1
  53. package/dist/assetstore/reviewFilter.js +36 -0
  54. package/dist/assetstore/unionReadStore.d.ts +25 -0
  55. package/dist/assetstore/unionReadStore.js +119 -0
  56. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  57. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  58. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  59. package/dist/benchmark/antiGeneRollout.js +4 -3
  60. package/dist/benchmark/index.d.ts +2 -1
  61. package/dist/benchmark/index.js +2 -1
  62. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  63. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  64. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  65. package/dist/bootstrap/envFingerprint.js +5 -0
  66. package/dist/bootstrap/index.d.ts +2 -1
  67. package/dist/bootstrap/index.js +2 -1
  68. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  69. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  70. package/dist/bootstrap/v1EnvCompat.d.ts +3 -0
  71. package/dist/bootstrap/v1EnvCompat.js +48 -1
  72. package/dist/events/eventArchive.d.ts +2 -0
  73. package/dist/events/eventArchive.js +13 -3
  74. package/dist/events/eventSchema.d.ts +7 -7
  75. package/dist/events/eventStore.d.ts +2 -0
  76. package/dist/events/eventStore.js +5 -1
  77. package/dist/events/ingest.d.ts +2 -1
  78. package/dist/events/ingest.js +6 -0
  79. package/dist/exec/autoExec.d.ts +30 -3
  80. package/dist/exec/autoExec.js +294 -29
  81. package/dist/exec/autonomousCycle.d.ts +7 -0
  82. package/dist/exec/autonomousCycle.js +2 -0
  83. package/dist/exec/claudeBridge.d.ts +28 -6
  84. package/dist/exec/claudeBridge.js +411 -258
  85. package/dist/exec/executionBinding.d.ts +414 -0
  86. package/dist/exec/executionBinding.js +588 -0
  87. package/dist/exec/index.d.ts +1 -0
  88. package/dist/exec/index.js +1 -0
  89. package/dist/exec/proofOfWork.d.ts +1 -1
  90. package/dist/exec/proofOfWork.js +2 -2
  91. package/dist/exec/runnerRegistry.d.ts +5 -0
  92. package/dist/exec/runnerRegistry.js +54 -14
  93. package/dist/hub/capability.d.ts +40 -1
  94. package/dist/hub/conversationDistiller.d.ts +19 -0
  95. package/dist/hub/conversationDistiller.js +115 -37
  96. package/dist/hub/fake.d.ts +1 -0
  97. package/dist/hub/fake.js +1 -0
  98. package/dist/hub/index.d.ts +1 -0
  99. package/dist/hub/index.js +1 -0
  100. package/dist/hub/recipeCompose.d.ts +27 -0
  101. package/dist/hub/recipeCompose.js +90 -0
  102. package/dist/hub/sanitize.js +119 -5
  103. package/dist/index.d.ts +2 -0
  104. package/dist/index.js +2 -0
  105. package/dist/mailbox/ipcServer.d.ts +2 -0
  106. package/dist/mailbox/ipcServer.js +20 -0
  107. package/dist/mailbox/store.d.ts +52 -2
  108. package/dist/mailbox/store.js +546 -81
  109. package/dist/modelCompatibility.d.ts +164 -0
  110. package/dist/modelCompatibility.js +309 -0
  111. package/dist/observers/valueDigestObserver.d.ts +9 -0
  112. package/dist/observers/valueDigestObserver.js +35 -2
  113. package/dist/ops/cleanup.js +1 -1
  114. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  115. package/dist/ops/evolutionGraphProjection.js +315 -0
  116. package/dist/ops/index.d.ts +2 -1
  117. package/dist/ops/index.js +2 -1
  118. package/dist/ops/selfUpdate.d.ts +8 -0
  119. package/dist/ops/selfUpdate.js +24 -8
  120. package/dist/ops/valueOutreach.d.ts +3 -1
  121. package/dist/ops/valueOutreach.js +5 -1
  122. package/dist/personality/schema.d.ts +18 -18
  123. package/dist/schema/evolutionGraph.d.ts +784 -0
  124. package/dist/schema/evolutionGraph.js +187 -0
  125. package/dist/schema/index.d.ts +1 -0
  126. package/dist/schema/index.js +1 -0
  127. package/dist/schema/proofOfWork.d.ts +125 -6
  128. package/dist/schema/proofOfWork.js +102 -4
  129. package/dist/shadow/shadowHub.js +1 -0
  130. package/dist/signals/expand.d.ts +15 -1
  131. package/dist/signals/expand.js +148 -0
  132. package/dist/signals/index.d.ts +3 -1
  133. package/dist/signals/index.js +3 -1
  134. package/dist/signals/scopeVocabulary.d.ts +75 -0
  135. package/dist/signals/scopeVocabulary.js +91 -0
  136. package/dist/signals/taskDomain.d.ts +22 -0
  137. package/dist/signals/taskDomain.js +43 -0
  138. package/dist/trace/learningTrace.d.ts +7 -0
  139. package/dist/trace/learningTrace.js +22 -0
  140. package/dist/trace/trajectory.d.ts +8 -0
  141. package/dist/trace/trajectory.js +14 -2
  142. package/dist/util/fileLock.d.ts +92 -5
  143. package/dist/util/fileLock.js +809 -70
  144. package/dist/util/index.d.ts +2 -1
  145. package/dist/util/index.js +1 -1
  146. package/dist/verify/index.d.ts +2 -1
  147. package/dist/verify/index.js +1 -1
  148. package/dist/verify/sandboxRunner.d.ts +2 -0
  149. package/dist/verify/sandboxRunner.js +154 -10
  150. package/dist/verify/sandboxedValidation.d.ts +7 -2
  151. package/dist/verify/sandboxedValidation.js +102 -24
  152. package/dist/verify/validation.d.ts +24 -3
  153. package/dist/verify/validation.js +141 -24
  154. package/dist/wire/geneHints.d.ts +88 -16
  155. package/dist/wire/geneHints.js +124 -15
  156. package/dist/wire/index.d.ts +8 -3
  157. package/dist/wire/index.js +2 -2
  158. package/dist/wire/schemaGate.d.ts +21 -0
  159. package/dist/wire/schemaGate.js +119 -10
  160. package/package.json +5 -4
@@ -1,5 +1,5 @@
1
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_publish_feishu_doc","category":"innovate","signals_match":["publish_markdown_to_feishu","create_feishu_doc","export_report_to_feishu","publish_results_to_lark_doc","feishu_doc","lark_doc"],"preconditions":["lark-cli is installed and available on PATH.","lark-cli auth status reports a ready user or bot identity."],"strategy":["Run lark-cli doctor and require an ok result before publishing.","Use the Docs v2 API and avoid deprecated Docs v1 flags.","Write long Markdown content to a temporary file and pass it as an @file input.","Create or update the document with the intended user or bot identity, then return the document URL.","Never overwrite local lark-cli credential files while preparing the document."],"constraints":{"max_files":2,"forbidden_paths":[".git","node_modules","~/.lark-cli/config.json"]},"validation":["node --version"],"summary":"Publish Markdown content as a Feishu/Lark document through the official CLI, using file-backed content and Docs v2.","asset_id":"sha256:4cdcf2b2a30bcb29a3310360c3cb16c7fc7d427d23a43c836ffa5076cfe3c041"}
2
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_conventional_git_commit","category":"optimize","signals_match":["git_commit","create_commit","commit_changes","conventional_commit","stage_and_commit","write_commit_message"],"preconditions":["A git repository has staged or unstaged changes that should become one logical commit."],"strategy":["Inspect git status plus the staged or unstaged diff before choosing a commit shape.","Pick a Conventional Commits type and optional scope from the actual changed files.","Stage only the intended logical files and never stage credentials or unrelated changes.","Write a present-tense imperative subject under 72 characters, with body or footer when needed.","Do not amend, force-push, hard-reset, or bypass hooks without explicit operator approval."],"constraints":{"max_files":50,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Create a focused Conventional Commits-style git commit from the real diff while avoiding secrets and unrelated changes.","asset_id":"sha256:8f0acd8af3d0a94d6f50e7fe20d96ff39dc982022b851b204eddce47e177f467"}
3
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_poll_bugbot_review","category":"optimize","signals_match":["poll_bugbot","bugbot_review","wait_for_ci_review","pr_review_gate","cursor_bugbot","pr_opened"],"preconditions":["An open GitHub PR exists and Cursor Bugbot is expected to review it."],"strategy":["Poll the Cursor Bugbot check by name until it reaches a terminal conclusion or the operator timeout is reached.","Treat SUCCESS as mergeable only when required checks are green and there are no unresolved Bugbot comments.","Treat NEUTRAL as not passed; fetch and summarize Cursor Bugbot inline comments for the operator.","On FAILURE or ACTION_REQUIRED, surface the findings and do not merge automatically.","Only squash-merge when the operator explicitly asked for merge and the review gate is clean."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Wait for Cursor Bugbot on a GitHub PR and gate merge decisions on the actual check conclusion and comments.","asset_id":"sha256:87737bf9d2702667c31e336fc79096f4da7f12a84baae25e827ad62b4a08d332"}
4
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_gateway_timeout_recovery","category":"repair","signals_match":["gateway_timeout","upstream_timeout","http_524","http_504","request_timed_out","retry_on_timeout"],"preconditions":["A tool call, fetch, subagent request, or long command returned a gateway-class timeout."],"strategy":["Treat gateway timeout as transient or size-driven until one bounded recovery attempt proves otherwise.","Retry the exact same operation once, then stop retrying the monolithic call.","If the retry also times out, split the work by file, directory, endpoint, record, section, or time window.","Run independent slices in parallel when safe and merge the results.","Apply the same decomposition recursively only to the slice that still times out."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Recover from gateway or upstream timeouts by retrying once, then decomposing the work instead of looping the same large call.","asset_id":"sha256:fe6820477e47a8eb848ecfaa33cdb4242d9af77b78fac21fa1d716244984615c"}
5
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_github_webhook_listener","category":"innovate","signals_match":["github_webhook_listener","bugbot_webhook","passive_pr_notifications","notify_when_bugbot_finishes","webhook_tunnel","github_pr_inbox"],"preconditions":["A developer machine can run a local listener and an outbound tunnel for GitHub webhook delivery."],"strategy":["Use an HMAC-validated local listener for GitHub webhook payloads and deduplicate delivery ids.","Expose the listener through an outbound-only tunnel rather than an inbound public port.","Write vetted notifications into a local inbox with restrictive permissions; never execute webhook payloads.","On session start, re-fetch PR state through GitHub before surfacing any inbox item.","Rotate webhook secrets periodically and immediately after suspected exposure."],"constraints":{"max_files":20,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Deploy a local HMAC-validated GitHub webhook listener that turns PR and Bugbot events into safe operator notifications.","asset_id":"sha256:a64e8d0a91cebd54290451527987230572d8a5bfe90b6ca73cd3368fd3d1f689"}
1
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_publish_feishu_doc","category":"innovate","signals_match":["publish_markdown_to_feishu","create_feishu_doc","export_report_to_feishu","publish_results_to_lark_doc","feishu_doc","lark_doc"],"preconditions":["lark-cli is installed and available on PATH.","lark-cli auth status reports a ready user or bot identity."],"strategy":["Run lark-cli doctor and require an ok result before publishing.","Use the Docs v2 API and avoid deprecated Docs v1 flags.","Write long Markdown content to a temporary file and pass it as an @file input.","Create or update the document with the intended user or bot identity, then return the document URL.","Never overwrite local lark-cli credential files while preparing the document."],"constraints":{"max_files":2,"forbidden_paths":[".git","node_modules","~/.lark-cli/config.json"]},"validation":["node --version"],"summary":"Publish Markdown content as a Feishu/Lark document through the official CLI, using file-backed content and Docs v2.","asset_id":"sha256:7a8f6c4217ab2594b1412e4db1eccd4c3ac30d3a689054b956c0da6477c154bd"}
2
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_conventional_git_commit","category":"optimize","signals_match":["git_commit","create_commit","commit_changes","conventional_commit","stage_and_commit","write_commit_message"],"preconditions":["A git repository has staged or unstaged changes that should become one logical commit."],"strategy":["Inspect git status plus the staged or unstaged diff before choosing a commit shape.","Pick a Conventional Commits type and optional scope from the actual changed files.","Stage only the intended logical files and never stage credentials or unrelated changes.","Write a present-tense imperative subject under 72 characters, with body or footer when needed.","Do not amend, force-push, hard-reset, or bypass hooks without explicit operator approval."],"constraints":{"max_files":50,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Create a focused Conventional Commits-style git commit from the real diff while avoiding secrets and unrelated changes.","asset_id":"sha256:46b34569c273b60d84501764ae20e15c6b7eb52a743db1a393e0d52272212574"}
3
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_poll_bugbot_review","category":"optimize","signals_match":["poll_bugbot","bugbot_review","wait_for_ci_review","pr_review_gate","cursor_bugbot","pr_opened"],"preconditions":["An open GitHub PR exists and Cursor Bugbot is expected to review it."],"strategy":["Poll the Cursor Bugbot check by name until it reaches a terminal conclusion or the operator timeout is reached.","Treat SUCCESS as mergeable only when required checks are green and there are no unresolved Bugbot comments.","Treat NEUTRAL as not passed; fetch and summarize Cursor Bugbot inline comments for the operator.","On FAILURE or ACTION_REQUIRED, surface the findings and do not merge automatically.","Only squash-merge when the operator explicitly asked for merge and the review gate is clean."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Wait for Cursor Bugbot on a GitHub PR and gate merge decisions on the actual check conclusion and comments.","asset_id":"sha256:e137a220e84e08c784f438186c100d785c45bcc3d327082fc5661f5a76cb8de1"}
4
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_gateway_timeout_recovery","category":"repair","signals_match":["gateway_timeout","upstream_timeout","http_524","http_504","request_timed_out","retry_on_timeout"],"preconditions":["A tool call, fetch, subagent request, or long command returned a gateway-class timeout."],"strategy":["Treat gateway timeout as transient or size-driven until one bounded recovery attempt proves otherwise.","Retry the exact same operation once, then stop retrying the monolithic call.","If the retry also times out, split the work by file, directory, endpoint, record, section, or time window.","Run independent slices in parallel when safe and merge the results.","Apply the same decomposition recursively only to the slice that still times out."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Recover from gateway or upstream timeouts by retrying once, then decomposing the work instead of looping the same large call.","asset_id":"sha256:72177d7186fe623de4d9fcc440e1e6d36055467e7f86b248a97318db04be26e5"}
5
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_github_webhook_listener","category":"innovate","signals_match":["github_webhook_listener","bugbot_webhook","passive_pr_notifications","notify_when_bugbot_finishes","webhook_tunnel","github_pr_inbox"],"preconditions":["A developer machine can run a local listener and an outbound tunnel for GitHub webhook delivery."],"strategy":["Use an HMAC-validated local listener for GitHub webhook payloads and deduplicate delivery ids.","Expose the listener through an outbound-only tunnel rather than an inbound public port.","Write vetted notifications into a local inbox with restrictive permissions; never execute webhook payloads.","On session start, re-fetch PR state through GitHub before surfacing any inbox item.","Rotate webhook secrets periodically and immediately after suspected exposure."],"constraints":{"max_files":20,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Deploy a local HMAC-validated GitHub webhook listener that turns PR and Bugbot events into safe operator notifications.","asset_id":"sha256:dc6f0573c360e8e31b41142e3e4d782dcfb848a2256a4187079c3cea7edba584"}
@@ -1,6 +1,15 @@
1
1
  export interface FailedCapsuleRef {
2
2
  gene?: string;
3
3
  trigger?: readonly string[];
4
+ /** Stable logical FailureRecord id; duplicate deliveries of the same record count once. */
5
+ failureId?: string;
6
+ /** Root attempt shared by automatic retries; sharing this id makes retry fan-out count once. */
7
+ rootAttemptId?: string;
8
+ /** Execution/run id; duplicate deliveries of one execution count once even if other metadata diverges. */
9
+ executionId?: string;
10
+ /** Optional verifier+artifact digest pair refines legacy evidence when no direct failure/root/execution id exists. */
11
+ verifierDigest?: string;
12
+ artifactDigest?: string;
4
13
  }
5
- /** Gene ids with >=2 failed capsules whose trigger covers >=60% of the current signals. */
14
+ /** Gene ids with >=2 independent failed capsules whose trigger covers >=60% of the current signals. */
6
15
  export declare function bannedGenesFromFailures(failures: readonly FailedCapsuleRef[], signals: readonly string[]): Set<string>;
package/dist/algo/bans.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Ban genes that have repeatedly failed on the SAME signals (ported from v1 selector.js
2
2
  // banGenesFromFailedCapsules). Complements epigenetic suppression (which is per-environment): this is a
3
- // hard, signal-overlap-based exclusion — a gene with >=2 failed capsules whose trigger covers the current
4
- // signals is dropped from the candidate set, so a strategy that keeps failing here is not resurrected.
3
+ // hard, signal-overlap-based exclusion — but only when the failures carry counted identity quality. Legacy raw
4
+ // or partial-digest rows remain audit/advisory evidence and cannot satisfy the automatic tau=2 quarantine.
5
5
  const BAN_THRESHOLD = 2;
6
6
  const OVERLAP_MIN = 0.6;
7
7
  /** Fraction of the current signals covered by a failure's trigger (how much it is "the same problem"). */
@@ -15,7 +15,52 @@ function signalCoverage(signals, trigger) {
15
15
  hit += 1;
16
16
  return hit / signals.length;
17
17
  }
18
- /** Gene ids with >=2 failed capsules whose trigger covers >=60% of the current signals. */
18
+ function nonEmpty(s) {
19
+ return typeof s === 'string' && s.trim().length > 0 ? s.trim() : undefined;
20
+ }
21
+ function failureEvidenceIdentity(fc) {
22
+ const failure = nonEmpty(fc.failureId);
23
+ const root = nonEmpty(fc.rootAttemptId);
24
+ const execution = nonEmpty(fc.executionId);
25
+ const direct = [
26
+ failure ? `failure:${failure}` : undefined,
27
+ root ? `root:${root}` : undefined,
28
+ execution ? `execution:${execution}` : undefined,
29
+ ].filter((key) => key !== undefined);
30
+ if (direct.length > 0)
31
+ return { quality: 'strong_id', keys: direct };
32
+ const verifier = nonEmpty(fc.verifierDigest);
33
+ const artifact = nonEmpty(fc.artifactDigest);
34
+ if (verifier && artifact)
35
+ return { quality: 'complete_verifier_artifact_digest', keys: [`evidence:${verifier}:${artifact}`] };
36
+ if (verifier || artifact)
37
+ return { quality: 'legacy_partial', keys: [] };
38
+ return { quality: 'legacy_raw', keys: [] };
39
+ }
40
+ function isCountedIdentity(identity) {
41
+ return identity.quality === 'strong_id' || identity.quality === 'complete_verifier_artifact_digest';
42
+ }
43
+ function addFailureIdentity(clusters, keys) {
44
+ const matches = [];
45
+ for (let i = 0; i < clusters.length; i++) {
46
+ if (keys.some((key) => clusters[i].has(key)))
47
+ matches.push(i);
48
+ }
49
+ if (matches.length === 0) {
50
+ clusters.push(new Set(keys));
51
+ return;
52
+ }
53
+ const target = clusters[matches[0]];
54
+ for (const key of keys)
55
+ target.add(key);
56
+ for (let i = matches.length - 1; i > 0; i--) {
57
+ const index = matches[i];
58
+ for (const key of clusters[index])
59
+ target.add(key);
60
+ clusters.splice(index, 1);
61
+ }
62
+ }
63
+ /** Gene ids with >=2 independent failed capsules whose trigger covers >=60% of the current signals. */
19
64
  export function bannedGenesFromFailures(failures, signals) {
20
65
  const counts = new Map();
21
66
  for (const fc of failures) {
@@ -23,11 +68,16 @@ export function bannedGenesFromFailures(failures, signals) {
23
68
  continue;
24
69
  if (signalCoverage(signals, fc.trigger ?? []) < OVERLAP_MIN)
25
70
  continue;
26
- counts.set(fc.gene, (counts.get(fc.gene) ?? 0) + 1);
71
+ const identity = failureEvidenceIdentity(fc);
72
+ if (!isCountedIdentity(identity))
73
+ continue;
74
+ const clusters = counts.get(fc.gene) ?? [];
75
+ addFailureIdentity(clusters, identity.keys);
76
+ counts.set(fc.gene, clusters);
27
77
  }
28
78
  const banned = new Set();
29
- for (const [gene, c] of counts)
30
- if (c >= BAN_THRESHOLD)
79
+ for (const [gene, clusters] of counts)
80
+ if (clusters.length >= BAN_THRESHOLD)
31
81
  banned.add(gene);
32
82
  return banned;
33
83
  }
@@ -3,8 +3,12 @@ import { type ReuseCounts } from '../ops/reuseOutcomes.js';
3
3
  import type { ProvenanceStore } from '../assetstore/provenance.js';
4
4
  import type { ReviewLedger } from '../assetstore/reviewLedger.js';
5
5
  import type { AntiWarning, GeneCandidateInput } from './geneSelection.js';
6
+ import { type RuntimeRegistry } from './kautoValidator.js';
7
+ import { type CompatibilityEvidenceIndex } from '../modelCompatibility.js';
6
8
  export interface AssembleOptions {
7
9
  limit?: number;
10
+ /** Build the bounded trusted pre-admission corpus used by semantic IDF. Default true. */
11
+ includeSemanticCorpus?: boolean;
8
12
  provenance?: ProvenanceStore;
9
13
  includeUntrusted?: boolean;
10
14
  review?: ReviewLedger;
@@ -31,6 +35,22 @@ export interface AssembleOptions {
31
35
  * (default-off). Never resurrects a hard-gated gene (the trust/review/ban filters above run first).
32
36
  */
33
37
  reuseCounts?: ReadonlyMap<string, ReuseCounts>;
38
+ /** Exact compatibility evidence; only quarantine decisions remove candidates. */
39
+ compatibility?: CompatibilityEvidenceIndex;
40
+ /**
41
+ * K_auto EvidenceProjection revocation guard (T1, default-OFF). When provided, a candidate whose five
42
+ * coordinates are all machine-decidable AND whose current projection touches a revoked (coordinate, value)
43
+ * pair is treated as NOT eligible — the fail-safe "a revoked projection is not eligible" rule, applied
44
+ * coordinate-locally (a sibling that differs on the revoked coordinate stays eligible). `revoked` is the set
45
+ * produced by `projectRevocations(rootEvents)` (algo/kautoProjection). Absent (default) = deployed T2 behavior
46
+ * unchanged: no projection guard runs, so signal-scoped `bannedGenesFromFailures` + the soft λ preference are
47
+ * the only K_auto-related effects. `runtimeRegistry` is forwarded to `decideKauto` so the runtime coordinate
48
+ * resolves against the same registry the caller uses elsewhere.
49
+ */
50
+ kautoProjection?: {
51
+ revoked: ReadonlySet<string>;
52
+ runtimeRegistry?: RuntimeRegistry;
53
+ };
34
54
  }
35
55
  /** The selection pool for one cycle: the normal scored candidates + the last-resort distilled fallback (#97). */
36
56
  export interface SelectionPool {
@@ -39,10 +59,13 @@ export interface SelectionPool {
39
59
  /**
40
60
  * Distilled genes that passed the trust/review/ban gates but do NOT match the live signals. Surfaced
41
61
  * separately (NOT in `candidates`, so they never compete in normal scoring) for the v1 #97 fallback: when no
42
- * candidate clears the floor, selection reuses one of these broadly-applicable distilled strategies instead of
43
- * falling through to a blind innovate. Kept lightweight (no learning-history aggregation) — picked first-match.
62
+ * normal selection has no reusable positive choice, selection reuses one of these broadly-applicable distilled
63
+ * strategies instead of falling through to a blind innovate. Kept lightweight (no learning-history aggregation)
64
+ * — picked first-match.
44
65
  */
45
66
  distilledFallback: GeneCandidateInput[];
67
+ /** Trust-filtered library documents captured before live-signal relevance admission. */
68
+ semanticCorpus: GeneCandidateInput[];
46
69
  /**
47
70
  * Matched AntiGene guardrails for the live signals. This list is advisory-only and is kept out of candidates and
48
71
  * distilledFallback so negative memory cannot be selected as a strategy.
@@ -1,14 +1,50 @@
1
1
  import { aggregateLearningHistory } from '../assetstore/learningHistory.js';
2
2
  import { reuseSentiment } from '../ops/reuseOutcomes.js';
3
+ import { decideKauto } from './kautoValidator.js';
4
+ import { isProjectionRevoked } from './kautoProjection.js';
3
5
  import { expandSignals, geneTags, tagOverlapScore, } from '../signals/expand.js';
6
+ import { resolveTaskDomainSignals, withoutTaskDomainSignals } from '../signals/taskDomain.js';
4
7
  import { bannedGenesFromFailures } from './bans.js';
5
8
  import { geneGenerationSource } from './geneIntake.js';
9
+ import { isCompatibilityBlocked } from '../modelCompatibility.js';
10
+ const SEMANTIC_CORPUS_LIMIT = 1_000;
6
11
  function asStrings(v) {
7
12
  return Array.isArray(v) ? v.filter((x) => typeof x === 'string') : [];
8
13
  }
9
14
  function stringField(v) {
10
15
  return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined;
11
16
  }
17
+ function firstStringField(record, keys) {
18
+ for (const key of keys) {
19
+ const value = stringField(record[key]);
20
+ if (value)
21
+ return value;
22
+ }
23
+ return undefined;
24
+ }
25
+ function recordList(v) {
26
+ return Array.isArray(v) ? v.filter((x) => x !== null && typeof x === 'object' && !Array.isArray(x)) : [];
27
+ }
28
+ function failureIdentitySource(record) {
29
+ for (const trace of recordList(record['execution_trace'])) {
30
+ const stage = stringField(trace['stage']);
31
+ if (stage === 'validate' && firstStringField(trace, ['root_attempt_id', 'rootAttemptId', 'execution_id', 'executionId', 'failure_id', 'failureId', 'verifier_digest', 'verifierDigest', 'artifact_digest', 'artifactDigest']))
32
+ return trace;
33
+ }
34
+ return record;
35
+ }
36
+ function failureRefFromCapsule(record) {
37
+ const identity = failureIdentitySource(record);
38
+ return {
39
+ gene: stringField(record['gene']),
40
+ trigger: asStrings(record['trigger']),
41
+ failureId: firstStringField(identity, ['failure_id', 'failureId']),
42
+ rootAttemptId: firstStringField(identity, ['root_attempt_id', 'rootAttemptId', 'attempt_root_id', 'attemptRootId']),
43
+ executionId: firstStringField(identity, ['execution_id', 'executionId', 'run_id', 'runId', 'cycle_id', 'cycleId']),
44
+ verifierDigest: firstStringField(identity, ['verifier_digest', 'verifierDigest']),
45
+ artifactDigest: firstStringField(identity, ['artifact_digest', 'artifactDigest']),
46
+ };
47
+ }
12
48
  function severityOf(v) {
13
49
  return v === 'low' || v === 'medium' || v === 'high' ? v : undefined;
14
50
  }
@@ -18,6 +54,25 @@ function candidateIds(geneId, assetId) {
18
54
  function isBannedCandidate(banned, geneId, assetId) {
19
55
  return candidateIds(geneId, assetId).some((id) => banned.has(id));
20
56
  }
57
+ function requiredSignals(signalsMatch) {
58
+ return signalsMatch.flatMap((signal) => signal.startsWith('required:') ? [signal.slice('required:'.length).trim()] : []).filter(Boolean);
59
+ }
60
+ function matchSignals(signalsMatch) {
61
+ return signalsMatch.map((signal) => signal.startsWith('required:') ? signal.slice('required:'.length).trim() : signal).filter(Boolean);
62
+ }
63
+ function passesRequiredSignals(signalsMatch, liveSignals) {
64
+ const required = requiredSignals(signalsMatch);
65
+ return required.length === 0 || required.every((signal) => liveSignals.has(signal));
66
+ }
67
+ function passesFallbackTaskDomain(liveSignals, candidateSignals) {
68
+ const candidateDomain = resolveTaskDomainSignals(candidateSignals);
69
+ if (candidateDomain.status === 'absent')
70
+ return true;
71
+ if (candidateDomain.status !== 'resolved')
72
+ return false;
73
+ const liveDomain = resolveTaskDomainSignals(liveSignals);
74
+ return liveDomain.status === 'resolved' && liveDomain.slug === candidateDomain.slug;
75
+ }
21
76
  function passesInjectedCandidateGates(candidate, opts, provenance, review) {
22
77
  const assetId = candidate.assetId;
23
78
  if (opts.provenance && !opts.includeUntrusted) {
@@ -59,13 +114,12 @@ function combinedReuseSentiment(map, ...ids) {
59
114
  }
60
115
  /** Genes banned by repeated signal-matched failures (>=2 failed capsules covering the signals). */
61
116
  async function computeBans(store, signals, limit) {
117
+ if (signals.length === 0)
118
+ return new Set();
62
119
  const caps = await store.search({ kind: 'Capsule', signalsAny: [...signals], limit });
63
120
  const failures = caps
64
121
  .filter((c) => c['outcome']?.status === 'failed')
65
- .map((c) => ({
66
- gene: typeof c['gene'] === 'string' ? String(c['gene']) : undefined,
67
- trigger: asStrings(c['trigger']),
68
- }));
122
+ .map((c) => failureRefFromCapsule(c));
69
123
  return bannedGenesFromFailures(failures, signals);
70
124
  }
71
125
  const GENERIC_NAMESPACE_TAGS = new Set(['action', 'area', 'problem', 'risk', 'signal']);
@@ -95,18 +149,36 @@ function hasSelectionAdmissionEvidence(liveSignals, gene) {
95
149
  */
96
150
  export async function assembleSelectionPool(store, signals, opts = {}) {
97
151
  const limit = opts.limit ?? 500;
98
- const genes = await store.list('Gene', limit);
99
- const banned = await computeBans(store, signals, limit);
100
- const sigSet = new Set(signals);
152
+ const includeSemanticCorpus = opts.includeSemanticCorpus !== false;
153
+ const scanLimit = includeSemanticCorpus ? Math.max(limit, SEMANTIC_CORPUS_LIMIT) : limit;
154
+ const genes = await store.list('Gene', scanLimit);
155
+ const matchingSignals = withoutTaskDomainSignals(signals);
156
+ const banned = await computeBans(store, matchingSignals, limit);
157
+ const liveSignalSet = new Set(signals);
158
+ const sigSet = new Set(matchingSignals);
101
159
  const provenance = opts.provenance?.snapshot();
102
160
  const review = opts.review?.snapshot();
103
161
  const out = [];
104
162
  const distilledFallback = [];
163
+ const semanticCorpus = [];
164
+ const semanticIdentities = new Set();
165
+ const addSemanticCandidate = (candidate) => {
166
+ if (!includeSemanticCorpus || semanticCorpus.length >= SEMANTIC_CORPUS_LIMIT)
167
+ return;
168
+ const identity = candidate.assetId ?? candidate.geneId;
169
+ if (semanticIdentities.has(identity))
170
+ return;
171
+ semanticIdentities.add(identity);
172
+ semanticCorpus.push(candidate);
173
+ };
105
174
  const antiWarnings = [];
106
- for (const g of genes) {
175
+ for (const [geneIndex, g] of genes.entries()) {
176
+ const provenanceRecord = provenance?.get(String(g.asset_id));
177
+ if (isCompatibilityBlocked({ assetType: 'Gene', assetId: String(g.asset_id), revision: stringField(g['revision']) ?? '' }, opts.compatibility))
178
+ continue;
107
179
  // Trust-first (#30): untrusted (e.g. hub-ingested) genes are excluded from the candidate pool by default;
108
180
  // they enter only with includeUntrusted or after an explicit promotion. Provenance is keyed by asset_id.
109
- if (opts.provenance && !opts.includeUntrusted && provenance?.get(String(g.asset_id))?.trusted === false)
181
+ if (opts.provenance && !opts.includeUntrusted && provenanceRecord?.trusted === false)
110
182
  continue;
111
183
  // Review-first (#89/#91): auto-distilled drafts land quarantined until a human approves; rejected drafts stay
112
184
  // out too. No record → eligible (cycle/migrate genes). Symmetric to the provenance trust-first filter above.
@@ -120,12 +192,38 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
120
192
  const assetId = String(g.asset_id);
121
193
  if (isBannedCandidate(banned, geneId, assetId))
122
194
  continue; // repeated signal-matched failures → not a candidate (nor a fallback)
195
+ // K_auto projection revocation (T1, opt-in). A decidable record whose current five-coordinate projection
196
+ // touches a revoked (coordinate, value) pair is not eligible — coordinate-locally, so a sibling differing on
197
+ // the revoked coordinate is unaffected. Runs AFTER trust/review/ban (those are the deployed hard gates) and
198
+ // before signal matching, so a revoked projection is dropped from both the candidate and #97 fallback pools.
199
+ // Default-off: without opts.kautoProjection the deployed T2 path is unchanged.
200
+ if (opts.kautoProjection
201
+ && isProjectionRevoked(decideKauto(g, opts.kautoProjection.runtimeRegistry ? { runtimeRegistry: opts.kautoProjection.runtimeRegistry } : undefined), opts.kautoProjection.revoked))
202
+ continue;
123
203
  const signalsMatch = asStrings(g['signals_match']);
204
+ const effectiveSignalsMatch = matchSignals(signalsMatch);
124
205
  const category = typeof g['category'] === 'string' ? String(g['category']) : undefined;
125
206
  const summary = typeof g['summary'] === 'string' ? String(g['summary']) : undefined;
126
- const literal = signalsMatch.some((m) => sigSet.has(m));
127
- const tagInput = { signalsMatch, geneId, category, summary };
128
- const relevant = literal || hasSelectionAdmissionEvidence(signals, tagInput);
207
+ // Experimental probation and explicitly untrusted records may be selectable by opt-in, but never shape global
208
+ // document frequency. Only trusted, approved/legacy-local records enter the pre-admission semantic corpus.
209
+ if (provenanceRecord?.trusted !== false && (reviewRecord === undefined || reviewRecord.state === 'approved')) {
210
+ addSemanticCandidate({
211
+ geneId,
212
+ assetId,
213
+ signalsMatch: effectiveSignalsMatch,
214
+ view: emptyLearningView(geneId),
215
+ ...(category ? { category } : {}),
216
+ ...(summary ? { summary } : {}),
217
+ });
218
+ }
219
+ if (geneIndex >= limit)
220
+ continue;
221
+ if (!passesRequiredSignals(signalsMatch, liveSignalSet))
222
+ continue;
223
+ const matchingSignalsForGene = withoutTaskDomainSignals(effectiveSignalsMatch);
224
+ const literal = matchingSignalsForGene.some((m) => sigSet.has(m));
225
+ const tagInput = { signalsMatch: matchingSignalsForGene, geneId, category, summary };
226
+ const relevant = literal || hasSelectionAdmissionEvidence(matchingSignals, tagInput);
129
227
  if (!relevant) {
130
228
  // #97: a trusted, approved, non-banned distilled (or evolved) gene that doesn't match the live signals is not
131
229
  // a normal candidate, but it IS eligible as a last-resort fallback (selection uses it only when nothing clears
@@ -133,11 +231,12 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
133
231
  // generation_meta (V1 #302); a legacy gene without it falls back to the `gene_distilled_` id namespace.
134
232
  // Lightweight: no learning-history aggregation — it is picked first-match, not ranked by health.
135
233
  const gsrc = geneGenerationSource(g, geneId);
136
- if (gsrc === 'distilled' || gsrc === 'evolved') {
234
+ if ((gsrc === 'distilled' || gsrc === 'evolved')
235
+ && passesFallbackTaskDomain(signals, effectiveSignalsMatch)) {
137
236
  distilledFallback.push({
138
237
  geneId,
139
238
  assetId,
140
- signalsMatch,
239
+ signalsMatch: effectiveSignalsMatch,
141
240
  view: emptyLearningView(geneId),
142
241
  reuseCount: 0,
143
242
  generationSource: gsrc,
@@ -148,14 +247,19 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
148
247
  continue;
149
248
  }
150
249
  const view = await aggregateLearningHistory(store, geneId);
250
+ const explorationEligible = provenanceRecord?.trusted !== false
251
+ && (reviewRecord === undefined || reviewRecord.state === 'approved');
151
252
  // #268 Bugbot: reuse events key by assetId, which may be the content asset_id (sha256:…) OR the logical id —
152
253
  // candidates key by the logical geneId. Look the sentiment up by BOTH so the soft re-order actually matches
153
254
  // regardless of which form the reuse-result carried (recall's resolveGene accepts both for the same reason).
154
255
  const reuseAdjustVal = combinedReuseSentiment(opts.reuseCounts, geneId, String(g.asset_id));
256
+ // Soft K_auto preference: stamp only when the source record clears the strict five-coordinate bar.
257
+ // Absent/false leaves score unchanged so the historical non-member catalogue is not zeroed.
258
+ const kautoMember = decideKauto(g).inKauto;
155
259
  out.push({
156
260
  geneId,
157
261
  assetId: String(g.asset_id),
158
- signalsMatch,
262
+ signalsMatch: effectiveSignalsMatch,
159
263
  view,
160
264
  // #195: inert (zero-work) cycles are not productive reuse — exclude them so a do-nothing gene can't earn
161
265
  // the reuse bonus. success + failed === total when there are no inert cycles (back-compat).
@@ -163,25 +267,40 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
163
267
  ...(category ? { category } : {}),
164
268
  ...(summary ? { summary } : {}),
165
269
  ...(reuseAdjustVal !== undefined ? { reuseAdjust: reuseAdjustVal } : {}),
270
+ ...(kautoMember ? { kautoMember: true } : {}),
271
+ explorationEligible,
166
272
  });
167
273
  }
168
274
  // #110: merge hub reuse candidates into the same pool, trust-first — local (trusted) genes win on a
169
275
  // geneId collision, and a hub candidate banned by repeated signal-matched failures stays out too.
170
276
  if (opts.hubCandidates && opts.hubCandidates.length > 0) {
171
277
  const localIds = new Set(out.map((c) => c.geneId));
278
+ const localAssetIds = new Set(out.map((c) => c.assetId).filter((id) => id !== undefined));
172
279
  for (const h of opts.hubCandidates) {
280
+ if (isCompatibilityBlocked({ assetType: 'Gene', assetId: h.assetId ?? h.geneId, revision: String(h.hubAsset?.['revision'] ?? '') }, opts.compatibility))
281
+ continue;
173
282
  if (!passesInjectedCandidateGates(h, opts, provenance, review))
174
283
  continue;
284
+ if (!passesRequiredSignals(h.signalsMatch, sigSet))
285
+ continue;
175
286
  if (localIds.has(h.geneId))
176
287
  continue; // a trusted local gene already covers this id
288
+ if (h.assetId && localAssetIds.has(h.assetId))
289
+ continue; // the same content identity is already local/trusted
177
290
  if (isBannedCandidate(banned, h.geneId, h.assetId))
178
291
  continue;
179
292
  // Stamp the reuse sentiment onto a hub candidate too (only when it didn't carry one), matched by its geneId
180
293
  // OR its asset_id — symmetric with local genes, so a map keyed like reuse events (often sha256:…) still hits
181
294
  // (#268 Bugbot). A hub candidate that exposes neither matching id is simply left unstamped.
295
+ const hubSignalsMatch = matchSignals(h.signalsMatch);
182
296
  const hubAdj = h.reuseAdjust === undefined ? combinedReuseSentiment(opts.reuseCounts, h.geneId, h.assetId) : undefined;
183
- out.push(hubAdj !== undefined ? { ...h, reuseAdjust: hubAdj } : h);
297
+ // A transient Hub candidate must never self-assert active-bandit cold-start eligibility. It still competes
298
+ // under the existing base score and legacy drift; UCB1 fails safe for a mixed window.
299
+ const guarded = { ...h, signalsMatch: hubSignalsMatch, explorationEligible: false };
300
+ out.push(hubAdj !== undefined ? { ...guarded, reuseAdjust: hubAdj } : guarded);
184
301
  localIds.add(h.geneId);
302
+ if (h.assetId)
303
+ localAssetIds.add(h.assetId);
185
304
  }
186
305
  }
187
306
  for (const a of await store.list('AntiGene', limit)) {
@@ -196,8 +315,10 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
196
315
  continue;
197
316
  const antiGeneId = stringField(a['id']) ?? String(a.asset_id);
198
317
  const summary = stringField(a['summary']);
199
- const relevant = trigger.some((m) => sigSet.has(m))
200
- || tagOverlapScore(signals, { signalsMatch: trigger, geneId: antiGeneId, summary }) > 0;
318
+ const matchingTrigger = withoutTaskDomainSignals(trigger);
319
+ // This is a safety inclusion gate: ubiquitous evidence must still emit warnings.
320
+ const relevant = matchingTrigger.some((m) => sigSet.has(m))
321
+ || tagOverlapScore(matchingSignals, { signalsMatch: matchingTrigger, geneId: antiGeneId, summary }) > 0;
201
322
  if (!relevant)
202
323
  continue;
203
324
  const severity = severityOf(a['severity']);
@@ -212,7 +333,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
212
333
  ...(rationale ? { rationale } : {}),
213
334
  });
214
335
  }
215
- return { candidates: out, distilledFallback, antiWarnings };
336
+ return { candidates: out, distilledFallback, semanticCorpus, antiWarnings };
216
337
  }
217
338
  /**
218
339
  * Assemble selection candidates from the store for the given signals (the relevant scored set only).
@@ -220,5 +341,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
220
341
  * distilled-gene fallback pool.
221
342
  */
222
343
  export async function assembleCandidates(store, signals, opts = {}) {
223
- return (await assembleSelectionPool(store, signals, opts)).candidates;
344
+ // This compatibility wrapper discards semanticCorpus, so preserve its legacy I/O bound instead of scanning
345
+ // 1,000 records for data the caller cannot consume.
346
+ return (await assembleSelectionPool(store, signals, { ...opts, includeSemanticCorpus: false })).candidates;
224
347
  }
@@ -20,6 +20,16 @@ const SIGNAL_CANDIDATES = [
20
20
  { signal: 'capability_gap', title: 'Fill capability gap' },
21
21
  { signal: 'stable_success_plateau', title: 'Explore new strategies during stability plateau' },
22
22
  { signal: 'external_opportunity', title: 'Evaluate external A2A asset for local adoption' },
23
+ // Harness context-budget family (v1 context-compression gene family port). v2 ships no seed-gene catalog, so
24
+ // the reusable strategies arrive as ADVISORY candidates the runtime may distill into genes — one per bloat
25
+ // source, because a single "shorten the prompt" proposal loses the distinction that decides what is safe to
26
+ // drop (an always-on tool schema is lazy-loadable; an approval gate is not).
27
+ { signal: 'prompt_budget_measurement', title: 'Measure the prompt budget per source before compressing context' },
28
+ { signal: 'context_explosion', title: 'Attribute a context-window overflow to its largest prompt source' },
29
+ { signal: 'tool_schema_bloat', title: 'Load exact tool/MCP schemas from the runtime loader instead of always-on prompt text' },
30
+ { signal: 'skill_list_bloat', title: 'Compress skill and agent-type descriptions into selectable route cards' },
31
+ { signal: 'transcript_context_bloat', title: 'Compress a pasted transcript into a session handoff packet' },
32
+ { signal: 'memory_index_budget', title: 'Keep the memory index lean and rely on relevance recall for detail' },
23
33
  ];
24
34
  /** Highest → lowest priority problem class used to pick a failure cluster's grouping key (ported v1). */
25
35
  const PROBLEM_PRIORITY = ['problem:performance', 'problem:protocol', 'problem:reliability', 'problem:stagnation', 'problem:capability'];
@@ -4,13 +4,15 @@ import type { GepCategory, Mutation, Capsule, EvolutionEvent } from '../wire/ind
4
4
  import type { AssetStoreProvider } from '../assetstore/provider.js';
5
5
  import type { StrategyPoint } from '../strategy/strategyPoint.js';
6
6
  import { type CycleStage } from '../cycle/stateMachine.js';
7
- import { type GeneDecision, type SelectionInput, type GeneCandidateInput, type AntiWarning } from './geneSelection.js';
7
+ import { type GeneDecision, type SelectionInput, type GeneCandidateInput, type AntiWarning, type SelectionGuardMode } from './geneSelection.js';
8
8
  import { type EnvFingerprint } from '../bootstrap/envFingerprint.js';
9
9
  import type { PersonalityStore } from '../personality/store.js';
10
- import { type ResolutionStatus } from './solidify.js';
10
+ import { type FailureEvidenceIdentityInput, type ResolutionStatus } from './solidify.js';
11
11
  import type { ProofOfWork } from '../schema/proofOfWork.js';
12
+ import type { ExecutionTerminalDisposition, FrozenExecutionBinding, ImmutableExecutionProvenance } from '../exec/executionBinding.js';
12
13
  import { type ClassifyRecentEvent } from './cycleFailureClassifier.js';
13
14
  import type { MemoryGraphGeneEvidence } from './memoryGraph.js';
15
+ import { type SelectionPolicy } from './ucb1.js';
14
16
  export interface TriggerEval {
15
17
  trigger: boolean;
16
18
  reasons: string[];
@@ -23,8 +25,23 @@ export interface ExecutionResult {
23
25
  score: number;
24
26
  reason?: string;
25
27
  };
28
+ bindingCorrelation?: {
29
+ bindingDigest: `sha256:${string}`;
30
+ runId: string;
31
+ };
32
+ executionTerminal?: {
33
+ status: 'success' | 'failed';
34
+ disposition: ExecutionTerminalDisposition;
35
+ };
36
+ provenance?: ImmutableExecutionProvenance;
37
+ hubLifecycle?: {
38
+ state: 'not_submitted';
39
+ };
40
+ nativeSessionId?: string;
26
41
  proofOfWork?: ProofOfWork;
27
42
  strongEvidence?: boolean;
43
+ /** Optional logical failure identity supplied by the runner; absent keeps the failure as legacy advisory evidence. */
44
+ failureIdentity?: FailureEvidenceIdentityInput;
28
45
  /** Structured runner failure metadata. Safe to persist; unlike sessionLog it contains no transcript text. */
29
46
  failureKind?: ExecutionFailureKind;
30
47
  exitCode?: number | null;
@@ -33,13 +50,17 @@ export interface ExecutionResult {
33
50
  * outcome. It is the host-side context classifyCycleFailure needs to reach the host_no_transcript /
34
51
  * host_provider_error buckets on the PRODUCTION path (#279): an empty transcript means the host gave evolver
35
52
  * nothing to evolve from; a provider-error string (429 / quota / context-length) means the host's LLM call
36
- * failed, not the gene. Consumed only for failure triage and NEVER persisted to event payloads (the engine
37
- * writes only the derived failure_class), so it adds no root_events bloat and leaks no transcript content.
53
+ * failed, not the gene. Consumed only for failure triage and NEVER persisted to event payloads; the engine
54
+ * writes only the derived failure_class, so it adds no root_events bloat and leaks no transcript content.
38
55
  */
39
56
  sessionLog?: string;
40
57
  }
41
58
  export interface SolidifyPermitContext {
42
59
  cycleId: string;
60
+ bindingDigest?: `sha256:${string}`;
61
+ runId?: string;
62
+ executionTerminal?: ExecutionResult['executionTerminal'];
63
+ provenance?: ImmutableExecutionProvenance;
43
64
  geneId: string;
44
65
  signals: readonly string[];
45
66
  mutation: Mutation;
@@ -90,6 +111,14 @@ export interface CycleInput {
90
111
  /** Optional explicit strategy preset name; when set, it wins over history-derived meta-signal auto-detection. */
91
112
  strategyName?: string;
92
113
  candidates: readonly GeneCandidateInput[];
114
+ /** Trust-filtered library corpus captured before relevance admission for stable semantic IDF. */
115
+ semanticCorpus?: readonly GeneCandidateInput[];
116
+ /** Emergency rollback: use the pre-IDF semantic scorer for this cycle. */
117
+ disableSemanticIdf?: boolean;
118
+ /** Experimental plateau selection policy. Default preserves engine-health + legacy random drift. */
119
+ selectionPolicy?: SelectionPolicy;
120
+ /** Versioned relevance-guard rollout. Omit for legacy direct-call behavior. */
121
+ selectionGuard?: SelectionGuardMode;
93
122
  selectionFloor?: number;
94
123
  /**
95
124
  * Explicit GEP/runtime-selected gene. Forwarded after candidate assembly and local hard filters, so it cannot
@@ -101,12 +130,14 @@ export interface CycleInput {
101
130
  summary: string;
102
131
  confidence: number;
103
132
  execute: (mutation: Mutation, decision: GeneDecision) => Promise<ExecutionResult> | ExecutionResult;
133
+ /** Immutable external execution binding; omitted for legacy local cycles. */
134
+ executionBinding?: FrozenExecutionBinding;
104
135
  /** Optional adapter/runtime permit gate. Runs after execute succeeds and before solidify writes assets. */
105
136
  solidifyPermit?: SolidifyPermitGate;
106
137
  /**
107
138
  * Distilled-gene fallback pool (ported from v1 #97): broadly-applicable distilled genes that do NOT match the
108
139
  * live signals, assembled (trust/review/ban-filtered) upstream. Forwarded to selection, which uses one only as a
109
- * last resort when no candidate clears the floor — reusing a known distilled strategy instead of a blind innovate.
140
+ * last resort when normal selection has no reusable positive choice — reusing a known distilled strategy instead of a blind innovate.
110
141
  */
111
142
  distilledFallback?: readonly GeneCandidateInput[];
112
143
  /** Advisory-only AntiGene warnings matched upstream for this cycle's base signals. */
@@ -134,6 +165,10 @@ export interface CycleInput {
134
165
  }
135
166
  export interface CycleResult {
136
167
  cycleId: string;
168
+ bindingDigest?: `sha256:${string}`;
169
+ runId?: string;
170
+ terminalDisposition?: ExecutionTerminalDisposition;
171
+ execution?: ExecutionResult;
137
172
  triggered: boolean;
138
173
  finalStage: CycleStage;
139
174
  /** Real CycleEngine returns this on every path; optional keeps injected legacy engines source-compatible. */