Keep original case provenance while revalidating a fresh caller session
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
This commit is contained in:
tegwick 2026-10-18 11:32:19 +02:00
parent b593c87983
commit 08c098e64a
4 changed files with 180 additions and 1 deletions

View file

@ -152,3 +152,41 @@ recovery can count. No actual original holder/verifier or native adapter is adde
NetKingdom's current registration adapter still has no reconciliation callback;
source tests do not authorize its post-restart undo, replay or a changed caller/
session/run-sheet migration. WARDEN-WP-0044-T03 remains a native acceptance gate.
## Current caller versus original session reference — 2026-10-18 source
`Executor.admit` now accepts the optional `original_session_reference` for an
exact existing original-run continuation. It is metadata, never current
authentication. The normal `session_evidence` is independently verified as the
current caller's principal/actor/lane proof, including after restart, and remains
the evidence rechecked immediately before every journal write/owner callback.
The optional reference preserves the original run-sheet digest rather than
replacing immutable provenance with the new login. Before admitting it, under
the existing journal lock, the kernel requires an existing matching step journal
and `Journal.original_intent` correspondence: the original running receipt,
attempt, owner/target/principal/actor/lane, all six pins and exact adapter source.
Missing/orphan/legacy context, a changed reference or caller and a stale current
session refuse. The reference must differ from the current evidence identifier;
the actual session owner must additionally establish distinct authenticated
token/session identity, not just two labels. No new original context is created,
no completed operation can replay, and uncertainty is not cleared by readmission.
Omitting this option retains the old admission/pin semantics. Supplying a fresh
session alone still creates a different run sheet and cannot reuse a previous
journal. No existing descriptor, Invocation, Pins or receipt schema changes.
The new source seam does not implement changed-source/catalog/caller migration
or loss of rollback result; those still require the existing owner reconciliation
and recovery evidence. It is not general session management or another issuer.
A source integration fixture runs the actual producer-owned KeyCape status CLI
over HTTPS, the actual Warden kernel and a separately compiled generated Go
consumer. It verifies revoked-parent refusal before writes, distinct inverse
authentication in another Python process, original journal pin retention,
no business replay, exact synthetic target restore and independent read-only
negative cleanup observation. Case/type/catalog/owner attestations and the
target are synthetic; this is not Forgejo/browser/native acceptance or a real
approval. KeyCape's proposed online profile remains unadmitted. The private
unsigned CLI observation is not portable execution authority; actual owner
custody and original grant ownership are still mandatory before an inactive
response can establish revocation. No native verifier or adapter is installed.

View file

