@tangle-network/agent-eval 0.115.2 → 0.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/analyst/index.d.ts +8 -10
  3. package/dist/analyst/index.js +28 -23
  4. package/dist/analyst/index.js.map +1 -1
  5. package/dist/{analyst-C8HHvfJp.d.ts → analyst-CFBc14Wc.d.ts} +1 -1
  6. package/dist/{analyze-runs-BYHg6Irm.d.ts → analyze-runs-0rz_m29H.d.ts} +3 -3
  7. package/dist/belief-state/index.d.ts +3 -3
  8. package/dist/benchmarks/index.d.ts +7 -3
  9. package/dist/benchmarks/index.js +5 -5
  10. package/dist/campaign/index.d.ts +212 -23
  11. package/dist/campaign/index.js +20 -5
  12. package/dist/{chunk-N6MTC3GK.js → chunk-3274WNK7.js} +428 -94
  13. package/dist/chunk-3274WNK7.js.map +1 -0
  14. package/dist/{chunk-DRPIZQIT.js → chunk-4D5RVB3W.js} +2 -2
  15. package/dist/{chunk-LVTGFSHF.js → chunk-7GKEAIAD.js} +2 -2
  16. package/dist/{chunk-DWLIGZBX.js → chunk-CIUOICJT.js} +748 -3
  17. package/dist/chunk-CIUOICJT.js.map +1 -0
  18. package/dist/{chunk-5NVBGKPH.js → chunk-GSW3OBHK.js} +1284 -182
  19. package/dist/chunk-GSW3OBHK.js.map +1 -0
  20. package/dist/{chunk-FUCQVFMU.js → chunk-GY4SYVPJ.js} +12 -3
  21. package/dist/chunk-GY4SYVPJ.js.map +1 -0
  22. package/dist/chunk-MPHTT5HE.js +74 -0
  23. package/dist/chunk-MPHTT5HE.js.map +1 -0
  24. package/dist/{chunk-I2HNIE6N.js → chunk-NBSS5NDZ.js} +4 -4
  25. package/dist/{chunk-QG5F6463.js → chunk-ONM6PEAE.js} +2 -2
  26. package/dist/cli.js +2 -2
  27. package/dist/{code-agent-session-D-g04tcy.d.ts → code-agent-session-CdxteG0y.d.ts} +1 -1
  28. package/dist/contract/index.d.ts +19 -19
  29. package/dist/contract/index.js +6 -4
  30. package/dist/contract/index.js.map +1 -1
  31. package/dist/{control-CcBiAEnn.d.ts → control-DbcDxouY.d.ts} +1 -1
  32. package/dist/control.d.ts +2 -2
  33. package/dist/{default-registry-DltpYR5u.d.ts → default-registry-DDfv22MQ.d.ts} +2 -1
  34. package/dist/{gepa-dne9JDPL.d.ts → gepa-CQelRtuC.d.ts} +10 -8
  35. package/dist/hosted/index.d.ts +8 -4
  36. package/dist/{index-BTEpx9He.d.ts → index-DbCXJfZ1.d.ts} +2 -2
  37. package/dist/index.d.ts +27 -30
  38. package/dist/index.js +28 -22
  39. package/dist/index.js.map +1 -1
  40. package/dist/{insight-report-IwwvqZZv.d.ts → insight-report-oMVxDTxl.d.ts} +1 -1
  41. package/dist/{integrity-qemeBAyx.d.ts → integrity-C6PZ73iC.d.ts} +1 -1
  42. package/dist/kind-factory-DWOvXjR_.d.ts +171 -0
  43. package/dist/meta-eval/index.d.ts +2 -2
  44. package/dist/multishot/index.d.ts +6 -2
  45. package/dist/openapi.json +1 -1
  46. package/dist/policy-edit-Clb2v6Oa.d.ts +708 -0
  47. package/dist/{pre-registration-D8h7ZxNL.d.ts → pre-registration--vU0mMtD.d.ts} +4 -4
  48. package/dist/{provenance-Bibyg1U9.d.ts → provenance-BbVagC68.d.ts} +26 -14
  49. package/dist/{release-report-CCtzajxP.d.ts → release-report-CamNDe90.d.ts} +2 -2
  50. package/dist/reporting.d.ts +4 -4
  51. package/dist/{researcher-Dq-EtpbE.d.ts → researcher-Dwbo_Fxx.d.ts} +5 -5
  52. package/dist/rl.d.ts +11 -9
  53. package/dist/rl.js +2 -2
  54. package/dist/{rubric-predictive-validity-DYTLjGWu.d.ts → rubric-predictive-validity-BIdf9h4R.d.ts} +1 -1
  55. package/dist/{run-record-B7RTi_ix.d.ts → run-record-CZmcpWPo.d.ts} +1 -1
  56. package/dist/{runtime-trajectory-Dws7Kpgi.d.ts → runtime-trajectory-CC0jx9ql.d.ts} +1 -1
  57. package/dist/{semantic-concept-judge-DxJmRkyJ.d.ts → semantic-concept-judge-CKjePUMh.d.ts} +3 -3
  58. package/dist/{store-C1YxJDEK.d.ts → store-9cAScOcb.d.ts} +132 -1
  59. package/dist/{summary-report-BJ5aNwZ1.d.ts → summary-report-DTNgQycC.d.ts} +1 -1
  60. package/dist/traces.d.ts +6 -8
  61. package/dist/{types-C5gJrOVT.d.ts → types-Ca_63YSD.d.ts} +59 -2
  62. package/dist/wire/index.js +2 -2
  63. package/docs/design/loop-taxonomy.md +1 -2
  64. package/package.json +1 -1
  65. package/dist/chunk-5NVBGKPH.js.map +0 -1
  66. package/dist/chunk-AN5UYSVD.js +0 -761
  67. package/dist/chunk-AN5UYSVD.js.map +0 -1
  68. package/dist/chunk-DWLIGZBX.js.map +0 -1
  69. package/dist/chunk-FUCQVFMU.js.map +0 -1
  70. package/dist/chunk-N6MTC3GK.js.map +0 -1
  71. package/dist/kind-factory-DcNg13sZ.d.ts +0 -508
  72. package/dist/llm-client-DyqEH4jH.d.ts +0 -265
  73. package/dist/policy-edit-RLn8GWof.d.ts +0 -103
  74. package/dist/raw-provider-sink-C46HDghv.d.ts +0 -132
  75. /package/dist/{chunk-DRPIZQIT.js.map → chunk-4D5RVB3W.js.map} +0 -0
  76. /package/dist/{chunk-LVTGFSHF.js.map → chunk-7GKEAIAD.js.map} +0 -0
  77. /package/dist/{chunk-I2HNIE6N.js.map → chunk-NBSS5NDZ.js.map} +0 -0
  78. /package/dist/{chunk-QG5F6463.js.map → chunk-ONM6PEAE.js.map} +0 -0
@@ -2,6 +2,9 @@ import {
2
2
  runCampaign,
3
3
  summarizeBackendIntegrity
4
4
  } from "./chunk-FAOEFFRT.js";
5
+ import {
6
+ validatePolicyEditCandidateRecord
7
+ } from "./chunk-CIUOICJT.js";
5
8
  import {
6
9
  detectRewardHacking
7
10
  } from "./chunk-ARU2PZFM.js";
@@ -13,7 +16,7 @@ import {
13
16
  } from "./chunk-GGE4NNQT.js";
14
17
  import {
15
18
  callLlm
16
- } from "./chunk-FUCQVFMU.js";
19
+ } from "./chunk-GY4SYVPJ.js";
17
20
  import {
18
21
  ValidationError
19
22
  } from "./chunk-ONWEPEDO.js";
@@ -922,8 +925,14 @@ function pairHoldout(candidate, baseline, scenarioIds, select) {
922
925
  if (!scores) return void 0;
923
926
  const vals = [];
924
927
  for (const s of Object.values(scores)) {
928
+ if (s.failed === true) {
929
+ throw new Error(`pairHoldout: cell '${cellId}' contains a failed judge score`);
930
+ }
925
931
  const v = select(s);
926
- if (typeof v === "number" && Number.isFinite(v)) vals.push(v);
932
+ if (typeof v === "number" && !Number.isFinite(v)) {
933
+ throw new Error(`pairHoldout: cell '${cellId}' contains a non-finite selected score`);
934
+ }
935
+ if (typeof v === "number") vals.push(v);
927
936
  }
928
937
  if (vals.length === 0) return void 0;
929
938
  return vals.reduce((a, b) => a + b, 0) / vals.length;
@@ -942,7 +951,10 @@ function pairHoldout(candidate, baseline, scenarioIds, select) {
942
951
  for (const cellId of candCells) {
943
952
  const b = cellValue(baseline, cellId);
944
953
  const a = cellValue(candidate, cellId);
945
- if (b === void 0 || a === void 0) continue;
954
+ if (b === void 0 && a === void 0) continue;
955
+ if (b === void 0 || a === void 0) {
956
+ throw new Error(`pairHoldout: cell '${cellId}' has a selected score on only one arm`);
957
+ }
946
958
  before.push(b);
947
959
  after.push(a);
948
960
  cellIds.push(cellId);
@@ -1707,6 +1719,65 @@ function parseReflectionResponse(raw, maxProposals) {
1707
1719
  return out;
1708
1720
  }
1709
1721
 
1722
+ // src/campaign/surface-identity.ts
1723
+ import { createHash } from "crypto";
1724
+ var GIT_OBJECT_ID = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
1725
+ var SHA256 = /^sha256:[a-f0-9]{64}$/;
1726
+ function assertCodeSurfaceIdentity(surface) {
1727
+ if (!surface || typeof surface !== "object") {
1728
+ throw new TypeError("CodeSurface must be an object");
1729
+ }
1730
+ const candidate = surface;
1731
+ if (candidate.kind !== "code") throw new TypeError('CodeSurface.kind must be "code"');
1732
+ if (typeof candidate.worktreeRef !== "string" || candidate.worktreeRef.trim().length === 0) {
1733
+ throw new TypeError("CodeSurface.worktreeRef must be a non-empty locator");
1734
+ }
1735
+ if (typeof candidate.baseRef !== "string" || candidate.baseRef.trim().length === 0) {
1736
+ throw new TypeError("CodeSurface.baseRef must be a non-empty ref label");
1737
+ }
1738
+ for (const [field, value] of [
1739
+ ["baseCommit", candidate.baseCommit],
1740
+ ["baseTree", candidate.baseTree],
1741
+ ["candidateCommit", candidate.candidateCommit],
1742
+ ["candidateTree", candidate.candidateTree]
1743
+ ]) {
1744
+ if (typeof value !== "string" || !GIT_OBJECT_ID.test(value)) {
1745
+ throw new TypeError(`CodeSurface.${field} must be a full Git object id`);
1746
+ }
1747
+ }
1748
+ const patch = candidate.patch;
1749
+ if (!patch || typeof patch !== "object" || patch.format !== "git-diff-binary") {
1750
+ throw new TypeError('CodeSurface.patch.format must be "git-diff-binary"');
1751
+ }
1752
+ if (typeof patch.sha256 !== "string" || !SHA256.test(patch.sha256)) {
1753
+ throw new TypeError("CodeSurface.patch.sha256 must be a sha256 digest");
1754
+ }
1755
+ if (!Number.isSafeInteger(patch.byteLength) || patch.byteLength < 0) {
1756
+ throw new TypeError("CodeSurface.patch.byteLength must be a non-negative safe integer");
1757
+ }
1758
+ }
1759
+ function codeSurfaceIdentityMaterial(surface) {
1760
+ assertCodeSurfaceIdentity(surface);
1761
+ return JSON.stringify({
1762
+ schema: "tangle.code-surface.v1",
1763
+ baseCommit: surface.baseCommit,
1764
+ baseTree: surface.baseTree,
1765
+ candidateTree: surface.candidateTree,
1766
+ patch: {
1767
+ format: surface.patch.format,
1768
+ sha256: surface.patch.sha256,
1769
+ byteLength: surface.patch.byteLength
1770
+ }
1771
+ });
1772
+ }
1773
+ function surfaceContentHash(surface) {
1774
+ const material = typeof surface === "string" ? surface : codeSurfaceIdentityMaterial(surface);
1775
+ return `sha256:${createHash("sha256").update(material).digest("hex")}`;
1776
+ }
1777
+ function surfaceHash(surface) {
1778
+ return surfaceContentHash(surface).slice("sha256:".length, "sha256:".length + 16);
1779
+ }
1780
+
1710
1781
  // src/campaign/proposers/gepa.ts
1711
1782
  var REFLECTION_SYSTEM = 'You are an expert prompt engineer performing GEPA-style reflective mutation. You are given a prompt surface, its top trials (preserve what works) and its bottom trials (the evidence to fix). For each proposal, reason in this order before writing the payload: (1) LOCALIZE \u2014 point to the exact span of the current surface responsible for a bottom-trial failure; (2) DIAGNOSE the root cause (a missing rule, an ambiguous instruction, an over-broad directive), not just the symptom; (3) propose the MINIMAL, GENERALIZABLE edit that fixes the whole failure class \u2014 state it as a rule the agent should follow, never a patch memorized to the shown trials (that is overfitting and will not transfer to the held-out set); (4) PRESERVE every instruction the top trials depend on \u2014 do not delete or weaken working guidance. Put this localize\u2192diagnose\u2192fix reasoning in each proposal\'s `rationale`. Output ONLY a JSON object of shape {"proposals":[{"label":string,"rationale":string,"payload":string}]} where each `payload` is the FULL improved surface text. No prose outside the JSON.';
1712
1783
  var COMBINE_SYSTEM = 'You are an expert prompt engineer performing a GEPA "combine complementary lessons" merge. You are given several non-dominated versions of one surface; each is uniquely best on different scenarios. Produce ONE new version that keeps what makes each version strong on its winning scenarios and resolves conflicts in favor of the more general rule. Output ONLY a JSON object of shape {"proposals":[{"label":string,"rationale":string,"payload":string}]} with exactly one proposal whose `payload` is the FULL merged surface text. No prose outside the JSON.';
@@ -1854,10 +1925,11 @@ function validatePreservedSections(candidate, required) {
1854
1925
  }
1855
1926
  function buildEvidence(ctx, evidenceK, baseTarget) {
1856
1927
  const last = ctx.history.at(-1);
1857
- if (!last || last.candidates.length === 0) {
1858
- return { top: [], bottom: [], target: baseTarget };
1859
- }
1860
- const best = [...last.candidates].sort((a, b) => b.composite - a.composite)[0];
1928
+ const currentSurfaceHash = surfaceHash(ctx.currentSurface);
1929
+ const measuredCurrentSurface = ctx.history.flatMap((record) => record.candidates).reverse().find(
1930
+ (candidate) => candidate.surfaceHash === currentSurfaceHash && candidate.eligibleForPromotion !== false
1931
+ );
1932
+ const best = ctx.incumbentOutcome ?? measuredCurrentSurface ?? (last ? [...last.candidates].filter((candidate) => candidate.eligibleForPromotion !== false).sort((a, b) => b.composite - a.composite)[0] : void 0);
1861
1933
  if (!best) return { top: [], bottom: [], target: baseTarget };
1862
1934
  const byScore = [...best.scenarios].sort((a, b) => b.composite - a.composite);
1863
1935
  const toTrace = (s) => ({
@@ -1878,7 +1950,7 @@ function buildEvidence(ctx, evidenceK, baseTarget) {
1878
1950
  function campaignMeanComposite(campaign) {
1879
1951
  const composites = [];
1880
1952
  for (const cell of campaign.cells) {
1881
- const cellComposites = Object.values(cell.judgeScores).map((s) => s.composite);
1953
+ const cellComposites = Object.values(cell.judgeScores).filter((score) => score.failed !== true && Number.isFinite(score.composite)).map((score) => score.composite);
1882
1954
  if (cellComposites.length > 0) {
1883
1955
  composites.push(cellComposites.reduce((a, b) => a + b, 0) / cellComposites.length);
1884
1956
  }
@@ -1891,7 +1963,9 @@ function campaignBreakdown(campaign) {
1891
1963
  const byScenario = /* @__PURE__ */ new Map();
1892
1964
  const notesByScenario = /* @__PURE__ */ new Map();
1893
1965
  for (const cell of campaign.cells) {
1894
- const judgeScores = Object.values(cell.judgeScores);
1966
+ const judgeScores = Object.values(cell.judgeScores).filter(
1967
+ (score) => score.failed !== true && Number.isFinite(score.composite)
1968
+ );
1895
1969
  if (judgeScores.length === 0) continue;
1896
1970
  const cellComposite = judgeScores.reduce((a, s) => a + s.composite, 0) / judgeScores.length;
1897
1971
  const arr = byScenario.get(cell.scenarioId) ?? [];
@@ -1906,6 +1980,7 @@ function campaignBreakdown(campaign) {
1906
1980
  }
1907
1981
  for (const score of judgeScores) {
1908
1982
  for (const [key, value] of Object.entries(score.dimensions)) {
1983
+ if (!Number.isFinite(value)) continue;
1909
1984
  dimSums[key] = (dimSums[key] ?? 0) + value;
1910
1985
  dimCounts[key] = (dimCounts[key] ?? 0) + 1;
1911
1986
  }
@@ -1928,84 +2003,120 @@ function campaignBreakdown(campaign) {
1928
2003
  return { dimensions, scenarios };
1929
2004
  }
1930
2005
 
1931
- // src/campaign/surface-identity.ts
1932
- import { createHash } from "crypto";
1933
- var GIT_OBJECT_ID = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
1934
- var SHA256 = /^sha256:[a-f0-9]{64}$/;
1935
- function assertCodeSurfaceIdentity(surface) {
1936
- if (!surface || typeof surface !== "object") {
1937
- throw new TypeError("CodeSurface must be an object");
1938
- }
1939
- const candidate = surface;
1940
- if (candidate.kind !== "code") throw new TypeError('CodeSurface.kind must be "code"');
1941
- if (typeof candidate.worktreeRef !== "string" || candidate.worktreeRef.trim().length === 0) {
1942
- throw new TypeError("CodeSurface.worktreeRef must be a non-empty locator");
1943
- }
1944
- if (typeof candidate.baseRef !== "string" || candidate.baseRef.trim().length === 0) {
1945
- throw new TypeError("CodeSurface.baseRef must be a non-empty ref label");
1946
- }
1947
- for (const [field, value] of [
1948
- ["baseCommit", candidate.baseCommit],
1949
- ["baseTree", candidate.baseTree],
1950
- ["candidateCommit", candidate.candidateCommit],
1951
- ["candidateTree", candidate.candidateTree]
1952
- ]) {
1953
- if (typeof value !== "string" || !GIT_OBJECT_ID.test(value)) {
1954
- throw new TypeError(`CodeSurface.${field} must be a full Git object id`);
2006
+ // src/campaign/coverage.ts
2007
+ function campaignCoverage(cells, scenarios, reps, requireJudgeScore) {
2008
+ const expectedCellIds = designedCellIds(scenarios, reps);
2009
+ const cellsById = /* @__PURE__ */ new Map();
2010
+ for (const cell of cells) {
2011
+ const matches = cellsById.get(cell.cellId) ?? [];
2012
+ matches.push(cell);
2013
+ cellsById.set(cell.cellId, matches);
2014
+ }
2015
+ const scorableCellIds = [];
2016
+ const unscorableCells = [];
2017
+ for (const cellId of expectedCellIds) {
2018
+ const matches = cellsById.get(cellId) ?? [];
2019
+ if (matches.length === 0) {
2020
+ unscorableCells.push({ cellId, reason: "missing campaign cell" });
2021
+ continue;
2022
+ }
2023
+ if (matches.length > 1) {
2024
+ unscorableCells.push({ cellId, reason: `duplicate campaign cell (${matches.length})` });
2025
+ continue;
2026
+ }
2027
+ const cell = matches[0];
2028
+ const scoreEntries = Object.entries(cell.judgeScores);
2029
+ const successfulScores = scoreEntries.map(([, score]) => score).filter((score) => score.failed !== true && Number.isFinite(score.composite));
2030
+ const nonFiniteScores = scoreEntries.filter(
2031
+ ([, score]) => score.failed !== true && (!Number.isFinite(score.composite) || Object.values(score.dimensions).some((value) => !Number.isFinite(value)))
2032
+ );
2033
+ const reasons = [];
2034
+ if (cell.error) reasons.push(cell.error);
2035
+ if (cell.artifact === null || cell.artifact === void 0) reasons.push("missing artifact");
2036
+ if (!cell.error && requireJudgeScore && successfulScores.length === 0) {
2037
+ reasons.push("no successful finite judge score");
2038
+ }
2039
+ if (scoreEntries.some(([, score]) => score.failed === true)) {
2040
+ reasons.push("judge score marked failed");
2041
+ }
2042
+ if (nonFiniteScores.length > 0) {
2043
+ reasons.push(
2044
+ `non-finite judge score: ${nonFiniteScores.map(([name]) => name).sort().join(", ")}`
2045
+ );
2046
+ }
2047
+ if (reasons.length > 0) {
2048
+ unscorableCells.push({ cellId, reason: reasons.join("; ") });
2049
+ } else {
2050
+ scorableCellIds.push(cellId);
1955
2051
  }
1956
2052
  }
1957
- const patch = candidate.patch;
1958
- if (!patch || typeof patch !== "object" || patch.format !== "git-diff-binary") {
1959
- throw new TypeError('CodeSurface.patch.format must be "git-diff-binary"');
1960
- }
1961
- if (typeof patch.sha256 !== "string" || !SHA256.test(patch.sha256)) {
1962
- throw new TypeError("CodeSurface.patch.sha256 must be a sha256 digest");
1963
- }
1964
- if (!Number.isSafeInteger(patch.byteLength) || patch.byteLength < 0) {
1965
- throw new TypeError("CodeSurface.patch.byteLength must be a non-negative safe integer");
1966
- }
1967
- }
1968
- function codeSurfaceIdentityMaterial(surface) {
1969
- assertCodeSurfaceIdentity(surface);
1970
- return JSON.stringify({
1971
- schema: "tangle.code-surface.v1",
1972
- baseCommit: surface.baseCommit,
1973
- baseTree: surface.baseTree,
1974
- candidateTree: surface.candidateTree,
1975
- patch: {
1976
- format: surface.patch.format,
1977
- sha256: surface.patch.sha256,
1978
- byteLength: surface.patch.byteLength
2053
+ const expected = new Set(expectedCellIds);
2054
+ for (const cell of cells) {
2055
+ if (!expected.has(cell.cellId)) {
2056
+ unscorableCells.push({ cellId: cell.cellId, reason: "unexpected campaign cell" });
1979
2057
  }
1980
- });
2058
+ }
2059
+ return {
2060
+ complete: unscorableCells.length === 0 && scorableCellIds.length === expectedCellIds.length,
2061
+ expectedCellIds,
2062
+ scorableCellIds,
2063
+ unscorableCells
2064
+ };
1981
2065
  }
1982
- function surfaceContentHash(surface) {
1983
- const material = typeof surface === "string" ? surface : codeSurfaceIdentityMaterial(surface);
1984
- return `sha256:${createHash("sha256").update(material).digest("hex")}`;
2066
+ function formatCoverageFailures(coverage) {
2067
+ const shown = coverage.unscorableCells.slice(0, 3).map((cell) => `${cell.cellId}: ${cell.reason}`).join("; ");
2068
+ const remainder = coverage.unscorableCells.length - Math.min(3, coverage.unscorableCells.length);
2069
+ return remainder > 0 ? `${shown}; +${remainder} more` : shown || "unknown coverage failure";
1985
2070
  }
1986
- function surfaceHash(surface) {
1987
- return surfaceContentHash(surface).slice("sha256:".length, "sha256:".length + 16);
2071
+ function designedCellIds(scenarios, reps) {
2072
+ const ids = [];
2073
+ for (const scenario of scenarios) {
2074
+ for (let rep = 0; rep < reps; rep++) ids.push(`${scenario.id}:${rep}`);
2075
+ }
2076
+ return ids;
1988
2077
  }
1989
2078
 
1990
2079
  // src/campaign/presets/run-optimization.ts
1991
2080
  async function runOptimization(opts) {
1992
2081
  const { proposer } = opts;
1993
- const promoteTopK = opts.promoteTopK ?? 2;
1994
2082
  if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
1995
2083
  throw new Error("runOptimization: runDir is required and must be a non-empty string");
1996
2084
  }
2085
+ if (opts.promoteTopK !== void 0 && opts.promoteTopK !== 1) {
2086
+ throw new Error(
2087
+ "runOptimization: promoteTopK must be 1 because the loop has one global incumbent"
2088
+ );
2089
+ }
1997
2090
  const baselineCampaign = await runCampaign({
1998
2091
  ...opts,
1999
2092
  dispatch: (scenario, ctx) => opts.dispatchWithSurface(opts.baselineSurface, scenario, ctx),
2000
2093
  runDir: `${opts.runDir}/baseline`
2001
2094
  });
2095
+ const requireJudgeScore = (opts.judges?.length ?? 0) > 0;
2096
+ const baselineCoverage = campaignCoverage(
2097
+ baselineCampaign.cells,
2098
+ opts.scenarios,
2099
+ opts.reps ?? 1,
2100
+ requireJudgeScore
2101
+ );
2102
+ if (!baselineCoverage.complete) {
2103
+ throw new Error(
2104
+ `runOptimization: baseline is incomplete (${baselineCoverage.scorableCellIds.length}/${baselineCoverage.expectedCellIds.length} designed cells scorable) \u2014 ${formatCoverageFailures(baselineCoverage)}. Refusing to optimize against an incomplete incumbent.`
2105
+ );
2106
+ }
2002
2107
  const generations = [];
2003
2108
  const history = [];
2004
2109
  let currentFindings = opts.findings ?? [];
2005
- let currentSurfaces = [opts.baselineSurface];
2006
2110
  let winnerSurface = opts.baselineSurface;
2007
2111
  let winnerSurfaceHash = surfaceHash(opts.baselineSurface);
2008
2112
  let winnerComposite = campaignMeanComposite(baselineCampaign);
2113
+ const baselineOutcome = toScoredSurfaceOutcome(
2114
+ winnerSurfaceHash,
2115
+ baselineCampaign,
2116
+ baselineCoverage,
2117
+ -1
2118
+ );
2119
+ let winnerOutcome = baselineOutcome;
2009
2120
  let winnerLabel;
2010
2121
  let winnerRationale;
2011
2122
  const scored = [
@@ -2025,13 +2136,20 @@ async function runOptimization(opts) {
2025
2136
  for (let gen = 0; gen < opts.maxGenerations; gen++) {
2026
2137
  if (proposer.decide?.({ history }).stop) break;
2027
2138
  const paretoParents = computeParetoFrontier(scored);
2139
+ const parentSurfaceHash = winnerSurfaceHash;
2140
+ const parentComposite = winnerComposite;
2028
2141
  const proposed = await proposer.propose({
2029
- currentSurface: currentSurfaces[0] ?? opts.baselineSurface,
2142
+ // The mutation anchor is always the best complete surface seen across the
2143
+ // whole run. Exploratory losers remain in history/Pareto evidence, but a
2144
+ // later generation never compounds a candidate already known to regress.
2145
+ currentSurface: winnerSurface,
2030
2146
  history,
2031
2147
  findings: currentFindings,
2032
2148
  populationSize: opts.populationSize,
2033
2149
  generation: gen,
2034
2150
  signal: new AbortController().signal,
2151
+ baselineOutcome,
2152
+ incumbentOutcome: winnerOutcome,
2035
2153
  report: opts.report,
2036
2154
  dataset: opts.labeledStore && opts.labeledStore !== "off" ? opts.labeledStore : void 0,
2037
2155
  maxImprovementShots: opts.maxImprovementShots,
@@ -2042,7 +2160,7 @@ async function runOptimization(opts) {
2042
2160
  );
2043
2161
  const surfaceResults = [];
2044
2162
  for (let i = 0; i < candidates.length; i++) {
2045
- const { surface, label, rationale } = candidates[i];
2163
+ const { surface, label, rationale, candidateRecord } = candidates[i];
2046
2164
  const hash = surfaceHash(surface);
2047
2165
  const campaign = await runCampaign({
2048
2166
  ...opts,
@@ -2050,21 +2168,43 @@ async function runOptimization(opts) {
2050
2168
  runDir: `${opts.runDir}/gen-${gen}/candidate-${i}`
2051
2169
  });
2052
2170
  const composite = campaignMeanComposite(campaign);
2053
- surfaceResults.push({ surfaceHash: hash, surface, label, rationale, campaign, composite });
2054
- scored.push(
2055
- toParetoParent(surface, hash, campaign, gen, label || void 0, rationale || void 0)
2171
+ const coverage = campaignCoverage(
2172
+ campaign.cells,
2173
+ opts.scenarios,
2174
+ opts.reps ?? 1,
2175
+ requireJudgeScore
2056
2176
  );
2177
+ surfaceResults.push({
2178
+ surfaceHash: hash,
2179
+ surface,
2180
+ label,
2181
+ rationale,
2182
+ ...candidateRecord ? { candidateRecord } : {},
2183
+ campaign,
2184
+ composite,
2185
+ coverage
2186
+ });
2187
+ if (coverage.complete) {
2188
+ scored.push(
2189
+ toParetoParent(surface, hash, campaign, gen, label || void 0, rationale || void 0)
2190
+ );
2191
+ }
2057
2192
  }
2058
- surfaceResults.sort((a, b) => b.composite - a.composite);
2059
- const promoted = surfaceResults.slice(0, promoteTopK);
2060
- currentSurfaces = promoted.map((p) => p.surface);
2061
- const top = surfaceResults[0];
2062
- if (top && top.composite > winnerComposite) {
2063
- winnerSurface = top.surface;
2064
- winnerSurfaceHash = top.surfaceHash;
2065
- winnerComposite = top.composite;
2066
- winnerLabel = top.label || void 0;
2067
- winnerRationale = top.rationale || void 0;
2193
+ surfaceResults.sort((a, b) => {
2194
+ if (a.coverage.complete !== b.coverage.complete) return a.coverage.complete ? -1 : 1;
2195
+ return b.composite - a.composite;
2196
+ });
2197
+ const eligibleResults = surfaceResults.filter((result) => result.coverage.complete);
2198
+ const top = eligibleResults[0];
2199
+ const promoted = top && top.composite > winnerComposite ? [top] : [];
2200
+ if (promoted[0]) {
2201
+ const top2 = promoted[0];
2202
+ winnerSurface = top2.surface;
2203
+ winnerSurfaceHash = top2.surfaceHash;
2204
+ winnerComposite = top2.composite;
2205
+ winnerOutcome = toScoredSurfaceOutcome(top2.surfaceHash, top2.campaign, top2.coverage, gen);
2206
+ winnerLabel = top2.label || void 0;
2207
+ winnerRationale = top2.rationale || void 0;
2068
2208
  }
2069
2209
  const record = {
2070
2210
  generationIndex: gen,
@@ -2074,11 +2214,21 @@ async function runOptimization(opts) {
2074
2214
  surfaceHash: s.surfaceHash,
2075
2215
  composite: s.composite,
2076
2216
  ci95: [s.composite, s.composite],
2217
+ parentSurfaceHash,
2218
+ parentComposite,
2219
+ ...s.coverage.complete ? { observedDeltaFromParent: s.composite - parentComposite } : {},
2220
+ eligibleForPromotion: s.coverage.complete,
2221
+ coverage: {
2222
+ expectedCells: s.coverage.expectedCellIds.length,
2223
+ scorableCells: s.coverage.scorableCellIds.length,
2224
+ unscorableCells: s.coverage.unscorableCells
2225
+ },
2077
2226
  dimensions: breakdown.dimensions,
2078
2227
  scenarios: breakdown.scenarios
2079
2228
  };
2080
2229
  if (s.label) candidate.label = s.label;
2081
2230
  if (s.rationale) candidate.rationale = s.rationale;
2231
+ if (s.candidateRecord) candidate.candidateRecord = s.candidateRecord;
2082
2232
  return candidate;
2083
2233
  }),
2084
2234
  promoted: promoted.map((p) => p.surfaceHash)
@@ -2156,6 +2306,21 @@ function computeParetoFrontier(scored) {
2156
2306
  }));
2157
2307
  return paretoFrontier(scored, objectives).frontier;
2158
2308
  }
2309
+ function toScoredSurfaceOutcome(surfaceHash2, campaign, coverage, generation) {
2310
+ const breakdown = campaignBreakdown(campaign);
2311
+ return {
2312
+ split: "search",
2313
+ generation,
2314
+ surfaceHash: surfaceHash2,
2315
+ composite: campaignMeanComposite(campaign),
2316
+ dimensions: breakdown.dimensions,
2317
+ scenarios: breakdown.scenarios,
2318
+ coverage: {
2319
+ expectedCells: coverage.expectedCellIds.length,
2320
+ scorableCells: coverage.scorableCellIds.length
2321
+ }
2322
+ };
2323
+ }
2159
2324
 
2160
2325
  // src/campaign/presets/run-improvement-loop.ts
2161
2326
  var DEFAULT_DISPATCH_TIMEOUT_MS = 6e5;
@@ -2200,15 +2365,23 @@ async function runImprovementLoop(opts) {
2200
2365
  dispatch: (scenario, ctx) => opts.dispatchWithSurface(optimization.winnerSurface, scenario, ctx),
2201
2366
  runDir: `${opts.runDir}/holdout-winner`
2202
2367
  });
2203
- const scorable = (r) => r.cells.filter((c) => !c.error && c.artifact != null);
2204
- const baseScorable = scorable(baselineOnHoldout);
2205
- const winnerScorable = scorable(winnerOnHoldout);
2206
- if (baseScorable.length === 0 || winnerScorable.length === 0) {
2207
- const firstErr = (r) => r.cells.find((c) => c.error)?.error ?? "unknown";
2208
- throw new Error(
2209
- `runImprovementLoop: holdout produced no scorable cells (baseline ${baseScorable.length}/${baselineOnHoldout.cells.length}, winner ${winnerScorable.length}/${winnerOnHoldout.cells.length}) \u2014 every holdout dispatch or judge failed. Refusing to emit a gate decision over an empty holdout. First baseline error: "${firstErr(baselineOnHoldout)}"; first winner error: "${firstErr(winnerOnHoldout)}".`
2368
+ const requireJudgeScore = (opts.judges?.length ?? 0) > 0;
2369
+ const reps = opts.reps ?? 1;
2370
+ const assertCompleteHoldout = (arm, campaign) => {
2371
+ const coverage = campaignCoverage(
2372
+ campaign.cells,
2373
+ opts.holdoutScenarios,
2374
+ reps,
2375
+ requireJudgeScore
2210
2376
  );
2211
- }
2377
+ if (!coverage.complete) {
2378
+ throw new Error(
2379
+ `runImprovementLoop: ${arm} holdout is incomplete (${coverage.scorableCellIds.length}/${coverage.expectedCellIds.length} designed cells scorable) \u2014 ${formatCoverageFailures(coverage)}. Refusing to compare unequal holdout results.`
2380
+ );
2381
+ }
2382
+ };
2383
+ assertCompleteHoldout("baseline", baselineOnHoldout);
2384
+ assertCompleteHoldout("winner", winnerOnHoldout);
2212
2385
  const candidateArtifacts = /* @__PURE__ */ new Map();
2213
2386
  const baselineArtifacts = /* @__PURE__ */ new Map();
2214
2387
  const judgeScores = /* @__PURE__ */ new Map();
@@ -2232,6 +2405,7 @@ async function runImprovementLoop(opts) {
2232
2405
  dispatch: (scenario, ctx) => opts.dispatchWithSurface(neutralizedSurface, scenario, ctx),
2233
2406
  runDir: `${opts.runDir}/holdout-neutralized`
2234
2407
  });
2408
+ assertCompleteHoldout("neutralized", neutralizedOnHoldout);
2235
2409
  neutralizedArtifacts = /* @__PURE__ */ new Map();
2236
2410
  neutralizedJudgeScores = /* @__PURE__ */ new Map();
2237
2411
  for (const cell of neutralizedOnHoldout.cells) {
@@ -2335,28 +2509,103 @@ function meanHoldoutComposite(campaign) {
2335
2509
  function buildLoopProvenanceRecord(args) {
2336
2510
  const integrity = summarizeBackendIntegrity(args.workerRecords);
2337
2511
  const models = [...new Set(args.workerRecords.map((r) => r.model))].sort();
2512
+ if (!Number.isFinite(args.baselineSearchComposite)) {
2513
+ throw new Error("buildLoopProvenanceRecord: baselineSearchComposite must be finite");
2514
+ }
2338
2515
  const candidates = [];
2516
+ let incumbentSurfaceHash = surfaceHash(args.baselineSurface);
2517
+ let incumbentComposite = args.baselineSearchComposite;
2518
+ let previousGeneration = -1;
2339
2519
  for (const gen of args.generations) {
2520
+ if (!Number.isSafeInteger(gen.generationIndex) || gen.generationIndex <= previousGeneration) {
2521
+ throw new Error(
2522
+ "buildLoopProvenanceRecord: generation indices must be strictly increasing integers"
2523
+ );
2524
+ }
2525
+ previousGeneration = gen.generationIndex;
2526
+ if (new Set(gen.promoted).size !== gen.promoted.length || gen.promoted.length > 1) {
2527
+ throw new Error(
2528
+ "buildLoopProvenanceRecord: each generation may promote at most one candidate"
2529
+ );
2530
+ }
2340
2531
  const promotedSet = new Set(gen.promoted);
2341
2532
  const surfaceByHash = new Map(gen.surfaces.map((s) => [s.surfaceHash, s.surface]));
2533
+ const candidateByHash = new Map(
2534
+ gen.candidates.map((candidate) => [candidate.surfaceHash, candidate])
2535
+ );
2536
+ if (candidateByHash.size !== gen.candidates.length) {
2537
+ throw new Error("buildLoopProvenanceRecord: duplicate candidate surface hash");
2538
+ }
2539
+ if (surfaceByHash.size !== gen.surfaces.length) {
2540
+ throw new Error("buildLoopProvenanceRecord: duplicate candidate surface entry");
2541
+ }
2542
+ if (surfaceByHash.size !== candidateByHash.size) {
2543
+ throw new Error(
2544
+ "buildLoopProvenanceRecord: every measured candidate requires exactly one surface"
2545
+ );
2546
+ }
2547
+ for (const promotedHash2 of promotedSet) {
2548
+ if (!candidateByHash.has(promotedHash2)) {
2549
+ throw new Error("buildLoopProvenanceRecord: promoted hash has no measured candidate");
2550
+ }
2551
+ }
2342
2552
  for (const c of gen.candidates) {
2553
+ validateCandidateMeasurement(
2554
+ c,
2555
+ incumbentSurfaceHash,
2556
+ incumbentComposite,
2557
+ promotedSet.has(c.surfaceHash)
2558
+ );
2343
2559
  const surface = surfaceByHash.get(c.surfaceHash);
2560
+ if (surface === void 0) {
2561
+ throw new Error("buildLoopProvenanceRecord: measured candidate is missing its surface");
2562
+ }
2563
+ if (surfaceHash(surface) !== c.surfaceHash) {
2564
+ throw new Error(
2565
+ "buildLoopProvenanceRecord: candidate surface hash does not match its surface bytes"
2566
+ );
2567
+ }
2344
2568
  const entry = {
2345
2569
  generation: gen.generationIndex,
2346
2570
  surfaceHash: c.surfaceHash,
2347
- contentHash: surface !== void 0 ? surfaceContentHash(surface) : `sha256:${c.surfaceHash}`,
2571
+ contentHash: surfaceContentHash(surface),
2572
+ parentSurfaceHash: c.parentSurfaceHash,
2573
+ parentComposite: c.parentComposite,
2574
+ eligibleForPromotion: c.eligibleForPromotion,
2575
+ coverage: {
2576
+ expectedCells: c.coverage.expectedCells,
2577
+ scorableCells: c.coverage.scorableCells,
2578
+ unscorableCells: c.coverage.unscorableCells.map((cell) => ({ ...cell }))
2579
+ },
2348
2580
  composite: c.composite,
2349
2581
  promoted: promotedSet.has(c.surfaceHash)
2350
2582
  };
2351
2583
  if (c.label) entry.label = c.label;
2352
2584
  if (c.rationale) entry.rationale = c.rationale;
2585
+ if (c.candidateRecord) {
2586
+ entry.candidateRecord = validatePolicyEditCandidateRecord(c.candidateRecord);
2587
+ }
2588
+ if (c.observedDeltaFromParent !== void 0) {
2589
+ entry.observedDeltaFromParent = c.observedDeltaFromParent;
2590
+ }
2353
2591
  candidates.push(entry);
2354
2592
  }
2593
+ const promotedHash = gen.promoted[0];
2594
+ if (promotedHash) {
2595
+ const promoted = candidateByHash.get(promotedHash);
2596
+ incumbentSurfaceHash = promoted.surfaceHash;
2597
+ incumbentComposite = promoted.composite;
2598
+ }
2599
+ }
2600
+ if (surfaceHash(args.winnerSurface) !== incumbentSurfaceHash) {
2601
+ throw new Error(
2602
+ "buildLoopProvenanceRecord: winner surface does not match the final promoted incumbent"
2603
+ );
2355
2604
  }
2356
2605
  const baselineHoldoutComposite = meanHoldoutComposite(args.baselineOnHoldout);
2357
2606
  const winnerHoldoutComposite = meanHoldoutComposite(args.winnerOnHoldout);
2358
2607
  const record = {
2359
- schema: "tangle.loop-provenance.v2",
2608
+ schema: "tangle.loop-provenance.v3",
2360
2609
  runId: args.runId,
2361
2610
  runDir: args.runDir,
2362
2611
  timestamp: args.timestamp,
@@ -2364,6 +2613,7 @@ function buildLoopProvenanceRecord(args) {
2364
2613
  winnerContentHash: surfaceContentHash(args.winnerSurface),
2365
2614
  diff: args.diff,
2366
2615
  candidates,
2616
+ baselineSearchComposite: args.baselineSearchComposite,
2367
2617
  gate: {
2368
2618
  decision: args.gate.decision,
2369
2619
  reasons: args.gate.reasons,
@@ -2391,6 +2641,77 @@ function buildLoopProvenanceRecord(args) {
2391
2641
  if (args.winnerRationale) record.winnerRationale = args.winnerRationale;
2392
2642
  return record;
2393
2643
  }
2644
+ function validateCandidateMeasurement(candidate, expectedParentHash, expectedParentComposite, promoted) {
2645
+ if (!Number.isFinite(candidate.composite)) {
2646
+ throw new Error("buildLoopProvenanceRecord: candidate composite must be finite");
2647
+ }
2648
+ if (!candidate.parentSurfaceHash || !/^[a-f0-9]{16}$/.test(candidate.parentSurfaceHash)) {
2649
+ throw new Error(
2650
+ "buildLoopProvenanceRecord: parentSurfaceHash must be 16 lowercase hex characters"
2651
+ );
2652
+ }
2653
+ if (candidate.parentSurfaceHash !== expectedParentHash) {
2654
+ throw new Error("buildLoopProvenanceRecord: candidate parent does not match the incumbent");
2655
+ }
2656
+ if (candidate.parentComposite === void 0 || !Number.isFinite(candidate.parentComposite) || Math.abs(candidate.parentComposite - expectedParentComposite) > 1e-12) {
2657
+ throw new Error(
2658
+ "buildLoopProvenanceRecord: candidate parentComposite does not match the incumbent"
2659
+ );
2660
+ }
2661
+ if (candidate.eligibleForPromotion === void 0 || !candidate.coverage) {
2662
+ throw new Error(
2663
+ "buildLoopProvenanceRecord: candidate measurement requires eligibility and coverage"
2664
+ );
2665
+ }
2666
+ if (candidate.observedDeltaFromParent !== void 0) {
2667
+ if (!Number.isFinite(candidate.observedDeltaFromParent)) {
2668
+ throw new Error("buildLoopProvenanceRecord: observedDeltaFromParent must be finite");
2669
+ }
2670
+ if (candidate.eligibleForPromotion !== true) {
2671
+ throw new Error(
2672
+ "buildLoopProvenanceRecord: observedDeltaFromParent requires a complete eligible candidate and parentSurfaceHash"
2673
+ );
2674
+ }
2675
+ }
2676
+ const coverage = candidate.coverage;
2677
+ if (!Number.isSafeInteger(coverage.expectedCells) || coverage.expectedCells <= 0 || !Number.isSafeInteger(coverage.scorableCells) || coverage.scorableCells < 0 || coverage.scorableCells > coverage.expectedCells) {
2678
+ throw new Error("buildLoopProvenanceRecord: invalid candidate coverage denominator");
2679
+ }
2680
+ const unscorableIds = /* @__PURE__ */ new Set();
2681
+ for (const failure of coverage.unscorableCells) {
2682
+ if (typeof failure.cellId !== "string" || failure.cellId.length === 0 || typeof failure.reason !== "string" || failure.reason.length === 0 || unscorableIds.has(failure.cellId)) {
2683
+ throw new Error("buildLoopProvenanceRecord: invalid candidate coverage failures");
2684
+ }
2685
+ unscorableIds.add(failure.cellId);
2686
+ }
2687
+ if (coverage.expectedCells - coverage.scorableCells !== coverage.unscorableCells.length) {
2688
+ throw new Error(
2689
+ "buildLoopProvenanceRecord: candidate coverage counts do not match its failures"
2690
+ );
2691
+ }
2692
+ const complete = coverage.scorableCells === coverage.expectedCells && coverage.unscorableCells.length === 0;
2693
+ if (candidate.eligibleForPromotion !== void 0 && candidate.eligibleForPromotion !== complete) {
2694
+ throw new Error(
2695
+ "buildLoopProvenanceRecord: candidate eligibility contradicts its coverage receipt"
2696
+ );
2697
+ }
2698
+ if (complete) {
2699
+ if (candidate.observedDeltaFromParent === void 0) {
2700
+ throw new Error(
2701
+ "buildLoopProvenanceRecord: complete candidate is missing observedDeltaFromParent"
2702
+ );
2703
+ }
2704
+ const recomputed = candidate.composite - candidate.parentComposite;
2705
+ if (Math.abs(candidate.observedDeltaFromParent - recomputed) > 1e-12) {
2706
+ throw new Error("buildLoopProvenanceRecord: observed delta does not match measured scores");
2707
+ }
2708
+ } else if (candidate.observedDeltaFromParent !== void 0) {
2709
+ throw new Error("buildLoopProvenanceRecord: incomplete candidate cannot carry observed delta");
2710
+ }
2711
+ if (promoted && (!complete || (candidate.observedDeltaFromParent ?? 0) <= 0)) {
2712
+ throw new Error("buildLoopProvenanceRecord: promoted candidate must improve the incumbent");
2713
+ }
2714
+ }
2394
2715
  var DECISION_OK = ["ship"];
2395
2716
  function hashId(parts) {
2396
2717
  return createHash2("sha256").update(parts.join(":")).digest("hex");
@@ -2415,6 +2736,7 @@ function loopProvenanceSpans(record, opts = {}) {
2415
2736
  "tangle.runDir": record.runDir,
2416
2737
  "tangle.baselineContentHash": record.baselineContentHash,
2417
2738
  "tangle.winnerContentHash": record.winnerContentHash,
2739
+ "tangle.baselineSearchComposite": record.baselineSearchComposite,
2418
2740
  "tangle.heldOutLift": record.heldOutLift,
2419
2741
  "tangle.gateDecision": record.gate.decision,
2420
2742
  "tangle.backendVerdict": record.backend.verdict,
@@ -2432,7 +2754,7 @@ function loopProvenanceSpans(record, opts = {}) {
2432
2754
  }
2433
2755
  for (const [generation, cands] of [...byGen.entries()].sort((a, b) => a[0] - b[0])) {
2434
2756
  const genSpanId = hashId(["gen", record.runId, String(generation)]).slice(0, 16);
2435
- const bestComposite = cands.reduce((m, c) => Math.max(m, c.composite), 0);
2757
+ const bestComposite = Math.max(...cands.map((candidate) => candidate.composite));
2436
2758
  spans.push({
2437
2759
  traceId,
2438
2760
  spanId: genSpanId,
@@ -2460,9 +2782,21 @@ function loopProvenanceSpans(record, opts = {}) {
2460
2782
  "tangle.generation": generation,
2461
2783
  "tangle.surfaceHash": c.surfaceHash,
2462
2784
  "tangle.contentHash": c.contentHash,
2785
+ "tangle.parentSurfaceHash": c.parentSurfaceHash,
2786
+ "tangle.parentComposite": c.parentComposite,
2463
2787
  "tangle.composite": c.composite,
2788
+ "tangle.eligibleForPromotion": c.eligibleForPromotion,
2789
+ "tangle.expectedCells": c.coverage.expectedCells,
2790
+ "tangle.scorableCells": c.coverage.scorableCells,
2791
+ "tangle.unscorableCells": c.coverage.unscorableCells.length,
2464
2792
  "tangle.promoted": c.promoted
2465
2793
  };
2794
+ if (c.observedDeltaFromParent !== void 0) {
2795
+ attributes["tangle.observedDeltaFromParent"] = c.observedDeltaFromParent;
2796
+ }
2797
+ if (c.candidateRecord) {
2798
+ attributes["tangle.policyEditId"] = c.candidateRecord.policyEdit.editId;
2799
+ }
2466
2800
  if (c.label) attributes["tangle.candidateLabel"] = c.label;
2467
2801
  if (c.rationale) attributes["tangle.candidateRationale"] = c.rationale;
2468
2802
  spans.push({
@@ -2613,15 +2947,15 @@ export {
2613
2947
  buildReflectionPrompt,
2614
2948
  renderAnalystEvidence,
2615
2949
  parseReflectionResponse,
2950
+ assertCodeSurfaceIdentity,
2951
+ codeSurfaceIdentityMaterial,
2952
+ surfaceContentHash,
2953
+ surfaceHash,
2616
2954
  gepaProposer,
2617
2955
  extractH2Sections,
2618
2956
  countSentenceEdits,
2619
2957
  campaignMeanComposite,
2620
2958
  campaignBreakdown,
2621
- assertCodeSurfaceIdentity,
2622
- codeSurfaceIdentityMaterial,
2623
- surfaceContentHash,
2624
- surfaceHash,
2625
2959
  runOptimization,
2626
2960
  runImprovementLoop,
2627
2961
  defaultRenderDiff,
@@ -2633,4 +2967,4 @@ export {
2633
2967
  provenanceSpansPath,
2634
2968
  emitLoopProvenance
2635
2969
  };
2636
- //# sourceMappingURL=chunk-N6MTC3GK.js.map
2970
+ //# sourceMappingURL=chunk-3274WNK7.js.map