1
0

rewrite 2.0.0: real process — extract the algorithm into DMN

The 1.x package was a single ai.extract call wrapped in three BPMN
service tasks. No decision logic, no dmn cornerstone, no weights — the
risk/routing/validation algorithm lived invisibly in host code. There
was nothing for a runtime to actually execute.

2.0.0 makes it a real process:

- dmn cornerstone added with three decision tables:
  * assess-personal-data-risk  — PII regex signals -> risk level
  * gdpr-processing-route      — risk x centralisation -> CENTRAL/LOCAL,
                                  anonymisation, redaction level
  * human-validation-gate      — confidence thresholds + PII re-scan
                                  -> REJECTED/PENDING_REVIEW/APPROVED_AUTO
- BPMN expanded 3 -> 6 nodes (3 serviceTask + 3 businessRuleTask),
  with horizontal DI.
- Task ids, mappings, docs, manifest (dmn:true), uapf.yaml, lifecycle
  and eval-set updated; added a PII-bearing fixture.

Only the semantic extraction remains a model step. Risk classification,
GDPR routing and validation gating are now explicit ranked DMN rules —
inspectable, versioned, portable. Breaking change: structure + outputs.
This commit is contained in:
UAPF Steward
2026-05-17 20:00:36 +00:00
parent 3f1d62c748
commit dd69a04355
15 changed files with 496 additions and 120 deletions

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<dmn:definitions xmlns:dmn="https://www.omg.org/spec/DMN/20191111/MODEL/"
id="Definitions_AssessPersonalDataRisk"
namespace="https://uapf.dev/processes/semantic-document-analysis">
<dmn:decision id="assess-personal-data-risk" name="Assess personal-data risk">
<dmn:decisionTable hitPolicy="FIRST">
<dmn:input id="i_pk" label="Personas kods present">
<dmn:inputExpression typeRef="boolean"><dmn:text>personasKodaPresent</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:input id="i_fin" label="Financial identifier present">
<dmn:inputExpression typeRef="boolean"><dmn:text>financialDataPresent</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:input id="i_contact" label="Contact data present">
<dmn:inputExpression typeRef="boolean"><dmn:text>contactDataPresent</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:input id="i_cnt" label="Distinct PII categories">
<dmn:inputExpression typeRef="number"><dmn:text>piiCategoryCount</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:output id="o_risk" label="Personal-data risk" name="personalDataRisk" typeRef="string"/>
<dmn:output id="o_rat" label="Rationale" name="riskRationale" typeRef="string"/>
<dmn:rule id="R1_personas_kods">
<dmn:inputEntry><dmn:text>true</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"HIGH"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"Personas kods (national ID) detected"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R2_financial">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>true</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"HIGH"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"Financial account identifier (IBAN) detected"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R3_multi_category">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>[2..999]</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"MEDIUM"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"Two or more distinct PII categories present"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R4_contact">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>true</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"MEDIUM"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"Contact data (email/phone) detected"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R5_single">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>1</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"LOW"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"Single PII category present"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R6_none">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"NONE"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"No personal data detected by regex scan"</dmn:text></dmn:outputEntry>
</dmn:rule>
</dmn:decisionTable>
</dmn:decision>
</dmn:definitions>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<dmn:definitions xmlns:dmn="https://www.omg.org/spec/DMN/20191111/MODEL/"
id="Definitions_GdprProcessingRoute"
namespace="https://uapf.dev/processes/semantic-document-analysis">
<dmn:decision id="gdpr-processing-route" name="GDPR processing route">
<dmn:decisionTable hitPolicy="FIRST">
<dmn:input id="i_risk" label="Personal-data risk">
<dmn:inputExpression typeRef="string"><dmn:text>personalDataRisk</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:input id="i_central" label="Centralisation permitted">
<dmn:inputExpression typeRef="boolean"><dmn:text>allowCentralization</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:output id="o_route" label="Processing route" name="processingRoute" typeRef="string"/>
<dmn:output id="o_anon" label="Anonymisation required" name="anonymizationRequired" typeRef="boolean"/>
<dmn:output id="o_redact" label="Redaction level" name="redactionLevel" typeRef="string"/>
<dmn:rule id="R1_sensitive_local">
<dmn:inputEntry><dmn:text>"HIGH","MEDIUM"</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>false</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"LOCAL"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"FULL"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R2_any_local">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>false</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"LOCAL"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>false</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"PARTIAL"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R3_sensitive_central">
<dmn:inputEntry><dmn:text>"HIGH","MEDIUM"</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>true</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"CENTRAL"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"FULL"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R4_low_central">
<dmn:inputEntry><dmn:text>"LOW"</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>true</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"CENTRAL"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>false</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"PARTIAL"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R5_none_central">
<dmn:inputEntry><dmn:text>"NONE"</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>true</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"CENTRAL"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>false</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"NONE"</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="R6_default">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"CENTRAL"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>false</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>"PARTIAL"</dmn:text></dmn:outputEntry>
</dmn:rule>
</dmn:decisionTable>
</dmn:decision>
</dmn:definitions>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<dmn:definitions xmlns:dmn="https://www.omg.org/spec/DMN/20191111/MODEL/"
id="Definitions_HumanValidationGate"
namespace="https://uapf.dev/processes/semantic-document-analysis">
<dmn:decision id="human-validation-gate" name="Human-validation gate">
<dmn:decisionTable hitPolicy="FIRST">
<dmn:input id="i_pii" label="Output PII error count">
<dmn:inputExpression typeRef="number"><dmn:text>outputPiiErrorCount</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:input id="i_conf" label="AI confidence score">
<dmn:inputExpression typeRef="number"><dmn:text>aiConfidenceScore</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:input id="i_risk" label="Personal-data risk">
<dmn:inputExpression typeRef="string"><dmn:text>personalDataRisk</dmn:text></dmn:inputExpression>
</dmn:input>
<dmn:output id="o_status" label="Human-validation status" name="humanValidationStatus" typeRef="string"/>
<dmn:output id="o_review" label="Requires human review" name="requiresHumanReview" typeRef="boolean"/>
<dmn:rule id="V1_pii_leak">
<dmn:inputEntry><dmn:text>[1..9999]</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"REJECTED"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="V2_low_confidence">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>[0..0.3)</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"REJECTED"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="V3_mid_confidence">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>[0.3..0.7)</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"PENDING_REVIEW"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="V4_high_risk_review">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>"HIGH"</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"PENDING_REVIEW"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="V5_auto_approve">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>[0.7..1]</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"APPROVED_AUTO"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>false</dmn:text></dmn:outputEntry>
</dmn:rule>
<dmn:rule id="V6_default">
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:inputEntry><dmn:text>-</dmn:text></dmn:inputEntry>
<dmn:outputEntry><dmn:text>"PENDING_REVIEW"</dmn:text></dmn:outputEntry>
<dmn:outputEntry><dmn:text>true</dmn:text></dmn:outputEntry>
</dmn:rule>
</dmn:decisionTable>
</dmn:decision>
</dmn:definitions>