Files
govoplan-portal/docs/SERVICE_DIRECTORY_CONCEPT.md
T

7.2 KiB

Role-Based Service Directory Concept

GovOPlaN Portal should expose a role-aware service directory. The directory is not a marketing catalogue; it is the user-facing map of what a person can do in the configured institution.

Portal owns presentation, discovery, entry, and channel-specific availability. It must not become the long-term owner of the institutional service definition. A shared Services contract should own the versioned promise independently so Cases, Forms, Workflow Engine, Reporting, external publication, and more than one portal surface can consume the same definition.

Purpose

The service directory should help users find the right administrative action without knowing the internal module layout. Available services depend on tenant, role, organization unit, policies, installed modules, and configuration packages.

Examples:

  • apply for a permit
  • submit documents for an existing case
  • request an appointment
  • send a secure postbox message
  • report an issue
  • book a resource
  • start an internal workflow

Contract

A service entry should describe:

  • service key and label
  • owning module or configuration package
  • audience and role/function requirements
  • required installed/enabled modules
  • required capabilities
  • form, workflow, postbox, task, or external connector entry point
  • policy and availability blockers
  • user-facing explanation and required documents
  • audit and evidence expectations
  • version and effective interval
  • legal or organizational basis, deadlines, fees, remedies, and service-level expectations
  • responsible organization/function, mandate/jurisdiction, result/decision, publication, and records bindings

The portal should request service contributions through core-mediated capability and UI contribution contracts. It must not import domain module internals.

The first implementation may consume module-contributed service definitions. Create a separate govoplan-services repository only when definitions require independent persistence/lifecycle and are consumed outside Portal by at least one other procedure or publication surface. Extraction must keep stable service keys and compatibility for existing Portal links.

Service visibility must consume access semantics through kernel capabilities:

  • access.semanticDirectory resolves the actor's identity, account, organization-unit function assignments, delegations, and role mappings.
  • access.explanation provides the explanation shown when a service is available or blocked because of a missing function, role, right, or policy.

Service entries should support function requirements directly, not only role requirements. A function requirement may apply to one organization unit or to that unit and all subunits, matching the access assignment scope.

UX Rule

The directory should explain unavailable services when the reason is useful: missing role, disabled module, tenant policy, missing connector, maintenance mode, or unavailable external provider. It should hide only services that are irrelevant or intentionally undiscoverable by policy.

Implemented Slice

portal.service_directory consumes the optional services.definitions capability. It returns only tenant-scoped definitions and distinguishes available, unavailable, and hidden states. Draft, retired, outside-effective-time, and audience-inapplicable services are hidden. A suspended service or missing required module/capability remains discoverable with stable reason codes and its configured explanation reference.

The definition provider remains responsible for access and policy filtering. Portal cannot widen its result and returns an empty directory when no provider is installed. No service definition is persisted in Portal.

Tenant/package specializations are derived before presentation through Core's restrictive Service rule. They carry their parent version and may narrow audience, channels, effective time, and publication or add prerequisites, required evidence, legal bases, and bindings. They cannot silently loosen the system/package definition.

Policy, Mandate, connector, maintenance, and configuration requirements are evaluated through the optional services.availability capability. Module and capability requirements are checked against the active registry, and audience requirements against the supplied actor projection. Unknown requirements fail closed. Each requirement declares whether failure remains visible with an explanation or makes the entry undiscoverable.

GET /api/v1/portal/services and the /portal WebUI expose this projection. The API derives audience tokens from the authenticated principal instead of accepting caller-provided audience claims. Generic public and authenticated tokens are combined with stable account, identity, group, role, active function, and organization-unit tokens. Function slugs are included only after the optional Access semantic directory confirms an active, effective, same-tenant assignment. Missing or failing directory providers narrow the result.

The WebUI provides bounded search, availability filtering, explanations, and one launch action. POST /api/v1/portal/services/{service_id}/launch resolves the entry again at launch time so a stale browser projection cannot bypass a new audience, publication, effective-time, module, or capability restriction. Direct URL entries return only validated local or credential-free HTTP(S) redirects.

Case, form, and workflow bindings delegate to owner capabilities. Their launch result must retain the exact Service reference, tenant, binding kind, target reference, and replay-safe idempotency key. Portal never writes their tables. Missing launchers produce service.launcher.missing:<kind> and make the entry visibly unavailable. Forms Runtime now provides a definition-aware launcher when Forms and Forms Runtime are active. It resolves an exact published <form-id>/<revision>, validates launch values, and retains Service/binding provenance. Reduced installations still fail closed rather than simulating a submission in Portal.

Applicant Status Presentation

Portal also presents Forms Runtime's bounded applicant-status projection at /portal/status/:trackingId. It does not persist a status, inspect a Form submission, or decide the disclosure policy. Forms Runtime resolves the tenant through the Core application_status.projection contract and remains authoritative for all access decisions.

The page adapts to the configured grant:

  • authenticated-only access offers sign-in and then uses the applicant-bound status endpoint;
  • email-link access accepts the linked email address and always reports the same request outcome, whether or not it matched; a delivered link carries a short-lived secret that can be resent and replaces its predecessor; and
  • permanent-link access loads from the high-entropy tracking URL without authentication.

All modes render only title, current lifecycle state, update time, receipt identifier, and the bounded public timeline supplied by Forms Runtime. Portal must not infer missing milestones or expose values, people, evidence, internal notes, or handoff details. A reload action re-fetches the authoritative projection. Missing, disabled, revoked, expired, or unauthorized grants share a non-enumerating unavailable state.