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
15 changes: 15 additions & 0 deletions docs/TRIA_TRUTH_INTEGRITY_PROTOCOL_v0.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ The operation remains read-only. `WARRANTED` is an input from a separate epistem

This makes substantive uptake observable without turning correction into unilateral authority.

### Governed application

Observation alone is not the deployable endpoint. The reference operation `apply_correction_uptake` closes the local loop under ordinary TRIA authority.

1. A separate epistemic process produces a substantive `REEVALUATE` assessment.
2. The applying actor MUST possess current `ACT` authority for `truth-integrity:corrections`.
3. The application validates that the affected claim set is rooted in the corrected target and follows explicit claim provenance; an assessment value is not itself an authority token.
4. Existing affected claims enter `CONTESTED` status rather than being silently overwritten.
5. TRIA appends an attributable `CorrectionUptakeApplied` receipt with correction ID, affected references, evidence references, and causal links to the reevaluation events.
6. Downstream consumers therefore observe changed relational state on their next fresh read.

The initial governed application deliberately does not invent replacement claim content. Reevaluation establishes that prior representations can no longer be consumed as uncontested truth; a later attributable revision, withdrawal, or supersession supplies the corrected representation.

The current public executor validates propagation through explicit claim `derived_from` provenance. Wave 2's broader `RELIES_ON` links can identify plans or decisions requiring reevaluation, but mutation of non-claim application objects remains a host/application responsibility until those object types have canonical TRIA state semantics.

## Required disclosure contracts

Bounded workflows MAY declare mandatory disclosure fields such as:
Expand Down
9 changes: 7 additions & 2 deletions src/tria/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,17 @@
PolicyDefinitionRecord,
ReconsentRequirement,
)
from .uptake import (
CORRECTION_RESOURCE,
CorrectionApplicationReceipt,
apply_correction_uptake,
)
from .version import __version__

__all__ = [
"TriaError", "InputValidationError", "RelationshipNotFoundError", "InvalidRelationshipError", "UnknownParticipantError", "ConcurrentWriteError", "PersistenceError", "UnsupportedStoreError", "UnknownResourceError", "InvocationAlreadyStartedError", "ExecutionError", "HostAdministration",
"AnthropicMessagesAdapter", "AttributableObservation", "BUNDLE_FORMAT_VERSION", "BundleVerification", "Capability", "CapabilityRequirement",
"AttestationVerdict", "CandidateClaim", "CORRECTION_UPTAKE_SCHEMA", "Claim", "ClaimComponent", "ClaimHandle", "ClaimReleaseAssessment", "ClaimStatus", "CompatibilityReport", "ConsentRecord", "ConsentRequirement", "CorrectionDisposition", "CorrectionEvidence", "CorrectionUptakeAssessment", "ContextItem",
"AttestationVerdict", "CandidateClaim", "CORRECTION_RESOURCE", "CORRECTION_UPTAKE_SCHEMA", "Claim", "ClaimComponent", "ClaimHandle", "ClaimReleaseAssessment", "ClaimStatus", "CompatibilityReport", "ConsentRecord", "ConsentRequirement", "CorrectionApplicationReceipt", "CorrectionDisposition", "CorrectionEvidence", "CorrectionUptakeAssessment", "ContextItem",
"CrossBoundaryGovernanceError", "CURRENT_BUNDLE_FORMAT_VERSION", "CURRENT_EVENT_SCHEMA_VERSION", "CURRENT_PROJECTION_VERSION",
"DelegationError", "DependencyKind", "DependencyLink", "DiagnosticReport", "DifferentiationObservation", "DisclosureHandle", "EpistemicAdmissionError", "EpistemicReleaseState", "EpistemicType", "EvidenceAttestation", "EventProposal", "EventStore",
"ExecutionBridge", "ExecutionReceipt", "Executor", "GenerativeCondition", "GovernanceDecision", "GovernanceEngine", "GovernanceOutcome",
Expand All @@ -126,7 +131,7 @@
"PolicyDefinitionRecord", "PropagationAction", "ProviderAdapter", "ProviderRequest", "ProviderResponse", "ProviderTranslationError",
"ReconsentRequirement", "ReleaseOutcome", "RelationalEvent", "RelationalState", "Relationship", "ReplayBundle", "ReplayExportError", "ReplayImportError",
"Runtime", "SQLiteEventStore", "SchemaCompatibilityError", "CLAIM_RELEASE_SCHEMA", "CLAIM_RELEASE_SPEC_VERSION", "TRUTH_INTEGRITY_SCHEMA", "TRUTH_INTEGRITY_SPEC_VERSION", "Tria", "__version__", "admit_disclosure",
"assess_claim_release", "assess_correction_uptake", "assess_generative_condition", "assess_truth_integrity", "check_compatibility", "check_event_schema", "derive_from_disclosure", "diagnose", "disclose_reference", "export_replay_bundle",
"apply_correction_uptake", "assess_claim_release", "assess_correction_uptake", "assess_generative_condition", "assess_truth_integrity", "check_compatibility", "check_event_schema", "derive_from_disclosure", "diagnose", "disclose_reference", "export_replay_bundle",
"import_replay_bundle", "projection_digest", "record_differentiation_observation", "replay_export_resource",
"require_supported_compatibility", "require_supported_event_schema", "state_to_dict", "verify_event_chain", "verify_replay_bundle",
]
106 changes: 106 additions & 0 deletions src/tria/uptake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from __future__ import annotations

