chore: consolidate platform split checks
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Module Matrix / module-matrix (push) Has been cancelled

This commit is contained in:
2026-07-10 12:51:19 +02:00
parent 150b720f12
commit 635d25c74c
216 changed files with 23336 additions and 4077 deletions

View File

@@ -2,10 +2,17 @@
GovOPlaN is structured as a platform kernel plus installable modules. The kernel starts and composes the platform. Modules own product behavior and contribute backend routes, database metadata, permissions, WebUI routes, navigation metadata, capabilities, and events.
The current package name is still `govoplan-core`, but the architecture target is a smaller kernel. Access, tenancy, policy, audit, and admin semantics are platform-module responsibilities and should be extracted in stages.
The current package name is still `govoplan-core`, but the architecture target is a smaller kernel. Access, tenancy, policy, audit, and admin semantics are platform-module responsibilities.
The concrete access/auth/RBAC extraction path is tracked in
[`ACCESS_EXTRACTION_PLAN.md`](ACCESS_EXTRACTION_PLAN.md).
Access extraction is complete enough that current ownership is described here,
in [`ACCESS_RBAC_MODEL.md`](ACCESS_RBAC_MODEL.md), and in the
`govoplan-access` repository docs.
The event and audit trace contract is tracked in
[`EVENTS_AND_AUDIT.md`](EVENTS_AND_AUDIT.md).
Policy decision, source provenance, and explain-response contracts are tracked
in [`POLICY_CONTRACTS.md`](POLICY_CONTRACTS.md).
The experimental remote WebUI bundle loading design is tracked in
[`REMOTE_WEBUI_BUNDLES.md`](REMOTE_WEBUI_BUNDLES.md).
## Layer Model
@@ -35,14 +42,15 @@ The kernel must not own product semantics such as users, tenants, RBAC decisions
## Current Compatibility Responsibilities
During the staged split, `govoplan-core` still contains compatibility surfaces
for access, auth, tenancy, RBAC, governance, audit, CSRF/API helpers, and
secret helpers. The extracted access implementation now lives in
`govoplan-access`; live legacy ORM table definitions have been split across
their platform owners while retaining historical table names. The old core
model, route, admin-service, and access-security import shims have been
removed; callers must use module-owned imports or kernel capabilities. The
remaining compatibility surfaces are temporary until the matching platform
modules are fully self-contained:
for tenancy settings, governance/policy contracts, audit helpers, CSRF/API
helpers, and secret helpers. The extracted access implementation lives in
`govoplan-access`; live ORM table definitions have been split across their
platform owners using module-prefixed table names. The old core route,
admin-service, and access-security re-export modules have been removed.
Callers must use module-owned imports, the public `govoplan_access.auth` request
dependency API, or kernel capabilities.
The remaining platform compatibility surfaces are temporary until the matching
platform modules are fully self-contained:
- `govoplan-access`
- `govoplan-tenancy`
@@ -54,6 +62,18 @@ New code should avoid deepening these compatibility dependencies. Prefer explici
Core must not import module feature pages or module business logic directly. It should interact with modules through manifests, entry points, metadata, capabilities, events, and route contributions.
The compatibility/deprecation plan for the current split line is:
- keep documented public compatibility imports until the owning module exposes a
stable replacement and all in-tree callers have migrated
- remove deep implementation re-export modules once callers can use module-owned
public APIs or kernel capabilities
- preserve migration/table compatibility for already-created development and
release databases
- document remaining compatibility surfaces here and in the owning module README
- reject new cross-module imports that bypass manifests, capabilities, events,
or public module APIs
## Stable Kernel Contracts
The following contracts are the baseline API that modules can rely on:
@@ -71,9 +91,15 @@ The following contracts are the baseline API that modules can rely on:
- WebUI module contribution contract
- navigation metadata contract
- command/event envelope contract
- policy decision and source provenance contract in `govoplan_core.core.policy`
Changes to these contracts must be versioned or accompanied by compatibility shims.
This list is the Milestone A kernel-contract freeze baseline. New module work
may extend the kernel by adding explicit contracts, but existing contracts must
remain source-compatible through the 0.1.x split line unless a migration shim
and deprecation note are provided.
Known access-related capability names are defined in
`govoplan_core.core.access`, including:
@@ -99,22 +125,23 @@ access/tenant ORM models when they need labels, group membership, default
access provisioning, counts, audit actor labels, or tenant metadata.
FastAPI route dependencies for authenticated endpoints are access-owned and
published from `govoplan_access.backend.auth.dependencies`. Routers may import
that dependency module directly until a more generic request-principal adapter
exists; they must not import access ORM models or other access implementation
internals.
published from `govoplan_access.auth`. Routers may import that public API for
`ApiPrincipal`, `get_api_principal`, `has_scope`, `require_scope`, and
`require_any_scope`; they must not import access ORM models or
`govoplan_access.backend.*` implementation internals.
Current live table ownership:
- `govoplan-tenancy`: `tenants`
- `govoplan-access`: `accounts`, `users`, `groups`, `roles`,
`system_role_assignments`, `user_group_memberships`,
`user_role_assignments`, `group_role_assignments`, `api_keys`,
`auth_sessions`
- `govoplan-admin`: `governance_templates`,
`governance_template_assignments`
- `govoplan-tenancy`: `tenancy_tenants`
- `govoplan-access`: `access_accounts`, `access_users`, `access_groups`,
`access_roles`, `access_system_role_assignments`,
`access_user_group_memberships`, `access_user_role_assignments`,
`access_group_role_assignments`, `access_api_keys`,
`access_auth_sessions`
- `govoplan-admin`: `admin_governance_templates`,
`admin_governance_template_assignments`
- `govoplan-audit`: `audit_log`
- `govoplan-core`: `system_settings`
- `govoplan-core`: `core_system_settings`
Current admin route ownership follows the same boundary: access contributes
users, groups, roles, system accounts/roles, auth, sessions, and API-key
@@ -143,6 +170,183 @@ campaign file-share access, and core retention can call campaign-owned cleanup
logic without importing campaign ORM models. Keep these contracts small
DTO/protocol surfaces and register concrete behavior from the owning module.
## API Efficiency Contracts
GovOPlaN uses conditional GET and delta collections to reduce reload cost
without giving every module a custom synchronization format.
### Conditional GET
Core applies conditional GET handling centrally for successful JSON `GET`
responses:
- Responses receive a weak `ETag` based on the serialized JSON body.
- Responses are marked `Cache-Control: private, no-cache`.
- Responses vary by `Authorization`, `Cookie`, `X-API-Key`, and
`Accept-Language`.
- Matching `If-None-Match` requests return `304 Not Modified` without a body.
- Responses with `Set-Cookie`, `Content-Disposition`, `Content-Encoding`, a
non-JSON content type, a non-200 status, or `Cache-Control: no-store` are not
converted.
The WebUI `apiFetch` client keeps an in-memory conditional cache for reusable
safe requests. It sends `If-None-Match` after an endpoint has returned an ETag,
returns the cached payload on `304`, and clears the cache generation after
unsafe methods.
This avoids retransmitting unchanged snapshots. It does not identify which row
changed inside a collection.
### Delta Collections
Collection endpoints that can expose row-level changes should use the shared
delta contract instead of inventing module-specific formats.
Core provides `core_change_sequence` as the shared monotonic change sequence.
Modules record append-only entries in the same database transaction as the
resource write. Watermarks are encoded as `seq:<number>` and should be treated
as opaque by clients.
Backend shape:
```json
{
"items": [],
"deleted": [],
"watermark": "opaque-next-watermark",
"has_more": false,
"full": false
}
```
Fields:
- `items`: changed or current items since the requested watermark.
- `deleted`: deleted item markers with at least `id`, and optionally
`resource_type`, `revision`, and `deleted_at`.
- `watermark`: opaque value the client sends as `since` on the next request.
- `has_more`: true when the client should request the next page with the
returned watermark.
- `full`: true when the response is a full snapshot rather than an incremental
delta.
Section-level settings endpoints use the same contract but replace `items`
with:
- `item`: the full settings object when `full: true`.
- `sections`: a map of changed settings sections when `full: false`.
- `changed_sections`: ordered section identifiers the client can merge into its
local settings object.
Recommended query parameters:
- `since`: opaque previous watermark. If omitted or expired, return a full
snapshot with `full: true`.
- `limit`: maximum number of changed items plus deleted markers.
- `include_deleted`: whether deleted markers should be returned.
- `cursor`: opaque keyset cursor for table pages where offset shifts would make
row-level merging unsafe.
Modules should record changes with:
- `module_id`: the owning module, for example `files`.
- `collection`: the delta collection, for example `files.assets`.
- `resource_type`: stable row kind, for example `file` or `folder`.
- `resource_id`: stable resource identifier.
- `operation`: `created`, `updated`, or `deleted`.
- `tenant_id`: tenant scope when the change is tenant-owned.
- `payload`: small, non-secret routing metadata that helps determine whether a
tombstone belongs to the requested view.
Sequence retention is explicit. Cleanup jobs must call
`prune_sequence_entries(...)` rather than deleting `core_change_sequence` rows
directly. Pruning records a retention floor per module, collection, and tenant
scope. Endpoints compare incoming watermarks with that floor; a watermark older
than the floor is not safe for incremental replay, so the endpoint must return a
full snapshot with `full: true`. A first-use `seq:0` watermark remains valid
until such a floor exists, even if unrelated collections have advanced the
global sequence.
### Cursor/Keyset Pages
Offset pagination remains supported for compatibility and for first page loads,
but it is not safe as the merge anchor for row-level deltas on page 2 and later.
When a delta-capable table can be paged beyond the first page, the endpoint
should expose keyset cursors:
- Core provides `encode_keyset_cursor`, `decode_keyset_cursor`, and
`keyset_query_fingerprint` in `govoplan_core.core.pagination`.
- Cursors are opaque to clients and contain the endpoint scope, query
fingerprint, and last-row keyset values.
- The fingerprint must include every query input that changes membership or
order: scope, tenant, page size, sort column, sort direction, and filters.
- Reusing a cursor with different sort or filter parameters must fail with a
client error rather than returning a mismatched slice.
- Responses may still include `page`, `page_size`, `pages`, and `total` for
existing UI components, but `cursor` identifies the current slice and
`next_cursor` is the safe anchor for the next slice.
- The first visit to an arbitrary page can use offset compatibility. The
response should include the start cursor for that page so later reloads and
delta requests use keyset semantics.
Concrete consumers:
- `GET /api/v1/files/delta`: without `since`, returns the current files/folders
snapshot for the requested owner/campaign scope. With `since=seq:<number>`,
returns changed files, changed folders, and tombstones for resources that
left the current view.
- `GET /api/v1/campaigns/delta`: returns accessible campaign rows and campaign
tombstones when ownership, sharing, or soft deletion removes a campaign from
the current list.
- `GET /api/v1/campaigns/{campaign_id}/workspace/delta`: returns a workspace
snapshot first, then changed campaign/version metadata and optional summary
refreshes when version, job, issue, or delivery-attempt changes invalidate the
workspace view.
- `GET /api/v1/campaigns/{campaign_id}/jobs/delta`: returns a paginated job
table snapshot first, then stable row deltas for cursor-backed job pages.
The job list also supports offset compatibility for first visits to a page and
returns `cursor`/`next_cursor` for stable reloads. Filtered, created, deleted,
or stale-watermark requests fall back to a full page snapshot when pagination
membership can shift.
- `GET /api/v1/admin/users/delta`, `/groups/delta`, `/roles/delta`,
`/system/roles/delta`, `/system/accounts/delta`, and `/api-keys/delta`:
return access administration row deltas with tombstones where rows leave the
visible view.
- `GET /api/v1/admin/system/settings/delta`: returns section deltas for system
defaults, tenant capability flags, language packages, privacy retention
policy, maintenance mode, and raw settings.
- `GET /api/v1/admin/tenant/settings/delta`: returns tenant-local setting
sections and also reports language-section changes when system language
packages or enabled language codes change.
- `GET /api/v1/admin/configuration-changes/delta`: returns changed
configuration requests and history records.
- `GET /api/v1/admin/audit` and `/api/v1/admin/audit/delta`: return append-only
audit events using the same scope, sort, and filter query parameters. The list
supports offset compatibility plus `cursor`/`next_cursor` keyset paging; the
delta endpoint can replay changes against a cursor-backed slice.
- `GET /api/v1/mail/settings/delta`: returns mail profile row deltas plus the
current scoped mail profile policy when profile-policy dependencies changed.
The WebUI consumes this for system, tenant, user, group, and campaign mail
settings panels.
- `GET /api/v1/files/connectors/settings/delta`: returns file connector
profile, credential, connector-space, and scoped connector-policy deltas.
Credential changes also include referencing profiles because profile rows
display credential-derived state.
Open retrofit scope:
- Additional module-specific settings pages should expose section deltas as
their settings APIs stabilize. Remaining likely candidates are future
booking/resource configuration pages and settings pages introduced by new
modules.
- Remaining high-volume tables should adopt the cursor/keyset contract before
enabling arbitrary-page row deltas. Current rollout follow-ups:
`govoplan-files#22` for large file-space server windows,
`govoplan-mail#9` for provider-aware mailbox message cursors,
`govoplan-calendar#7` for event-window deltas,
`govoplan-tenancy#1` for tenant administration row deltas, and
`govoplan-admin#2` for governance/module-operation list deltas.
## Module Responsibilities
A module owns one bounded feature area. A module can include both backend and WebUI code in the same repository so feature behavior and frontend integration evolve together.
@@ -195,6 +399,18 @@ NavItem(
)
```
Core validates manifest shape when the platform registry is built. The current
supported manifest contract version is `1`, and frontend asset manifests use
contract version `1`. Registry validation rejects unsupported contract versions,
invalid module ids, duplicate dependency declarations, self-dependencies,
mismatched migration/frontend metadata, invalid frontend package names, and
frontend/nav routes that do not declare usable paths and labels.
Backend route contributions are also validated before they are mounted. Startup
routers and live module activation fail fast if two routers register the same
HTTP method and path. That keeps OpenAPI output and FastAPI route order from
silently masking a module collision.
## Database And Migrations
Core owns the database/session lifecycle. Modules access the database through core session dependencies and register their models/migrations through their manifest.
@@ -209,6 +425,10 @@ Rules:
- Optional module migrations may create multiple Alembic heads. Verification
should compare the database heads to the configured script heads instead of
assuming one linear revision when multiple modules are enabled.
- Treat migrations as release artifacts. Unreleased migrations may be squashed
or rewritten before a stable release; released revision IDs are immutable
once an installation may have recorded them. Each stable release records its
public migration heads in `docs/migration-release-baselines.json`.
## Install, Uninstall, And Catalogs
@@ -290,6 +510,31 @@ capabilities with `usePlatformUiCapabilities("admin.sections")`, filters them
by `anyOf`/`allOf`, and renders them without importing the contributing module's
components directly.
The configurable dashboard follows the same pattern. Core contributes only a
minimal `/dashboard` fallback when no `dashboard` WebUI module is active. The
`govoplan-dashboard` module owns the real `/dashboard` route and collects
widgets exposed through the `dashboard.widgets` capability:
```ts
const dashboardWidgets: DashboardWidgetsUiCapability = {
widgets: [
{
id: "ops.health",
title: "Operations health",
moduleId: "ops",
defaultSize: "wide",
anyOf: ["ops:operations:read"],
render: ({ settings, refreshKey }) => createElement(OpsHealthWidget, { settings, refreshKey })
}
]
};
```
Dashboard widgets are module contributions, not cross-module imports. A widget
may render components from its own module and core components only. The
dashboard module is responsible for layout, visibility, refresh context, and
future server-side layout persistence.
## Icon Rules
Icons are resolved centrally by core.
@@ -354,14 +599,221 @@ The repository includes `scripts/check_dependency_boundaries.py`. It enforces th
- access source may not import files/mail/campaign internals
- feature modules may not import access implementation internals
- feature modules may not add new direct imports of sibling feature modules
- FastAPI routers may import the published
`govoplan_access.backend.auth.dependencies` dependency API
- FastAPI routers may import the published `govoplan_access.auth` dependency API
- the transitional allowlist is expected to stay empty
Any future exception is extraction debt and must be temporary, documented in the
script with a reason, and removed when a capability/API/event contract replaces
it.
## Boundary Decision Register
These durable decisions close older exploratory core issues. Implementation
work should live in the owning module repositories once a boundary is clear.
Decision principles:
- Prefer connector-first when an external specialist system is likely to remain
the system of record.
- Create a native module only when GovOPlaN must own domain semantics,
permissions, audit, retention, configuration-package fragments, or workflow
state.
- Keep optional behavior behind core-mediated capabilities, events, DTOs, route
contributions, and UI contribution points.
- Do not create repositories just because a possible product area exists.
### Templates And Reporting
Tracking: `govoplan-core#190`, `govoplan-templates#1`,
`govoplan-reporting#1`.
Decision: templates and reporting are separate modules.
`govoplan-templates` owns:
- reusable renderable templates for letters, permits, emails, forms, reports,
certificates, and notices
- template versioning, merge-field declarations, rendering profiles, output
format choices, and preview contracts
- template package fragments that other modules can reference
`govoplan-reporting` owns:
- report definitions, data selection, dashboards, BI views, scheduled outputs,
and export targets
- report permissions, report execution history, generated report evidence, and
report-specific retention inputs
- downstream export handoff to files, dataflow, connectors, or publication
surfaces
Boundary:
- Templates do not own data selection, aggregation, scheduling, or BI semantics.
- Reporting may call template rendering through a capability when a formatted
report output is needed.
- Campaign, mail, files, workflow, and cases use templates/reporting through
capabilities and DTOs, never direct imports.
### Sources, RSS, Datasources, And Dataflow
Tracking: `govoplan-core#192`, `govoplan-core#197`,
`govoplan-core#198`, `govoplan-connectors#3`,
`govoplan-connectors#4`.
Decision: do not create `govoplan-datasources` or `govoplan-dataflow` until a
first executable use case proves that connector/reporting/workflow ownership is
too narrow.
First slice:
- `govoplan-connectors` owns RSS/Atom consume/emit connector profiles,
connector health, external references, source lifecycle metadata, and
source/publish capability boundaries.
- `govoplan-files` owns file-backed governed locations and uploaded/stored file
evidence.
- `govoplan-reporting` owns report/data views and scheduled outputs.
- `govoplan-workflow` owns process state, approvals, scheduling of process
steps, and human review.
Future `govoplan-datasources` is justified when GovOPlaN needs a broad source
catalogue for SQL databases, CSV/Excel files, APIs, RSS feeds, uploaded files,
and governed file locations with shared ownership, credentials, schema
discovery, refresh cadence, provenance, and permission boundaries.
Future `govoplan-dataflow` is justified when GovOPlaN needs first-class
pipelines for ingestion, transformation, validation, scheduling, lineage,
publication, audit events, reruns, and source-to-source workflows.
Monthly extraction/transformation work should start as a configuration package
and module collaboration across connectors, files, workflow, reporting, and
possibly templates. Create datasources/dataflow repositories only after that
package exposes repeated contracts that do not belong to an existing module.
### Calendar, Scheduling, And Appointments
Tracking: `govoplan-core#193`, `govoplan-calendar#1`,
`govoplan-calendar#2`, `govoplan-scheduling#1`,
`govoplan-appointments#1`.
Decision: use three separate modules.
`govoplan-calendar` owns:
- calendar collections, events, recurrence, availability/free-busy, resources,
iCalendar import/export, CalDAV/Open-Xchange-style calendar adapters, and
calendar WebUI surfaces
`govoplan-scheduling` owns:
- Terminfindung, meeting-time polls, participant availability collection,
candidate-slot ranking, conflict explanations, reminders, and the handoff
from a selected slot to calendar/appointment/workflow modules
`govoplan-appointments` owns:
- Terminbuchung/fixed-slot appointment booking, appointment types, booking
rules, capacity, cancellation/no-show state, public/internal booking flows,
and appointment evidence
Boundary:
- Calendar provides time primitives and external calendar integration.
- Scheduling chooses a suitable time.
- Appointments owns booked appointment workflows and public/internal booking
semantics.
- Mail and notifications deliver invitations/reminders through capabilities.
### Forms And Workflow Handoff
Tracking: `govoplan-core#194`, `govoplan-forms#1`.
Decision: forms are a reusable module boundary, with runtime behavior separated
from workflow semantics.
`govoplan-forms` owns:
- form definitions, schemas, validation rules, field visibility rules,
localization, versioning, admin editing, and reusable form package fragments
`govoplan-forms-runtime` owns, when implemented:
- public/internal submissions, drafts, submitted values, validation evidence,
attachment references, submission receipts, and handoff events
Boundary:
- Forms do not own cases, workflow transitions, tasks, or portal identity.
- Workflow/cases consume form submission events and evidence references.
- Files owns uploaded file storage and file permissions.
- Reporting/dataflow may consume submitted data through governed DTOs or
source lifecycle contracts.
### OpenDesk Integration Profile
Tracking: `govoplan-core#195`, `govoplan-connectors#5`,
`govoplan-idm#1`, `govoplan-mail#5`, `govoplan-calendar#2`,
`govoplan-connectors#1`.
Decision: OpenDesk is an integration profile, not a monolithic module.
Ownership:
- identity: `govoplan-idm` plus `govoplan-access`
- mail/groupware: `govoplan-mail`
- calendar: `govoplan-calendar`
- files/documents: `govoplan-files` and later `govoplan-dms`
- projects/tasks: `govoplan-connectors` OpenProject connector first
- inventory/health/profile diagnostics: `govoplan-connectors`
The OpenDesk profile should describe required connector profiles, shared
identity assumptions, health checks, and optional module combinations. It must
not create direct module-to-module imports.
### Project Management And OpenProject
Tracking: `govoplan-core#196`, `govoplan-connectors#1`.
Decision: connector-first. Do not create a native `govoplan-projects` module
yet.
OpenProject integration belongs in `govoplan-connectors` first:
- profile test
- project and work-package lookup
- external-reference storage
- selected publish/synchronize capabilities for tasks, workflow, or cases
A native project module is justified only if GovOPlaN needs to own project
semantics beyond cases, tasks, workflow, appointments, documents, and reporting,
for example portfolios, project budgets, project-level resource planning, or
governed project records that cannot remain in OpenProject.
### Public-Sector Integration Landscape
Tracking: `govoplan-core#186`, `govoplan-core#215`,
`govoplan-connectors#2`, `govoplan-connectors#3`.
Decision: core owns strategy and routing; connectors owns executable
integration catalogue entries and operator inventory.
Core documents:
- product-level integration strategy
- native-vs-connector decisions
- owning module routing
- roadmap sequencing
`govoplan-connectors` owns:
- connector entry schema
- external system catalogue
- connector profiles and diagnostics
- source consume/publish lifecycle
- external references
When a target needs executable behavior, create the implementation issue in the
owning module repository and keep only cross-module decisions in core.
## Module Lifecycle
Core exposes the installed module catalog through the admin API and WebUI. The
@@ -565,6 +1017,8 @@ directory with:
- `GOVOPLAN_INSTALLER_RUN_DIR`
- `GOVOPLAN_DATABASE_URL`
- `GOVOPLAN_DATABASE_URL_PGTOOLS` for PostgreSQL URLs converted to the
`postgresql://` form expected by `pg_dump`, `pg_restore`, and `psql`
- `GOVOPLAN_DATABASE_BACKUP_PATH`
- `GOVOPLAN_DATABASE_BACKUP_METADATA`
@@ -631,10 +1085,20 @@ Backend verification from core:
```bash
cd /mnt/DATA/git/govoplan-core
./.venv/bin/python -m compileall src/govoplan_core ../govoplan-access/src/govoplan_access ../govoplan-admin/src/govoplan_admin ../govoplan-tenancy/src/govoplan_tenancy ../govoplan-policy/src/govoplan_policy ../govoplan-audit/src/govoplan_audit ../govoplan-files/src/govoplan_files ../govoplan-mail/src/govoplan_mail ../govoplan-campaign/src/govoplan_campaign
./.venv/bin/python -m compileall src/govoplan_core ../govoplan-access/src/govoplan_access ../govoplan-admin/src/govoplan_admin ../govoplan-tenancy/src/govoplan_tenancy ../govoplan-policy/src/govoplan_policy ../govoplan-audit/src/govoplan_audit ../govoplan-dashboard/src/govoplan_dashboard ../govoplan-files/src/govoplan_files ../govoplan-mail/src/govoplan_mail ../govoplan-campaign/src/govoplan_campaign
./.venv/bin/python scripts/check_dependency_boundaries.py
```
`scripts/check-focused.sh` runs npm with an isolated temporary npm user config
so developer-local npm settings do not create release-check warning noise.
Focused module contract and permutation verification:
```bash
cd /mnt/DATA/git/govoplan-core
bash scripts/check-module-matrix.sh
```
Core WebUI host verification:
```bash