Verify exact task metadata baseline and readback
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
parent
5f30ddd90c
commit
d870596cc7
8 changed files with 274 additions and 14 deletions
|
|
@ -54,9 +54,10 @@ with a preceding durable CAS/confirmation sequence.
|
|||
native calls continue at2. Pending/unknown/cleanup-closed state cannot supply a
|
||||
next ordinal. Each native request retains its own durable intent and outcome.
|
||||
8. Cleanup stays under the original distinct obligations even if business work
|
||||
fails. Returned evidence explicitly says native apply is unverified: six successful
|
||||
native calls do not establish total approved baseline/readback normalization,
|
||||
atomic CAS, service-login provenance or native production owner acceptance.
|
||||
fails. Wave 5 requires exact committed before/after snapshots before reporting
|
||||
native apply verified; six HTTP successes alone remain insufficient. This is
|
||||
sequential fixture acceptance, without atomic CAS, service-login provenance or
|
||||
native production owner acceptance.
|
||||
|
||||
The owner configuration selects provider route/read credential and the existing
|
||||
Secrets configuration. These are independently installed runtime inputs, never
|
||||
|
|
@ -105,3 +106,10 @@ per-call served-time/use enforcement, audit/outbox and protected uncertain/crash
|
|||
recovery. Then obtain and execute the exact founder-scoped attended trial.
|
||||
Approval replay prevention and uncertain native-effect reconciliation remain
|
||||
separate guarantees. T12, Secrets WP13 and native adoption stay open.
|
||||
|
||||
Wave 5 adds owner-installed exact metadata snapshots committed through the
|
||||
existing expected_before/expected_after descriptor fields, per-target baseline
|
||||
and complete role/policy readback comparison, durable readback/verified evidence,
|
||||
and a separate cleanup-confirmed transition. The sequence remains sequential,
|
||||
without native CAS or independently recoverable cleanup handles. See
|
||||
[fixture acceptance](task-execution-native-fixture.md#exact-disposable-metadata-acceptance-2026-10-16-wave-5).
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ metadata calls use independently recompiled source/method/path/body. A task use
|
|||
reserved for revoke; native token limits also enforce the ceiling. Business calls
|
||||
check wall and monotonic deadlines before effect, with a five-second cleanup reserve.
|
||||
No retry/remint, caller HCL/JSON/path, mount/AppRole/KV/SecretID or shell API exists.
|
||||
The adapter cannot claim complete baseline CAS or normalized readback acceptance.
|
||||
Wave 5 below adds exact sequential snapshot acceptance; native baseline CAS remains absent.
|
||||
|
||||
Every business/grant request records durable intent and closed status evidence.
|
||||
Native cleanup has its own durable per-request intents beneath the original
|
||||
|
|
@ -89,3 +89,35 @@ native token, then performs explicit synthetic owner recovery. No blind retry oc
|
|||
No umbrella broker, general executor, new credential catalogue or standing service
|
||||
admin entitlement is introduced. Approval CAS prevents approval replay; it cannot
|
||||
make uncertain native effects exactly-once. RPF T12 and Secrets WP13 remain open.
|
||||
|
||||
### Exact disposable metadata acceptance (2026-10-16, wave 5)
|
||||
|
||||
The owner-installed `FixtureAdmission` now carries canonical JSON before/after
|
||||
snapshot bytes. Their SHA-256 values must equal the existing descriptor's
|
||||
`expected_before` and `expected_after`, and both snapshots must name exactly the
|
||||
two compiled metadata paths. Explicit JSON null means absence. Only the actual
|
||||
OpenBao empty-errors 404 is accepted as absence; denied/malformed reads fail.
|
||||
These inputs contain metadata, never KV values or private token handles.
|
||||
|
||||
Policy comparison retains name, HCL, version and cas_required. Only the
|
||||
modification timestamp is excluded, after requiring an aware timestamp and the
|
||||
closed observed policy field set. Role comparison retains every returned field,
|
||||
including aliases, token defaults and unexpected additions, using canonical JSON
|
||||
bytes (therefore false cannot equal zero). The disposable Kubernetes fixture pins
|
||||
the complete OpenBao 2.5.5 response shape. Another plugin/version/default requires
|
||||
an independently reviewed owner snapshot; nothing infers new accepted defaults.
|
||||
|
||||
Each baseline gates its corresponding write. Policy readback occurs before role
|
||||
baseline/write/readback. A role conflict can therefore leave the policy applied;
|
||||
that operation remains unknown. This sequence has no native CAS, aggregate
|
||||
baseline gate, concurrent-writer exclusion, or simultaneous final-state proof.
|
||||
After all six calls match the committed snapshots, the same-live adapter records
|
||||
readback and verified with a value-free evidence digest. The mediator reports
|
||||
native_apply verified; cleanup-confirmed requires all separate cleanup receipts.
|
||||
Readback success with unconfirmed cleanup cannot reach cleanup-confirmed.
|
||||
|
||||
Reopening SQLite cannot dispatch, reconsume, restore token/accessor handles or
|
||||
retry cleanup. Native effects can survive process loss. Cleanup handles remain
|
||||
memory-only: no independently authorized recovery mechanism is implemented here.
|
||||
This closes disposable sequential apply acceptance only, not TSA03 or native
|
||||
production recovery/admission.
|
||||
|
|
|
|||
|
|
@ -169,12 +169,16 @@ def run_native_fixture_task(*,compiler:Compiler,journal:Journal,plan_request:dic
|
|||
_fresh(secrets_cfg,handoff,descriptor)
|
||||
native.metadata_call(ordinal)
|
||||
result['native_calls_completed']+=1
|
||||
result['readback_evidence_digest']=native.verify_metadata()
|
||||
result['native_apply']='verified'
|
||||
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.
|
||||
if result['native_apply']=='verified' and result['cleanup_confirmed']:
|
||||
journal.transition(binding,'verified','cleanup-confirmed',result['readback_evidence_digest'])
|
||||
# Readback and cleanup are independent outcomes; no recovery authority
|
||||
# or native compare-and-swap is implied by either fixture result.
|
||||
result['business_state']=journal.observe(binding)['state']
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ class PrivateSession:
|
|||
class FixtureAdmission:
|
||||
"""Independently installed owner fixture inputs, not a request schema."""
|
||||
descriptor: bytes
|
||||
expected_before: bytes
|
||||
expected_after: bytes
|
||||
owner_token: str = field(repr=False)
|
||||
identity_session: PrivateSession = field(repr=False)
|
||||
server_process: subprocess.Popen = field(repr=False)
|
||||
|
|
@ -78,6 +80,22 @@ class NativeFixtureAdapter:
|
|||
self.hcl+='path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n'
|
||||
if self.meta['execution_grant']['openbao']['policy']!={'name':self.policy_name,'hcl':self.hcl}:
|
||||
raise Refused('native grant body differs from bound owner projection')
|
||||
# Installed owner snapshots use the existing descriptor commitments. No
|
||||
# request-supplied expectation or response may authorize itself.
|
||||
self.expected={}
|
||||
for phase in ('before','after'):
|
||||
raw=getattr(admission,'expected_'+phase)
|
||||
if type(raw) is not bytes or 'sha256:'+content_hash(raw)!=descriptor['expected_'+phase]:
|
||||
raise Refused('owner expected-state digest mismatch')
|
||||
value=json.loads(raw)
|
||||
if type(value) is not dict or set(value)!=set(paths) or encoded(value)!=raw:
|
||||
raise Refused('canonical exact target snapshot required')
|
||||
if any(item is not None and type(item) is not dict for item in value.values()):
|
||||
raise Refused('metadata snapshot object or explicit absence required')
|
||||
if phase=='after' and any(item is None for item in value.values()):
|
||||
raise Refused('present final metadata required')
|
||||
self.expected[phase]=value
|
||||
self._observed={'before':{},'after':{}}
|
||||
self.cleanup=tuple(Cleanup(item['kind'],item['call_digest']) for item in cleanup_specs(binding.operation_id))
|
||||
if self.meta['cleanup'] != cleanup_specs(binding.operation_id): raise Refused('cleanup plan mismatch')
|
||||
self.task=None; self._mint_attempted=False; self._policy_attempted=False; self._ordinal=0; self._next_metadata=0
|
||||
|
|
@ -183,11 +201,53 @@ class NativeFixtureAdapter:
|
|||
call=self.compiler.before_call(self.plan,ordinal)
|
||||
status,data=self._business(call.method,call.path,self.task.token,call.payload,task=True)
|
||||
self._next_metadata+=1
|
||||
self._ok(status,data,{200,204,404} if ordinal in {0,3} else {200,204})
|
||||
# Metadata only. The trusted owner caller must compare native normalized
|
||||
# baseline/readback to separately admitted expected state. Not KV values.
|
||||
try:
|
||||
self._ok(status,data,{200,204,404} if ordinal in {0,3} else {200,204})
|
||||
if ordinal in {0,2,3,5}:
|
||||
phase='before' if ordinal in {0,3} else 'after'
|
||||
value=self._metadata_snapshot(call.path,status,data)
|
||||
if encoded(value)!=encoded(self.expected[phase][call.path]):
|
||||
raise Refused('native exact expected-state mismatch')
|
||||
self._observed[phase][call.path]=value
|
||||
except Exception:
|
||||
state=self.journal.observe(self.binding)['state']
|
||||
if state!='unknown': self.journal.transition(self.binding,state,'unknown')
|
||||
raise Refused('native baseline or readback unconfirmed') from None
|
||||
return status,data
|
||||
|
||||
@staticmethod
|
||||
def _metadata_snapshot(path,status,response):
|
||||
# HTTP errors/denials are never absence. OpenBao's exact empty 404
|
||||
# metadata response is the only absent representation admitted here.
|
||||
if status==404 and response=={'errors':[]}:
|
||||
return None
|
||||
if status!=200 or response.get('errors') or type(response.get('data')) is not dict:
|
||||
raise Refused('metadata read not confirmed')
|
||||
value=dict(response['data'])
|
||||
if path.startswith('sys/policies/acl/'):
|
||||
if set(value)!={'cas_required','modified','name','policy','version'}:
|
||||
raise Refused('unrecognized policy metadata fields')
|
||||
modified=value.pop('modified')
|
||||
if type(modified) is not str or datetime.fromisoformat(modified.replace('Z','+00:00')).tzinfo is None:
|
||||
raise Refused('invalid policy modification timestamp')
|
||||
# All role fields, aliases, defaults and unknown additions participate in
|
||||
# exact canonical JSON comparison. True never equals integer 1 here.
|
||||
return value
|
||||
|
||||
def verify_metadata(self):
|
||||
self._fresh()
|
||||
if self._next_metadata!=6 or self.journal.observe(self.binding)['state']!='applying':
|
||||
raise Refused('complete same-live metadata sequence required')
|
||||
for phase in ('before','after'):
|
||||
if encoded(self._observed[phase])!=encoded(self.expected[phase]):
|
||||
raise Refused('complete expected metadata evidence required')
|
||||
evidence=content_hash(encoded({'expected_before':'sha256:'+content_hash(self.admission.expected_before),
|
||||
'expected_after':'sha256:'+content_hash(self.admission.expected_after),
|
||||
'scope':'per-target-sequential-readback-no-native-cas'}))
|
||||
self.journal.transition(self.binding,'applying','readback',evidence)
|
||||
self.journal.transition(self.binding,'readback','verified',evidence)
|
||||
return evidence
|
||||
|
||||
def _cleanup_admitted(self, kind, digest):
|
||||
self._live()
|
||||
expected=next(item for item in self.cleanup if item.kind==kind)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ def test_whole_strict_consume_confirmation_precedes_real_native_effect(owner,tmp
|
|||
with whole_fixture(owner,tmp_path) as fixture:
|
||||
result=invoke(fixture)
|
||||
assert result['native_calls_completed']==6 and result['cleanup_confirmed']
|
||||
assert result['native_apply']=='unverified' # total owner baseline/readback acceptance remains separate
|
||||
assert result['native_apply']=='verified'
|
||||
assert result['business_state']=='cleanup-confirmed'
|
||||
assert len(result['readback_evidence_digest'])==64
|
||||
obj=fixture.approval.engine.get(fixture.approval_id)
|
||||
assert obj.status=='consumed'
|
||||
assert obj.consumed_digest==result['request_digest']
|
||||
|
|
|
|||
|
|
@ -78,6 +78,19 @@ def owner(compiler,bao_dev,tmp_path):
|
|||
'backend_origin':origin,'cluster_uid':'disposable-fixture-only',
|
||||
'operations':req['operations'],'operation_deadline':(datetime.now(timezone.utc)+timedelta(seconds=240)).isoformat(),
|
||||
'backend_budget':{'ttl_seconds':120,'uses':12},'expected_before':'sha256:'+'0'*64,'expected_after':'sha256:'+'1'*64}
|
||||
# Independently installed synthetic expected state, before any native write.
|
||||
policy_call=compiler.before_call(plan,1);role_call=compiler.before_call(plan,4)
|
||||
body=json.loads(role_call.payload)
|
||||
before=m.encoded({policy_call.path:None,role_call.path:None})
|
||||
after=m.encoded({policy_call.path:{'cas_required':False,'name':policy_call.path.rsplit('/',1)[1],
|
||||
'policy':json.loads(policy_call.payload)['policy'],'version':1},role_call.path:{
|
||||
'alias_name_source':'serviceaccount_uid','bound_service_account_names':body['bound_service_account_names'],
|
||||
'bound_service_account_namespace_selector':'','bound_service_account_namespaces':body['bound_service_account_namespaces'],
|
||||
'policies':body['policies'].split(','),'token_bound_cidrs':[],'token_explicit_max_ttl':0,
|
||||
'token_max_ttl':0,'token_no_default_policy':False,'token_num_uses':0,'token_period':0,
|
||||
'token_policies':body['policies'].split(','),'token_strictly_bind_ip':False,
|
||||
'token_ttl':900,'token_type':'default','ttl':900}})
|
||||
descriptor.update(expected_before=m.tagged(before),expected_after=m.tagged(after))
|
||||
# Exercise the actual consumer closed descriptor parser as well; source pins,
|
||||
# grant/expected-state digests remain deliberately synthetic owner fixtures.
|
||||
from secrets_engine.execution_contract import ExecutionContract
|
||||
|
|
@ -107,7 +120,7 @@ def owner(compiler,bao_dev,tmp_path):
|
|||
plan=compiler.bind_execution(compiler.compile(req),descriptor,installed)
|
||||
binding=Binding('SYNTHETIC-APPROVAL-NOT-PRODUCTION',req['request_digest'],req['operation_id'],plan.content_digest)
|
||||
state=tmp_path/'private-journal';state.mkdir(mode=0o700);journal=Journal(state)
|
||||
admission=FixtureAdmission(installed,root,identity,process)
|
||||
admission=FixtureAdmission(installed,before,after,root,identity,process)
|
||||
native=NativeFixtureAdapter(compiler,plan,journal,binding,admission)
|
||||
try: yield native,journal,binding,call,compiler,descriptor
|
||||
finally: journal.close()
|
||||
|
|
@ -140,6 +153,7 @@ def test_native_fixed_grant_metadata_denials_and_confirmed_cleanup(owner):
|
|||
assert role[1]['data']['bound_service_account_names']==expected['bound_service_account_names']
|
||||
assert role[1]['data']['bound_service_account_namespaces']==expected['bound_service_account_namespaces']
|
||||
assert role[1]['data']['token_policies']==expected['policies'].split(',')
|
||||
assert len(native.verify_metadata())==64
|
||||
# Root probes native capabilities without spending the limited task token.
|
||||
for path in ['sys/policies/acl/sibling','auth/kubernetes/role/sibling','secret/data/sibling','auth/token/create','sys/mounts/secret','auth/approle/role/sibling/secret-id']:
|
||||
status,data=call('POST','sys/capabilities',{'token':native.task.token,'paths':[path]})
|
||||
|
|
@ -152,7 +166,7 @@ def test_native_fixed_grant_metadata_denials_and_confirmed_cleanup(owner):
|
|||
assert status==400 and data.get('errors') in (['invalid accessor'],['1 error occurred:\n\t* invalid accessor\n\n'])
|
||||
assert result['confirmed']
|
||||
assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404
|
||||
assert journal.observe(binding)['state']=='applying' # native success is not full owner verification
|
||||
assert journal.observe(binding)['state']=='verified'
|
||||
with pytest.raises(Refused): native.metadata_call(6)
|
||||
with pytest.raises(Refused): run_cleanup(journal,binding,native)
|
||||
|
||||
|
|
@ -261,7 +275,7 @@ def test_nonloopback_estate_origin_is_refused_before_native_transport(owner):
|
|||
installed=m.encoded(changed)
|
||||
plan=changed_compiler.bind_execution(changed_compiler.compile(native.meta['request']),changed,installed)
|
||||
changed_binding=Binding(binding.approval_id,binding.request_digest,binding.operation_id,plan.content_digest)
|
||||
admission=FixtureAdmission(installed,native.admission.owner_token,native.admission.identity_session,native.admission.server_process)
|
||||
admission=replace(native.admission,descriptor=installed)
|
||||
with pytest.raises(Refused):NativeFixtureAdapter(changed_compiler,plan,journal,changed_binding,admission)
|
||||
|
||||
|
||||
|
|
@ -294,7 +308,7 @@ def test_proposed_source_grant_never_materializes_even_with_matching_digest(owne
|
|||
installed=m.encoded(changed_descriptor)
|
||||
plan=changed_compiler.bind_execution(changed_compiler.compile(native.meta['request']),changed_descriptor,installed)
|
||||
changed_binding=Binding(binding.approval_id,binding.request_digest,binding.operation_id,plan.content_digest)
|
||||
admission=FixtureAdmission(installed,native.admission.owner_token,native.admission.identity_session,native.admission.server_process)
|
||||
admission=replace(native.admission,descriptor=installed)
|
||||
with pytest.raises(Refused):NativeFixtureAdapter(changed_compiler,plan,journal,changed_binding,admission)
|
||||
assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404
|
||||
|
||||
|
|
|
|||
127
tests/test_task_execution_readback.py
Normal file
127
tests/test_task_execution_readback.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Exact disposable native acceptance, partial effects and restart refusal."""
|
||||
import json
|
||||
from dataclasses import replace
|
||||
import pytest
|
||||
from test_task_execution_native_fixture import owner,bao_dev,compiler,admit
|
||||
from task_execution_native_fixture import NativeFixtureAdapter
|
||||
from task_execution_journal import Refused,Journal
|
||||
from task_execution_cleanup import run_cleanup
|
||||
|
||||
|
||||
def start(native,journal,binding):
|
||||
admit(native,journal,binding);native.materialize_grant()
|
||||
journal.transition(binding,'grant-issued','applying')
|
||||
|
||||
|
||||
def test_expectation_digest_pin_rejected_before_reserve(owner):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
with pytest.raises(Refused,match='expected-state digest'):
|
||||
NativeFixtureAdapter(compiler,n.plan,j,b,replace(n.admission,expected_after=b'{}'))
|
||||
assert c('GET','sys/policies/acl/'+n.policy_name)[0]==404
|
||||
|
||||
|
||||
@pytest.mark.parametrize('target',[0,3])
|
||||
def test_actual_conflicting_baseline_gates_its_target_write(owner,target):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
call=compiler.before_call(n.plan,target+1)
|
||||
existing={'policy':'path "unrelated" { capabilities = ["read"] }'} if target==0 else json.loads(call.payload)
|
||||
assert c('PUT',call.path,existing)[0] in {200,204}
|
||||
with j.locked():
|
||||
start(n,j,b)
|
||||
for i in range(target): n.metadata_call(i)
|
||||
with pytest.raises(Refused,match='baseline or readback'):n.metadata_call(target)
|
||||
assert j.observe(b)['state']=='unknown'
|
||||
if target==3:
|
||||
# Policy effect already happened: this is deliberately no aggregate CAS.
|
||||
assert c('GET',compiler.before_call(n.plan,1).path)[1]['data']['policy']==json.loads(compiler.before_call(n.plan,1).payload)['policy']
|
||||
assert run_cleanup(j,b,n)['confirmed']
|
||||
assert j.observe(b)['state']=='unknown'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('target',[2,5])
|
||||
def test_actual_readback_drift_never_verifies(owner,target):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
with j.locked():
|
||||
start(n,j,b)
|
||||
for i in range(target):n.metadata_call(i)
|
||||
call=compiler.before_call(n.plan,target-1)
|
||||
body=json.loads(call.payload)
|
||||
if target==2:body['policy']+='\npath "sibling/*" { capabilities = ["read"] }'
|
||||
else:body['policies']+=',unexpected-policy'
|
||||
assert c('PUT',call.path,body)[0] in {200,204}
|
||||
with pytest.raises(Refused,match='baseline or readback'):n.metadata_call(target)
|
||||
with pytest.raises(Refused):n.verify_metadata()
|
||||
assert run_cleanup(j,b,n)['confirmed']
|
||||
assert j.observe(b)['state']=='unknown'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('status,response',[(403,{'errors':[]}), (404,{}),(404,{'errors':['permission denied']}), (200,{'data':None})])
|
||||
def test_only_exact_backend_absence_counts(status,response):
|
||||
with pytest.raises(Refused):NativeFixtureAdapter._metadata_snapshot('auth/kubernetes/role/x',status,response)
|
||||
|
||||
|
||||
def test_json_types_and_unknown_role_fields_remain_significant(owner):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
original=n._http
|
||||
def changed(method,path,token,body=b''):
|
||||
status,response=original(method,path,token,body)
|
||||
if method=='GET' and path==compiler.before_call(n.plan,5).path and status==200:
|
||||
response['data']['token_num_uses']=False # False == 0 in Python; never canonical JSON.
|
||||
return status,response
|
||||
n._http=changed
|
||||
with j.locked():
|
||||
start(n,j,b)
|
||||
for i in range(5):n.metadata_call(i)
|
||||
with pytest.raises(Refused):n.metadata_call(5)
|
||||
assert run_cleanup(j,b,n)['confirmed']
|
||||
assert j.observe(b)['state']=='unknown'
|
||||
|
||||
|
||||
def test_reopened_sqlite_cannot_resume_or_recover_memory_handles(owner):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
with j.locked():
|
||||
start(n,j,b)
|
||||
n.metadata_call(0);n.metadata_call(1)
|
||||
# Model loss of the live admission; handles deliberately are not reconstructed.
|
||||
# The still-running disposable server proves native effect survives reopening.
|
||||
location=j.directory
|
||||
assert c('GET',compiler.before_call(n.plan,1).path)[0]==200
|
||||
j.db.close()
|
||||
# Owner journal retains the exact DB path; this is a real reopened SQLite.
|
||||
reopened=Journal(location)
|
||||
try:
|
||||
with reopened.locked():
|
||||
assert reopened.observe(b)['dispatch_allowed'] is False
|
||||
with pytest.raises(Refused):reopened.reserve(b,n.cleanup)
|
||||
with pytest.raises(Refused):reopened.next_call_ordinal(b)
|
||||
finally:reopened.close()
|
||||
# No recovery/cleanup claim: only same-process fixture teardown retains handles.
|
||||
|
||||
|
||||
def test_verified_readback_with_real_cleanup_denial_is_not_complete(owner):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
with j.locked():
|
||||
start(n,j,b)
|
||||
for i in range(6):n.metadata_call(i)
|
||||
n.verify_metadata()
|
||||
# Actual backend removal denies the task's self-revoke capability.
|
||||
assert c('DELETE','sys/policies/acl/'+n.policy_name)[0] in {200,204}
|
||||
result=run_cleanup(j,b,n)
|
||||
assert result['confirmed'] is False
|
||||
assert j.observe(b)['state']=='verified'
|
||||
with pytest.raises(Refused):j.transition(b,'verified','cleanup-confirmed','0'*64)
|
||||
assert c('POST','auth/token/lookup-accessor',{'accessor':n.task.accessor})[0]==200
|
||||
|
||||
|
||||
def test_actual_denied_baseline_cannot_be_caught_then_write_retried(owner):
|
||||
n,j,b,c,compiler,*_=owner
|
||||
with j.locked():
|
||||
start(n,j,b)
|
||||
# Task still has update but loses read: real 403 is not absent baseline.
|
||||
denied=n.hcl.replace('["read", "create", "update"]','["create", "update"]')
|
||||
assert c('PUT','sys/policies/acl/'+n.policy_name,{'policy':denied})[0] in {200,204}
|
||||
with pytest.raises(Refused):n.metadata_call(0)
|
||||
assert j.observe(b)['state']=='unknown'
|
||||
with pytest.raises(Refused):n.metadata_call(1)
|
||||
assert c('GET',compiler.before_call(n.plan,1).path)[0]==404
|
||||
assert run_cleanup(j,b,n)['confirmed']
|
||||
|
|
@ -970,6 +970,19 @@ and defaults stay unchanged. T02 still owns login/claim/TTL/use/MASON acceptance
|
|||
Secrets T06 separately owns dispatcher adoption and steady-state default migration.
|
||||
|
||||
|
||||
2026-10-16 T12 wave 5 source preparation: disposable adapter now binds owner
|
||||
canonical before/after metadata snapshots to existing descriptor hashes, rejects
|
||||
per-target baseline/readback mismatch, and records readback/verified evidence.
|
||||
Mediator reports cleanup-confirmed only after independent fixed cleanup receipts.
|
||||
Complete role metadata and policy name/HCL/version/cas are compared; validated
|
||||
modification timestamp alone is excluded. This is sequential fixture acceptance,
|
||||
not aggregate CAS, concurrent-writer exclusion or native production completion.
|
||||
Role conflict may follow a policy write. Restart refuses dispatch and cannot
|
||||
recover memory-only token/accessor cleanup handles. T12 remains progress; actual
|
||||
fresh CCR/catalog/grant, source/trust/caller/policy admission, isolated owner custody,
|
||||
scoped recovery and attended native acceptance remain explicit prerequisites.
|
||||
|
||||
|
||||
2026-10-11 T11 refinement (same source-review trial): the exact Secrets Engine
|
||||
v3 manifest `6bb05bac74e4939c3e42db2ae95c5f9fb4616bb20cc285811a315c26669ebce1`
|
||||
and all named bytes/proof were verified and independently re-reviewed. The three
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue