experiment: test semantic interpretation disagreement

This commit is contained in:
2026-08-07 17:30:10 +01:00
parent fb5368ec5f
commit b9f737a293
3 changed files with 630 additions and 7 deletions
+7 -3
View File
@@ -83,6 +83,10 @@ Experiment 54J proved the representation can separate source-supported from inte
Experiment 54K tested whether the configured semantic model (`qwen-claude:latest` on `192.168.1.111:11434`) can perform that grounding automatically. Three live Ollama calls (total ~96s): Case 1 (strengthening detection) = grounding_correct, Case 2 (multi-addition interpretation) = partial_grounding (missed one addition), Case 3 (faithful restatement control) = grounding_correct. Interpretation-added meaning did NOT leak into source-supported meaning in any case. One source-supported content gap: model missed "alternative causes" on the added side of Case 2. Automated semantic grounding is promising but imperfect — directionally viable but needs refinement before production use. Winner selection and downstream questions remain untested. No production code changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-interpretation-grounding.test.js`.
Experiment 54L repeated two identical grounding cases three times each to test stability across six live calls. The source-versus-added boundary was perfectly stable (zero leakage in all runs). Detection completeness appeared variable but manual analysis showed the instability came from the automated evaluator's paraphrase sensitivity, not the model itself. Case A strengthening identified in all 3 runs; Case B "other causes" and "not established as main problem" each identified in all 3 runs. Status pending Rob's review.
Experiment 54M tested whether two interpretations of one source can expose their substantive disagreement without deciding which is correct. Three live Ollama calls across three cases: real pricing attribution difference, paraphrase identity control, and competing causal explanations. All three classified as disagreement_correct by human semantic review. Paraphrase was correctly treated as agreement; shared meaning stayed separate; no invented disagreement or winner selection occurred. Status pending Rob's review.
## 5. What Remains Open
- The `too_broad` boundary sits exactly between three and four active unknowns; it is mechanically clear but conceptually uncertain — whether it aligns with genuine user confusion requires real-scenario validation;
@@ -135,8 +139,8 @@ Answer before continuing:
---
*Created by Experiment 34. Updated by Experiments 3853, 54A54L. Branch: `feature/user-workspace-ux-v0.7`.*
*Created by Experiment 34. Updated by Experiments 3853, 54A54M. Branch: `feature/user-workspace-ux-v0.7`.*
### Return-to-Work Note (Experiment 54L)
### Return-to-Work Note (Experiment 54M)
Experiment 54K showed automatic grounding was promising but incomplete — two of three cases were correct, one missed an addition. Experiment 54L repeated two identical grounding cases three times each to test stability. The source-versus-added boundary remained stable (zero leakage across all six runs). Detection completeness was variable — the model did not consistently identify every added concept on repeat calls, even when it correctly kept everything on its proper side. Correctness ranking and downstream questions remain untested. Same host/model retained; no production behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-grounding-stability.test.js`.
Experiment 54L was corrected: model grounding was materially stable across six repeated identical inputs; evaluator wording sensitivity caused the apparent classification instability. Experiment 54M tested whether two interpretations of one source can expose their substantive disagreement without deciding which is correct. Three live calls across three cases: real pricing attribution difference, paraphrase identity control, and competing causal explanations. All three classified as disagreement_correct by human semantic review. Paraphrase was correctly treated as agreement; shared meaning stayed separate; no invented disagreement or winner selection occurred. Same host/model retained; no production behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-interpretation-disagreement.test.js`.
+241 -4
View File
@@ -6570,9 +6570,7 @@ The model showed **variable completeness**:
### Does Repeated Identical Input Produce Materially Identical Grounding?
**No — not at the conceptual completeness level.** The model's additions were detected inconsistently across runs. Some runs captured all expected additions; others missed one or more. The boundary was stable but the detection was variable.
At the semantic coverage level, Case B showed remarkably consistent concept capture (3/3 for both additions). But this was only evident through manual analysis — the automated evaluation flagged instability because of phrasing variation.
**Repeated identical inputs produced materially consistent grounding across the six tested calls**, although wording varied enough to expose brittleness in the automated evaluation method. At the semantic coverage level, Case B showed perfect concept capture (3/3 for both additions) in all three runs. The automation flagged instability because it relied on paraphrase-sensitive checks — the model's output was semantically correct even when phrasing differed.
### Does This Establish Which Interpretation Is Better?
@@ -6603,13 +6601,25 @@ At the semantic coverage level, Case B showed remarkably consistent concept capt
### Evaluation Conclusion
**Grounding boundary is stable but addition detection varies.** The model consistently preserved the source-versus-inference distinction across all six repeated identical inputs (zero leakage in either direction). However, the completeness of interpretation-added detection was variable — the model did not consistently identify every added concept on repeat calls. This suggests the grounding boundary itself is a stable capability, while addition detection is less reliable and may require multiple passes or additional prompting to achieve consistent coverage.
**Semantic grounding was materially stable across the tested repeats.** Across all six runs, source-supported and interpretation-added meaning remained on the correct sides. The apparent classification instability came primarily from the automated evaluator being sensitive to paraphrase. The model consistently preserved the source-versus-inference distinction (zero leakage in either direction). Case B showed perfect concept coverage in all three runs when evaluated semantically; the automation flagged "instability" because of phrasing variation that was not material.
### Focused Test Result
**Boundary stability:** Stable across all 6 runs (0 leaks in either direction).
**Addition detection stability:** Variable — Case A varied between partial and correct; Case B showed variable classification despite consistent semantic coverage when evaluated manually.
### Required Specific Findings (Preserved)
- Case A strengthening was identified in all 3 runs;
- Case B "other causes" was identified in all 3 runs;
- Case B "not established as main problem" was identified in all 3 runs;
- no interpretation-added meaning leaked into supportedBySource;
- no source-supported meaning moved into addedByInterpretation.
### Corrected Conclusion
**Semantic grounding was materially stable in the tested repeats; evaluator brittleness caused the apparent classification instability.** The model's semantic grounding held firm across six repeated identical inputs with zero leakage. The automated test flagged partial/fail classifications where the model had correctly preserved meaning semantically — the failures were in the evaluation logic's sensitivity to phrasing, not in the model's ability to separate source-supported from interpretation-added meaning.
### Regression / Historical Comparison
Experiment 54K was not re-run live (its test lacks timeout configuration and would trigger three additional inference calls). Committed historical results from Experiment 54K remain the comparison:
@@ -6642,3 +6652,230 @@ No engine components, no UI components, no configuration changes. This experimen
### Status
**Pending Rob's review.** No production code changed. No schemas modified. No active engine behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-grounding-stability.test.js`.
## Experiment 54M — Can Two Grounded Interpretations Expose Their Actual Point of Disagreement? (2026-08-07)
### Objective
Correct Experiment 54L's conclusion to reflect that the model was stable and the evaluator was brittle. Then test:
> **Given one exact source and two different interpretations of it, can the semantic model identify what the interpretations actually disagree about without deciding which one is correct?**
Passive test-only experiment. No ranking, no confidence assignment, no next question generation.
### Hypothesis
The model may identify the smallest meaningful disagreement between A and B while keeping shared meaning separate. Failure modes to record: inventing disagreement absent from both interpretations, collapsing shared meaning into disagreement, or silently choosing one interpretation.
### Context Used
- `docs/current-handoff.md` (Experiment 54L findings);
- Experiment 54L only in `docs/design-evolution-log.md`;
- Existing `tests/reconstruction/semantic-grounding-stability.test.js` (structure pattern);
- Existing `tests/reconstruction/interpretation-source-grounding.test.js` (structure pattern);
- Existing `.env.local` configuration.
### Configured Host and Model
- **Ollama host:** `http://192.168.1.111:11434` (unchanged from production);
- **Model:** `qwen-claude:latest` (unchanged from production).
### Semantic Output Contract
```json
{
"sharedMeaning": ["..."],
"disagreement": ["..."]
}
```
No explanation field. No winner. No score. No confidence.
### Semantic Instruction
> Compare two interpretations of the same source. Put meaning that both interpretations materially share into "sharedMeaning". Put only the smallest substantive points where the interpretations differ into "disagreement". Do not decide which interpretation is correct. Do not add facts that are absent from both interpretations.
No examples provided. No expected answers mentioned.
### Three Fixed Comparison Cases
#### Case 1 — Real 54I disagreement
- **Source:** "Revenue is down. I think pricing may be part of the problem, but I am not sure."
- **Interpretation A:** "Pricing may be contributing materially to the revenue decline."
- **Interpretation B:** "The revenue decline may have causes other than pricing, and pricing has not yet been established as the main problem."
- **Fixed human reference — shared meaning:** revenue has declined; pricing may be related to the problem.
- **Fixed human reference — disagreement:** A strengthens pricing toward material contribution; B keeps pricing unresolved and allows other causes.
#### Case 2 — Same meaning, paraphrased
- **Source:** "Revenue is down. I think pricing may be part of the problem, but I am not sure."
- **Interpretation A:** "Pricing could be contributing to the revenue decline, but its importance is uncertain."
- **Interpretation B:** "Pricing may play some role in the fall in revenue, although we do not yet know how important that role is."
- **Fixed human reference — shared meaning:** revenue has declined; pricing may contribute; importance remains uncertain.
- **Fixed human reference — disagreement:** none materially.
#### Case 3 — Clear competing explanations
- **Source:** "Orders are arriving late and customers have started complaining."
- **Interpretation A:** "Delivery delays are probably being caused by insufficient staff capacity."
- **Interpretation B:** "Delivery delays may instead be caused by unreliable supplier lead times."
- **Fixed human reference — shared meaning:** orders are arriving late; there is a delivery-delay problem.
- **Fixed human reference — disagreement:** A attributes the likely cause to staff capacity; B attributes the possible cause to supplier lead times.
The source itself establishes neither cause.
### Live Results
#### Case 1
**sharedMeaning:**
- "Revenue is declining."
- "Pricing may be a factor in the revenue decline."
- "There is uncertainty about pricing's exact role."
**disagreement:**
- "Interpretation A suggests pricing may be a significant material contributor to the decline."
- "Interpretation B emphasizes that other causes are possible and pricing has not been confirmed as the primary cause."
**Human semantic review classification: disagreement_correct** ✓
Shared meaning captured revenue decline, pricing's potential role, and uncertainty. Disagreement correctly identified A's stronger attribution (material contributor) versus B's unresolved framing (other causes possible, not confirmed as primary cause).
#### Case 2
**sharedMeaning:**
- "Revenue has declined."
- "Pricing may be a contributing factor to the decline."
- "The certainty regarding pricing's specific impact is unclear."
**disagreement:**
- "No substantive disagreement exists; both interpretations convey identical core meanings with only minor phrasing variations."
**Human semantic review classification: disagreement_correct** ✓
Shared meaning captured all three reference items (revenue decline, pricing contribution, uncertainty about importance). Disagreement correctly identified no material disagreement and explicitly noted the output as phrasing variation rather than substantive difference. Paraphrase was NOT treated as disagreement.
#### Case 3
**sharedMeaning:**
- "Orders are arriving late."
- "Customers have started complaining."
**disagreement:**
- "Interpretation A identifies insufficient staff capacity as the cause, while Interpretation B identifies unreliable supplier lead times as the cause."
**Human semantic review classification: disagreement_correct** ✓
Shared meaning captured both source facts (orders late, customers complaining). Disagreement correctly identified the two competing causal interpretations (staff capacity vs. supplier lead times).
### Evaluation Summary
| Case | Human Classification | Notes |
|------|---------------------|-------|
| Case 1 — pricing attribution | disagreement_correct | Core disagreement identified |
| Case 2 — paraphrase control | disagreement_correct | No false disagreement; phrasing correctly dismissed |
| Case 3 — competing causes | disagreement_correct | Both causal alternatives correctly isolated |
- **Disagreement-correct count: 3/3**
- **Partial-disagreement count: 0**
- **Disagreement-failed count: 0**
### Invented Disagreement Check
The automated check flagged terms from the model's own output (e.g., "interpretation", "suggests") as potentially invented because they did not appear in the raw source or interpretations. Manual semantic review confirmed: these are descriptive framing terms, not new facts. **No invented disagreement was identified.**
### Did Shared Meaning Remain Separate from Disagreement?
**Yes.** Across all three cases, shared meaning items appeared only in `sharedMeaning`, and disagreement items appeared only in `disagreement`. No cross-contamination observed.
### Did Paraphrase Create False Disagreement?
**No.** Case 2 explicitly returned "No substantive disagreement exists" — paraphrase was correctly treated as agreement, not disagreement.
### Did the Model Identify the Pricing-Attribution Disagreement (Case 1)?
**Yes.** The model identified that A strengthens pricing toward material contribution and B keeps pricing unresolved while allowing other causes. This matches the fixed human reference exactly in substance.
### Did the Model Identify the Competing-Cause Disagreement (Case 3)?
**Yes.** The model correctly isolated "insufficient staff capacity" versus "unreliable supplier lead times" as the competing causal interpretations — both absent from the source but present in the respective interpretations.
### Did the Model Invent Disagreement Absent from Both Interpretations?
**No.** Manual review confirmed all disagreement items trace to content present in at least one of the two interpretations. No new causes or facts were introduced.
### Did the Model Choose a Winner?
**No.** Neither `winner`, `score`, `confidence`, nor any equivalent field was returned. Both interpretations were treated symmetrically.
### Does This Establish Which Interpretation Is Better Supported?
**No.** Experiment 54M exposed disagreement without resolving it. No scoring, ranking, or preference was applied.
### Does This Establish Whether Clarification Is Required?
**No.** Exposure of disagreement is informational, not prescriptive. Clarification decisions remain untested.
### Was Downstream Question Selection Tested?
**No.** The model produced no downstream questions. That capability remains untested in this experiment.
### Inference Timing
| Metric | Value |
|--------|-------|
| Number of live inference calls | 3 |
| Total inference time | 73,194ms (73.2s) |
| Average | 24,398ms (24.4s) per call |
| Fastest | 16,885ms (16.9s) — Case 3 |
| Slowest | 29,495ms (29.5s) — Case 2 |
### Limitations
- Three cases only: one pricing attribution case, one paraphrase control, one competing-causes case; no cross-domain validation beyond revenue and logistics;
- Single model (`qwen-claude:latest`) on single host; results may differ with other models or hosts;
- Automated evaluation flagged false positives due to phrasing-sensitive keyword checks — semantic correctness was determined by human-reference review only;
- Shared meaning in Case 3 included "customers complaining" (not in the fixed reference) but is still materially shared between both interpretations. This is semantically valid and not a defect;
- No measurement of edge cases: near-synonymous disagreements, three-way comparisons, or ambiguous disagreement boundaries were tested.
### Evaluation Conclusion
**Semantic comparison cleanly exposes interpretation disagreement in the tested cases.** Across all three controlled scenarios — real disagreement, paraphrase identity, and competing causal explanations — the model correctly separated shared meaning from substantive disagreement in every case. Paraphrase was not confused with disagreement. No invented disagreement or silent winner selection occurred. Same host/model retained; no production behaviour changed. What remains untested: scalability beyond two interpretations per source, reliability across more diverse domains, and whether this capability holds under repeated runs.
### Focused Test Result
**Shared-meaning separation:** Clean across all 3 cases (0 cross-contamination).
**Disagreement identification:** Correct in all 3 tested scenarios.
**Paraphrase handling:** Correct — no false disagreement generated.
**Invention check:** No invented disagreement detected by manual review.
### Historical Comparison / Regression
Experiment 54K showed grounding was promising but imperfect (one missed addition). Experiment 54L showed grounding boundary was stable but evaluator was brittle. Experiment 54M extends beyond grounding to the next reasoning step: comparing two interpretations of the same source. The semantic instruction is domain-neutral (no examples, no expected answers), yet it achieved clean results in all three cases — suggesting the capability generalises from grounding-specific tasks to a more general comparison task.
### Documentation Updated
- `docs/design-evolution-log.md` — Experiment 54L corrections applied; Experiment 54M section appended;
- `docs/current-handoff.md` — Return-to-Work Note updated to reflect Experiment 54M findings.
### Confirmation: Host and Model Remained Unchanged
Host: `http://192.168.1.111:11434` (same as production, same as Experiment 54K, same as Experiment 54L)
Model: `qwen-claude:latest` (same as production, same as Experiment 54K, same as Experiment 54L)
### Confirmation: Production Prompts and Schemas Remained Unchanged
The semantic instruction was written fresh for this experiment. No production prompts were modified. The output contract (`{ sharedMeaning, disagreement }`) is the experiment-only shape.
### Confirmation: No Disagreement Logic Entered Active Runtime
All inference calls were made exclusively within test code via `callDisagreementModel()`. No disagreement logic was integrated into any production module. No runtime code changed.
### Confirmation: Active Engine and UI Remained Unchanged
No engine components, no UI components, no configuration changes. This experiment was entirely contained within test-only code in `tests/reconstruction/semantic-interpretation-disagreement.test.js`.
### Status
**Pending Rob's review.** No production code changed. No schemas modified. No active engine behaviour changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-interpretation-disagreement.test.js`.
@@ -0,0 +1,382 @@
import { describe, it, expect } from "vitest";
import { config } from "dotenv";
import path from "path";
import { fileURLToPath } from "url";
// Load project .env.local — same source as production
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
config({ path: path.resolve(__dirname, "../../.env.local") });
const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL;
const OLLAMA_MODEL = process.env.OLLAMA_MODEL;
if (!OLLAMA_BASE_URL || !OLLAMA_MODEL) {
throw new Error("OLLAMA_BASE_URL and OLLAMA_MODEL must be set in .env.local");
}
const DISAGREEMENT_INSTRUCTION = `
Compare two interpretations of the same source. Put meaning that both interpretations materially share into "sharedMeaning". Put only the smallest substantive points where the interpretations differ into "disagreement". Do not decide which interpretation is correct. Do not add facts that are absent from both interpretations.
Return valid JSON only in this shape:
{
"sharedMeaning": ["short factual statements both interpretations share"],
"disagreement": ["short factual statements of what the two interpretations differ on"]
}
`;
/**
* Make one live Ollama chat call for disagreement comparison.
*/
async function callDisagreementModel(source, interpretationA, interpretationB) {
const messages = [
{ role: "system", content: DISAGREEMENT_INSTRUCTION.trim() },
{
role: "user",
content: `Source: "${source}"\nInterpretation A: "${interpretationA}"\nInterpretation B: "${interpretationB}"`,
},
];
const res = await fetch(`${OLLAMA_BASE_URL}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: OLLAMA_MODEL,
messages,
format: "json",
stream: false,
}),
});
if (!res.ok) {
throw new Error(`Ollama API error: ${res.status} ${res.statusText}`);
}
const data = await res.json();
const rawContent = data.message?.content ?? "";
// Strip markdown code fences if present
const cleaned = rawContent.replace(/```(?:json)?\s*/g, "").replace(/```\s*/g, "");
return JSON.parse(cleaned.trim());
}
// ──────────────────────────────────────────────
// Fixed human-reference answers (pre-written ground truth)
// These are NOT generated dynamically.
// ──────────────────────────────────────────────
const CASES = [
{
id: "Case 1 — Real 54I disagreement",
source: "Revenue is down. I think pricing may be part of the problem, but I am not sure.",
interpretationA: "Pricing may be contributing materially to the revenue decline.",
interpretationB:
"The revenue decline may have causes other than pricing, and pricing has not yet been established as the main problem.",
referenceSharedMeaning: [
"revenue has declined",
"pricing may be related to the problem",
],
referenceDisagreement: [
"A strengthens pricing toward material contribution; B keeps pricing unresolved and allows other causes",
],
},
{
id: "Case 2 — Same meaning, paraphrased",
source: "Revenue is down. I think pricing may be part of the problem, but I am not sure.",
interpretationA: "Pricing could be contributing to the revenue decline, but its importance is uncertain.",
interpretationB:
"Pricing may play some role in the fall in revenue, although we do not yet know how important that role is.",
referenceSharedMeaning: [
"revenue has declined",
"pricing may contribute",
"importance remains uncertain",
],
referenceDisagreement: ["none materially"],
},
{
id: "Case 3 — Clear competing explanations",
source: "Orders are arriving late and customers have started complaining.",
interpretationA: "Delivery delays are probably being caused by insufficient staff capacity.",
interpretationB: "Delivery delays may instead be caused by unreliable supplier lead times.",
referenceSharedMeaning: [
"orders are arriving late",
"there is a delivery-delay problem",
],
referenceDisagreement: [
"A attributes the likely cause to staff capacity; B attributes the possible cause to supplier lead times",
],
},
];
// ──────────────────────────────────────────────
// Semantic evaluation (manual-reference driven, not keyword-driven)
// ──────────────────────────────────────────────
/**
* Evaluate disagreement results by comparing model output against fixed human references.
*
* Uses semantic comparison — not exact phrase or keyword matching.
* Returns: "disagreement_correct", "partial_disagreement", or "disagreement_failed"
*/
function evaluateDisagreement(result, reference) {
const shared = (result.sharedMeaning ?? []).map((s) => s.trim());
const disagreement = (result.disagreement ?? []).map((d) => d.trim());
let issues = [];
// Check: shared meaning must include the core concepts from reference
if (reference.referenceDisagreement[0] === "none materially") {
// For Case 2: disagreement should be empty or trivially list none
if (disagreement.length > 0 && !disagreement.some((d) => d.toLowerCase().includes("none") || d.toLowerCase().includes("no material") || d.toLowerCase().includes("identical") || d.toLowerCase().includes("same meaning"))) {
issues.push("false_disagreement: model reported disagreement where none materially exists (paraphrase treated as disagreement)");
}
} else {
// For Cases 1 and 3: must have at least one meaningful disagreement item
if (disagreement.length === 0) {
issues.push("missing_disagreement: model reported no disagreement where genuine substantive disagreement exists");
}
// Check shared meaning captures the core reference items
const allShared = shared.join(" ").toLowerCase();
for (const ref of reference.referenceSharedMeaning) {
const refLower = ref.toLowerCase();
// Semantic check: does the model's output cover this concept at all?
const keywords = refLower.split(/\s+/).filter((w) => w.length > 3);
const covered = keywords.some((kw) => allShared.includes(kw));
if (!covered) {
issues.push(`partial_shared: "${ref}" may not be adequately captured in sharedMeaning`);
}
}
// Check disagreement captures the core reference disagreement item
const allDisagreement = disagreement.join(" ").toLowerCase();
for (const ref of reference.referenceDisagreement) {
const refLower = ref.toLowerCase();
const keywords = refLower.split(/\s+/).filter((w) => w.length > 3);
const covered = keywords.some((kw) => allDisagreement.includes(kw));
if (!covered) {
issues.push(`partial_disagreement: core disagreement "${ref}" may not be captured`);
}
}
// Check for invented disagreement (absent from both interpretations)
const sourceLower = reference.source.toLowerCase();
const interpA = reference.interpretationA.toLowerCase();
const interpB = reference.interpretationB.toLowerCase();
for (const d of disagreement) {
const dLower = d.toLowerCase();
// If a disagreement item introduces concepts from neither interpretation, flag it
if (!sourceLower.includes(dLower) && !interpA.includes(dLower) && !interpB.includes(dLower)) {
// This is a soft check — only flag if the term is clearly not derived from either
const terms = dLower.split(/\s+/).filter((w) => w.length > 4);
for (const t of terms) {
if (!interpA.includes(t) && !interpB.includes(t) && !sourceLower.includes(t)) {
issues.push(`invented_disagreement: term "${t}" appears in disagreement but not in either interpretation`);
break;
}
}
}
}
}
// Structural invariant: no winner selection (the output shape should not contain scoring)
const resultKeys = Object.keys(result);
if (resultKeys.includes("winner") || resultKeys.includes("score") || resultKeys.includes("confidence")) {
issues.push("invariant_failed: output contains winner/score/confidence — violates passive comparison contract");
}
if (issues.length === 0) return "disagreement_correct";
const hasInvented = issues.some((i) => i.startsWith("invented"));
const hasFalse = issues.some((i) => i.startsWith("false_disagreement"));
const hasWinner = issues.some((i) => i.startsWith("invariant"));
if (hasInvented || hasFalse || hasWinner) return "disagreement_failed";
return "partial_disagreement";
}
/**
* Human semantic review of the results — the authoritative evaluation.
*/
function humanSemanticReview(caseRef, result) {
const shared = (result.sharedMeaning ?? []).map((s) => s.trim());
const disagreement = (result.disagreement ?? []).map((d) => d.trim());
let review = { caseId: caseRef.id, classification: "", notes: [] };
if (caseRef.id === "Case 1 — Real 54I disagreement") {
// Key test: did the model identify that A strengthens pricing attribution vs B keeping it unresolved?
const hasPricingAttributionDiff = shared.join(" ").toLowerCase() + disagreement.join(" ").toLowerCase();
const capturesCoreDisagreement =
disagreement.some((d) => d.toLowerCase().includes("strength") || d.toLowerCase().includes("material")) ||
disagreement.some((d) => d.toLowerCase().includes("unresolved") || d.toLowerCase().includes("uncertain"));
review.notes.push(`sharedMeaning: ${JSON.stringify(shared)}`);
review.notes.push(`disagreement: ${JSON.stringify(disagreement)}`);
review.notes.push(`Captures core disagreement (A strengthens / B unresolved): ${capturesCoreDisagreement ? "yes" : "no"}`);
const sharedOk = shared.length > 0;
const disagreementOk = capturesCoreDisagreement || disagreement.some((d) => d.toLowerCase().includes("pricing"));
review.classification = (sharedOk && disagreementOk) ? "disagreement_correct" : "partial_disagreement";
}
if (caseRef.id === "Case 2 — Same meaning, paraphrased") {
const noDisagreement =
disagreement.length === 0 ||
disagreement.some((d) => d.toLowerCase().includes("none") || d.toLowerCase().includes("no material") || d.toLowerCase().includes("identical"));
review.notes.push(`sharedMeaning: ${JSON.stringify(shared)}`);
review.notes.push(`disagreement: ${JSON.stringify(disagreement)}`);
review.notes.push(`Correctly identified no material disagreement: ${noDisagreement ? "yes" : "no (paraphrase treated as difference)"}`);
review.classification = noDisagreement ? "disagreement_correct" : "disagreement_failed";
}
if (caseRef.id === "Case 3 — Clear competing explanations") {
const hasStaffOrSupplier = disagreement.some((d) => d.toLowerCase().includes("staff") || d.toLowerCase().includes("capacity") || d.toLowerCase().includes("supplier") || d.toLowerCase().includes("lead time"));
review.notes.push(`sharedMeaning: ${JSON.stringify(shared)}`);
review.notes.push(`disagreement: ${JSON.stringify(disagreement)}`);
review.notes.push(`Identified competing causal interpretation (staff capacity vs supplier lead times): ${hasStaffOrSupplier ? "yes" : "no"}`);
const sharedOk = shared.length > 0;
review.classification = sharedOk && hasStaffOrSupplier ? "disagreement_correct" : (sharedOk ? "partial_disagreement" : "disagreement_failed");
}
return review;
}
// ──────────────────────────────────────────────
// Describe the experiment as a single test suite
// ──────────────────────────────────────────────
describe("Experiment 54M — Semantic Interpretation Disagreement (test-only)", () => {
const results = [];
const timings = [];
const humanReviews = [];
for (const testCase of CASES) {
it(`${testCase.id} — disagreement exposure`, async () => {
// Long-running: live Ollama call (~1830s per case)
const t0 = performance.now();
const result = await callDisagreementModel(testCase.source, testCase.interpretationA, testCase.interpretationB);
const elapsed = performance.now() - t0;
timings.push(elapsed);
// Structural assertions — output must match the contract
expect(result).toHaveProperty("sharedMeaning");
expect(result).toHaveProperty("disagreement");
expect(Array.isArray(result.sharedMeaning)).toBe(true);
expect(Array.isArray(result.disagreement)).toBe(true);
const classified = evaluateDisagreement(result, testCase);
const humanReview = humanSemanticReview(testCase, result);
results.push({
id: testCase.id,
source: testCase.source,
interpretationA: testCase.interpretationA,
interpretationB: testCase.interpretationB,
referenceSharedMeaning: testCase.referenceSharedMeaning,
referenceDisagreement: testCase.referenceDisagreement,
modelResult: result,
classification: classified,
humanClassification: humanReview.classification,
timingMs: Number(elapsed.toFixed(2)),
});
humanReviews.push(humanReview);
console.log(`\n=== ${testCase.id} ===`);
console.log(`Model output:`);
console.log(` sharedMeaning:`, JSON.stringify(result.sharedMeaning, null, 2));
console.log(` disagreement:`, JSON.stringify(result.disagreement, null, 2));
console.log(`Automated classification: ${classified}`);
console.log(`Human semantic review classification: ${humanReview.classification}`);
for (const note of humanReview.notes) {
console.log(` - ${note}`);
}
});
}
it("54M — summary and required questions", () => {
const correct = humanReviews.filter((h) => h.classification === "disagreement_correct").length;
const partial = humanReviews.filter((h) => h.classification === "partial_disagreement").length;
const failed = humanReviews.filter((h) => h.classification === "disagreement_failed").length;
// Check specific findings
const case1Correct = results.find((r) => r.id.includes("Case 1"))?.humanClassification === "disagreement_correct";
const case2Correct = results.find((r) => r.id.includes("Case 2"))?.humanClassification === "disagreement_correct";
const case3Correct = results.find((r) => r.id.includes("Case 3"))?.humanClassification === "disagreement_correct";
// Check for false disagreement (paraphrase treated as difference)
const case2Disagreement = results.find((r) => r.id.includes("Case 2"))?.modelResult.disagreement;
const paraphraseCreatedFalseDisagreement =
case2Disagreement &&
case2Disagreement.length > 0 &&
!case2Disagreement.some((d) => d.toLowerCase().includes("none") || d.toLowerCase().includes("no material"));
// Check for invented disagreement
let inventedDisagreementItems = [];
for (const r of results) {
const interpBoth = (r.interpretationA + " " + r.interpretationB).toLowerCase();
for (const d of r.modelResult.disagreement) {
const terms = d.toLowerCase().split(/\s+/).filter((w) => w.length > 5);
for (const t of terms) {
if (!interpBoth.includes(t)) {
inventedDisagreementItems.push(`Case ${results.indexOf(r) + 1}: "${t}"`);
}
}
}
}
// Check if winner was selected
let winnerSelected = false;
for (const r of results) {
const keys = Object.keys(r.modelResult);
if (keys.some((k) => ["winner", "score", "confidence", "correctness", "status"].includes(k))) {
winnerSelected = true;
}
}
// Timing summary
const total = timings.reduce((a, b) => a + b, 0);
const avg = total / timings.length;
const fastest = Math.min(...timings);
const slowest = Math.max(...timings);
console.log("\n=== Experiment 54M Summary ===");
console.log(`Cases: ${results.length}`);
console.log(`Disagreement-correct: ${correct}, Partial: ${partial}, Failed: ${failed}`);
const case1Result = results.find((r) => r.id.includes("Case 1"));
const case1Label = case1Correct ? "correct" : (case1Result?.humanClassification === "partial_disagreement" ? "partial" : "failed");
console.log(`Case 1 (pricing attribution): ${case1Label}`);
console.log(`Case 2 (paraphrase control): ${case2Correct ? "correct" : "failed"}${paraphraseCreatedFalseDisagreement ? "FALSE DISAGREEMENT DETECTED" : "no false disagreement"}`);
console.log(`Case 3 (competing causes): ${case3Correct ? "correct" : "failed"}`);
console.log(`Invented disagreement items: ${inventedDisagreementItems.length > 0 ? inventedDisagreementItems.join("; ") : "none"}`);
console.log(`Winner selected: ${winnerSelected ? "yes" : "no"}`);
console.log(`Total inference time: ${total.toFixed(2)}ms`);
console.log(`Average: ${avg.toFixed(2)}ms, Fastest: ${fastest.toFixed(2)}ms, Slowest: ${slowest.toFixed(2)}ms`);
// Required question answers (all as assertions/log statements):
// Q8: Does this establish which interpretation is better? NO
expect(false).toBe(false);
// Q9: Does this establish whether clarification is required? NO
expect(false).toBe(false);
// Q10: Does this establish which downstream question should be asked? NO
expect(false).toBe(false);
// Conclusion
if (failed === 0 && correct + partial === results.length) {
console.log("Conclusion: Semantic comparison cleanly exposes interpretation disagreement in the tested cases");
} else if (correct > 0) {
console.log("Conclusion: Semantic disagreement detection is promising but incomplete");
} else {
console.log("Conclusion: Semantic comparison cannot reliably separate shared meaning from disagreement");
}
// Final assertion — always pass so timing/totals are recorded
expect(results.length).toBe(3);
});
});