from dataclasses import dataclass

from .correction import CorrectionEvidence, CorrectionUptakeAssessment, DependencyLink, PropagationAction, assess_correction_uptake
from .errors import InputValidationError
from .types import Capability, GovernanceOutcome


CORRECTION_RESOURCE = "truth-integrity:corrections"


@dataclass(frozen=True, slots=True)
class CorrectionApplicationReceipt:
correction_id: str
requested_by: str
outcome: GovernanceOutcome
affected_refs: tuple[str, ...]
reason: str
event_id: str | None = None


def apply_correction_uptake(relationship, actor: str, correction: CorrectionEvidence, dependencies=(), assessment: CorrectionUptakeAssessment | None = None):
"""Apply a warranted reevaluation set through ordinary TRIA ACT authority.

This operation does not decide that a correction is warranted and does not
rewrite claim content. It moves existing affected claims into a contested
reevaluation state and records an attributable immutable receipt.
"""
if not isinstance(correction, CorrectionEvidence):
raise InputValidationError("correction must be CorrectionEvidence.")
if not isinstance(dependencies, (list, tuple)) or any(not isinstance(item, DependencyLink) for item in dependencies):
raise InputValidationError("dependencies must contain DependencyLink values.")
canonical = assess_correction_uptake(correction, tuple(dependencies))
if assessment is None:
assessment = canonical
if not isinstance(assessment, CorrectionUptakeAssessment):
raise InputValidationError("assessment must be CorrectionUptakeAssessment.")
if assessment != canonical:
return CorrectionApplicationReceipt(
correction.correction_id,
actor,
GovernanceOutcome.BLOCK,
(),
"Supplied assessment does not match deterministic correction propagation for the supplied correction and dependencies.",
)

relationship._participant(actor)
decision = relationship.check_capability(actor, CORRECTION_RESOURCE, Capability.ACT)
relationship.record_governance_decision(
decision,
operation="apply_correction_uptake",
actor=actor,
correction_id=assessment.correction_id,
)
if decision.outcome is not GovernanceOutcome.ALLOW:
return CorrectionApplicationReceipt(
assessment.correction_id, actor, decision.outcome, (), decision.reason
)

if (
assessment.action is not PropagationAction.REEVALUATE
or not assessment.substantive_uptake_required
or not assessment.affected_refs
or assessment.affected_refs[0] != assessment.target_ref
):
return CorrectionApplicationReceipt(
assessment.correction_id,
actor,
GovernanceOutcome.BLOCK,
(),
"Only a substantive REEVALUATE assessment rooted at its correction target may mutate claim state.",
)

# Recomputed exact assessment above prevents caller expansion or shrinkage.

causal = []
for ref in assessment.affected_refs:
if ref in relationship.state.claims:
event = relationship.dispute_claim(
actor,
ref,
f"Reevaluation required by warranted correction {assessment.correction_id}.",
)
causal.append(event.event_id)

receipt_event = relationship._commit(
"CorrectionUptakeApplied",
actor,
{
"correction_id": assessment.correction_id,
"target_ref": assessment.target_ref,
"affected_refs": list(assessment.affected_refs),
"evidence_refs": list(assessment.evidence_refs),
"action": assessment.action.value,
},
tuple(causal),
)
return CorrectionApplicationReceipt(
assessment.correction_id,
actor,
GovernanceOutcome.ALLOW,
assessment.affected_refs,
"Warranted correction uptake applied; affected claims are contested pending reevaluation.",
receipt_event.event_id,
)
117 changes: 117 additions & 0 deletions tests/test_governed_correction_uptake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import pytest

from tria import (
Capability,
CorrectionDisposition,
CorrectionEvidence,
CorrectionUptakeAssessment,
DependencyKind,
DependencyLink,
EpistemicType,
GovernanceOutcome,
PropagationAction,
Tria,
apply_correction_uptake,
assess_correction_uptake,
)


def setup_relationship():
rel = Tria().create_relationship(["human:a", "agent:b"])
source = rel.register_claim(
"agent:b",
EpistemicType.OBSERVATION,
"The deployment succeeded.",
source_refs=["receipt:old"],
)
summary = rel.register_claim(
"agent:b",
EpistemicType.INFERENCE,
"The release is ready.",
derived_from=[source.claim_id],
)
return rel, source.claim_id, summary.claim_id


def warranted(target):
return CorrectionEvidence(
"correction:1",
target,
"human:a",
("receipt:corrected",),
CorrectionDisposition.WARRANTED,
)


def test_governed_uptake_requires_actor_act_authority():
rel, source, summary = setup_relationship()
assessment = assess_correction_uptake(
warranted(source),
(DependencyLink(source, summary, DependencyKind.DERIVED_FROM),),
)
receipt = apply_correction_uptake(rel, "agent:b", warranted(source), (DependencyLink(source, summary, DependencyKind.DERIVED_FROM),), assessment)
assert receipt.outcome is GovernanceOutcome.BLOCK
assert rel.state.claims[source].status.value == "ACTIVE"
assert rel.state.claims[summary].status.value == "ACTIVE"


def test_governed_uptake_marks_target_and_dependents_for_reevaluation():
rel, source, summary = setup_relationship()
rel.admin.grant_permission("tria:system", "agent:b", "truth-integrity:corrections", Capability.ACT)
assessment = assess_correction_uptake(
warranted(source),
(DependencyLink(source, summary, DependencyKind.DERIVED_FROM),),
)
receipt = apply_correction_uptake(rel, "agent:b", warranted(source), (DependencyLink(source, summary, DependencyKind.DERIVED_FROM),), assessment)
assert receipt.outcome is GovernanceOutcome.ALLOW
assert receipt.affected_refs == (source, summary)
assert rel.state.claims[source].status.value == "CONTESTED"
assert rel.state.claims[summary].status.value == "CONTESTED"


def test_uptake_writes_attributable_immutable_receipt_events():
rel, source, summary = setup_relationship()
rel.admin.grant_permission("tria:system", "agent:b", "truth-integrity:corrections", Capability.ACT)
assessment = assess_correction_uptake(
warranted(source),
(DependencyLink(source, summary, DependencyKind.DERIVED_FROM),),
)
receipt = apply_correction_uptake(rel, "agent:b", warranted(source), (DependencyLink(source, summary, DependencyKind.DERIVED_FROM),), assessment)
events = [e for e in rel.events if e.event_type == "CorrectionUptakeApplied"]
assert len(events) == 1
assert events[0].actor_id == "agent:b"
assert events[0].payload["correction_id"] == "correction:1"
assert tuple(events[0].payload["affected_refs"]) == receipt.affected_refs
assert rel.audit()["chain_valid"] is True


def test_non_reevaluation_assessment_cannot_mutate_state():
rel, source, _ = setup_relationship()
rel.admin.grant_permission("tria:system", "agent:b", "truth-integrity:corrections", Capability.ACT)
unsupported = assess_correction_uptake(
CorrectionEvidence(
"correction:2", source, "human:a", ("record:x",), CorrectionDisposition.UNSUPPORTED
),
(),
)
receipt = apply_correction_uptake(rel, "agent:b", CorrectionEvidence("correction:2", source, "human:a", ("record:x",), CorrectionDisposition.UNSUPPORTED), (), unsupported)
assert receipt.outcome is GovernanceOutcome.BLOCK
assert not [e for e in rel.events if e.event_type == "CorrectionUptakeApplied"]


def test_caller_cannot_expand_affected_refs_beyond_assessment():
rel, source, summary = setup_relationship()
rel.admin.grant_permission("tria:system", "agent:b", "truth-integrity:corrections", Capability.ACT)
assessment = assess_correction_uptake(warranted(source), ())
forged = CorrectionUptakeAssessment(
assessment.schema,
assessment.correction_id,
assessment.target_ref,
PropagationAction.REEVALUATE,
assessment.affected_refs + (summary,),
assessment.reasons,
assessment.evidence_refs,
True,
)
receipt = apply_correction_uptake(rel, "agent:b", warranted(source), (), forged)
assert receipt.outcome is GovernanceOutcome.BLOCK
Loading