doxs, refactor, extra test

This commit is contained in:
Morten V. Christiansen 2026-03-11 07:50:48 +01:00
parent 4578d27433
commit 917c11b939
5 changed files with 652 additions and 393 deletions

View File

@ -1,361 +1,492 @@
{\rtf1\ansi\deff3\adeflang1025
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\froman\fprq2\fcharset0 Helvetica{\*\falt Arial};}{\f6\froman\fprq2\fcharset0 Courier{\*\falt Courier New};}{\f7\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f8\fnil\fprq2\fcharset0 Noto Sans Devanagari;}{\f9\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}}
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fmodern\fprq1\fcharset0 Liberation Mono{\*\falt Courier New};}{\f5\fnil\fprq0\fcharset2 OpenSymbol{\*\falt Arial Unicode MS};}{\f6\froman\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f7\froman\fprq2\fcharset0 Helvetica{\*\falt Arial};}{\f8\froman\fprq2\fcharset0 Courier{\*\falt Courier New};}{\f9\fmodern\fprq1\fcharset0 Noto Sans Mono CJK SC;}{\f10\fnil\fprq2\fcharset0 0;}{\f11\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f12\fnil\fprq2\fcharset0 Noto Serif CJK SC;}{\f13\fnil\fprq2\fcharset0 Noto Sans Devanagari;}}
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}
{\stylesheet{\s0\snext0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052 Normal;}
{\s15\sbasedon0\snext16\rtlch\af8\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af7 Heading;}
{\s16\sbasedon0\snext16\loch\sl276\slmult1\sb0\sa140 Body Text;}
{\s17\sbasedon16\snext17\rtlch\af9 \ltrch List;}
{\s18\sbasedon0\snext18\rtlch\af9\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i caption;}
{\s19\sbasedon0\snext19\rtlch\af9 \ltrch\loch\noline Index;}
}{\*\generator LibreOffice/25.2.7.2$Linux_X86_64 LibreOffice_project/520$Build-2}{\info{\creatim\yr0\mo0\dy0\hr0\min0}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab720
\hyphauto1\viewscale100\formshade\paperh15840\paperw12240\margl1440\margr1440\margt1440\margb1440\sectd\sbknone\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ftnbj\ftnstart1\ftnrstcont\ftnnar\fet\aftnrstcont\aftnstart1\aftnnrlc
{\*\ftnsep\chftnsep}\pgndec\pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
{\stylesheet{\s0\snext0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052 Normal;}
{\s1\sbasedon19\snext20\rtlch\af13\afs48\ab \ltrch\hich\af3\loch\ilvl0\outlinelevel0\sb240\sa120\f3\fs48\b\dbch\af12 heading 1;}
{\s2\sbasedon19\snext20\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12 heading 2;}
{\*\cs15\snext15\rtlch\ab \ltrch\loch\b Strong;}
{\*\cs16\snext16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9 Source Text;}
{\*\cs17\snext17\rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 Bullets;}
{\*\cs18\snext18 Numbering Symbols;}
{\s19\sbasedon0\snext20\rtlch\af13\afs28 \ltrch\hich\af6\loch\sb240\sa120\keepn\f6\fs28\dbch\af11 Heading;}
{\s20\sbasedon0\snext20\loch\sl276\slmult1\sb0\sa140 Body Text;}
{\s21\sbasedon20\snext21\rtlch\af13 \ltrch\loch\sl240\slmult1\sb0\sa0 List;}
{\s22\sbasedon0\snext22\rtlch\af13\afs24\ai \ltrch\loch\sb120\sa120\fs24\i caption;}
{\s23\sbasedon0\snext23\rtlch\af13 \ltrch Index;}
{\s24\sbasedon0\snext24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9 Preformatted Text;}
{\s25\sbasedon0\snext20\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12 Horizontal Line;}
{\s26\sbasedon0\snext26\loch\nowidctlpar\noline Table Contents;}
{\s27\sbasedon26\snext27\rtlch\ab \ltrch\loch\qc\noline\b Table Heading;}
}{\*\listtable{\list\listtemplateid1
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid1}
{\list\listtemplateid2
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid2}
{\list\listtemplateid3
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid3}
{\list\listtemplateid4
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid4}
{\list\listtemplateid5
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'00.;}{\levelnumbers\'01;}\fi-283\li709}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'01.;}{\levelnumbers\'01;}\fi-283\li1418}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-283\li2127}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-283\li2836}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-283\li3545}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-283\li4254}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-283\li4963}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-283\li5672}
{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-283\li6381}\listid5}
{\list\listtemplateid6
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672}
{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid6}
{\list\listtemplateid7
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid7}
}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}{\listoverride\listid6\listoverridecount0\ls6}{\listoverride\listid7\listoverridecount0\ls7}}{\*\generator LibreOffice/25.2.7.2$Linux_X86_64 LibreOffice_project/520$Build-2}{\info{\creatim\yr0\mo0\dy0\hr0\min0}{\revtim\yr2026\mo3\dy11\hr7\min49}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab720
\hyphauto1\viewscale100\formshade\paperh15840\paperw12240\margl1440\margr1440\margt1440\margb1440\sectd\sbknone\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\fet\aftnrstcont\aftnstart1\aftnnrlc
{\*\ftnsep\chftnsep}\pgndec\pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
CA/PKI Backend Project Context}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs32\b\f7\loch
Stack}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab Python 3.13}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab FastAPI (web API)}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab psycopg (PostgreSQL driver)}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab PostgreSQL database: }{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab PostgreSQL database: }{\hich\af8\loch\f8\loch
ca}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab Unit tests: }{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab Unit tests: }{\hich\af8\loch\f8\loch
unittest}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab HTTP testing: }{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab HTTP testing: }{\hich\af8\loch\f8\loch
fastapi.testclient}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab Zenroom cryptographic runtime (Docker container)}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab Zenroom cryptographic runtime }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs24\b\f7\dbch\af10\loch
(local execution via Python wrapper)}
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Run tests:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af8\loch\f8\loch
python3 -m unittest discover}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Integration tests require:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af8\loch\f8\loch
export DATABASE_URL="postgresql:///ca"}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Project Structure}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af8\loch\f8\loch
pki/\line \u9500\'3f\u9472\'3f\u9472\'3f ca_core/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f entity.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f group_member.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f property.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f metadata.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f db_logging.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f crypto/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f zenroom_client.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f zenroom_service_client.py\line \u9474\'3f\line \u9500\'3f\u9472\'3f\u9472\'3f ca_api/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f app.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f db.py\line \u9474\'3f\line \u9500\'3f\u9472\'3f\u9472\'3f tests/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_entity.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_group.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_property.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_metadata.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_api_smoke.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_api_unit.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_api_integration.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f integration/\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f zenroom tests\line \u9474\'3f\line \u9500\'3f\u9472\'3f\u9472\'3f create_tables.sql\line \u9492\'3f\u9472\'3f\u9472\'3f PROJECT_CONTEXT.md}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Architecture}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af8\loch\f8\loch
HTTP API (FastAPI)\line \u9474\'3f\line \u9660\'3f\line ca_api (thin HTTP adapter)\line \u9474\'3f\line \u9660\'3f\line ca_core (business logic)\line \u9474\'3f\line \u9660\'3f\line PostgreSQL}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
The system is layered to keep business logic independent from the HTTP interface.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Database Overview}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Database: }{\hich\af5\loch\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Database: }{\hich\af7\loch\b\f7\loch
ca}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Core tables:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab entity}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab group_member}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab property}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab metadata}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab log}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs32\b\f7\loch
Entity Rules}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Entity types:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab person}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab group}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab device}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Status values:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab active}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab revoked}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Groups must include }{\hich\af6\loch\f6\loch
ca_reference}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Groups must include }{\hich\af8\loch\f8\loch
ca_reference}{\hich\af7\loch\f7\loch
.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Persons and devices must }{\hich\af5\loch\b\f5\loch
not}{\hich\af5\loch\f5\loch
include }{\hich\af6\loch\f6\loch
ca_reference}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Persons and devices must }{\hich\af7\loch\b\f7\loch
not}{\hich\af7\loch\f7\loch
include }{\hich\af8\loch\f8\loch
ca_reference}{\hich\af7\loch\f7\loch
.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Revoked entities are immutable.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Logging}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
All mutations must call:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af8\loch\f8\loch
log_change(cursor, message)}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Exactly }{\hich\af5\loch\b\f5\loch
one log entry must be produced per mutation}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Exactly }{\hich\af7\loch\b\f7\loch
one log entry must be produced per mutation}{\hich\af7\loch\f7\loch
.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Logging occurs inside the same transaction.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Core Modules}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs32\b\f7\loch
entity.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Provides:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab insert_creator}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab enroll_person}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab create_group}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_entity}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab set_entity_status}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs32\b\f7\loch
group_member.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Provides:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab add_group_member}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab remove_group_member}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_members_of_group}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs32\b\f7\loch
property.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Provides:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab set_property}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab delete_property}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_properties}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\fs32\b\f7\loch
metadata.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7\loch
Provides:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_name}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_comment}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_public_key}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab get_defense_p}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab set_name}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0{\hich\af7\loch\f7
\u8226\'95}{\hich\af7\loch\f7\loch
\tab set_defense_p}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180{\hich\af7\loch\f7
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\ql\fi0\li0\lin0\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
Cryptographic Layer}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
The system integrates }{\hich\af5\loch\b\f5\loch
Zenroom}{\hich\af5\loch\f5\loch
for cryptographic operations.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Zenroom runs in an isolated environment, typically a }{\hich\af5\loch\b\f5\loch
Docker container}{\hich\af5\loch\f5\loch
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
The system integrates }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
Zenroom}{\hich\af7\loch\fs36\b\f7\loch
as its cryptographic runtime.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Zenroom is a deterministic virtual machine designed for secure execution of\line cryptographic protocols defined in }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
Zencode scripts}{\hich\af7\loch\fs36\b\f7\loch
.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
ca_core\line \u9474\'3f\line \u9660\'3f\line crypto clients\line \u9474\'3f\line \u9660\'3f\line Zenroom runtime}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
zenroom_client.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Local execution interface.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Responsibilities:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab execute Zenroom scripts}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab parse output}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab raise errors}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Used for development and some integration tests.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
In this project Zenroom is executed }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
locally via the official Python wrapper}{\hich\af7\loch\fs36\b\f7\loch
.\line No Docker container or HTTP service is used.}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
ca_core}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
\u9492\'3f\u9472\'3f }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
crypto}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
\u9492\'3f\u9472\'3f }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
zenroom_service_client.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
HTTP client for a Zenroom service container.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
\u9474\'3f}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
\u9660\'3f}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
}{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
zenroom Python wrapper}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
\u9474\'3f}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
\u9660\'3f}
\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10
}{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
Zenroom runtime (local process)}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
This architecture removes the previous HTTP service layer and simplifies\line deployment and testing.}
\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch
zenroom_service_client.py}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch
zenroom_service_client.py}{\hich\af7\loch\fs36\b\f7\loch
provides the }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
adapter between the CA backend and Zenroom}{\hich\af7\loch\fs36\b\f7\loch
.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Responsibilities:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab send scripts}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab pass JSON input}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab return parsed result}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Typical flow:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
Python\line \u8595\'3f\line ZenroomServiceClient\line \u8595\'3f\line HTTP request\line \u8595\'3f\line Zenroom container\line \u8595\'3f\line JSON result}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
API Layer (ca_api)}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
FastAPI provides the HTTP interface.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
execute Zencode scripts using the Zenroom Python wrapper}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
pass structured JSON input}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
parse Zenroom output}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
normalize results into Python structures}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
translate runtime errors into }{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch
ZenroomServiceError}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
All cryptographic operations are exposed through a }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
single client class}{\hich\af7\loch\fs36\b\f7\loch
:}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
{\*\bkmkstart code-block-viewer}{\*\bkmkend code-block-viewer}ZenroomServiceClient}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch
Supported Operations}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
The client provides the following cryptographic operations:}
\par \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s27\rtlch\ab \ltrch\loch\qc\noline\b\intbl{\loch
Method}\cell\pard\plain \s27\rtlch\ab \ltrch\loch\qc\noline\b\intbl{\loch
Description}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
generate_keypair()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Create an ECDH keypair}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
generate_public_key()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Derive public key from keyring}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
symmetric_encrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Encrypt message using password}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
symmetric_decrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Decrypt password-encrypted message}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
asymmetric_encrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Encrypt message for a recipient}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
asymmetric_decrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Decrypt message from a sender}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
sign_objects()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Sign a string field}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch
verify_signature()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch
Verify an ECDH signature}\cell\row\pard \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
These operations correspond directly to the Zencode scripts embedded in the module.}
\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch
Script Execution}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
All Zencode scripts are executed through a single internal helper:}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
{\*\bkmkstart code-block-viewer Copy 1}{\*\bkmkend code-block-viewer Copy 1}_run_script()}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Responsibilities:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab routing}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab validation}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab DB connection handling}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab error mapping}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Flow:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
HTTP request\line \u8595\'3f\line FastAPI route\line \u8595\'3f\line db_cursor()\line \u8595\'3f\line ca_core function\line \u8595\'3f\line commit / rollback}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
invoke }{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch
zenroom.zencode_exec()}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
pass JSON input}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
capture Zenroom logs}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
parse returned JSON output}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
raise }{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch
ZenroomServiceError}{\hich\af7\loch\fs36\b\f7\loch
on failure}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
This centralizes Zenroom interaction and ensures consistent error handling.}
\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch
Error Handling}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Zenroom failures are converted into Python exceptions.}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
{\*\bkmkstart code-block-viewer Copy 2}{\*\bkmkend code-block-viewer Copy 2}ZenroomServiceError}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
This exception is raised when:}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Zenroom execution fails}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
output cannot be parsed}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
expected fields are missing}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
verification fails}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
This allows the rest of the system to treat cryptographic failures as standard\line Python exceptions.}
\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch
Testing Strategy}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Three layers of tests exist.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
Core Unit Tests}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Test domain logic directly.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Examples:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab test_entity.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab test_group.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab test_property.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab test_metadata.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
API Unit Tests}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Test HTTP behaviour without DB.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Mocked components:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab db_cursor}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab ca_core functions}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
File:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab test_api_unit.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
API Integration Tests}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Full stack tests:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
FastAPI\line \u8595\'3f\line ca_core\line \u8595\'3f\line PostgreSQL}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
File:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab test_api_integration.py}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Requires:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
export DATABASE_URL="postgresql:///ca"}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch
Zenroom Integration Tests}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Located in:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
tests/integration/}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Verify:}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab Zenroom script execution}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab service communication}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5
\u8226\'95}{\hich\af5\loch\f5\loch
\tab error handling}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
Some tests require a running Zenroom container.}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5
\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch
Runtime Deployment Model}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch
FastAPI API\line \u9474\'3f\line \u9660\'3f\line ca_core\line \u9474\'3f\line \u9660\'3f\line crypto client\line \u9474\'3f\line \u9660\'3f\line Zenroom container\line \u9474\'3f\line \u9660\'3f\line PostgreSQL}
\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch
This design keeps cryptographic execution isolated while keeping the Python service lightweight.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Cryptographic functionality is tested using }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
direct functional tests}{\hich\af7\loch\fs36\b\f7\loch
.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Tests execute the real Zenroom runtime through the adapter and verify:}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
key generation}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
encryption/decryption roundtrips}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
signing}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
signature verification}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
signature rejection for tampered messages}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Example tests:}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
{\*\bkmkstart code-block-viewer Copy 3}{\*\bkmkend code-block-viewer Copy 3}tests/test_zenroom_service_client.py}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
The suite currently contains }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
9 tests}{\hich\af7\loch\fs36\b\f7\loch
:}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 1.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
generate keypair}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 2.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
generate public key}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 3.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
symmetric encryption}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 4.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
symmetric decryption}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 5.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
asymmetric encryption}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 6.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
asymmetric decryption}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 7.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
signing}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 8.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
successful verification}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 9.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
verification failure for modified message}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Tests run with:}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch
{\*\bkmkstart code-block-viewer Copy 4}{\*\bkmkend code-block-viewer Copy 4}python3 -m unittest discover}
\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Typical runtime is }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch
under 0.5 seconds}{\hich\af7\loch\fs36\b\f7\loch
.}
\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch
\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch
Security Notes}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Zenroom scripts execute in an isolated runtime.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
All inputs are passed as structured JSON.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Dynamic script generation restricts field names to safe identifiers.}
\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch
Cryptographic failures are surfaced immediately as exceptions.}
\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\hich\af7\loch\fs36\b\f7\loch
\par }

