feat: journal strict task consumption before native fixture effects
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
parent
d042d468df
commit
e2b832b5d4
8 changed files with 717 additions and 2 deletions
|
|
@ -231,6 +231,21 @@ class Journal:
|
|||
return {"state": row[3], "evidence_digest": row[4], "dispatch_allowed": False,
|
||||
"external_outcome_unknown": bool(pending)}
|
||||
|
||||
def next_call_ordinal(self, binding: Binding):
|
||||
"""Continue the same admitted invocation after owner auth/consume calls.
|
||||
|
||||
This never grants dispatch or resumes a persisted admission. The actual
|
||||
before_call transaction remains the final serialization/intent guard.
|
||||
"""
|
||||
if not self._held or self._admission != binding:
|
||||
raise Refused("next call requires same live admission")
|
||||
count,pending=self.db.execute("SELECT COUNT(*),COALESCE(SUM(outcome='pending'),0) FROM calls WHERE operation=?",(binding.operation_id,)).fetchone()
|
||||
cleanup=self.db.execute("SELECT COUNT(*) FROM cleanup WHERE operation=? AND outcome!='owed'",(binding.operation_id,)).fetchone()[0]
|
||||
state=self.db.execute("SELECT state FROM operations WHERE operation=?",(binding.operation_id,)).fetchone()[0]
|
||||
if pending or cleanup or state in {'failed','unknown','cleanup-confirmed'} or count>63:
|
||||
raise Refused("call sequence is uncertain, closed or exhausted")
|
||||
return count
|
||||
|
||||
def before_call(self, binding: Binding, ordinal: int, call_digest: str):
|
||||
"""Persist one exact plan-call intent before the mediator issues it.
|
||||
|
||||
|
|
|
|||
180
scripts/task_execution_mediator.py
Normal file
180
scripts/task_execution_mediator.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"""Owner mediator seam for the existing strict Secrets handoff and Approval GET.
|
||||
|
||||
Only the disposable NativeFixtureAdapter is callable here. No production backend,
|
||||
caller-supplied authorizer/transport, approval boolean or portable receipt exists.
|
||||
The strict Secrets module is an explicit external dependency: absence refuses
|
||||
before credential-bearing requests or native effects, never a permissive fallback.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime,timezone
|
||||
import importlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from task_execution_cleanup import run_cleanup
|
||||
from task_execution_journal import Binding,Cleanup,Journal,Refused
|
||||
from task_execution_plan import Compiler,encoded,content_hash
|
||||
from task_execution_native_fixture import FixtureAdmission,NativeFixtureAdapter
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConfirmationConfig:
|
||||
"""Owner-installed independent read credential/route, never request fields."""
|
||||
origin: str
|
||||
token_file: Path
|
||||
timeout_seconds: float = 3
|
||||
|
||||
|
||||
_HANDOFF_FIELDS={'execution_contract','approval_id','request_digest','decision_id',
|
||||
'native_binding','approval_binding_digest','submitted_request_digest',
|
||||
'decision_not_before','decision_expires_at','approval_expires_at'}
|
||||
|
||||
|
||||
def _strict_dependency():
|
||||
try:
|
||||
module=importlib.import_module('secrets_engine.task_execution_handoff')
|
||||
for name in ['TaskExecutionAuthorization','TaskExecutionConsumption',
|
||||
'authorize_task_execution','consume_task_execution']:
|
||||
if not hasattr(module,name):raise ImportError()
|
||||
return module
|
||||
except ImportError:
|
||||
raise Refused('strict Secrets task execution handoff dependency unavailable') from None
|
||||
|
||||
|
||||
def _fresh(cfg,handoff,descriptor):
|
||||
from secrets_engine.application_time import read_window,validity_bounds
|
||||
lower,upper=validity_bounds(window=read_window(cfg))
|
||||
try:
|
||||
parse=lambda value:datetime.fromisoformat(value.replace('Z','+00:00'))
|
||||
deadline=parse(descriptor['operation_deadline'])
|
||||
approval=parse(handoff['approval_expires_at']);decision=parse(handoff['decision_expires_at'])
|
||||
start=parse(handoff['decision_not_before']) if handoff['decision_not_before'] else None
|
||||
if any(value.tzinfo is None for value in [deadline,approval,decision]) or (start is not None and start.tzinfo is None):raise ValueError()
|
||||
if upper>=min(deadline,approval,decision) or deadline>min(approval,decision) or (start is not None and lower<start):raise ValueError()
|
||||
except (KeyError,TypeError,ValueError,AttributeError):
|
||||
raise Refused('original task authority deadline refused') from None
|
||||
|
||||
|
||||
def _intent(journal,binding,kind,fields):
|
||||
digest=content_hash(encoded({'kind':kind,**fields}))
|
||||
ordinal=journal.next_call_ordinal(binding)
|
||||
journal.before_call(binding,ordinal,digest)
|
||||
return ordinal,digest
|
||||
|
||||
|
||||
def _unknown(journal,binding):
|
||||
state=journal.observe(binding)['state']
|
||||
if state not in {'unknown','failed','cleanup-confirmed'}:
|
||||
journal.transition(binding,state,'unknown')
|
||||
|
||||
|
||||
def run_native_fixture_task(*,compiler:Compiler,journal:Journal,plan_request:dict,
|
||||
owner_descriptor:bytes,secrets_cfg,entry,
|
||||
confirmation:ConfirmationConfig,
|
||||
native_admission:FixtureAdmission):
|
||||
"""Authorize inside owner boundary, reserve, consume once, independently GET.
|
||||
|
||||
Strict authorization is executed here through the actual Secrets-owned module;
|
||||
an inbound TaskExecutionAuthorization or arbitrary callable is never accepted.
|
||||
This function returns closed metadata, not tokens or raw approval/decision data.
|
||||
Positive execution requires the module's real signed PDP path; actual unadmitted
|
||||
Access policy must still deny. Fixture policy/identity admission is independent
|
||||
synthetic setup and cannot become production acceptance.
|
||||
"""
|
||||
strict=_strict_dependency()
|
||||
from approval_engine.execution_confirmation import ExecutionConfirmationClient
|
||||
from secrets_engine.execution_contract import ExecutionContract
|
||||
if type(confirmation) is not ConfirmationConfig or type(native_admission) is not FixtureAdmission:
|
||||
raise Refused('independent owner provider configuration required')
|
||||
try:
|
||||
origin=urlsplit(confirmation.origin)
|
||||
if origin.scheme not in {'http','https'} or origin.hostname!='127.0.0.1' or not origin.port or origin.username or origin.password or origin.path or origin.query or origin.fragment:
|
||||
raise ValueError()
|
||||
if confirmation.origin!=str(getattr(secrets_cfg,'approval_url','')).rstrip('/'):
|
||||
raise ValueError()
|
||||
descriptor=ExecutionContract.parse(json.loads(owner_descriptor)).as_dict()
|
||||
if encoded(descriptor)!=owner_descriptor or native_admission.descriptor!=owner_descriptor:
|
||||
raise ValueError()
|
||||
except Exception:
|
||||
raise Refused('exact owner descriptor or provider route refused') from None
|
||||
# An independently pinned owner catalogue must match every engine field. Its
|
||||
# approval IDs are source references, never runtime authority.
|
||||
proposal=compiler.proposal()
|
||||
target={key:getattr(entry,key) for key in proposal['catalog_target']}
|
||||
if encoded(target)!=encoded(proposal['catalog_target']) or entry.id!=proposal['catalog_id'] or entry.stage!='prod':
|
||||
raise Refused('owner catalogue does not match strict task request')
|
||||
snapshot={path:compiler.read(Path(path)) for path in compiler.pins}
|
||||
owner_entry=compiler._catalogue_entry(snapshot)
|
||||
# Only the served approval pointer is added after issue. Purpose or other
|
||||
# source approval semantics cannot be substituted through an inbound entry.
|
||||
without_reference=lambda value:{key:item for key,item in value.items() if key!='authorization_id'}
|
||||
if encoded(without_reference(entry.approval))!=encoded(without_reference(owner_entry.approval)):
|
||||
raise Refused('owner approval metadata differs from source')
|
||||
client=ExecutionConfirmationClient(confirmation.origin,confirmation.token_file,
|
||||
timeout_seconds=confirmation.timeout_seconds)
|
||||
with journal.locked():
|
||||
authorization=strict.authorize_task_execution(secrets_cfg,entry,
|
||||
execution_contract=ExecutionContract.parse(descriptor))
|
||||
if type(authorization) is not strict.TaskExecutionAuthorization:
|
||||
raise Refused('strict task authorization missing')
|
||||
handoff=authorization.as_owner_handoff()
|
||||
if type(handoff) is not dict or set(handoff)!=_HANDOFF_FIELDS or encoded(handoff['execution_contract'])!=owner_descriptor:
|
||||
raise Refused('strict task handoff does not match owner descriptor')
|
||||
_fresh(secrets_cfg,handoff,descriptor)
|
||||
request=dict(plan_request,request_digest=handoff['request_digest'])
|
||||
plan=compiler.bind_execution(compiler.compile(request),descriptor,owner_descriptor)
|
||||
binding=Binding(handoff['approval_id'],handoff['request_digest'],descriptor['operation_id'],plan.content_digest)
|
||||
# Constructor validates fixed native origin/fixture process/grant sources
|
||||
# but performs no native HTTP request or credential acquisition.
|
||||
native=NativeFixtureAdapter(compiler,plan,journal,binding,native_admission)
|
||||
journal.reserve(binding,native.cleanup)
|
||||
fields={key:handoff[key] for key in ['approval_id','request_digest','decision_id']}
|
||||
try:
|
||||
ordinal,digest=_intent(journal,binding,'strict-secrets-task-consume',fields)
|
||||
consumed=strict.consume_task_execution(secrets_cfg,authorization)
|
||||
if type(consumed) is not strict.TaskExecutionConsumption:
|
||||
raise Refused('strict task consumption missing')
|
||||
receipt={key:getattr(consumed,key) for key in ['approval_id','request_digest','decision_id','consumed_at','idempotent']}
|
||||
if any(receipt[key]!=value for key,value in fields.items()) or receipt['idempotent'] is not False:
|
||||
raise Refused('historical or mismatched task consumption refused')
|
||||
stamp=datetime.fromisoformat(receipt['consumed_at'].replace('Z','+00:00'))
|
||||
if stamp.tzinfo is None:raise Refused('consumption timestamp missing')
|
||||
journal.after_call(binding,ordinal,digest,content_hash(encoded(receipt)))
|
||||
_fresh(secrets_cfg,handoff,descriptor)
|
||||
ordinal,digest=_intent(journal,binding,'independent-approval-consumption-read',fields)
|
||||
confirmed=client.confirm_consumption(binding.approval_id,binding.request_digest,
|
||||
handoff['decision_id'],expected_binding=handoff['native_binding'],
|
||||
expected_pdp_digest=handoff['approval_binding_digest'])
|
||||
if confirmed!={key:receipt[key] for key in ['approval_id','request_digest','decision_id','consumed_at']}:
|
||||
raise Refused('independent consumption correspondence refused')
|
||||
journal.after_call(binding,ordinal,digest,content_hash(encoded(confirmed)))
|
||||
_fresh(secrets_cfg,handoff,descriptor)
|
||||
journal.transition(binding,'reserved','consume-confirmed',content_hash(encoded(confirmed)))
|
||||
except Exception:
|
||||
_unknown(journal,binding)
|
||||
raise Refused('task consumption or independent confirmation unconfirmed; no native execution') from None
|
||||
result={'operation_id':binding.operation_id,'request_digest':binding.request_digest,
|
||||
'decision_id':handoff['decision_id'],'native_apply':'unverified',
|
||||
'native_calls_completed':0,'cleanup_confirmed':False}
|
||||
try:
|
||||
journal.transition(binding,'consume-confirmed','authenticating')
|
||||
# Fixture identity is independently seeded synthetic setup, not CCR38
|
||||
# provenance proof or a production service login.
|
||||
journal.transition(binding,'authenticating','grant-issuing')
|
||||
native.materialize_grant()
|
||||
journal.transition(binding,'grant-issued','applying')
|
||||
for ordinal in range(6):
|
||||
_fresh(secrets_cfg,handoff,descriptor)
|
||||
native.metadata_call(ordinal)
|
||||
result['native_calls_completed']+=1
|
||||
except Exception:
|
||||
_unknown(journal,binding)
|
||||
result['native_apply']='unknown'
|
||||
finally:
|
||||
result['cleanup_confirmed']=run_cleanup(journal,binding,native)['confirmed']
|
||||
# Native total expected-state normalization/acceptance remains explicitly
|
||||
# absent. Six successful calls cannot be relabelled a verified owner trial.
|
||||
result['business_state']=journal.observe(binding)['state']
|
||||
return result
|
||||
|
|
@ -122,8 +122,8 @@ class NativeFixtureAdapter:
|
|||
self.used+=1
|
||||
evidence_payload = encoded({'token':'owner-retained-task-token'}) if path=='auth/token/lookup' else payload
|
||||
digest=content_hash(encoded({'method':method,'path':path,'payload_sha256':'sha256:'+content_hash(evidence_payload),'credential_slot':'task' if task else 'owner'}))
|
||||
ordinal=self._ordinal
|
||||
self.journal.before_call(self.binding,ordinal,digest);self._ordinal+=1
|
||||
ordinal=self.journal.next_call_ordinal(self.binding)
|
||||
self.journal.before_call(self.binding,ordinal,digest);self._ordinal=ordinal+1
|
||||
try: status,data=self._http(method,path,token,payload)
|
||||
except Exception:
|
||||
state=self.journal.observe(self.binding)['state']
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue