feat: enforce shared UI foundations and pin reference journey
Dependency Audit / dependency-audit (push) Successful in 1m57s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 12m24s

This commit is contained in:
2026-08-18 21:32:25 +02:00
parent a1b80eda27
commit e2f505eeab
11 changed files with 445 additions and 64 deletions
+67
View File
@@ -0,0 +1,67 @@
{
"id": "resident-parking-permit-berlin-style-reference",
"title": "Resident parking permit",
"title_de": "Anwohnerparkausweis",
"locale": "de-DE",
"service": {
"object_id": "resident-parking-permit",
"key": "resident_parking_permit.apply",
"version": "6",
"audience": "resident",
"required_evidence_types": [
"application",
"identity",
"primary_residence",
"vehicle_registration"
],
"channels": ["portal", "assisted"]
},
"form": {
"object_id": "resident-parking-permit-application",
"version": "3",
"fields": {
"applicant_name": "Ada Lovelace",
"residence_address": "Musterstrasse 17, 10115 Berlin",
"licence_plate": "B-AL 1843"
}
},
"case": {
"type_key": "resident-parking-permit-application",
"number": "RPP-2026-0001",
"initial_status": "intake",
"decided_status": "decided",
"deadline_days": 30
},
"workflow": {
"definition_name": "Resident parking permit decision",
"work_item_title": "Decide the resident parking permit application",
"instructions": "Review identity, primary residence, vehicle evidence, and the effective local rule before recording the decision."
},
"decision": {
"type": "resident-parking-permit",
"operative_result": "Resident parking permit granted.",
"reasoning": "Identity, primary residence, vehicle registration, and the effective local rule were verified.",
"delivery_channel": "postbox",
"remedy": "review:administrative-court"
},
"records": {
"file_plan_key": "traffic.resident-parking-permits",
"retention_policy_ref": "records:resident-parking-permit"
},
"acceptance": {
"automated": [
"The published service and exact form revision drive digital intake.",
"An idempotent replay returns the same persisted submission.",
"The human review handoff survives a database-session restart and remains visible in Tasks until completion.",
"The formal decision retains party, mandate, legal-basis, evidence, delivery, review, and exact revision references.",
"Forms Runtime, Cases, and Decisions can expose exact snapshots for explicit eAkte filing."
],
"manual_or_target": [
"Complete the digital journey with keyboard and screen reader at desktop and mobile widths.",
"Repeat intake through an assisted-channel operator without weakening identity, purpose, or provenance.",
"Verify the configured Postbox or external delivery provider, including unknown outcome and reconciliation.",
"Restore the pinned composition and reconstruct the exact form, case, decision, delivery evidence, and eAkte chronology.",
"Transfer through a named archive profile and retain independently signed target evidence."
]
}
}
+39 -29
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import UTC, datetime, timedelta
import json
from pathlib import Path
import unittest
from sqlalchemy import create_engine
@@ -66,6 +68,11 @@ from govoplan_services.backend.service import SqlServiceDefinitionProvider, reco
NOW = datetime(2026, 8, 1, 10, 0, tzinfo=UTC)
JOURNEY = json.loads(
(Path(__file__).parent / "fixtures/resident_parking_permit_journey.json").read_text(
encoding="utf-8"
)
)
def _reference(
@@ -177,31 +184,31 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
service = ServiceDefinition(
reference=_reference(
"service",
"permit-service",
JOURNEY["service"]["object_id"],
owner="services",
version="5",
version=JOURNEY["service"]["version"],
),
key="permit.apply",
key=JOURNEY["service"]["key"],
temporal=TemporalRevision(
revision="5",
revision=JOURNEY["service"]["version"],
valid_from=NOW - timedelta(days=1),
valid_to=NOW + timedelta(days=30),
recorded_at=NOW - timedelta(days=2),
change_reason="Service published.",
),
title="Apply for a permit",
title=JOURNEY["title"],
audience=("resident",),
legal_bases=(legal_basis,),
required_evidence_types=("application", "identity"),
channels=("portal", "postbox"),
required_evidence_types=tuple(JOURNEY["service"]["required_evidence_types"]),
channels=tuple(JOURNEY["service"]["channels"]) + ("postbox",),
responsible_organization_ref=organization,
responsible_function_ref=function,
mandate_ref=mandate_ref,
jurisdiction_refs=(jurisdiction,),
bindings=(
ServiceBinding("case", "permit-application"),
ServiceBinding("workflow", "workflow:permit-review"),
ServiceBinding("result", "decision:permit"),
ServiceBinding("case", JOURNEY["case"]["type_key"]),
ServiceBinding("workflow", "workflow:resident-parking-permit-review"),
ServiceBinding("result", f"decision:{JOURNEY['decision']['type']}"),
),
remedy_refs=("review:administrative-court",),
publication_state="published",
@@ -230,37 +237,40 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
upsert_case_status(
session,
principal,
status_key="intake",
status_key=JOURNEY["case"]["initial_status"],
label="Intake",
)
upsert_case_status(
session,
principal,
status_key="decided",
status_key=JOURNEY["case"]["decided_status"],
label="Decided",
category="decided",
)
upsert_case_type(
session,
principal,
type_key="permit-application",
label="Permit application",
initial_status_key="intake",
allowed_status_keys=("intake", "decided"),
type_key=JOURNEY["case"]["type_key"],
label=JOURNEY["title"],
initial_status_key=JOURNEY["case"]["initial_status"],
allowed_status_keys=(
JOURNEY["case"]["initial_status"],
JOURNEY["case"]["decided_status"],
),
)
case_record = create_case_from_intake(
session,
principal,
plan=intake,
case_number="PERMIT-2026-0001",
title="Permit application",
status_key=None,
case_number=JOURNEY["case"]["number"],
title=JOURNEY["title"],
status_key=JOURNEY["case"]["initial_status"],
opened_at=NOW,
recorded_at=NOW,
change_reason="Portal application received.",
idempotency_key="journey-case-create",
evidence_refs=(application_evidence,),
deadline_at=NOW + timedelta(days=30),
deadline_at=NOW + timedelta(days=JOURNEY["case"]["deadline_days"]),
)
applicant = _reference("party", "applicant", owner="parties")
@@ -333,7 +343,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
change_reason="Permit authority delegated.",
),
task_types=("committee.formal_decision",),
authority_types=("permit",),
authority_types=(JOURNEY["decision"]["type"],),
organization_unit_refs=(organization,),
function_refs=(function,),
jurisdiction_refs=(jurisdiction,),
@@ -380,7 +390,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
object_id="item-1",
revision=1,
state="deliberating",
title="Permit application",
title=JOURNEY["title"],
parent_id=meeting.object_id,
recorded_at=NOW,
change_reason="Agenda item entered deliberation.",
@@ -398,7 +408,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
object_id="vote-1",
revision=1,
state="closed",
title="Vote on permit application",
title=f"Vote on {JOURNEY['title'].lower()}",
parent_id=agenda.object_id,
recorded_at=NOW,
change_reason="Vote result accepted.",
@@ -445,7 +455,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
effective_at=NOW,
meeting_ref="meeting-1",
agenda_item_ref="item-1",
decision_type="permit",
decision_type=JOURNEY["decision"]["type"],
subject_refs=(case_record.reference,),
organization_unit_ref=organization,
function_ref=function,
@@ -461,8 +471,8 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
),
fact_evidence=(application_evidence, address_evidence),
legal_bases=(legal_basis,),
operative_result="Permit granted.",
reasoning="The application satisfies the effective rule.",
operative_result=JOURNEY["decision"]["operative_result"],
reasoning=JOURNEY["decision"]["reasoning"],
case_ref=case_record.reference,
jurisdiction_refs=(jurisdiction,),
party_refs=(applicant, representative),
@@ -475,7 +485,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
classification="restricted",
purposes=("permit-decision", "party-delivery"),
legal_basis_refs=("permit-law:3@2026-01",),
retention_policy_ref="records:permit",
retention_policy_ref=JOURNEY["records"]["retention_policy_ref"],
disclosure_state="partly_disclosable",
),
),
@@ -568,7 +578,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
get_case(session, principal, case_id="case-1"),
)
self.assertEqual(
"decided",
JOURNEY["case"]["decided_status"],
get_workspace_object(
session,
principal,
@@ -583,7 +593,7 @@ class InstitutionalGovernanceJourneyTests(unittest.TestCase):
self.assertEqual("confirmed", reconstruction["observed_effects"][0]["state"])
self.assertEqual("audit:delivery-1", reconstruction["observed_effects"][0]["audit_event_refs"][0])
self.assertEqual("application-1", reconstruction["fact_evidence"][0]["evidence_id"])
self.assertEqual("The application satisfies the effective rule.", reconstruction["reasoning"])
self.assertEqual(JOURNEY["decision"]["reasoning"], reconstruction["reasoning"])
self.assertEqual("review:administrative-court", reconstruction["review_refs"][0])
+52 -27
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
import json
from pathlib import Path
from types import SimpleNamespace
import unittest
@@ -82,6 +84,11 @@ from govoplan_workflow_engine.backend.work_items import WorkflowWorkItemProvider
NOW = datetime(2026, 8, 1, 10, 0, tzinfo=UTC)
JOURNEY = json.loads(
(Path(__file__).parent / "fixtures/resident_parking_permit_journey.json").read_text(
encoding="utf-8"
)
)
def _service() -> ServiceDefinition:
@@ -89,24 +96,24 @@ def _service() -> ServiceDefinition:
reference=InstitutionalReference(
kind="service",
owner_module="portal",
object_id="permit",
object_id=JOURNEY["service"]["object_id"],
tenant_id="tenant-1",
version="5",
version=JOURNEY["service"]["version"],
),
key="permit.apply",
key=JOURNEY["service"]["key"],
temporal=TemporalRevision(
revision="5",
revision=JOURNEY["service"]["version"],
valid_from=NOW - timedelta(days=1),
valid_to=NOW + timedelta(days=1),
recorded_at=NOW - timedelta(days=2),
),
title="Apply for a permit",
audience=("resident",),
required_evidence_types=("application",),
title=JOURNEY["title"],
audience=(JOURNEY["service"]["audience"],),
required_evidence_types=tuple(JOURNEY["service"]["required_evidence_types"]),
bindings=(
ServiceBinding("capability", CAPABILITY_CASES_SERVICE_INTAKE),
ServiceBinding("case", "permit-application"),
ServiceBinding("workflow", "workflow:permit-review"),
ServiceBinding("case", JOURNEY["case"]["type_key"]),
ServiceBinding("workflow", "workflow:resident-parking-permit-review"),
),
publication_state="published",
)
@@ -230,8 +237,14 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
self.assertTrue(entries[0].available)
self.assertIs(definition, entries[0].definition)
self.assertEqual(definition.reference, plan.service_ref)
self.assertEqual("5", plan.context.service_ref.version)
self.assertEqual("workflow:permit-review", plan.workflow_refs[0])
self.assertEqual(JOURNEY["service"]["version"], plan.context.service_ref.version)
self.assertEqual("workflow:resident-parking-permit-review", plan.workflow_refs[0])
def test_reference_fixture_names_remaining_manual_target_evidence(self) -> None:
self.assertEqual("Anwohnerparkausweis", JOURNEY["title_de"])
self.assertEqual("de-DE", JOURNEY["locale"])
self.assertEqual(5, len(JOURNEY["acceptance"]["automated"]))
self.assertEqual(5, len(JOURNEY["acceptance"]["manual_or_target"]))
def test_portal_launches_exact_form_revision_and_persists_submission(self) -> None:
engine = create_engine("sqlite+pysqlite:///:memory:")
@@ -252,19 +265,19 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
reference=InstitutionalReference(
kind="form",
owner_module="forms",
object_id="permit-application",
object_id=JOURNEY["form"]["object_id"],
tenant_id="tenant-1",
version="3",
),
key="permit-application",
key=JOURNEY["form"]["object_id"],
temporal=TemporalRevision(
revision="3",
valid_from=NOW - timedelta(days=1),
valid_to=NOW + timedelta(days=1),
recorded_at=NOW - timedelta(days=2),
change_reason="Publish the permit application.",
change_reason="Publish the resident parking permit application.",
),
title="Permit application",
title=JOURNEY["title"],
fields=(
FormFieldDefinition(
key="applicant_name",
@@ -272,6 +285,18 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
required=True,
constraints={"min_length": 2},
),
FormFieldDefinition(
key="residence_address",
label="Primary residence address",
required=True,
constraints={"min_length": 5},
),
FormFieldDefinition(
key="licence_plate",
label="Vehicle licence plate",
required=True,
constraints={"min_length": 3},
),
),
publication_state="published",
allow_drafts=True,
@@ -286,18 +311,18 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
reference=InstitutionalReference(
kind="service",
owner_module="services",
object_id="permit",
object_id=JOURNEY["service"]["object_id"],
tenant_id="tenant-1",
version="6",
version=JOURNEY["service"]["version"],
),
key="permit.apply",
key=JOURNEY["service"]["key"],
temporal=TemporalRevision(
revision="6",
revision=JOURNEY["service"]["version"],
valid_from=NOW - timedelta(days=1),
valid_to=NOW + timedelta(days=1),
recorded_at=NOW - timedelta(days=2),
),
title="Apply for a permit",
title=JOURNEY["title"],
audience=("public",),
bindings=(binding,),
publication_state="published",
@@ -311,7 +336,7 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
reference=service.reference,
requested_at=NOW,
idempotency_key="portal-form-launch-1",
parameters={"applicant_name": "Ada Lovelace"},
parameters=JOURNEY["form"]["fields"],
)
replay = directory.launch_service(
session,
@@ -319,7 +344,7 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
reference=service.reference,
requested_at=NOW,
idempotency_key="portal-form-launch-1",
parameters={"applicant_name": "Ada Lovelace"},
parameters=JOURNEY["form"]["fields"],
)
instance = FormRuntimeService(registry).get_instance(
session,
@@ -348,7 +373,7 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
)
self.assertEqual(NOW, instance.definition_ref.valid_at)
self.assertEqual(service.reference, instance.service_ref)
self.assertEqual("Ada Lovelace", instance.values["applicant_name"])
self.assertEqual(JOURNEY["form"]["fields"], instance.values)
finally:
session.close()
engine.dispose()
@@ -392,7 +417,7 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
tenant_id="tenant-1",
actor_id="account-1",
payload=WorkflowDefinitionCreateRequest(
name="Permit decision",
name=JOURNEY["workflow"]["definition_name"],
graph=WorkflowGraph(
nodes=[
WorkflowNode(
@@ -403,8 +428,8 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
id="review",
type="workflow.activity",
config={
"title": "Decide the permit application",
"instructions": "Review the filed evidence and record the decision.",
"title": JOURNEY["workflow"]["work_item_title"],
"instructions": JOURNEY["workflow"]["instructions"],
"assignee": "account:account-1",
"due_after": "2d",
},
@@ -464,7 +489,7 @@ class InstitutionalServiceJourneyTests(unittest.TestCase):
self.assertEqual(1, work.total)
self.assertEqual(step_id, work.items[0].id)
self.assertEqual(
"Decide the permit application",
JOURNEY["workflow"]["work_item_title"],
work.items[0].title,
)
self.assertTrue(work.items[0].action_url.startswith("/workflow?"))