Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 106 additions & 3 deletions fogbinder/lib/ocaml/ContradictionDetector.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,110 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module ContradictionDetector;

// TODO: Complete semantic implementation
// ContradictionDetector.res
// Detects contradictions as LANGUAGE GAME CONFLICTS, not logical oppositions
// Late Wittgenstein: "A contradiction is a different kind of thing than you think"

open EpistemicState
open SpeechAct

// Conflict structs for contradictions
struct conflictType {
| SameWordsDifferentGames // Same words, different language games
| IncommensurableFrameworks // Utterly different frameworks
| ContextualAmbiguity // Depends on interpretation context
| TemporalShift // Meaning changed over time
| DisciplinaryClash // Different academic disciplines

// A contradiction is when different language games clash over the same utterance
struct contradiction { {
utterance1: SpeechAct.t,
utterance2: SpeechAct.t,
conflictType: conflictType,
severity: float, // 0.0-1.0: how serious is this clash?
resolution: option<string>, // Possible way to resolve (if any)
}

// Detect if two speech acts contradict
fn detectContradiction = (act1: SpeechAct.t, act2: SpeechAct.t): option<contradiction> => {
// Check if they're playing different language games
fn differentGames = act1.mood.context.domain != act2.mood.context.domain

// Check if illocutionary forces conflict
fn forcesConflict = SpeechAct.conflicts(act1, act2)

if differentGames && forcesConflict {
Some({
utterance1: act1,
utterance2: act2,
conflictType: SameWordsDifferentGames,
severity: 0.8,
resolution: Some("Recognize different contexts of use"),
})
} else if differentGames {
Some({
utterance1: act1,
utterance2: act2,
conflictType: DisciplinaryClash,
severity: 0.5,
resolution: Some("Acknowledge different disciplinary frameworks"),
})
} else if forcesConflict {
Some({
utterance1: act1,
utterance2: act2,
conflictType: ContextualAmbiguity,
severity: 0.6,
resolution: Some("Clarify context of utterance"),
})
} else {
None
}
}

// Batch detect contradictions across multiple sources
fn detectMultiple = (acts: array<SpeechAct.t>): array<contradiction> => {
fn contradictions = []

Js.Array2.forEach(acts, act1 => {
Js.Array2.forEach(acts, act2 => {
if act1.timestamp < act2.timestamp {
// Avoid duplicate pairs
switch detectContradiction(act1, act2) {
| Some(c) => Js.Array2.push(contradictions, c)->ignore
| None => ()
}
}
})
})

contradictions
}

// Visualize contradiction as network edge
fn toEdge = (c: contradiction): (string, string, string) => {
fn label = switch c.conflictType {
| SameWordsDifferentGames => "Different Games"
| IncommensurableFrameworks => "Incommensurable"
| ContextualAmbiguity => "Context-Dependent"
| TemporalShift => "Temporal Shift"
| DisciplinaryClash => "Disciplinary"
}

(c.utterance1.utterance, c.utterance2.utterance, label)
}

// Suggest resolution strategies
fn suggestResolution = (c: contradiction): string => {
switch c.resolution {
| Some(res) => res
| None =>
switch c.conflictType {
| IncommensurableFrameworks => "No resolution possible - acknowledge incommensurability"
| _ => "Investigate language games in play"
}
}
}

103 changes: 100 additions & 3 deletions fogbinder/lib/ocaml/EpistemicState.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,104 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module EpistemicState;

// TODO: Complete semantic implementation
// EpistemicState.res
// Models epistemic uncertainty as a feature, not a bug
// Based on late Wittgenstein: meaning emerges from use, not correspondence to facts

// Core epistemic modalities
struct certainty {
| Known // Clear, unambiguous
| Probable(float) // Statistical confidence
| Vague // Fuzzy boundaries (Wittgenstein's "family resemblance")
| Ambiguous(array<string>) // Multiple valid interpretations (language games)
| Mysterious // Resists factual reduction
| Contradictory(array<string>) // Conflicting language games

// Context of use (Wittgenstein's "language game")
struct languageGame { {
domain: string, // Academic discipline, cultural context, etc.
conventions: array<string>, // Rules of use in this game
participants: array<string>, // Who's playing?
purpose: string, // What are they doing with these words?
}

// Epistemic state combines modality with context
struct t { {
certainty: certainty,
context: languageGame,
evidence: array<string>, // Supporting citations/passages
timestamp: float,
}

// Create a new epistemic state
fn make = (~certainty, ~context, ~evidence, ()): t => {
{
certainty,
context,
evidence,
timestamp: Js.Date.now(),
}
}

// Check if state represents genuine uncertainty (not just lack of data)
fn isGenuinelyAmbiguous = (state: t): bool => {
switch state.certainty {
| Ambiguous(_) | Mysterious | Contradictory(_) => true
| Vague => true
| Known | Probable(_) => false
}
}

// Extract all possible interpretations from ambiguous state
fn getInterpretations = (state: t): array<string> => {
switch state.certainty {
| Ambiguous(interps) => interps
| Contradictory(conflicts) => conflicts
| _ => []
}
}

// Merge two epistemic states (may increase ambiguity!)
fn merge = (s1: t, s2: t): t => {
// When different language games clash, we get contradiction or ambiguity
fn newCertainty = switch (s1.certainty, s2.certainty) {
| (Known, Known) => Known
| (Probable(p1), Probable(p2)) => Probable((p1 +. p2) /. 2.0)
| (Ambiguous(a1), Ambiguous(a2)) => Ambiguous(Js.Array2.concat(a1, a2))
| (Contradictory(c1), Contradictory(c2)) => Contradictory(Js.Array2.concat(c1, c2))
| (_, Mysterious) | (Mysterious, _) => Mysterious
| _ => Ambiguous([
"Multiple interpretations from different contexts",
])
}

{
certainty: newCertainty,
context: s1.context, // Preserve primary context
evidence: Js.Array2.concat(s1.evidence, s2.evidence),
timestamp: Js.Date.now(),
}
}

// Convert to JSON for serialization
fn toJson = (state: t): Js.Json.t => {
open Js.Dict
fn dict = empty()

fn certaintyStr = switch state.certainty {
| Known => "known"
| Probable(p) => `probable:${Belt.Float.toString(p)}`
| Vague => "vague"
| Ambiguous(_) => "ambiguous"
| Mysterious => "mysterious"
| Contradictory(_) => "contradictory"
}

set(dict, "certainty", Js.Json.string(certaintyStr))
set(dict, "timestamp", Js.Json.number(state.timestamp))

Js.Json.object_(dict)
}

Loading
Loading