diff --git a/contracts/epistemics/truth.go b/contracts/epistemics/truth.go new file mode 100644 index 0000000..b597c61 --- /dev/null +++ b/contracts/epistemics/truth.go @@ -0,0 +1,38 @@ +package epistemics + +type BeliefState string + +const ( + BeliefProvisional BeliefState = "PROVISIONAL" + BeliefConflicted BeliefState = "CONFLICTED" + BeliefVerified BeliefState = "VERIFIED" +) + +type Observation struct { + ID ObservationID + SourceSnapshot SourceSnapshotID + Excerpt SourceExcerptID + Text string + ObservedAt int64 +} + +type Claim struct { + ID ClaimID + Statement string + ObservationID ObservationID + CitationID CitationID + Verification VerificationState +} + +type Belief struct { + ID BeliefID + ClaimID ClaimID + State BeliefState +} + +type TruthBundle struct { + Observation Observation + Claim Claim + Belief Belief + Citation Citation +} diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index 1d21d87..bd408de 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1,5 +1,5 @@ { - "task_file": "dev/tasks/DEV-000133-work-graph-persistence.json", - "task_id": "DEV-000133", + "task_file": "dev/tasks/DEV-000134-truth-bundle-compiler.json", + "task_id": "DEV-000134", "task_revision": 1 } diff --git a/dev/tasks/DEV-000134-truth-bundle-compiler.json b/dev/tasks/DEV-000134-truth-bundle-compiler.json new file mode 100644 index 0000000..e97005c --- /dev/null +++ b/dev/tasks/DEV-000134-truth-bundle-compiler.json @@ -0,0 +1,23 @@ +{ + "task_id": "DEV-000134", + "task_revision": 1, + "architecture_revision": "ADR-0018", + "base_repo_revision": "e9bb42c", + "risk_tier": "E", + "intent": "Implement the first Truth runtime compiler that produces typed Observation, Claim, Belief and Citation values from an exact source chain.", + "requirement_ids": ["WP-100", "Observation/Claim/Belief", "Truth citation provenance"], + "allowed_change_scope": {"modules": ["epistemics"], "paths": ["contracts/epistemics/truth.go", "modules/epistemics/domain/truth.go", "tests/research_contracts/truth_bundle_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000134-truth-bundle-compiler.json"]}, + "forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "adapters/**", "cmd/**", "runtime/**", ".gitea/**"]}, + "allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0}, + "contracts": {"consume": ["DEV-000109 research contracts", "DEV-000110 citation resolver"], "may_change": ["Typed Truth bundle contracts and pure compiler"]}, + "semantic_owner": "epistemics", + "canonical_state": ["Typed TruthBundle value only; persistence follows"], + "state_transitions": ["source chain -> observed observation -> unverified claim -> provisional/conflicted belief"], + "side_effects_and_privileges": ["Pure compiler; no fetching, external mutation or authority grant"], + "persistence_and_migrations": ["Truth persistence is a later task"], + "failure_retry_restart": ["Broken source chain rejects; conflict remains visible"], + "assumptions": ["Research executor will supply exact source snapshots/excerpts"], + "acceptance_scenarios": ["Observation/claim/citation refs align", "Conflict produces CONFLICTED belief", "Broken source chain is rejected"], + "required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"], + "terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"] +} diff --git a/modules/epistemics/domain/truth.go b/modules/epistemics/domain/truth.go new file mode 100644 index 0000000..3b9a1a0 --- /dev/null +++ b/modules/epistemics/domain/truth.go @@ -0,0 +1,31 @@ +package domain + +import ( + "errors" + "strings" + + "git.cin.su/m/Assistant/contracts/epistemics" +) + +var ErrInvalidTruthBundle = errors.New("invalid truth bundle") + +func CompileObservedClaim(source epistemics.Source, snapshot epistemics.SourceSnapshot, excerpt epistemics.SourceExcerpt, statement string, observedAt int64, conflict epistemics.ConflictState) (epistemics.TruthBundle, error) { + if strings.TrimSpace(statement) == "" || observedAt < 1 { + return epistemics.TruthBundle{}, ErrInvalidTruthBundle + } + citation, err := epistemics.ResolveCitation(source, snapshot, excerpt, epistemics.EvidenceObserved, epistemics.CoverageComplete, conflict, observedAt) + if err != nil { + return epistemics.TruthBundle{}, ErrInvalidTruthBundle + } + observation := epistemics.Observation{ID: epistemics.ObservationID("observation:" + string(excerpt.ID)), SourceSnapshot: snapshot.ID, Excerpt: excerpt.ID, Text: excerpt.Text, ObservedAt: observedAt} + claim := epistemics.Claim{ID: epistemics.ClaimID("claim:" + string(excerpt.ID)), Statement: statement, ObservationID: observation.ID, CitationID: citation.ID, Verification: epistemics.VerificationUnverified} + state := epistemics.BeliefProvisional + if conflict == epistemics.ConflictPresent || conflict == epistemics.ConflictUnknown { + state = epistemics.BeliefConflicted + } + bundle := epistemics.TruthBundle{Observation: observation, Claim: claim, Belief: epistemics.Belief{ID: epistemics.BeliefID("belief:" + string(excerpt.ID)), ClaimID: claim.ID, State: state}, Citation: citation} + if bundle.Observation.SourceSnapshot != snapshot.ID || bundle.Claim.ObservationID != bundle.Observation.ID || bundle.Claim.CitationID != bundle.Citation.ID || bundle.Belief.ClaimID != bundle.Claim.ID { + return epistemics.TruthBundle{}, ErrInvalidTruthBundle + } + return bundle, nil +} diff --git a/tests/research_contracts/truth_bundle_test.go b/tests/research_contracts/truth_bundle_test.go new file mode 100644 index 0000000..c972185 --- /dev/null +++ b/tests/research_contracts/truth_bundle_test.go @@ -0,0 +1,27 @@ +package research_contracts_test + +import ( + "testing" + + "git.cin.su/m/Assistant/contracts/epistemics" + episdomain "git.cin.su/m/Assistant/modules/epistemics/domain" +) + +func TestCompileObservedClaimPreservesEvidenceChainAndConflict(t *testing.T) { + source := epistemics.Source{ID: "source-1", Locator: "https://example.test/source"} + snapshot := epistemics.SourceSnapshot{ID: "snapshot-1", SourceID: source.ID, ContentDigest: "sha256:test", CapturedAtUnixMillis: 100, Freshness: epistemics.FreshnessCurrent} + excerpt := epistemics.SourceExcerpt{ID: "excerpt-1", SnapshotID: snapshot.ID, Text: "exact observation", Start: 0, End: 17} + bundle, err := episdomain.CompileObservedClaim(source, snapshot, excerpt, "The observed fact is true", 200, epistemics.ConflictPresent) + if err != nil || bundle.Claim.ObservationID != bundle.Observation.ID || bundle.Belief.State != epistemics.BeliefConflicted || bundle.Citation.Evidence != epistemics.EvidenceObserved { + t.Fatalf("invalid truth bundle: %#v %v", bundle, err) + } +} + +func TestCompileObservedClaimRejectsBrokenSourceChain(t *testing.T) { + source := epistemics.Source{ID: "source-1", Locator: "https://example.test/source"} + snapshot := epistemics.SourceSnapshot{ID: "snapshot-1", SourceID: "other-source", ContentDigest: "sha256:test", CapturedAtUnixMillis: 100, Freshness: epistemics.FreshnessCurrent} + excerpt := epistemics.SourceExcerpt{ID: "excerpt-1", SnapshotID: snapshot.ID, Text: "exact observation", Start: 0, End: 17} + if _, err := episdomain.CompileObservedClaim(source, snapshot, excerpt, "statement", 200, epistemics.ConflictNone); err != episdomain.ErrInvalidTruthBundle { + t.Fatalf("broken chain was accepted: %v", err) + } +}