core/core
Shay 72fac59029
feat(ADR-0161.3): submission-time invariants — duplicate + dependent_on_pending auto-reject (#313)
Adds two pre-gate checks to propose_from_candidate that fire after the
Step 2 capacity check and before the replay gate.  No log entry is
written on either refusal — the append-only invariant holds.

Check order at function entry (ADR-0161 §3):
  1. Capacity (Step 2)          → RefusedAtCapacity
  2. Duplicate                  → RefusedAsDuplicate
  3. Dependent_on_pending       → RefusedAsDependent
  4. Replay gate                → auto-reject on regression

New frozen dataclasses:

  @dataclass(frozen=True, slots=True)
  class RefusedAsDuplicate:
      proposal_id: str
      existing_state: str        # covers all states: pending/accepted/rejected/withdrawn
      reason: str = "duplicate"

  @dataclass(frozen=True, slots=True)
  class RefusedAsDependent:
      candidate_id: str
      dependent_on: tuple[str, ...]       # pending proposal_ids that block
      overlapping_lemmas: tuple[str, ...] # normalised lemmas that triggered
      reason: str = "dependent_on_pending"

Lemma-overlap rule: case-insensitive exact-match on strip().lower().
Conservative — over-reject rather than admit-with-hidden-dependency.
False positives are recoverable (re-emit after blocker is ratified);
false negatives silently couple ratification choices.

CLI surfaces both outcomes in cmd_teaching_propose and
cmd_teaching_propose_from_exemplars (exit code 1).

Step 2 backpressure tests updated: made pre-populated candidates use
unique objects to avoid triggering the new dependency check, and
updated idempotency assertions to reflect the new RefusedAsDuplicate
return for re-submitted content.

Co-references: ADR-0161 §3, Step 1 PR #296, Step 2 PR #311,
ADR-0057, ADR-0151.
2026-05-26 16:46:25 -07:00
..
capability feat(ADR-0120 math, ledger flip): mathematics_logic → expert tier (first-ever) (#195) 2026-05-23 18:55:34 -07:00
cognition feat(W-020b): DerivedRecognizer producer wiring (ADR-0154) (#278) 2026-05-25 18:44:12 -07:00
contemplation feat(contemplation): land ADR-0080 phase 1 (#119) 2026-05-22 13:10:03 -07:00
demos fix(quarantine): drain all 60 quarantined tests — QUARANTINE=∅ (#267) 2026-05-25 11:22:12 -07:00
physics feat(W-020a): TurnEvent.trace_hash back-stamp (ADR-0153) (#277) 2026-05-25 18:42:35 -07:00
protocol feat(protocol): ADR-0140 CORE Trace Protocol v0 (#259) 2026-05-25 06:08:51 -07:00
__init__.py chore: fix package name, add core/__init__.py, ADR-0011, session note 2026-05-13 2026-05-13 10:44:42 -07:00
_safe_display.py feat(adr-0051): trust-boundary hardening pass 2026-05-18 07:09:55 -07:00
cli.py feat(ADR-0161.3): submission-time invariants — duplicate + dependent_on_pending auto-reject (#313) 2026-05-26 16:46:25 -07:00
config.py feat(W-017): load-time auto-proposal pipeline from enriched candidates (ADR-0151) (#275) 2026-05-25 12:46:10 -07:00
epistemic_state.py feat(workbench-ui): design system v1 scaffold (ADR-0162 Branch 1) (#295) 2026-05-26 11:33:27 -07:00