View File

@ -6,18 +6,26 @@ from zenroom import zenroom
class ZenroomServiceError(RuntimeError):
pass
"""Raised when local Zenroom execution fails or returns invalid data."""
class ZenroomServiceClient:
"""
Local Zenroom client using the installed `zenroom` Python wrapper.
Local Zenroom client.
This preserves the public API of the old HTTP/Docker-backed client as much
as possible, so callers should not need changes.
This class replaces the old HTTP/Docker service client and talks directly to
the installed Zenroom Python wrapper plus local Zenroom binary.
The public method names intentionally mirror the old service-oriented API so
the rest of the codebase can keep using the same interface.
"""
SCRIPT_GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair from a name passed from data/keys
# -------------------------------------------------------------------------
# Embedded Zencode scripts
# -------------------------------------------------------------------------
SCRIPT_GENERATE_KEYPAIR = """\
Scenario 'ecdh': Create the keypair from a name passed from data/keys
# Here we load the identity of the executor
Given my name is in a 'string' named 'myName'
@ -27,7 +35,8 @@ When I create the ecdh key
Then print my 'keyring'
"""
SCRIPT_GENERATE_PUBLIC_KEY = """# Loading scenarios
SCRIPT_GENERATE_PUBLIC_KEY = """\
# Loading scenarios
Scenario 'ecdh': Create the public key
# Loading the private keys
@ -40,7 +49,8 @@ When I create the ecdh public key
Then print the 'ecdh public key'
"""
SCRIPT_SYMMETRIC_ENCRYPT = """Scenario 'ecdh': Encrypt a message with the password
SCRIPT_SYMMETRIC_ENCRYPT = """\
Scenario 'ecdh': Encrypt a message with the password
Given that I have a 'string' named 'password'
Given that I have a 'string' named 'header'
Given that I have a 'string' named 'message'
@ -48,7 +58,8 @@ When I encrypt the secret message 'message' with 'password'
Then print the 'secret message'
"""
SCRIPT_SYMMETRIC_DECRYPT = """Scenario 'ecdh': Decrypt the message with the password
SCRIPT_SYMMETRIC_DECRYPT = """\
Scenario 'ecdh': Decrypt the message with the password
Given that I have a valid 'secret message'
Given that I have a 'string' named 'password'
When I decrypt the text of 'secret message' with 'password'
@ -56,7 +67,8 @@ When I rename the 'text' to 'textDecrypted'
Then print the 'textDecrypted' as 'string'
"""
SCRIPT_ASYMMETRIC_ENCRYPT = """Scenario 'ecdh': Alice encrypts a message for Bob
SCRIPT_ASYMMETRIC_ENCRYPT = """\
Scenario 'ecdh': Alice encrypts a message for Bob
Given that I am known as 'sender'
Given that I have my valid 'keyring'
@ -70,7 +82,8 @@ When I rename the 'secret message' to 'secret'
Then print the 'secret'
"""
SCRIPT_ASYMMETRIC_DECRYPT = """Scenario 'ecdh': Bob decrypts the message from Alice
SCRIPT_ASYMMETRIC_DECRYPT = """\
Scenario 'ecdh': Bob decrypts the message from Alice
Given that I am known as 'reciever'
Given I have my 'keyring'
Given I have a 'public key' from 'sender'
@ -80,8 +93,10 @@ Then print the 'text' as 'string'
Then print the 'header' from 'secret' as 'string'
"""
# Used as a template so sign_objects() can sign any single string field.
SCRIPT_SIGN_TEMPLATE = """Scenario 'ecdh': create the signature of an object
# Dynamic templates are used for sign/verify so the public Python methods
# can work with arbitrary safe field names such as "myMessage".
SCRIPT_SIGN_TEMPLATE = """\
Scenario 'ecdh': create the signature of an object
Given I am 'signer'
Given I have my 'keyring'
Given that I have a 'string' named '{field_name}' inside 'mySecretStuff'
@ -93,8 +108,8 @@ Then print the '{field_name}'
Then print the '{field_name}.signature'
"""
# Used as a template so verify_signature() can verify any single string field.
SCRIPT_VERIFY_TEMPLATE = """rule check version 3.0.0
SCRIPT_VERIFY_TEMPLATE = """\
rule check version 3.0.0
Scenario 'ecdh': Bob verifies the signature from Alice
# Here we load the pubkey we'll verify the signature against
@ -103,38 +118,46 @@ Given I have a 'public key' from 'signer'
# Here we load the objects to be verified
Given I have a 'string' named '{field_name}'
# Here we load the objects's signatures
# Here we load the object's signature
Given I have a 'signature' named '{field_name}.signature'
# Here we perform the verifications
# Here we perform the verification
When I verify the '{field_name}' has a ecdh signature in '{field_name}.signature' by 'signer'
# Here we print out the result: if the verifications succeeded, a string will be printed out
# if the verifications failed, Zenroom will throw an error
# If verification succeeds, Zenroom prints the success string.
# If verification fails, Zenroom raises an error.
Then print the string 'Zenroom certifies that signature is correct!'
Then print the '{field_name}'
"""
def __init__(self) -> None:
"""Construct a local client. No external service configuration is needed."""
pass
# -------------------------------------------------------------------------
# Validation helpers
# -------------------------------------------------------------------------
@staticmethod
def _require_non_empty_str(name: str, value: str) -> str:
"""Validate that a value is a non-empty string and return the stripped value."""
if not isinstance(value, str):
raise TypeError(f"{name} must be a string")
v = value.strip()
if not v:
value = value.strip()
if not value:
raise ValueError(f"{name} cannot be empty")
return v
return value
@staticmethod
def _require_dict(name: str, value: Any) -> Dict[str, Any]:
"""Validate that a value is a dict."""
if not isinstance(value, dict):
raise TypeError(f"{name} must be a dict")
return value
@staticmethod
def _require_keys(d: Dict[str, Any], *, required: Tuple[str, ...], ctx: str) -> None:
"""Ensure a dict contains the required keys."""
missing = [k for k in required if k not in d]
if missing:
raise ZenroomServiceError(f"Missing {missing} in {ctx}: {d!r}")
@ -142,16 +165,31 @@ Then print the '{field_name}'
@staticmethod
def _require_safe_field_name(field_name: str) -> str:
"""
Restrict dynamic field names used inside generated Zencode to avoid script injection.
Restrict dynamic field names used in generated Zencode.
This avoids accidentally generating unsafe script fragments.
"""
if not isinstance(field_name, str):
raise TypeError("field_name must be a string")
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", field_name):
raise ValueError(
"field_name must match [A-Za-z_][A-Za-z0-9_]* for safe Zencode generation"
"field_name must match [A-Za-z_][A-Za-z0-9_]*"
)
return field_name
# -------------------------------------------------------------------------
# Zenroom execution helpers
# -------------------------------------------------------------------------
@staticmethod
def _normalize_logs(logs: Any) -> str:
"""Convert Zenroom logs to a readable string for error messages."""
if logs is None:
return ""
if isinstance(logs, list):
return "\n".join(str(item) for item in logs)
return str(logs)
def _run_script(
self,
script_name: str,
@ -161,25 +199,31 @@ Then print the '{field_name}'
keys: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Execute a local Zencode script and return parsed JSON result as a dict.
Execute a Zencode script locally and return the parsed JSON result.
Zenroom's Python wrapper returns an object with attributes such as:
- output : raw textual output
- logs : Zenroom logs
- result : parsed JSON object when output is JSON
We prefer `result`, but fall back to parsing `output` if necessary.
"""
try:
result = zenroom.zencode_exec(
exec_result = zenroom.zencode_exec(
script_text,
data=json.dumps(data or {}),
keys=json.dumps(keys) if keys is not None else None,
)
except Exception as e:
raise ZenroomServiceError(f"Failed to execute Zenroom script {script_name}: {e}") from e
logs = getattr(result, "logs", None)
output = getattr(result, "output", None)
parsed = getattr(result, "result", None)
except Exception as exc:
raise ZenroomServiceError(
f"Failed to execute Zenroom script {script_name}: {exc}"
) from exc
parsed = getattr(exec_result, "result", None)
if isinstance(parsed, dict):
return parsed
# Fallback: sometimes output may still be JSON text even if .result is None.
output = getattr(exec_result, "output", None)
if isinstance(output, str):
try:
parsed_output = json.loads(output)
@ -188,23 +232,23 @@ Then print the '{field_name}'
if isinstance(parsed_output, dict):
return parsed_output
log_text = logs
if isinstance(log_text, list):
log_text = "\n".join(str(x) for x in log_text)
elif log_text is None:
log_text = ""
logs_text = self._normalize_logs(getattr(exec_result, "logs", None))
out_preview = output if isinstance(output, str) else repr(output)
raise ZenroomServiceError(
f"Zenroom script {script_name} did not return a JSON object.\n"
f"Output: {out_preview[:800]}\n"
f"Logs: {str(log_text)[:2000]}"
f"Logs: {logs_text[:2000]}"
)
def _pick_owner_block(self, res: Dict[str, Any], my_name: str, ctx: str) -> Dict[str, Any]:
@staticmethod
def _pick_owner_block(res: Dict[str, Any], my_name: str, ctx: str) -> Dict[str, Any]:
"""
Zenroom often returns: { "<identity>": { ... } }
Prefer res[my_name] when present, else accept single-entry dict.
Zenroom often returns data shaped like:
{ "<identity>": { ... } }
Prefer `res[my_name]` when present. If not present, accept a single-entry
dict as long as it is unambiguous.
"""
if my_name in res:
owner = res[my_name]
@ -219,10 +263,48 @@ Then print the '{field_name}'
raise ZenroomServiceError(f"Invalid {ctx} response structure: {res!r}")
return owner
@staticmethod
def _validate_secret_box(secret: Dict[str, Any], *, ctx: str) -> Dict[str, str]:
"""
Validate the standard Zenroom encrypted object shape.
Expected keys:
- checksum
- header
- iv
- text
"""
if not isinstance(secret, dict):
raise ZenroomServiceError(f"Invalid {ctx} response: {secret!r}")
required = ("checksum", "header", "iv", "text")
missing = [k for k in required if not isinstance(secret.get(k), str) or not secret[k].strip()]
if missing:
raise ZenroomServiceError(f"Invalid {ctx} fields {missing}: {secret!r}")
return {
"checksum": secret["checksum"],
"header": secret["header"],
"iv": secret["iv"],
"text": secret["text"],
}
# -------------------------------------------------------------------------
# Service 1: Generate-a-keypair,-reading-identity-from-data
# -------------------------------------------------------------------------
def generate_keypair(self, my_name: str) -> Dict[str, Any]:
"""
Generate a keypair for the supplied identity name.
Returns a normalized structure:
{
"my_name": "<identity>",
"keyring": {"ecdh": "<private_b64>"},
"private_key": "<private_b64>",
# optionally "public_key" if Zenroom emits it
}
"""
my_name = self._require_non_empty_str("my_name", my_name)
res = self._run_script(
@ -258,7 +340,9 @@ Then print the '{field_name}'
# -------------------------------------------------------------------------
# Service 2: Generate-public-key
# -------------------------------------------------------------------------
def generate_public_key(self, keyring: Dict[str, Any]) -> str:
"""Derive the public key from a Zenroom keyring."""
keyring = self._require_dict("keyring", keyring)
res = self._run_script(
@ -267,15 +351,23 @@ Then print the '{field_name}'
data={"keyring": keyring},
)
pub = res.get("ecdh_public_key")
if not isinstance(pub, str) or not pub.strip():
public_key = res.get("ecdh_public_key")
if not isinstance(public_key, str) or not public_key.strip():
raise ZenroomServiceError(f"Invalid public key response: {res!r}")
return pub
return public_key
# -------------------------------------------------------------------------
# Service 3: Encrypt-a-message-with-the-password
# -------------------------------------------------------------------------
def symmetric_encrypt(self, *, header: str, message: str, shared_key: str) -> Dict[str, str]:
"""
Encrypt a message symmetrically using a password/shared key.
Returns a standard Zenroom encrypted object with checksum/header/iv/text.
Note that Zenroom may encode the returned header representation.
"""
header = self._require_non_empty_str("header", header)
message = self._require_non_empty_str("message", message)
shared_key = self._require_non_empty_str("shared_key", shared_key)
@ -283,48 +375,44 @@ Then print the '{field_name}'
res = self._run_script(
"Encrypt-a-message-with-the-password",
self.SCRIPT_SYMMETRIC_ENCRYPT,
data={"header": header, "message": message, "password": shared_key},
data={
"header": header,
"message": message,
"password": shared_key,
},
)
sm = res.get("secret_message")
if not isinstance(sm, dict):
raise ZenroomServiceError(
f"Invalid encrypt response (missing secret_message): {res!r}"
)
self._require_keys(sm, required=("checksum", "header", "iv", "text"), ctx="secret_message")
for k in ("checksum", "header", "iv", "text"):
if not isinstance(sm.get(k), str) or not sm[k].strip():
raise ZenroomServiceError(f"Invalid secret_message.{k}: {sm!r}")
return {
"checksum": sm["checksum"],
"header": sm["header"],
"iv": sm["iv"],
"text": sm["text"],
}
secret_message = res.get("secret_message")
return self._validate_secret_box(secret_message, ctx="secret_message")
# -------------------------------------------------------------------------
# Service 4: Decrypt-the-message-with-the-password
# -------------------------------------------------------------------------
def symmetric_decrypt(self, *, secret_message: Dict[str, Any], shared_key: str) -> str:
"""Decrypt a symmetrically encrypted secret message and return plaintext."""
secret_message = self._require_dict("secret_message", secret_message)
shared_key = self._require_non_empty_str("shared_key", shared_key)
res = self._run_script(
"Decrypt-the-message-with-the-password",
self.SCRIPT_SYMMETRIC_DECRYPT,
data={"secret_message": secret_message, "password": shared_key},
data={
"secret_message": secret_message,
"password": shared_key,
},
)
txt = res.get("textDecrypted")
if not isinstance(txt, str):
text = res.get("textDecrypted")
if not isinstance(text, str):
raise ZenroomServiceError(f"Invalid decrypt response: {res!r}")
return txt
return text
# -------------------------------------------------------------------------
# Service 5: Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography
# -------------------------------------------------------------------------
def asymmetric_encrypt(
self,
*,
@ -333,6 +421,12 @@ Then print the '{field_name}'
header: str,
message: str,
) -> Dict[str, str]:
"""
Encrypt a message asymmetrically for a receiver using sender key material.
Note: the Zencode script expects the legacy spelling `reciever`, so the
payload preserves that exact key.
"""
receiver_public_key = self._require_non_empty_str(
"receiver_public_key", receiver_public_key
)
@ -351,27 +445,13 @@ Then print the '{field_name}'
},
)
sec = res.get("secret")
if not isinstance(sec, dict):
raise ZenroomServiceError(
f"Invalid asymmetric encrypt response (missing secret): {res!r}"
)
self._require_keys(sec, required=("checksum", "header", "iv", "text"), ctx="secret")
for k in ("checksum", "header", "iv", "text"):
if not isinstance(sec.get(k), str) or not sec[k].strip():
raise ZenroomServiceError(f"Invalid secret.{k}: {sec!r}")
return {
"checksum": sec["checksum"],
"header": sec["header"],
"iv": sec["iv"],
"text": sec["text"],
}
secret = res.get("secret")
return self._validate_secret_box(secret, ctx="secret")
# -------------------------------------------------------------------------
# Service 6: Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography
# -------------------------------------------------------------------------
def asymmetric_decrypt(
self,
*,
@ -379,6 +459,15 @@ Then print the '{field_name}'
receiver_keyring: Dict[str, Any],
secret: Dict[str, Any],
) -> Dict[str, str]:
"""
Decrypt an asymmetrically encrypted message.
Returns:
{
"header": "...",
"text": "..."
}
"""
sender_public_key = self._require_non_empty_str("sender_public_key", sender_public_key)
receiver_keyring = self._require_dict("receiver_keyring", receiver_keyring)
secret = self._require_dict("secret", secret)
@ -393,24 +482,25 @@ Then print the '{field_name}'
},
)
hdr = res.get("header")
txt = res.get("text")
if not isinstance(hdr, str) or not isinstance(txt, str):
header = res.get("header")
text = res.get("text")
if not isinstance(header, str) or not isinstance(text, str):
raise ZenroomServiceError(f"Invalid asymmetric decrypt response: {res!r}")
return {"header": hdr, "text": txt}
return {"header": header, "text": text}
# -------------------------------------------------------------------------
# Service 7: Sign-objects-using-asymmetric-cryptography
# -------------------------------------------------------------------------
def sign_objects(self, *, objects: Dict[str, Any], signer_keyring: Dict[str, Any]) -> Dict[str, Any]:
"""
Signs exactly one string field from `objects`.
Sign exactly one string field from `objects`.
Example:
sign_objects(objects={"myMessage": "hello"}, signer_keyring=...)
Example input:
{"myMessage": "hello"}
Returns e.g.:
Example output:
{
"myMessage": "hello",
"myMessage.signature": {"r": "...", "s": "..."}
@ -440,12 +530,12 @@ Then print the '{field_name}'
)
sig_key = f"{field_name}.signature"
sig = res.get(sig_key)
signature = res.get(sig_key)
if not isinstance(sig, dict):
if not isinstance(signature, dict):
raise ZenroomServiceError(f"Invalid signature object for {sig_key}: {res!r}")
if not isinstance(sig.get("r"), str) or not isinstance(sig.get("s"), str):
raise ZenroomServiceError(f"Invalid signature fields for {sig_key}: {sig!r}")
if not isinstance(signature.get("r"), str) or not isinstance(signature.get("s"), str):
raise ZenroomServiceError(f"Invalid signature fields for {sig_key}: {signature!r}")
if not isinstance(res.get(field_name), str):
raise ZenroomServiceError(f"Missing signed field {field_name!r} in response: {res!r}")
@ -455,6 +545,7 @@ Then print the '{field_name}'
# -------------------------------------------------------------------------
# Service 8: Verify-asymmetric-cryptography-signature
# -------------------------------------------------------------------------
def verify_signature(
self,
*,
@ -463,6 +554,12 @@ Then print the '{field_name}'
signature: Dict[str, Any],
signer_public_key: str,
) -> bool:
"""
Verify a signature over one named string field.
Returns True on success. Raises ZenroomServiceError on failure or if the
returned structure is unexpectedly malformed.
"""
message_field = self._require_safe_field_name(message_field)
message_value = self._require_non_empty_str("message_value", message_value)
signature = self._require_dict("signature", signature)
@ -470,39 +567,50 @@ Then print the '{field_name}'
script = self.SCRIPT_VERIFY_TEMPLATE.format(field_name=message_field)
payload: Dict[str, Any] = {
message_field: message_value,
f"{message_field}.signature": signature,
"signer": {"public_key": signer_public_key},
}
res = self._run_script(
"Verify-asymmetric-cryptography-signature",
script,
data=payload,
data={
message_field: message_value,
f"{message_field}.signature": signature,
"signer": {"public_key": signer_public_key},
},
)
out = res.get("output")
if isinstance(out, list) and out:
output = res.get("output")
if isinstance(output, list) and output:
return True
# Some Zenroom variants may print the success string directly as a named field
# or return the verified message only. If execution succeeded and no exception
# was raised, we still treat that as success when the original message is present.
# Some Zenroom variants may return only the verified field itself.
if res.get(message_field) == message_value:
return True
raise ZenroomServiceError(f"Invalid verify response: {res!r}")
# -------------------------------------------------------------------------
# Backward-compatible alias names
# Backward-compatible aliases
# -------------------------------------------------------------------------
def generate_a_keypair_reading_identity_from_data(self, my_name: str) -> Dict[str, Any]:
"""Backward-compatible alias."""
return self.generate_keypair(my_name)
def encrypt_a_message_with_the_password(self, *, header: str, message: str, password: str) -> Dict[str, str]:
def encrypt_a_message_with_the_password(
self,
*,
header: str,
message: str,
password: str,
) -> Dict[str, str]:
"""Backward-compatible alias."""
return self.symmetric_encrypt(header=header, message=message, shared_key=password)
def decrypt_the_message_with_the_password(self, *, secret_message: Dict[str, Any], password: str) -> Dict[str, str]:
txt = self.symmetric_decrypt(secret_message=secret_message, shared_key=password)
return {"textDecrypted": txt}
def decrypt_the_message_with_the_password(
self,
*,
secret_message: Dict[str, Any],
password: str,
) -> Dict[str, str]:
"""Backward-compatible alias preserving the historical return shape."""
text = self.symmetric_decrypt(secret_message=secret_message, shared_key=password)
return {"textDecrypted": text}

View File

@ -1,6 +1,9 @@
import unittest
from ca_core.crypto.zenroom_service_client import ZenroomServiceClient
from ca_core.crypto.zenroom_service_client import (
ZenroomServiceClient,
ZenroomServiceError,
)
class TestZenroomServiceClient(unittest.TestCase):
@ -157,6 +160,23 @@ class TestZenroomServiceClient(unittest.TestCase):
self.assertTrue(verified)
def test_9_verify_signature_rejects_modified_message(self):
alice = self.client.generate_keypair("Alice")
alice_public_key = self.client.generate_public_key(alice["keyring"])
signed = self.client.sign_objects(
objects={"myMessage": "hello"},
signer_keyring=alice["keyring"],
)
with self.assertRaises(ZenroomServiceError):
self.client.verify_signature(
message_field="myMessage",
message_value="tampered",
signature=signed["myMessage.signature"],
signer_public_key=alice_public_key,
)
if __name__ == "__main__":
unittest.main()