Add governed public form intake
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import replace
|
||||
from datetime import UTC, date, datetime
|
||||
import hashlib
|
||||
import json
|
||||
@@ -38,6 +39,7 @@ from govoplan_forms_runtime.backend.db.models import (
|
||||
FormInstanceRevision,
|
||||
)
|
||||
from govoplan_forms_runtime.backend.domain import FormInstance
|
||||
from govoplan_forms_runtime.backend.evidence import FormEvidenceCoordinator
|
||||
|
||||
|
||||
CAPABILITY_FORMS_RUNTIME_REGISTRY = "forms_runtime.registry"
|
||||
@@ -82,6 +84,7 @@ class FormRuntimePolicyEvaluator(Protocol):
|
||||
class FormRuntimeService:
|
||||
def __init__(self, registry: object | None) -> None:
|
||||
self._registry = registry
|
||||
self._evidence = FormEvidenceCoordinator(registry)
|
||||
|
||||
def create_instance(
|
||||
self,
|
||||
@@ -200,6 +203,27 @@ class FormRuntimeService:
|
||||
changed_by=actor_id,
|
||||
metadata=dict(metadata or {}),
|
||||
)
|
||||
evidence_diagnostics, evidence_snapshots = self._inspect_evidence(
|
||||
session,
|
||||
principal,
|
||||
instance=instance,
|
||||
definition=definition,
|
||||
values=clean_values,
|
||||
attachment_refs=attachments,
|
||||
signature_refs=signatures,
|
||||
purpose="start Form instance",
|
||||
final=False,
|
||||
observed_at=recorded_at,
|
||||
)
|
||||
if evidence_diagnostics or evidence_snapshots:
|
||||
instance = replace(
|
||||
instance,
|
||||
validation_results=(*diagnostics, *evidence_diagnostics),
|
||||
metadata={
|
||||
**dict(instance.metadata),
|
||||
"evidence_verification": list(evidence_snapshots),
|
||||
},
|
||||
)
|
||||
return _record_instance(
|
||||
session,
|
||||
principal,
|
||||
@@ -619,6 +643,111 @@ class FormRuntimeService:
|
||||
for row in rows
|
||||
)
|
||||
|
||||
def create_evidence_grant(
|
||||
self,
|
||||
session: Session,
|
||||
principal: object,
|
||||
*,
|
||||
instance_id: str,
|
||||
expected_revision: int,
|
||||
provider_id: str,
|
||||
custodian_ref: str,
|
||||
purpose: str,
|
||||
idempotency_key: str,
|
||||
expires_at: datetime,
|
||||
max_size_bytes: int | None = None,
|
||||
allowed_content_types: Sequence[str] = (),
|
||||
attachment_refs: Sequence[EvidenceReference] = (),
|
||||
allow_all: bool = False,
|
||||
):
|
||||
current, _identity = _current_instance(
|
||||
session,
|
||||
principal,
|
||||
instance_id=instance_id,
|
||||
lock=True,
|
||||
allow_all=allow_all,
|
||||
)
|
||||
if current.revision != expected_revision:
|
||||
raise FormRuntimeError(
|
||||
"Form instance revision conflict: the expected revision is stale."
|
||||
)
|
||||
definition = self._definition(
|
||||
session,
|
||||
principal,
|
||||
reference=current.definition_ref,
|
||||
effective_at=current.recorded_at,
|
||||
)
|
||||
try:
|
||||
return self._evidence.create_upload_grant(
|
||||
session,
|
||||
principal,
|
||||
instance=current,
|
||||
definition=definition,
|
||||
provider_id=provider_id,
|
||||
custodian_ref=custodian_ref,
|
||||
purpose=purpose,
|
||||
idempotency_key=idempotency_key,
|
||||
expires_at=expires_at,
|
||||
max_size_bytes=max_size_bytes,
|
||||
allowed_content_types=allowed_content_types,
|
||||
attachment_refs=attachment_refs,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise FormRuntimeError(str(exc)) from exc
|
||||
|
||||
def acknowledge(
|
||||
self,
|
||||
session: Session,
|
||||
principal: object,
|
||||
*,
|
||||
instance_id: str,
|
||||
expected_revision: int,
|
||||
statement_id: str,
|
||||
statement_version: str,
|
||||
values: Mapping[str, object],
|
||||
attachment_refs: Sequence[EvidenceReference],
|
||||
accepted_at: datetime,
|
||||
idempotency_key: str,
|
||||
allow_all: bool = False,
|
||||
) -> EvidenceReference:
|
||||
current, _identity = _current_instance(
|
||||
session,
|
||||
principal,
|
||||
instance_id=instance_id,
|
||||
lock=True,
|
||||
allow_all=allow_all,
|
||||
)
|
||||
definition = self._definition(
|
||||
session,
|
||||
principal,
|
||||
reference=current.definition_ref,
|
||||
effective_at=accepted_at,
|
||||
)
|
||||
clean_values = normalize_form_values(
|
||||
definition,
|
||||
_mapping_copy(values, "Form acknowledgement values"),
|
||||
)
|
||||
attachments = tuple(attachment_refs)
|
||||
if any(item.tenant_id != current.tenant_id for item in attachments):
|
||||
raise FormRuntimeError(
|
||||
"Form acknowledgement attachments cannot cross tenants."
|
||||
)
|
||||
try:
|
||||
return self._evidence.create_acknowledgement(
|
||||
session,
|
||||
principal,
|
||||
instance=current,
|
||||
expected_revision=expected_revision,
|
||||
statement_id=statement_id,
|
||||
statement_version=statement_version,
|
||||
values=clean_values,
|
||||
attachment_refs=attachments,
|
||||
accepted_at=accepted_at,
|
||||
idempotency_key=idempotency_key,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise FormRuntimeError(str(exc)) from exc
|
||||
|
||||
def _revise(
|
||||
self,
|
||||
session: Session,
|
||||
@@ -657,6 +786,19 @@ class FormRuntimeService:
|
||||
signature_refs=signatures,
|
||||
final=final_validation,
|
||||
)
|
||||
evidence_diagnostics, evidence_snapshots = self._inspect_evidence(
|
||||
session,
|
||||
principal,
|
||||
instance=current,
|
||||
definition=definition,
|
||||
values=clean_values,
|
||||
attachment_refs=attachments,
|
||||
signature_refs=signatures,
|
||||
purpose=operation.replace("_", " "),
|
||||
final=final_validation,
|
||||
observed_at=recorded_at,
|
||||
)
|
||||
diagnostics = (*diagnostics, *evidence_diagnostics)
|
||||
request = {
|
||||
"operation": operation,
|
||||
"instance_id": current.instance_id,
|
||||
@@ -716,7 +858,14 @@ class FormRuntimeService:
|
||||
change_reason=clean_reason,
|
||||
created_by=current.created_by,
|
||||
changed_by=actor_id,
|
||||
metadata=current.metadata,
|
||||
metadata=(
|
||||
{
|
||||
**dict(current.metadata),
|
||||
"evidence_verification": list(evidence_snapshots),
|
||||
}
|
||||
if evidence_snapshots
|
||||
else current.metadata
|
||||
),
|
||||
)
|
||||
current_row = (
|
||||
session.query(FormInstanceRevision)
|
||||
@@ -744,6 +893,38 @@ class FormRuntimeService:
|
||||
operation=operation,
|
||||
)
|
||||
|
||||
def _inspect_evidence(
|
||||
self,
|
||||
session: Session,
|
||||
principal: object,
|
||||
*,
|
||||
instance: FormInstance,
|
||||
definition: FormDefinition,
|
||||
values: Mapping[str, object],
|
||||
attachment_refs: Sequence[EvidenceReference],
|
||||
signature_refs: Sequence[EvidenceReference],
|
||||
purpose: str,
|
||||
final: bool,
|
||||
observed_at: datetime,
|
||||
) -> tuple[tuple[Mapping[str, object], ...], tuple[Mapping[str, object], ...]]:
|
||||
if not attachment_refs and not signature_refs:
|
||||
return (), ()
|
||||
try:
|
||||
return self._evidence.inspect(
|
||||
session,
|
||||
principal,
|
||||
instance=instance,
|
||||
definition=definition,
|
||||
values=values,
|
||||
attachment_refs=attachment_refs,
|
||||
signature_refs=signature_refs,
|
||||
purpose=purpose,
|
||||
final=final,
|
||||
observed_at=observed_at,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise FormRuntimeError(str(exc)) from exc
|
||||
|
||||
def _definition(
|
||||
self,
|
||||
session: Session,
|
||||
|
||||
Reference in New Issue
Block a user