@ -663,7 +663,20 @@ class Executor:
materialization_evidence: str,
descriptor_evidence: str,
session_evidence: str,
original_session_reference: str | None = None,
) -> tuple[Descriptor, Invocation, StepState]:
# Current authentication is distinct from an immutable original receipt
# reference. The latter can only resume an exact existing journal intent;
# it is never passed to the current-session verifier as authority.
receipt_session = session_evidence
if original_session_reference is not None:
if (
type(original_session_reference) is not str
or not IDENTIFIER.fullmatch(original_session_reference)
or original_session_reference == session_evidence
):
raise Refused("distinct original session reference required")
receipt_session = original_session_reference
for source in (case_source, type_source, descriptor_source, catalog_source):
bounded_source(source)
descriptor = Descriptor.decode(descriptor_source)
@ -781,7 +794,7 @@ class Executor:
acceptance_evidence,
materialization_evidence,
descriptor_evidence,
session_evidence,
receipt_session,
],
separators=(",", ":"),
).encode()
@ -803,6 +816,13 @@ class Executor:
descriptor.allowed_created,
descriptor.retry_safe,
)
if original_session_reference is not None:
with self.journal.locked():
binding = f"{invocation.step}:{invocation.target}"
existing = self.journal.load(binding, initial)
if not existing.last:
raise Refused("original dispatch journal required; no backfill")
self.journal.original_intent(binding, invocation, adapter.source)
# Keep the mode and exact authenticated catalog bytes with this admission;
# dispatch must not substitute a later ambient catalog or lose its mode.
self._admitted[invocation] = (

View file

@ -0,0 +1,105 @@
"""Current caller authentication never replaces historical dispatch provenance."""
from dataclasses import replace
import pytest
from tests.test_case_run import make_rig
from warden.case_receipt_state import Outcome, Refused
from warden.case_run import Executor, Journal
def fresh_verifier(executor, *, allow=True):
original = executor.evidence_verifier
def verify(purpose, source, evidence):
if purpose.startswith("caller-session:") and evidence == "fresh-session":
return allow
return original(purpose, source, evidence)
executor.evidence_verifier = verify
return verify
def rows(executor):
return tuple(executor.journal.db.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
for table in ("receipts", "intents"))
def readmit(executor, sources, kwargs, reference="session"):
return executor.admit(*sources, **{**kwargs, "session_evidence": "fresh-session",
"original_session_reference": reference})
def test_normal_new_session_cannot_rewrite_original_journal_pins(tmp_path):
executor, admitted, _, sources, kwargs = make_rig(tmp_path)
executor.run(*admitted)
fresh_verifier(executor)
before = rows(executor)
changed = executor.admit(*sources, **{**kwargs,"session_evidence":"fresh-session"})
assert changed[1].pins != admitted[1].pins
with pytest.raises(Refused,match="journal pins changed"):
executor.rollback(*changed)
assert rows(executor) == before
def test_fresh_process_inverse_retains_original_pins_without_replay(tmp_path):
executor, admitted, calls, sources, kwargs = make_rig(tmp_path)
assert executor.run(*admitted).last.outcome is Outcome.SUCCESS
before = rows(executor)
fresh = Executor(Journal(executor.journal.directory), executor.adapters,
fresh_verifier(executor))
renewed = readmit(fresh, sources, kwargs)
assert renewed[1].pins == admitted[1].pins
assert rows(fresh) == before
with pytest.raises(Refused):
fresh.run(*renewed)
assert len(calls) == 1
assert fresh.rollback(*renewed).last.outcome is Outcome.ROLLED_BACK
@pytest.mark.parametrize("kind", ["missing-intent", "orphan-intent", "wrong-principal", "wrong-reference", "same-as-current", "invalid-reference", "inactive-current"])
def test_original_reference_is_never_authentication_or_new_intent(tmp_path, kind):
executor, admitted, calls, sources, kwargs = make_rig(tmp_path)
if kind != "missing-intent":
executor.run(*admitted)
if kind == "orphan-intent":
executor.journal.db.execute("DELETE FROM intents")
if kind == "wrong-principal":
kwargs = {**kwargs,"principal":"another-operator"}
fresh_verifier(executor, allow=kind != "inactive-current")
before = rows(executor)
reference = {"wrong-reference": "unrelated-session", "same-as-current": "fresh-session",
"invalid-reference": "not valid"}.get(kind, "session")
with pytest.raises(Refused):
readmit(executor, sources, kwargs, reference)
assert rows(executor) == before
def test_unknown_effect_stays_unknown_after_current_readmission(tmp_path):
executor, admitted, calls, sources, kwargs = make_rig(tmp_path)
old = executor.adapters[admitted[0].adapter]
def interrupted(inv):
calls.append(inv)
raise KeyboardInterrupt
executor.adapters[old.reference] = replace(old, invoke=interrupted)
with pytest.raises(KeyboardInterrupt):
executor.run(*admitted)
fresh_verifier(executor)
before = rows(executor)
renewed = readmit(executor, sources, kwargs)
assert rows(executor) == before
with pytest.raises(Refused):
executor.run(*renewed, retry=True)
state = executor.journal.load("mint-secret:fixture-client", renewed[2])
assert state.last.outcome is Outcome.UNCERTAIN
assert len(calls) == 1
def test_current_session_revalidated_after_readmission_before_journal_write(tmp_path):
executor, admitted, _, sources, kwargs = make_rig(tmp_path)
executor.run(*admitted)
fresh_verifier(executor)
renewed = readmit(executor, sources, kwargs)
executor.evidence_verifier = lambda *_: False
before = rows(executor)
with pytest.raises(Refused, match="caller session no longer verified"):
executor.rollback(*renewed)
assert rows(executor) == before

View file

@ -151,6 +151,22 @@ state_hub_task_id: "bcbf6c91-2c04-5c16-ae8c-5d6c06fd6b89"
### T03 — Run and verify a case
2026-10-18 current-session continuation: the new optional
`Executor.admit(original_session_reference=...)` separates immutable original
run-sheet provenance from fresh independently verified current session evidence.
It requires the exact existing journal and original running intent under the
normal lock, refuses missing/orphan/changed context and retains all original
pins. Current authority is rechecked before every subsequent write/callback;
neither the original reference nor a fresh login can replay a completed effect
or clear uncertainty. No descriptor/Invocation/receipt schema or default
admission behavior changes. Source integration uses the actual KeyCape CLI over
HTTPS, a fresh Python owner process and actual Warden SQLite, with synthetic
case/catalog/owner/effect attestations only. Exact source/proof is retained by
CUST-WP-0080 under `keycape-current-client-r1`. Native owner mappings, current
operation/effect authority, credential custody, case/type/descriptor refreeze,
Forgejo inverse semantics and first native acceptance remain open; no lane,
adapter, runtime or caller scope is activated by this source change.
`warden case run <case.yaml>` executes the plan. Owner entry points run through
the existing proxy as the caller; steps that need an attended login use the
existing contained login envelope (silent command, session revoked afterwards),