11"""Generated save privacy; no generated source inspection or synthetic traces."""
22import asyncio
3+ import json
34import os
45import uuid
56
910from teaql .provider .sqlite import create_sqlite_service
1011from teaql .runtime import UserContext
1112
12- from main import AuditSink , ObservedService , new , reset
13+ from main import AuditSink , ObservedService , new , reset , readback_fact , scalar
14+
15+
16+ def complete_lineage (nodes , root_id , reason , boundary ):
17+ """Empty/redacted-away responsibility is not a successful privacy projection."""
18+ assert len (nodes ) == 1 , boundary + ': complete typed root lineage required'
19+ node = nodes [0 ]
20+ assert (node .kind , node .name , node .entity_id , node .comment ) == (
21+ 'auditReason' , 'CustomerOrder' , root_id , reason ), boundary + ': wrong root responsibility'
22+
23+
24+ def assert_private_lineage (service , sink , context , root_id , reason , identities , secrets ,
25+ expected_bindings , phase ):
26+ safe_reason = reason
27+ for secret in secrets :
28+ safe_reason = safe_reason .replace (secret , '[REDACTED]' )
29+ expected = set (identities )
30+ assert len (expected ) == len (identities ), 'privacy graph needs distinct typed identities'
31+ count = len (identities )
32+ assert len (service .raw_requests ) == len (service .results ) == len (sink .events ) == count
33+ assert service .finishes == ['commit' ], 'privacy graph must commit before audit delivery'
34+ assert len (context .sql_logs ()) == count * 2
35+ commands = []
36+ seen = set ()
37+ for request , result in zip (service .raw_requests , service .results ):
38+ command = request ._data
39+ identity = (command .entity , scalar (getattr (command , 'id' , None ) or command .values .get ('id' )))
40+ assert identity in expected and identity not in seen , 'actual privacy command identity mismatch'
41+ seen .add (identity )
42+ assert request .comment () == result .comment == reason , 'privacy must not rewrite command reason'
43+ complete_lineage (request .mutation_lineage , root_id , reason , 'raw privacy command' )
44+ complete_lineage (result .mutation_lineage , root_id , reason , 'raw provider mutation' )
45+ assert len (result .statements ) == 2 and result .statements [0 ].affected_rows == 1
46+ for field , value in expected_bindings .get (command .entity , {}).items ():
47+ assert scalar (command .values [field ]) == value , 'privacy must not rewrite command bindings'
48+ assert value in [scalar (parameter ) for parameter in result .statements [0 ].parameters ], 'privacy must not rewrite provider parameters'
49+ commands .append ({'entity' : identity [0 ], 'id' : identity [1 ], 'comment' : request .comment (),
50+ 'lineage' : readback_nodes (request .mutation_lineage )})
51+ assert seen == expected
52+ for index , (request , result ) in enumerate (zip (service .raw_requests , service .results )):
53+ write , read = context .sql_logs ()[index * 2 :index * 2 + 2 ]
54+ for fact in (write , read ):
55+ complete_lineage (fact .mutation_lineage , root_id , safe_reason , 'safe privacy SQL' )
56+ assert fact .comment == fact .audit_reason == safe_reason , 'safe SQL must retain public reason'
57+ assert fact .execution_outcome == 'success'
58+ assert [node .kind for node in fact .trace_path ] == [
59+ 'operation' , 'entity' if fact is write else 'request' , 'provider' , 'sql' ]
60+ assert fact .trace_path [0 ].name == 'CustomerOrder'
61+ assert fact .trace_path [2 ].name == 'sqlite'
62+ assert write .trace_path [1 ].name == request ._data .entity
63+ assert write .trace_path [- 1 ].name == service .actions [index ]
64+ assert read .trace_path [- 1 ].name == 'select'
65+ assert write .affected_rows == read .result_count == 1 and read .affected_rows is None
66+ assert read .purpose == 'verify the persisted mutation result'
67+ assert write .ended_at <= read .started_at
68+ assert not result .statements [0 ].statements and not result .statements [1 ].statements
69+ audits = []
70+ seen = set ()
71+ for event in sink .events :
72+ identity = (event .entity , scalar (event .entity_id ))
73+ assert identity in expected and identity not in seen , 'committed privacy audit identity mismatch'
74+ seen .add (identity )
75+ complete_lineage (event .trace_chain , root_id , safe_reason , 'safe privacy audit' )
76+ audits .append ({'entity' : identity [0 ], 'id' : identity [1 ],
77+ 'lineage' : readback_nodes (event .trace_chain )})
78+ assert seen == expected
79+ for secret in secrets :
80+ assert secret not in repr (context .sql_logs ()) and secret not in repr (sink .events )
81+ print ('PRIVATE_LINEAGE_OBSERVED ' + json .dumps ({
82+ 'phase' : phase , 'rootId' : root_id , 'rawReason' : reason , 'safeReason' : safe_reason ,
83+ 'commands' : commands , 'sql' : [readback_fact (fact ) for fact in context .sql_logs ()],
84+ 'audit' : audits }, sort_keys = True ))
85+ print ('PASS Python complete private lineage: raw commands, safe SQL/readback and committed audit' )
86+
87+
88+ def readback_nodes (nodes ):
89+ return [{'Kind' : node .kind , 'Name' : node .name , 'EntityId' : node .entity_id ,
90+ 'Comment' : node .comment } for node in nodes ]
1391
1492
1593async def main ():
@@ -52,6 +130,10 @@ async def main():
52130 for metadata in service .results :
53131 assert len (metadata .statements ) == 2
54132 assert metadata .comment == reason # raw owned intent is not rewritten
133+ assert_private_lineage (service , sink , context , E .customer_order (root ).id ().eval (), reason ,
134+ [('CustomerOrder' , E .customer_order (root ).id ().eval ()),
135+ ('OrderItem' , E .order_item (item ).id ().eval ()), ('Payment' , E .payment (payment ).id ().eval ())],
136+ [secret , old ], {'Payment' : {'reference_code' : secret }}, phase )
55137 loaded = await Q .payments ().with_id_is (E .payment (payment ).id ().eval ()).limit (1 ).comment (
56138 'reload authoritative payment' ).purpose ('verify safe logs do not alter business data' ).execute_for_one (context )
57139 assert E .payment (loaded ).reference_code ().eval () == secret
@@ -77,6 +159,9 @@ async def main():
77159 assert secret not in repr (context .sql_logs ()), 'loaded delete value leaked into SQL intent'
78160 assert secret not in repr (sink .events ), 'loaded delete value leaked into committed audit'
79161 assert all (metadata .comment == reason for metadata in service .results )
162+ assert_private_lineage (service , sink , context , E .customer_order (root ).id ().eval (), reason ,
163+ [('CustomerOrder' , E .customer_order (root ).id ().eval ()), ('Payment' , payment_id )],
164+ [secret ], {}, 'delete' )
80165 missing = await Q .payments ().with_id_is (payment_id ).limit (1 ).comment ('verify deleted payment' ).purpose (
81166 'prove soft delete hides the row' ).execute_for_one (context )
82167 assert missing is None
@@ -94,10 +179,14 @@ async def main():
94179 'load all order fields' ).purpose ('retain an unchanged private description' ).execute_for_one (context )
95180 root .update_order_number (label + '-revised' )
96181 reset (service , sink , context )
97- await root .audit_as (f'renumber { private_description } ' ).save (context )
182+ reason = f'renumber { private_description } '
183+ await root .audit_as (reason ).save (context )
98184 assert len (service .requests ) == len (sink .events ) == 1
99185 assert private_description not in repr (context .sql_logs ()), 'unchanged loaded scalar leaked into SQL intent'
100186 assert private_description not in repr (sink .events ), 'unchanged loaded scalar leaked into audit'
187+ assert_private_lineage (service , sink , context , E .customer_order (root ).id ().eval (), reason ,
188+ [('CustomerOrder' , E .customer_order (root ).id ().eval ())], [private_description ],
189+ {'CustomerOrder' : {'order_number' : label + '-revised' }}, 'unchanged' )
101190 reloaded = await Q .customer_orders ().with_id_is (E .customer_order (root ).id ().eval ()).limit (1 ).comment (
102191 'verify unchanged description' ).purpose ('prove provenance did not alter business values' ).execute_for_one (context )
103192 assert E .customer_order (reloaded ).description ().eval () == private_description
0 commit comments