feat(records): complete eAkte reference journey
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-08-22 18:42:09 +02:00
parent 38f203a906
commit a5eee2c23f
15 changed files with 1020 additions and 36 deletions
+178 -3
View File
@@ -1,18 +1,193 @@
{
"id": "resident-parking-permit-assisted-record",
"description": "A completed assisted resident parking permit service is filed, closed, held, appraised, independently approved, and exercised through the archive simulation boundary.",
"description": "A completed assisted resident parking permit service is filed as an eAkte, closed, held, appraised, independently approved, restored from backup, and exercised through the archive simulation boundary.",
"record": {
"record_id": "record-1",
"record_number": "2026/0001",
"class_id": "class-permit"
"class_id": "class-permit",
"title": "Permit application Ada Example"
},
"institutional_context": {
"service_id": "resident-parking-permit",
"case_id": "case-parking-2026-0001",
"applicant_party_id": "party-ada-example",
"representative_party_id": "party-alex-example",
"representation_id": "representation-2026-0001",
"authority_id": "authority-mobility-berlin",
"responsible_function_id": "function-parking-permits",
"mandate_id": "mandate-parking-permits-2026",
"purpose": "decide the resident parking permit application",
"legal_basis": "StVG section 6 and StVO section 45",
"retention_policy_ref": "records-policy:parking-permit:v1"
},
"digital_equivalent": {
"record": {
"record_id": "record-digital",
"record_number": "2026/0002",
"class_id": "class-permit",
"title": "Permit application Dana Digital"
},
"institutional_context_overrides": {
"case_id": "case-parking-2026-0002",
"applicant_party_id": "party-dana-digital",
"representative_party_id": "party-dana-digital",
"representation_id": "self-representation-2026-0002"
},
"intake_source": {
"id": "digital-submission",
"source_module": "forms_runtime",
"resource_type": "form_submission_revision",
"resource_id": "submission-parking-2026-0002",
"source_revision": "revision-2-submitted",
"label": "Digital resident parking permit submission",
"authority_mode": "native_authoritative",
"content_type": "application/json",
"size_bytes": 3968,
"relationship": "initiates",
"purpose": "document the submitted application",
"filing_reason": "The authenticated digital submission initiated the administrative procedure.",
"launch_url": "/forms-runtime/submissions/submission-parking-2026-0002",
"metadata": {
"evidence_role": "application",
"channel": "digital",
"authenticated": true,
"form_definition_revision": "resident-parking-permit:v4"
}
}
},
"sources": [
{
"id": "assisted-submission",
"source_module": "forms_runtime",
"resource_type": "form_submission_revision",
"resource_id": "submission-parking-2026-0001",
"source_revision": "revision-3-submitted",
"label": "Assisted resident parking permit submission",
"authority_mode": "native_authoritative",
"content_type": "application/json",
"size_bytes": 4096,
"relationship": "initiates",
"purpose": "document the submitted application",
"filing_reason": "The assisted, read-back submission initiated the administrative procedure.",
"launch_url": "/forms-runtime/submissions/submission-parking-2026-0001",
"metadata": {
"evidence_role": "application",
"channel": "assisted",
"read_back_confirmed": true,
"form_definition_revision": "resident-parking-permit:v4"
}
},
{
"id": "application-attachment",
"source_module": "files",
"resource_type": "file_version",
"resource_id": "file-registration-certificate",
"source_revision": "version-2",
"label": "Vehicle registration certificate.pdf",
"authority_mode": "external_authoritative",
"content_type": "application/pdf",
"size_bytes": 245760,
"relationship": "supports",
"purpose": "document application eligibility",
"filing_reason": "The submitted certificate supports the applicant's vehicle eligibility.",
"launch_url": "/files?fileId=file-registration-certificate",
"metadata": {
"evidence_role": "application_attachment",
"malware_scan": "clean"
}
},
{
"id": "case-and-representation",
"source_module": "cases",
"resource_type": "case_revision",
"resource_id": "case-parking-2026-0001",
"source_revision": "revision-5-representation-verified",
"label": "Resident parking permit case with verified representation",
"authority_mode": "native_authoritative",
"content_type": "application/json",
"size_bytes": 8192,
"relationship": "governs",
"purpose": "document case ownership and representation",
"filing_reason": "The case revision identifies the applicant, representative, authority, and verified representation used for the decision.",
"launch_url": "/cases/case-parking-2026-0001",
"metadata": {
"evidence_role": "case_context",
"representation_id": "representation-2026-0001",
"representation_status": "verified"
}
},
{
"id": "formal-decision",
"source_module": "decisions",
"resource_type": "decision_revision",
"resource_id": "decision-parking-2026-0001",
"source_revision": "revision-2-issued",
"label": "Resident parking permit decision",
"authority_mode": "native_authoritative",
"content_type": "application/json",
"size_bytes": 6144,
"relationship": "decides",
"purpose": "document the formal administrative outcome",
"filing_reason": "The issued decision records the competent authority, legal basis, outcome, and reasons.",
"launch_url": "/decisions/decision-parking-2026-0001",
"metadata": {
"evidence_role": "formal_decision",
"outcome": "approved",
"legal_basis": "StVG section 6 and StVO section 45"
}
},
{
"id": "delivery-receipt",
"source_module": "files",
"resource_type": "file_version",
"resource_id": "file-postbox-delivery-receipt",
"source_revision": "version-1",
"label": "Trusted postbox delivery receipt.json",
"authority_mode": "external_authoritative",
"content_type": "application/json",
"size_bytes": 2048,
"relationship": "proves_delivery",
"purpose": "document delivery of the formal decision",
"filing_reason": "The immutable receipt proves delivery of the issued decision to the represented applicant's trusted postbox.",
"launch_url": "/files?fileId=file-postbox-delivery-receipt",
"metadata": {
"evidence_role": "delivery_receipt",
"decision_revision": "revision-2-issued",
"delivery_channel": "trusted_postbox",
"delivery_status": "delivered"
}
},
{
"id": "correction-evidence",
"source_module": "files",
"resource_type": "file_version",
"resource_id": "file-decision-correction-evidence",
"source_revision": "version-1",
"label": "Decision address correction evidence.json",
"authority_mode": "external_authoritative",
"content_type": "application/json",
"size_bytes": 2304,
"relationship": "corrects",
"purpose": "preserve correction chronology without replacing prior evidence",
"filing_reason": "The correction evidence links the corrected delivery address to the original decision and receipt while preserving both.",
"launch_url": "/files?fileId=file-decision-correction-evidence",
"metadata": {
"evidence_role": "correction",
"corrects_source_id": "delivery-receipt",
"previous_evidence_retained": true
}
}
],
"expected": {
"equivalent_record_count": 2,
"filed_item_count": 6,
"closed_state": "retention_running",
"appraised_state": "appraised",
"approved_state": "transfer_pending",
"hold_blocks_disposition": true,
"disposition_status": "approved",
"transfer_status": "simulated_accepted",
"custody_transferred": false
"custody_transferred": false,
"restored_search_query": "Ada Example"
}
}
+503 -1
View File
@@ -2,8 +2,11 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
import hashlib
import json
from pathlib import Path
import sqlite3
import tempfile
import unittest
from sqlalchemy import create_engine
@@ -73,6 +76,48 @@ class SourceProvider:
)
class ReferenceJourneySourceProvider:
def __init__(self, provider_id: str, resource_types: tuple[str, ...]) -> None:
self.provider_id = provider_id
self._resource_types = resource_types
def resource_types(self):
return self._resource_types
def resolve(self, session, principal, *, locator, purpose):
del session, purpose
if principal.tenant_id != locator.tenant_id:
raise ValueError("Source access denied.")
if locator.source_module != self.provider_id:
raise ValueError("The source module does not match this provider.")
if locator.resource_type not in self._resource_types:
raise ValueError("The source type is not supported by this provider.")
metadata = dict(locator.metadata)
reference = dict(metadata.get("reference") or {})
digest_input = ":".join(
(
locator.source_module,
locator.resource_type,
locator.resource_id,
locator.source_revision,
)
).encode("utf-8")
return RecordSourceReference(
locator=locator,
label=str(reference.get("label") or locator.resource_id),
authority_mode=str(
reference.get("authority_mode") or "linked_reference"
),
content_sha256=hashlib.sha256(digest_input).hexdigest(),
content_type=str(reference.get("content_type") or "application/json"),
size_bytes=int(reference.get("size_bytes") or 0),
valid_from=NOW,
recorded_at=NOW,
launch_url=str(reference.get("launch_url") or "") or None,
metadata=metadata,
)
class UnknownOutcomeArchiveProvider:
provider_id = "unknown_simulation"
@@ -131,6 +176,33 @@ class Registry:
}.get(name)
class ReferenceJourneyRegistry(Registry):
def __init__(self) -> None:
super().__init__()
self.source_providers_by_module = {
"files": ReferenceJourneySourceProvider("files", ("file_version",)),
"forms_runtime": ReferenceJourneySourceProvider(
"forms_runtime", ("form_submission_revision",)
),
"cases": ReferenceJourneySourceProvider("cases", ("case_revision",)),
"decisions": ReferenceJourneySourceProvider(
"decisions", ("decision_revision",)
),
}
def capability_names(self):
return (
*(f"records.source.{name}" for name in self.source_providers_by_module),
"records.archive.simulation",
"records.archive.unknown_simulation",
)
def tenant_capability(self, name, session, *, tenant_id):
if name.startswith("records.source.") and tenant_id == "tenant-1":
return self.source_providers_by_module.get(name.removeprefix("records.source."))
return super().tenant_capability(name, session, tenant_id=tenant_id)
class ApprovalProvider:
def __init__(self) -> None:
self.approved = False
@@ -231,7 +303,12 @@ class RecordsTests(unittest.TestCase):
"file_plan_node_id": "plan-permits",
"key": "permit.application",
"label": "Permit application",
"allowed_source_types": ["files:file_version"],
"allowed_source_types": [
"files:file_version",
"forms_runtime:form_submission_revision",
"cases:case_revision",
"decisions:decision_revision",
],
"retention_period_days": 3650,
"access_mode": "tenant",
"recorded_at": NOW,
@@ -727,6 +804,431 @@ class RecordsTests(unittest.TestCase):
self.assertTrue(recovery["healthy"])
self.assertTrue(recovery["package_checks"][0]["manifest_verified"])
def test_assisted_service_reference_journey_survives_backup_and_restore(
self,
) -> None:
fixture = json.loads(
(
Path(__file__).parent / "fixtures/service_to_decision_journey.json"
).read_text(encoding="utf-8")
)
expected = fixture["expected"]
context = fixture["institutional_context"]
self.records = SqlRecordRegistry(ReferenceJourneyRegistry())
def create_journey_record(
record_data: dict[str, object],
record_context: dict[str, object],
*,
idempotency_suffix: str,
channel: str,
) -> dict[str, object]:
return self.records.create_record(
self.session,
self.principal,
payload={
**record_data,
"file_plan_node_id": "plan-permits",
"description": fixture["description"],
"state": "open",
"source_authority_mode": "native_authoritative",
"access_mode": "tenant",
"purpose": record_context["purpose"],
"responsible_unit_id": "unit-mobility-services",
"responsible_function_id": record_context[
"responsible_function_id"
],
"institutional_context": {**record_context, "channel": channel},
"recorded_at": NOW + timedelta(minutes=1),
"valid_from": NOW,
"change_reason": f"The {channel} application was accepted for processing.",
"idempotency_key": f"reference-record-create-{idempotency_suffix}",
},
)
def filing_request(
record_id: str,
source: dict[str, object],
record_context: dict[str, object],
*,
idempotency_suffix: str = "",
) -> RecordFilingRequest:
metadata = dict(source["metadata"])
return RecordFilingRequest(
tenant_id="tenant-1",
record_id=record_id,
source=RecordSourceLocator(
tenant_id="tenant-1",
source_module=str(source["source_module"]),
resource_type=str(source["resource_type"]),
resource_id=str(source["resource_id"]),
source_revision=str(source["source_revision"]),
metadata={
"reference": {
"label": source["label"],
"authority_mode": source["authority_mode"],
"content_type": source["content_type"],
"size_bytes": source["size_bytes"],
"launch_url": source["launch_url"],
},
**metadata,
},
),
purpose=str(source["purpose"]),
filing_reason=str(source["filing_reason"]),
relationship=str(source["relationship"]),
institutional_context={
**record_context,
"evidence_role": metadata["evidence_role"],
},
metadata={"fixture_source_id": source["id"]},
idempotency_key=(
f"reference-file-{source['id']}{idempotency_suffix}"
),
)
record = create_journey_record(
fixture["record"], context, idempotency_suffix="assisted", channel="assisted"
)
self.assertEqual(fixture["record"]["title"], record["title"])
filed_results = []
for source in fixture["sources"]:
filed_results.append(
self.records.file(
self.session,
self.principal,
request=filing_request(str(record["record_id"]), source, context),
)
)
replay = self.records.file(
self.session,
self.principal,
request=filing_request(
str(record["record_id"]), fixture["sources"][0], context
),
)
self.assertTrue(replay.replayed)
self.assertEqual(filed_results[0].item_id, replay.item_id)
detail = self.records.get_record(
self.session, self.principal, record_id=str(record["record_id"])
)
self.assertEqual(expected["filed_item_count"], len(detail["items"]))
self.assertEqual(
[source["source_revision"] for source in fixture["sources"]],
[item["source"]["source_revision"] for item in detail["items"]],
)
self.assertEqual(
[source["relationship"] for source in fixture["sources"]],
[item["relationship"] for item in detail["items"]],
)
self.assertEqual(
{
"application",
"application_attachment",
"case_context",
"formal_decision",
"delivery_receipt",
"correction",
},
{item["source_metadata"]["evidence_role"] for item in detail["items"]},
)
digital_fixture = fixture["digital_equivalent"]
digital_context = {
**context,
**digital_fixture["institutional_context_overrides"],
}
digital_record = create_journey_record(
digital_fixture["record"],
digital_context,
idempotency_suffix="digital",
channel="digital",
)
digital_sources = [digital_fixture["intake_source"]]
for source in fixture["sources"][1:]:
digital_sources.append(
{
**source,
"id": f"digital-{source['id']}",
"resource_id": f"{source['resource_id']}-digital",
"label": f"Digital equivalent · {source['label']}",
}
)
for source in digital_sources:
self.records.file(
self.session,
self.principal,
request=filing_request(
str(digital_record["record_id"]),
source,
digital_context,
idempotency_suffix="-digital",
),
)
digital_detail = self.records.get_record(
self.session,
self.principal,
record_id=str(digital_record["record_id"]),
)
def equivalence_fields(item):
return (
item["source"]["source_module"],
item["source"]["resource_type"],
item["relationship"],
item["source_metadata"]["evidence_role"],
)
self.assertEqual(
[equivalence_fields(item) for item in detail["items"]],
[equivalence_fields(item) for item in digital_detail["items"]],
)
all_records, total_records = self.records.list_records(
self.session, self.principal
)
self.assertEqual(expected["equivalent_record_count"], total_records)
self.assertEqual(
{fixture["record"]["record_id"], digital_fixture["record"]["record_id"]},
{item["record_id"] for item in all_records},
)
lifecycle_start = datetime.now(UTC).replace(microsecond=0) + timedelta(
minutes=10
)
closed = self.records.close_record(
self.session,
self.principal,
record_id=str(record["record_id"]),
payload={
"expected_revision": 1,
"purpose": "close completed resident parking permit record",
"reason": "Decision, delivery, and correction evidence are complete.",
"recorded_at": lifecycle_start,
"idempotency_key": "reference-close",
},
)
self.assertEqual(expected["closed_state"], closed["state"])
appraised = self.records.appraise_record(
self.session,
self.principal,
record_id=str(record["record_id"]),
payload={
"expected_revision": 2,
"outcome": "transfer",
"purpose": "appraise completed resident parking permit record",
"reason": "Offer the complete record after governed review.",
"policy_refs": [context["retention_policy_ref"]],
"override_retention_not_due": True,
"recorded_at": lifecycle_start + timedelta(minutes=1),
"idempotency_key": "reference-appraise",
},
)
self.assertEqual(expected["appraised_state"], appraised["state"])
hold = self.records.apply_hold(
self.session,
self.principal,
record_id=str(record["record_id"]),
payload={
"expected_record_revision": 3,
"reason": "Preserve the record while a correction is reviewed.",
"authority": "Mobility authority review 2026-17",
"purpose": "preserve correction evidence",
"policy_refs": [context["retention_policy_ref"]],
"institutional_context": context,
"recorded_at": lifecycle_start + timedelta(minutes=2),
"idempotency_key": "reference-hold",
},
)
with self.assertRaisesRegex(RecordConflictError, "active record hold"):
self.records.propose_disposition(
self.session,
self.principal,
record_id=str(record["record_id"]),
payload={
"expected_record_revision": 3,
"action": "transfer",
"reason": "Attempt transfer while evidence is held.",
"purpose": "prove hold enforcement",
"recorded_at": lifecycle_start + timedelta(minutes=3),
"idempotency_key": "reference-disposition-blocked",
},
)
self.records.release_hold(
self.session,
self.principal,
record_id=str(record["record_id"]),
hold_id=str(hold["hold_id"]),
payload={
"expected_hold_revision": 1,
"reason": "Correction review completed with prior evidence preserved.",
"purpose": "resume governed disposition",
"recorded_at": lifecycle_start + timedelta(minutes=4),
"idempotency_key": "reference-hold-release",
},
)
disposition = self.records.propose_disposition(
self.session,
self.principal,
record_id=str(record["record_id"]),
payload={
"disposition_id": "reference-disposition",
"expected_record_revision": 3,
"action": "transfer",
"reason": "The complete record is ready for an independently reviewed offer.",
"purpose": "prepare governed archive offer",
"policy_refs": [context["retention_policy_ref"]],
"institutional_context": context,
"recorded_at": lifecycle_start + timedelta(minutes=5),
"idempotency_key": "reference-disposition",
},
)
self.records.registry.approvals.approved = True
finalized = self.records.finalize_disposition(
self.session,
self.principal,
record_id=str(record["record_id"]),
disposition_id=str(disposition["disposition_id"]),
payload={
"expected_disposition_revision": 1,
"purpose": "record independent disposition approval",
"recorded_at": lifecycle_start + timedelta(minutes=6),
"idempotency_key": "reference-disposition-finalize",
},
)
self.assertEqual(expected["approved_state"], finalized["record"]["state"])
package = self.records.prepare_transfer_package(
self.session,
self.principal,
record_id=str(record["record_id"]),
payload={
"package_id": "reference-transfer-package",
"disposition_id": str(disposition["disposition_id"]),
"expected_record_revision": 4,
"provider_id": "simulation",
"profile": "govoplan-simulation-v1",
"purpose": "validate archive transfer boundary",
"recorded_at": lifecycle_start + timedelta(minutes=7),
"idempotency_key": "reference-transfer-prepare",
},
)
receipt = self.records.dispatch_transfer_package(
self.session,
self.principal,
record_id=str(record["record_id"]),
package_id=str(package["package_id"]),
payload={
"expected_package_revision": 1,
"purpose": "validate archive receipt handling",
"recorded_at": lifecycle_start + timedelta(minutes=8),
"idempotency_key": "reference-transfer-dispatch",
},
)
self.assertEqual(expected["transfer_status"], receipt["status"])
self.assertEqual(
expected["custody_transferred"],
receipt["receipt"]["metadata"]["custody_transferred"],
)
self.session.commit()
with tempfile.TemporaryDirectory(prefix="govoplan-records-restore-") as temp_dir:
backup_path = Path(temp_dir) / "restored-records.sqlite3"
source_connection = self.engine.raw_connection()
try:
with sqlite3.connect(backup_path) as backup_connection:
source_connection.driver_connection.backup(backup_connection)
finally:
source_connection.close()
restored_engine = create_engine(f"sqlite+pysqlite:///{backup_path}")
restored_session = Session(restored_engine)
try:
restored_records = SqlRecordRegistry(ReferenceJourneyRegistry())
matches, total = restored_records.list_records(
restored_session,
self.principal,
query=expected["restored_search_query"],
)
self.assertEqual(1, total)
self.assertEqual(record["record_id"], matches[0]["record_id"])
restored = restored_records.get_record(
restored_session,
self.principal,
record_id=str(record["record_id"]),
)
self.assertEqual(expected["filed_item_count"], len(restored["items"]))
self.assertEqual(
{**context, "channel": "assisted"},
restored["record"]["institutional_context"],
)
self.assertEqual("released", restored["holds"][0]["status"])
self.assertEqual(
expected["disposition_status"],
restored["dispositions"][0]["status"],
)
self.assertEqual(
[context["retention_policy_ref"]],
restored["dispositions"][0]["policy_refs"],
)
self.assertEqual(
expected["transfer_status"],
restored["transfer_packages"][0]["status"],
)
self.assertTrue(restored["transfer_packages"][0]["simulated"])
self.assertEqual(
expected["filed_item_count"],
sum(
entry["event_type"] == "record.item_filed"
for entry in restored["chronology"]
),
)
recovery = restored_records.recovery_status(
restored_session,
self.principal,
record_id=str(record["record_id"]),
)
self.assertTrue(recovery["healthy"])
self.assertEqual(
expected["filed_item_count"], len(recovery["source_checks"])
)
self.assertTrue(
all(
check["status"] == "verified"
for check in recovery["source_checks"]
)
)
self.assertTrue(
all(
check["manifest_verified"]
for check in recovery["package_checks"]
)
)
token = bind_temporal_data_context(
TemporalDataContext(
validity_mode="at",
valid_at=lifecycle_start - timedelta(minutes=1),
recorded_at=lifecycle_start - timedelta(minutes=1),
)
)
try:
historical = restored_records.get_record(
restored_session,
self.principal,
record_id=str(record["record_id"]),
)
self.assertEqual("open", historical["record"]["state"])
self.assertEqual(
expected["filed_item_count"], len(historical["items"])
)
self.assertEqual([], historical["transfer_packages"])
finally:
reset_temporal_data_context(token)
finally:
restored_session.close()
restored_engine.dispose()
def test_disposition_waits_for_optional_approvals_and_can_be_withdrawn(
self,
) -> None:
+71
View File
@@ -0,0 +1,71 @@
from __future__ import annotations
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[1]
class ReferenceJourneyUiTests(unittest.TestCase):
def test_actor_can_resume_and_reconstruct_the_record_journey(self) -> None:
page = (ROOT / "webui/src/features/records/RecordsPage.tsx").read_text(
encoding="utf-8"
)
lifecycle = (
ROOT / "webui/src/features/records/RecordLifecyclePanel.tsx"
).read_text(encoding="utf-8")
api = (ROOT / "webui/src/api/records.ts").read_text(encoding="utf-8")
for marker in (
'searchParams.get("recordId")',
'next.set("recordId", selectedRecordId)',
"filingPresetFrom(searchParams)",
'searchParams.get("sourceRevision")',
'<WorkspaceActionBar',
"refreshable",
'reloadAction={{ onReload: reload',
'data-help-context-id="records.governance-context"',
'data-help-context-id="records.record-items"',
'data-help-context-id="records.chronology"',
"item.source.source_revision",
"item.authority_mode",
"item.relationship",
"item.purpose",
"item.source_metadata.evidence_role",
"detail.access_explanation.current_authorization",
):
self.assertIn(marker, page)
for action in (
'openAction("close")',
'openAction("reopen")',
'openAction("appraise")',
'openAction("hold")',
'openAction("release-hold", holdId)',
'openAction("disposition")',
'openAction("finalize")',
'openAction("prepare-transfer")',
'openAction("dispatch-transfer")',
"openRecovery",
"Release all active holds first.",
"Simulation only",
"custody",
"item.policy_refs",
):
self.assertIn(action, lifecycle)
for endpoint in (
'"close"',
'"reopen"',
'"appraise"',
'"holds"',
'"dispositions"',
'"transfer-packages"',
"/recovery`",
):
self.assertIn(endpoint, api)
if __name__ == "__main__":
unittest.main()