facetted-interfaces/history/FIAM_v0.2_Draft6/tools/identity_view.py
tegwick 1353267569 Establish FIAM repository governance baseline
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a087b0-7b1e-7531-8ef3-6525ebaf7ee7
2026-09-09 21:53:43 +02:00

63 lines
3 KiB
Python
Executable file

#!/usr/bin/env python3
"""Render FIAM identity query semantics without destructively merging entities."""
from __future__ import annotations
import argparse
from pathlib import Path
import yaml
ROOT=Path(__file__).resolve().parents[1]
CANON=ROOT/'canon/FIAM-Canon-v0.2.yaml'
def evidence_records(e): return e if isinstance(e,list) else ([e] if isinstance(e,dict) else [])
def source_signature(r):
s=r.get('source')
if isinstance(s,dict): return (s.get('type'),s.get('ref'),s.get('locator'))
return ('source',str(s),None)
def main():
ap=argparse.ArgumentParser(description=__doc__); ap.add_argument('model',type=Path); ap.add_argument('-o','--output',type=Path)
args=ap.parse_args(); doc=yaml.safe_load(args.model.read_text()); canon=yaml.safe_load(CANON.read_text())
policy=canon.get('semantic_policies',{}).get('identity_relations',{})
threshold=float(policy.get('default_reconciliation_threshold',0.98)); need=int(policy.get('required_independent_signals_for_inferred_promotion',2))
parent={}
def find(x):
parent.setdefault(x,x)
if parent[x]!=x: parent[x]=find(parent[x])
return parent[x]
def union(x,y):
rx,ry=find(x),find(y)
if rx!=ry: parent[ry]=rx
same=[]; corr=[]
for x in doc.get('assertions',[]) or []:
if x.get('assertion_kind')!='relation' or x.get('polarity')!='asserted': continue
if x.get('predicate')=='fiam.relation.same-entity-as':
union(x['subject'],x['object_ref']); same.append(x)
elif x.get('predicate')=='fiam.relation.corresponds-to': corr.append(x)
members={}
for x in list(parent): members.setdefault(find(x),[]).append(x)
candidates=[]
for x in corr:
recs=evidence_records(x.get('evidence'))
inferred=[r for r in recs if r.get('mode')=='fiam.evidence.inferred' and float(r.get('confidence',0))>=threshold]
distinct=len({source_signature(r) for r in inferred})
candidates.append({
'subject':x['subject'],'object_ref':x['object_ref'],'assertion_id':x.get('id'),
'evidence_confidences':[r.get('confidence') for r in recs],
'meets_default_inferred_signal_threshold': len(inferred)>=need and distinct>=need,
'automatic_promotion':'never',
'recommended_next_state':'governed identity review' if len(inferred)>=need and distinct>=need else 'remain corresponds-to'
})
result={
'policy':{
'query_semantics':'Asserted same-entity-as defines query-time equivalence only; ids and evidence remain distinct.',
'default_inferred_threshold':threshold,'required_independent_signals':need,
'automatic_promotion':'disabled'
},
'equivalence_classes':[{'members':sorted(v),'merged':False} for v in members.values() if len(v)>1],
'correspondence_candidates':candidates
}
text=yaml.safe_dump(result,sort_keys=False,allow_unicode=True,width=120)
if args.output: args.output.write_text(text,encoding='utf-8')
else: print(text,end='')
if __name__=='__main__': main()