Sealed ProposalSource type widening TeachingChainProposal and
PackMutationProposal schemas with typed (kind, source_id,
emitted_at_revision) provenance. Schema-only widening; no runtime
behavior changes. Unblocks ADR-0095 miner-sourced proposals.
- new teaching/source.py: frozen ProposalSource dataclass with sealed
ProposalKind Literal["operator","miner","curriculum"], runtime
invariants (operator → empty source_id; miner/curriculum → non-empty),
serialize() ("operator" / "miner:<id>" / "curriculum:<id>"),
as_dict/from_dict round-trip, ProposalSource.operator() helper
- TeachingChainProposal.source field added (proposals.py)
- PackMutationProposal.source field added (store.py)
- build_proposal() accepts optional source kwarg; default uses
_default_operator_source() pinned at cached git HEAD SHA
- ProposalLog.current_state() now strictly requires source on every
created event; raises ProposalError with migration pointer if missing;
validates via ProposalSource.from_dict so malformed payloads reject
- teaching/migrate_proposals_source_field.py: deterministic one-shot
migration script using PRE_MIGRATION_SENTINEL ("pre-adr-0094-migration")
as the emitted_at_revision so re-runs across commits produce identical
bytes
- migration applied to live proposals.jsonl: 11 created events gained
source field; 33 non-created events untouched; idempotent verified
- 29 unit tests in test_proposal_source.py covering construction,
serialization, exhaustive-match pattern with assert_never,
migration determinism (3 idempotence/cross-run tests), strict-parse
rejection, live-log loads
- 2 test fixes in test_epistemic_invariants.py for new required source param
- smoke 67/67, teaching 17/17, cognition 120/121 (1 pre-existing skip),
runtime 19/19; cognition eval byte-identical 100/100/100/100
147 lines
4.8 KiB
Python
147 lines
4.8 KiB
Python
"""ADR-0021 non-hardening invariant tests.
|
|
|
|
The Epistemic Grade Policy commits to:
|
|
|
|
1. `epistemic_status` is a position in the revision graph, not a trust
|
|
tier.
|
|
2. No reviewed claim, relation, or proposition-graph edge ever becomes
|
|
unrevisable — no `final`, `frozen`, `axiom`, or `permanent` flag may
|
|
exist or be added on the runtime data model.
|
|
3. Coherence is the only admission signal — source-trust labels must not
|
|
be part of the schema.
|
|
|
|
These tests are the structural checks behind those commitments. They
|
|
are intentionally simple and read like contract assertions, not
|
|
behaviour tests.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
|
|
from teaching import EpistemicStatus, PackMutationProposal, ReviewedTeachingExample
|
|
from teaching.correction import CorrectionCandidate
|
|
from teaching.review import ReviewOutcome
|
|
from teaching.source import ProposalSource
|
|
|
|
|
|
_FORBIDDEN_HARDENING_NAMES: frozenset[str] = frozenset({
|
|
"final",
|
|
"frozen",
|
|
"axiom",
|
|
"permanent",
|
|
"immutable_truth",
|
|
"sealed",
|
|
})
|
|
|
|
# Source-trust tier names that ADR-0021 §3 forbids in the schema.
|
|
_FORBIDDEN_TRUST_TIER_NAMES: frozenset[str] = frozenset({
|
|
"peer_consensus",
|
|
"outsider_empirical",
|
|
"established",
|
|
"unauthoritative",
|
|
"credentialed",
|
|
"source_trust",
|
|
"authority",
|
|
"trust_tier",
|
|
})
|
|
|
|
|
|
def _field_names(cls: type) -> set[str]:
|
|
return {f.name for f in dataclasses.fields(cls)}
|
|
|
|
|
|
def _enum_value_strings(enum_cls) -> set[str]:
|
|
return {member.value for member in enum_cls}
|
|
|
|
|
|
def test_pack_mutation_proposal_has_no_hardening_flag():
|
|
"""PackMutationProposal must not expose any name implying permanence."""
|
|
names = _field_names(PackMutationProposal)
|
|
forbidden = names & _FORBIDDEN_HARDENING_NAMES
|
|
assert forbidden == set(), (
|
|
f"PackMutationProposal exposes hardening flag(s) {forbidden}; "
|
|
"ADR-0021 §2 forbids non-revisable state on the runtime data model."
|
|
)
|
|
|
|
|
|
def test_reviewed_teaching_example_has_no_hardening_flag():
|
|
"""ReviewedTeachingExample must not expose any name implying permanence."""
|
|
names = _field_names(ReviewedTeachingExample)
|
|
forbidden = names & _FORBIDDEN_HARDENING_NAMES
|
|
assert forbidden == set(), (
|
|
f"ReviewedTeachingExample exposes hardening flag(s) {forbidden}; "
|
|
"ADR-0021 §2 forbids non-revisable state on the runtime data model."
|
|
)
|
|
|
|
|
|
def test_epistemic_status_enum_carries_no_trust_tier_names():
|
|
"""EpistemicStatus must describe revision-graph position, not source trust."""
|
|
values = _enum_value_strings(EpistemicStatus)
|
|
forbidden = values & _FORBIDDEN_TRUST_TIER_NAMES
|
|
assert forbidden == set(), (
|
|
f"EpistemicStatus enum contains source-trust tier value(s) {forbidden}; "
|
|
"ADR-0021 §3 forbids credentialing the schema."
|
|
)
|
|
|
|
|
|
def test_epistemic_status_enum_has_exactly_the_four_positions():
|
|
"""ADR-0021 §1 names the enum members precisely; no silent additions."""
|
|
assert _enum_value_strings(EpistemicStatus) == {
|
|
"coherent",
|
|
"contested",
|
|
"speculative",
|
|
"falsified",
|
|
}
|
|
|
|
|
|
def test_proposal_default_is_speculative():
|
|
"""ADR-0021 §Schema impact: proposals start SPECULATIVE; promotion is
|
|
a separate, review-mediated transition."""
|
|
proposal = PackMutationProposal(
|
|
proposal_id="x",
|
|
candidate_id="y",
|
|
subject="z",
|
|
correction_text="a knows b",
|
|
prior_surface="",
|
|
source=ProposalSource.operator(emitted_at_revision="test"),
|
|
)
|
|
assert proposal.epistemic_status is EpistemicStatus.SPECULATIVE
|
|
|
|
|
|
def test_proposal_with_status_returns_new_immutable_proposal():
|
|
"""Immutable update — the original is never mutated."""
|
|
original = PackMutationProposal(
|
|
proposal_id="x",
|
|
candidate_id="y",
|
|
subject="z",
|
|
correction_text="a knows b",
|
|
prior_surface="",
|
|
source=ProposalSource.operator(emitted_at_revision="test"),
|
|
)
|
|
promoted = original.with_status(EpistemicStatus.COHERENT)
|
|
|
|
assert promoted.epistemic_status is EpistemicStatus.COHERENT
|
|
assert original.epistemic_status is EpistemicStatus.SPECULATIVE
|
|
assert promoted is not original
|
|
|
|
|
|
def test_reviewed_example_default_is_speculative_for_accepted_outcome():
|
|
"""Acceptance is orthogonal to coherence ratification per ADR-0021
|
|
§Schema impact: 'Accepting a proposal is not the same as ratifying
|
|
it as COHERENT.'"""
|
|
from generate.intent import DialogueIntent, IntentTag
|
|
|
|
intent = DialogueIntent(tag=IntentTag.DEFINITION, subject="a")
|
|
candidate = CorrectionCandidate(
|
|
candidate_id="c1",
|
|
intent=intent,
|
|
correction_text="a is b",
|
|
prior_surface="",
|
|
prior_turn=0,
|
|
)
|
|
from teaching import review_correction
|
|
|
|
reviewed = review_correction(candidate)
|
|
assert reviewed.outcome is ReviewOutcome.ACCEPTED
|
|
assert reviewed.epistemic_status is EpistemicStatus.SPECULATIVE
|