# APIX MVP — Work Log **Goal:** Deployable, publicly queryable APIX registry PoC on Hetzner by end of 2026. **Constraint:** Solo development, LLM-assisted. Security hygiene (not hardening). Speed over completeness. **Success criterion:** A reviewer can open a public URL, run a capability query, see a result from a registered service, and confirm the Spider has checked it. --- ## Status Legend | Symbol | Meaning | |---|---| | `[ ]` | Not started | | `[~]` | In progress | | `[x]` | Done | | `[!]` | Blocked / decision needed | --- ## Arc42 Documentation Deliverables ### 1. Introduction and Goals → `docs/arc42/01-introduction-goals.md` | # | Deliverable | Status | |---|---|---| | D-01 | MVP goal statement (what must be provable at the end) | `[ ]` | | D-02 | Quality goals table (top 3–5, measurable) | `[ ]` | | D-03 | Stakeholder table (STF reviewer, agent developer, service registrant, BSF) | `[ ]` | | D-04 | Explicit out-of-scope list for MVP (billing, full trust model, multi-region) | `[ ]` | --- ### 2. Architecture Constraints → `docs/arc42/02-constraints.md` | # | Deliverable | Status | |---|---|---| | D-05 | Technical constraints (Hetzner, Docker Compose, Python, PostgreSQL, open source stack) | `[ ]` | | D-06 | Organisational constraints (solo dev, LLM-assisted, public GitHub repo required) | `[ ]` | | D-07 | Regulatory constraints (HTTPS mandatory, GDPR-lite: no PII stored beyond registrant email) | `[ ]` | | D-08 | Convention constraints (HATEOAS API style, IETF Internet-Draft alignment) | `[ ]` | --- ### 3. Context and Scope → `docs/arc42/03-context-scope.md` | # | Deliverable | Status | |---|---|---| | D-09 | System context diagram — PlantUML, external actors: Agent, Service Registrant, Spider, Admin | `[ ]` | | D-10 | External interface table (what each actor sends/receives) | `[ ]` | | D-11 | Technical context diagram — PlantUML, network boundary (Caddy → API → DB, Spider → external services) | `[ ]` | --- ### 4. Solution Strategy → `docs/arc42/04-solution-strategy.md` | # | Deliverable | Status | |---|---|---| | D-12 | Tech stack decision table with rationale (FastAPI, PostgreSQL JSONB, Caddy, HTMX) | `[ ]` | | D-13 | Architectural pattern decisions (REST + HATEOAS, async Spider, single-process portal) | `[ ]` | | D-14 | Quality goal → architecture decision mapping | `[ ]` | | D-15 | MVP shortcuts explicitly listed (manual O-level assignment, no billing, no rate limiting beyond Caddy) | `[ ]` | --- ### 5. Building Block View → `docs/arc42/05-building-blocks.md` | # | Deliverable | Status | |---|---|---| | D-16 | Level 1 diagram — PlantUML: API service, Spider service, Portal, PostgreSQL, Caddy | `[ ]` | | D-17 | Level 2 diagram — API internals: router, service layer, BSM validator, DB adapter | `[ ]` | | D-18 | Level 2 diagram — Spider internals: scheduler, fetcher, liveness evaluator, DB writer | `[ ]` | | D-19 | Component responsibility table (one sentence per component) | `[ ]` | --- ### 6. Runtime View → `docs/arc42/06-runtime-view.md` | # | Deliverable | Status | |---|---|---| | D-20 | Scenario 1: Agent queries registry by capability — PlantUML sequence diagram | `[ ]` | | D-21 | Scenario 2: Service registrant submits BSM via portal — PlantUML sequence diagram | `[ ]` | | D-22 | Scenario 3: Spider runs liveness check and updates service status — PlantUML sequence diagram | `[ ]` | | D-23 | Scenario 4: Agent navigates index via HATEOAS links — PlantUML sequence diagram | `[ ]` | --- ### 7. Deployment View → `docs/arc42/07-deployment-view.md` | # | Deliverable | Status | |---|---|---| | D-24 | Hetzner deployment diagram — PlantUML: VPS, Docker Compose services, Caddy, volumes | `[ ]` | | D-25 | Environment table (dev / staging / prod differences) | `[ ]` | | D-26 | Backup and restore strategy (pg_dump, Hetzner volume snapshot) | `[ ]` | | D-27 | Domain and DNS setup notes | `[ ]` | --- ### 8. Crosscutting Concepts → `docs/arc42/08-crosscutting-concepts.md` | # | Deliverable | Status | |---|---|---| | D-28 | Logging concept (structured JSON, levels, what is logged per component) | `[ ]` | | D-29 | Error handling concept (HTTP error codes, error response schema) | `[ ]` | | D-30 | Security hygiene concept (HTTPS via Caddy, API key on write endpoints, rate limiting, no PII logging) | `[ ]` | | D-31 | BSM validation concept (schema version, required fields, validation error format) | `[ ]` | | D-32 | Liveness check concept (what "live" means, check frequency, status transition rules) | `[ ]` | | D-33 | Idempotency concept (re-registration behaviour, Spider re-check behaviour) | `[ ]` | | D-33a | i18n concept (locale resolution order, @MessageBundle keys, help content localisation, language switcher) | `[ ]` | --- ### 9. Architecture Decisions → `docs/arc42/09-architecture-decisions.md` | # | ADR | Status | |---|---|---| | D-34 | ADR-001: Python + FastAPI over Node/Go — rationale: AI ecosystem, speed, solo dev | `[ ]` | | D-35 | ADR-002: PostgreSQL + JSONB over MongoDB — rationale: relational integrity for registry + flexible BSM payload | `[ ]` | | D-36 | ADR-003: Caddy over nginx/traefik — rationale: auto-TLS, zero config, solo maintainability | `[ ]` | | D-37 | ADR-004: HTMX + Jinja2 over React/Vue — rationale: no JS build pipeline, speed, portal is admin-grade not consumer-grade | `[ ]` | | D-38 | ADR-005: Automated O-1/O-2/O-3 verification in MVP — DNS + GLEIF + OpenCorporates + HTTP hygiene checks; O-4/O-5 post-MVP | `[ ]` | | D-39 | ADR-006: Two-VPS Hetzner deployment — apix-app (Swarm) + apix-gitea (Gitea + CI) | `[ ]` | | D-47 | ADR-007: Register verification APIs (GLEIF, OpenCorporates, EU Sanctions) as reference APIX entries | `[ ]` | | D-48 | ADR-009: Maven multi-module with separated Spider module | `[ ]` | | D-49 | ADR-010: Self-hosted Gitea primary; GitHub push mirror | `[ ]` | | D-50 | ADR-011: Docker Swarm single-node for zero-downtime production deployment | `[ ]` | | D-51 | ADR-012: Three-stage CI/CD pipeline (fast / native build / deploy) | `[ ]` | | D-52 | ADR-013: Server-side i18n via Quarkus @MessageBundle; EN + DE; cookie + Accept-Language locale resolution | `[ ]` | | D-53 | ADR-014: Client-side help overlay engine; server-rendered locale-aware tour content; 5 tours (register, search, trust, admin, agent-setup) | `[ ]` | --- ### 10. Quality Requirements → `docs/arc42/10-quality-requirements.md` | # | Deliverable | Status | |---|---|---| | D-40 | Quality tree (functionality, reliability, security hygiene, operability) | `[ ]` | | D-41 | Quality scenarios table (stimulus → response → measurable outcome) | `[ ]` | | D-42 | MVP acceptance criteria (what "done" looks like — the STF reviewer test) | `[ ]` | --- ### 11. Risks and Technical Debt → `docs/arc42/11-risks-technical-debt.md` | # | Deliverable | Status | |---|---|---| | D-43 | Risk register (chicken-and-egg, big tech competition, single point of failure, solo bus factor) | `[ ]` | | D-44 | Technical debt log (MVP shortcuts accepted, with explicit exit path for each) | `[ ]` | | D-45 | Mitigation actions per risk | `[ ]` | --- ### 12. Glossary → `docs/arc42/12-glossary.md` | # | Deliverable | Status | |---|---|---| | D-46 | Glossary: APIX, BSM, Spider, O-level, S-level, Liveness, AE, HATEOAS, DC-1 | `[ ]` | --- ## Code Deliverables **Stack:** Java 21 + Quarkus 3.x, Maven multi-module. Five modules: two plain Java libraries (`apix-common`, `apix-verification`) and three independently deployable Quarkus apps (`apix-registry`, `apix-spider`, `apix-portal`). Spider runs as its own process — separate module, separate container, separate lifecycle. Build tool: Maven with parent POM as BOM. Tests: plain JUnit 5 for library modules; JUnit 5 + `@QuarkusTest` + RestAssured + WireMock for Quarkus modules. Source root per module: `/src/main/java/org/botstandards/apix//` --- ### Parent POM — `pom.xml` | # | Deliverable | Status | |---|---|---| | C-00 | `pom.xml` — parent; imports Quarkus BOM; declares all five modules; manages `maven-compiler-plugin` (Java 21 release); `quarkus-maven-plugin` config inherited by Quarkus modules | `[ ]` | --- ### apix-common — Shared Library (plain Java 21) No Quarkus dependency. Shared enums and DTOs used by all modules. | # | Deliverable | Status | |---|---|---| | C-01 | `OLevel.java` — enum: UNVERIFIED, IDENTITY_VERIFIED, LEGAL_ENTITY_VERIFIED, HYGIENE_VERIFIED, OPERATIONALLY_VERIFIED, AUDITED | `[ ]` | | C-02 | `LivenessStatus.java` — enum: PENDING, LIVE, DEGRADED, UNREACHABLE | `[ ]` | | C-03 | `BsmPayload.java` — Java record; all BSM fields per Internet-Draft; Bean Validation annotations (`@NotBlank`, `@Valid`, `@URL`) | `[ ]` | | C-04 | `ServiceSummaryDto.java` — Java record; fields exposed in search results (id, name, capabilities, olevel, liveness_status, endpoint, last_checked_at) | `[ ]` | | C-05 | `VerificationResult.java` — Java record: olevel achieved, step that blocked (if any), message | `[ ]` | --- ### apix-verification — Verification Library (plain Java 21) No Quarkus dependency. Uses `java.net.http.HttpClient` and `dnsjava`. All classes are plain CDI-free POJOs — fully testable with plain JUnit. Depends on `apix-common`. | # | Deliverable | What it does | O-level unlocked | |---|---|---|---| | C-06 | `O1DnsVerifier.java` | DNS TXT record lookup (dnsjava); business email MX check | O-1 | | C-07 | `O2GleifVerifier.java` | GLEIF REST API via `java.net.http.HttpClient`; LEI lookup by company name + jurisdiction | O-2 | | C-08 | `O2OpenCorporatesVerifier.java` | OpenCorporates REST API fallback for registrants without LEI; covers 130+ jurisdictions | O-2 (fallback) | | C-09 | `O3HygieneVerifier.java` | HTTP fetch of `/.well-known/security.txt`; DNS DMARC + SPF check; policy URL reachability | O-3 | | C-10 | `SanctionsScreener.java` | Screens name + jurisdiction against locally cached OFAC/EU/UN lists (CSV/XML read from file path) | Pre-condition for O-2 | | C-11 | `VerificationPipeline.java` | Orchestrates O-1 → sanctions → O-2 → O-3 in sequence; returns `VerificationResult`; no I/O side effects (caller persists result) | All | | C-12 | `VerificationConfig.java` | Plain POJO: gleifApiUrl, openCorporatesApiKey, sanctionsCachePath — injected by caller (Quarkus `@ConfigProperty` in registry) | — | --- ### apix-registry — REST API (Quarkus 3.x app) Depends on `apix-common` + `apix-verification`. Owns the database schema (runs Liquibase at startup). Quarkus extensions: RESTEasy Reactive, Hibernate ORM + Panache, PostgreSQL JDBC, Liquibase, SmallRye Health, Quarkus Security. #### Resource layer | # | Deliverable | Status | |---|---|---| | C-13 | `resource/IndexResource.java` — `GET /` HATEOAS root; navigation links JSON | `[ ]` | | C-14 | `resource/ServiceResource.java` — `GET /services` (capability search + filters), `GET /services/{id}` | `[ ]` | | C-15 | `resource/RegisterResource.java` — `POST /register` (API-key protected); triggers `VerificationOrchestrator` asynchronously | `[ ]` | #### Service layer | # | Deliverable | Status | |---|---|---| | C-16 | `service/RegistryService.java` — register (UPSERT on endpoint URL), search by capability (JPQL + JSONB), dedup | `[ ]` | | C-17 | `service/VerificationOrchestrator.java` — CDI bean; injects `VerificationConfig` from Quarkus `@ConfigProperty`; calls `VerificationPipeline`; persists `VerificationResult` to `ServiceRecord`; fires admin notification event if manual step needed | `[ ]` | #### Model + repository | # | Deliverable | Status | |---|---|---| | C-18 | `model/ServiceRecord.java` — Panache entity; `services` table; JSONB via `@JdbcTypeCode(SqlTypes.JSON)` for `bsm_payload` | `[ ]` | | C-19 | `repository/ServiceRepository.java` — capability search query; upsert; O-level update | `[ ]` | #### Liquibase migrations | # | Deliverable | Status | |---|---|---| | C-20 | `resources/db/changelog/db.changelog-master.xml` | `[ ]` | | C-21 | `changes/001-initial-schema.xml` — `services` table: id, endpoint_url (unique), bsm_payload (JSONB), olevel, slevel, liveness_status, registered_at | `[ ]` | | C-22 | `changes/002-verification-columns.xml` — verification_status, olevel_checked_at, sanctions_cleared, gleif_lei | `[ ]` | | C-23 | `changes/003-liveness-metrics.xml` — last_checked_at, uptime_30d_percent, avg_response_ms, consecutive_failures | `[ ]` | #### Configuration | # | Deliverable | Status | |---|---|---| | C-24 | `resources/application.properties` — datasource, Liquibase enabled, port 8180, GLEIF URL, OpenCorporates key, sanctions cache path, log level | `[ ]` | --- ### apix-spider — Liveness Scheduler (Quarkus 3.x app) Separate module, separate container, separate lifecycle. Depends on `apix-common`. Connects to same PostgreSQL DB; Liquibase **disabled** (`quarkus.liquibase.migrate-at-start=false`). Quarkus extensions: Quarkus Scheduler, Hibernate ORM + Panache, PostgreSQL JDBC, REST Client Reactive, SmallRye Health. | # | Deliverable | Status | |---|---|---| | C-25 | `SpiderScheduler.java` — `@Scheduled(every="${spider.interval:15m}")`; loads all active services; dispatches to `LivenessFetcher` via virtual thread pool | `[ ]` | | C-26 | `LivenessFetcher.java` — `@RestClient`; async HTTP GET with 5s timeout; `@RunOnVirtualThread` | `[ ]` | | C-27 | `LivenessEvaluator.java` — pure logic: HTTP status code + response time ms → `LivenessStatus`; no I/O; no Quarkus dependency | `[ ]` | | C-28 | `OpenApiParser.java` — fetch + parse OpenAPI spec; verify declared capabilities present | `[ ]` | | C-29 | `McpParser.java` — fetch + parse MCP spec URL | `[ ]` | | C-30 | `model/SpiderServiceView.java` — Panache entity (read/write subset of `services` table): endpoint_url, liveness_status, last_checked_at, uptime_30d_percent, avg_response_ms, consecutive_failures | `[ ]` | | C-31 | `repository/SpiderRepository.java` — load services due for check; write liveness result | `[ ]` | | C-32 | `resources/application.properties` — datasource, Liquibase disabled, scheduler interval, spider HTTP timeout, port 8082 (internal only) | `[ ]` | --- ### apix-portal — Web Portal (Quarkus 3.x app) Depends on `apix-common`. Calls `apix-registry` via REST Client — no direct DB access. Quarkus extensions: RESTEasy Reactive, Qute, REST Client Reactive, SmallRye Health. | # | Deliverable | Status | |---|---|---| | C-33 | `resource/PortalResource.java` — `GET /`, `/services/{id}`, `/search`, `/register`; delegates to `RegistryClient` | `[ ]` | | C-34 | `resource/AdminResource.java` — `GET /admin`, `POST /admin/olevel` (API-key protected) | `[ ]` | | C-35 | `client/RegistryClient.java` — `@RegisterRestClient`; two methods: `search(capability, page)` → `List`; `getDetail(id)` → `ServiceDetailDto` (full BSM payload + all registry metadata); typed REST client calling `apix-registry` | `[ ]` | | C-36 | `templates/index.html` (Qute, `@CheckedTemplate`) — registry stats, search box | `[ ]` | | C-37 | `templates/register.html` (Qute) — BSM registration form; HTMX inline validation | `[ ]` | | C-38 | `templates/service.html` (Qute) — human-targeted service detail page; see layout spec below | `[ ]` | | C-39 | `templates/search.html` (Qute) — capability search results, HTMX pagination | `[ ]` | | C-40 | `templates/admin.html` (Qute) — pending verifications, O-level assignment, reference registration flags | `[ ]` | | C-41 | `resources/META-INF/resources/style.css` — minimal CSS, no framework | `[ ]` | | C-42 | `resources/application.properties` — registry base URL, port 8081, log level | `[ ]` | #### service.html — human-readable service detail page (C-38 layout spec) A human visiting `/services/{id}` is evaluating whether to use this service. The page answers four questions in order: *Who is this? Can I trust them? What exactly does it do? How do I call it?* **Section 1 — Identity hero** - Service `name` (h1, prominent) - `description` (full text, not truncated) - Two inline trust chips: O-level badge (colored pill: grey=O-0, blue=O-1/O-2/O-3, green=O-4/O-5) + liveness dot (green/amber/red) — both visible above the fold without scrolling **Section 2 — Trust verification card** - O-level: large badge icon + level name (e.g. "Legal Entity Verified") + 2-sentence plain-English explanation (e.g. *"The provider's legal incorporation has been confirmed against the GLEIF global legal entity database. This means APIX has independently verified that the registrant is a real legal entity — not self-declared."*) + GLEIF LEI link if present - S-level: same pattern (badge + name + explanation) - "Verified on" date — relative ("3 days ago") with absolute date as tooltip; "Reference entry by BSF" badge if applicable (grey label "Registered by Bot Standards Foundation — operator not yet self-registered") **Section 3 — Liveness status card** - Colored status row: dot + label (LIVE / DEGRADED / UNREACHABLE / PENDING) - Uptime last 30 days: percentage bar + number (e.g. "98.4%") - Average response time: "142 ms" - Last checked: relative time ("8 minutes ago") - Check frequency note: "Automatically verified every 15 minutes by the APIX Spider" **Section 4 — Capabilities** - Each `capabilities[]` entry as a visual chip (tag-style) - If BSM includes a description per capability, show it beneath the chip **Section 5 — Pricing** (conditional; omit section if BSM has no pricing object) - Per-call price + currency + billing unit (e.g. "€0.02 / call") - Billing model if declared - "Pricing not declared — contact provider" if the BSM pricing object is absent **Section 6 — Contact & registration** - Registrant contact email (mailto link) - Registered since (human date) - BSM version **Section 7 — Integration** (`
` collapsible; closed by default) - Endpoint URL with one-click copy button - OpenAPI spec URL link + MCP spec URL link (if declared in BSM) - Minimal HTTP example snippet: ``` GET {endpoint} X-APIX-Caller: your-agent-id ``` - Link to machine-readable entry: `GET /api/services/{id}` (JSON) #### ServiceDetailViewModel — portal-internal view model | # | Deliverable | Status | |---|---|---| | C-65 | `model/ServiceDetailViewModel.java` — Java record; portal-internal (not in `apix-common`); carries: all BSM payload fields; `oLevelLabel` (e.g. "Legal Entity Verified"); `oLevelDescription` (2-sentence plain-English, locale-resolved from Messages); `oLevelColorClass` (CSS class: `trust-unverified`/`trust-basic`/`trust-strong`); same pattern for `sLevel*`; `livenessLabel`, `livenessColorClass`, `livenessExplanation`; `formattedUptime` ("98.4%"), `formattedAvgResponseMs` ("142 ms"); `registeredAtRelative`, `registeredAtIso`; `lastCheckedAtRelative`, `lastCheckedAtIso`; `gleifLeiUrl` (null or `https://search.gleif.org/#/record/{lei}`); `isReferenceEntry` boolean | `[ ]` | | C-66 | Update `PortalResource.java` — `GET /services/{id}`: call `RegistryClient.getDetail(id)`, build `ServiceDetailViewModel` via `ServiceDetailViewModelFactory`, pass to `service.html` template | `[ ]` | | C-67 | `service/ServiceDetailViewModelFactory.java` — CDI bean; receives `ServiceDetailDto` + `Locale` + injected `Messages`; resolves O-level description string by level index (keys: `service.oLevel.0.description` … `service.oLevel.5.description`); computes relative timestamps via `java.time`; builds `ServiceDetailViewModel` | `[ ]` | **O-level description message keys** (to be added to C-54 `Messages.java` + C-55/C-56 properties files): | Key | EN value | |---|---| | `service.oLevel.0.name` | Unverified | | `service.oLevel.0.description` | Self-declared only. No identity claims have been independently confirmed by APIX. Treat output with appropriate caution. | | `service.oLevel.1.name` | Identity Verified | | `service.oLevel.1.description` | The provider's domain ownership has been confirmed via DNS verification and their business email is reachable. APIX has confirmed this registrant controls the declared domain. | | `service.oLevel.2.name` | Legal Entity Verified | | `service.oLevel.2.description` | The provider's legal incorporation has been confirmed against the GLEIF global legal entity database or an authoritative company register. This is a real, registered legal entity — not self-declared. | | `service.oLevel.3.name` | Hygiene Verified | | `service.oLevel.3.description` | The provider publishes a security contact, enforces email authentication (DMARC/SPF), and maintains reachable legal documents. They meet baseline operational transparency standards. | | `service.oLevel.4.name` | Operationally Verified | | `service.oLevel.4.description` | An accredited APIX Verifier has assessed this provider's operational practices, incident response, and SLA track record. This level requires a human review process. | | `service.oLevel.5.name` | Audited | | `service.oLevel.5.description` | The provider holds a third-party security audit certificate (SOC 2 Type II or ISO 27001) confirmed by an APIX Accredited Verifier. The highest trust level available. | Same key pattern for `service.sLevel.*`. German equivalents go in `messages_de.properties`. #### i18n — server-side message bundles (Quarkus @MessageBundle) | # | Deliverable | Status | |---|---|---| | C-54 | `Messages.java` — `@MessageBundle` interface; one method per translatable string key; sections: `nav.*`, `home.*`, `register.*`, `search.*`, `service.*`, `admin.*`, `help.*`, `error.*` | `[ ]` | | C-55 | `resources/i18n/messages.properties` — English strings (all keys defined; this is the build-time default) | `[ ]` | | C-56 | `resources/i18n/messages_de.properties` — German strings (same key set as EN) | `[ ]` | | C-57 | `LocaleResolver.java` — CDI bean; reads `apix-locale` cookie first, then `Accept-Language` header, then falls back to `Locale.ENGLISH`; returns `java.util.Locale` | `[ ]` | | C-58 | `resource/LocaleResource.java` — `POST /locale`; validates `lang` param against `["en","de"]`; sets `apix-locale` cookie (HttpOnly, SameSite=Lax, path `/`); redirects to `Referer` | `[ ]` | #### Help system — overlay engine + tour content | # | Deliverable | Status | |---|---|---| | C-59 | `resources/META-INF/resources/help.js` — client-side tour engine; spotlight four-wing dimming (`help-dim-top/left/right/bottom`) + highlight ring; draggable tour card (header grip); progress dots; state indicator; page-help drawer (slide-in right); context filter (shows only tours whose `pages` includes current ``); reads `window.PAGE_TOURS` + `window.PAGE_HELP`; no external dependency | `[ ]` | | C-60 | `templates/layout.html` — Qute base layout extended by all portal pages; contains: nav bar with help button (?) and language switcher form; overlay HTML (4 wing divs + highlight ring + tour card shell + progress dots); help drawer HTML (guided tour list + page help section); `