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: <module>/src/main/java/org/botstandards/apix/<module-suffix>/
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<ServiceSummaryDto>; 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 (<details> 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:
- 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 <body data-page-id>); 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); <script src="/help.js">; all nav/chrome strings via {inject:msg.*} |
[ ] |
| C-61 |
model/TourDefinition.java + model/TourStep.java — Java records; TourDefinition: tourId, pages (list of page IDs), title, steps; TourStep: targetSelector (CSS selector), title, body, checkFn (optional JS function name for step validation) |
[ ] |
| C-62 |
service/HelpContentService.java — CDI bean; builds locale-resolved list of TourDefinition using injected Messages; serializes to JSON (Jackson); defines 5 MVP tours: tour-agent-setup (3 steps, home), tour-register (5 steps, register), tour-search (3 steps, search), tour-trust (4 steps, service detail), tour-admin (4 steps, admin) |
[ ] |
Tests
Tests live in each module's src/test/. Library module tests are plain JUnit (fast). Quarkus module tests use @QuarkusTest with @QuarkusTestResource for Testcontainers (PostgreSQL).
apix-verification tests (plain JUnit 5 + WireMock)
| # |
Deliverable |
Status |
| C-43 |
O1DnsVerifierTest — valid domain, missing TXT record, wrong value |
[ ] |
| C-44 |
O2GleifVerifierTest — LEI match, unknown LEI, timeout (WireMock HTTP server) |
[ ] |
| C-45 |
O3HygieneVerifierTest — security.txt present/absent, DMARC/SPF checks |
[ ] |
| C-46 |
SanctionsScreenerTest — match, no match, false positive (local CSV fixture) |
[ ] |
| C-47 |
VerificationPipelineTest — O-0 → O-3 happy path; blocked at O-2 (sanctions); O-3 failure preserves O-2 |
[ ] |
| C-48 |
LivenessEvaluatorTest — pure logic: 200+fast=LIVE, 200+slow=DEGRADED, 503=UNREACHABLE, timeout=UNREACHABLE |
[ ] |
apix-registry tests (@QuarkusTest + Testcontainers + RestAssured)
| # |
Deliverable |
Status |
| C-49 |
RegistryServiceTest — register happy path, duplicate UPSERT, invalid BSM rejected |
[ ] |
| C-50 |
ServiceResourceTest — capability search match/no-match/partial; HATEOAS root links present |
[ ] |
| C-51 |
RegisterResourceTest — valid registration 201, missing API key 401, invalid payload 400 |
[ ] |
apix-spider tests (@QuarkusTest + WireMock)
| # |
Deliverable |
Status |
| C-52 |
SpiderSchedulerTest — trigger one check cycle; verify liveness written to DB |
[ ] |
apix-portal tests (@QuarkusTest + WireMock for registry)
| # |
Deliverable |
Status |
| C-53 |
PortalResourceTest — homepage renders; search form submits; registration form renders |
[ ] |
| C-63 |
LocaleResolverTest — DE Accept-Language header → Locale.GERMAN; EN cookie overrides DE header → Locale.ENGLISH; absent header + absent cookie → Locale.ENGLISH default |
[ ] |
| C-64 |
HelpContentServiceTest — register page returns exactly 5 tour steps; home page tour excludes tour-admin; DE locale produces DE strings in tour title/body JSON; page filter returns only tours referencing current page ID |
[ ] |
| C-68 |
ServiceDetailViewModelFactoryTest — O-0 produces colorClass trust-unverified + EN description; O-2 with LEI → gleifLeiUrl non-null and correct; registeredAtRelative computed from fixed Instant; isReferenceEntry true when flag set; DE locale → DE description strings from properties |
[ ] |
Infrastructure Deliverables
Docker / Compose — infra/
| # |
Deliverable |
Status |
| I-01 |
docker-compose.yml — five services: registry (:8180), spider (:8082 internal), portal (:8081), db (postgres:16-alpine), caddy (:80/:443) |
[ ] |
| I-02 |
docker-compose.override.yml — dev overrides: JVM mode for all three Quarkus apps (quarkus dev); all ports exposed; no TLS; hot reload |
[ ] |
| I-03 |
Caddyfile — HTTPS auto-cert; /api/* → registry:8180; /* → portal:8081; spider has no public route |
[ ] |
| I-04 |
apix-registry/Dockerfile — multi-stage: GraalVM 21 Maven builder → UBI Minimal 8 runtime; non-root user; exposes 8180 |
[ ] |
| I-05 |
apix-spider/Dockerfile — multi-stage: GraalVM 21 Maven builder → UBI Minimal 8 runtime; non-root user; no exposed port (internal only) |
[ ] |
| I-06 |
apix-portal/Dockerfile — multi-stage: GraalVM 21 Maven builder → UBI Minimal 8 runtime; non-root user; exposes 8081 |
[ ] |
| I-07 |
.env.example — QUARKUS_DATASOURCE_JDBC_URL, API_KEY, GLEIF_API_URL, OPENCORPORATES_API_KEY, SANCTIONS_CACHE_PATH, SPIDER_INTERVAL, LOG_LEVEL, REGISTRY_BASE_URL (portal→registry) |
[ ] |
Hetzner — infra/hetzner/apix-app/ (APIX application VPS)
| # |
Deliverable |
Status |
| I-08 |
provision.sh — VPS bootstrap: Docker install + Swarm init (docker swarm init), firewall (80/443 only), swap, non-root user, Hetzner volume mount |
[ ] |
| I-09 |
backup.sh — pg_dump to Hetzner volume; retain 7 dumps; run via cron at 03:00 UTC |
[ ] |
| I-10 |
DNS setup notes — A record for registry.botstandards.org (or agreed domain) → apix-app VPS IP |
[ ] |
| I-11 |
sanctions/download.sh — download OFAC SDN, EU consolidated, UN SC sanctions lists; run weekly via cron |
[ ] |
| I-12 |
docker-stack.yml — production Swarm stack; all five services (registry, spider, portal, db, caddy); deploy.update_config.order: start-first; deploy.rollback_config; health check references; image tags pulled from Gitea registry |
[ ] |
Hetzner — infra/hetzner/apix-gitea/ (Gitea VPS)
| # |
Deliverable |
Status |
| I-13 |
provision.sh — Gitea VPS bootstrap: Docker install, firewall (22/80/443), swap, non-root user |
[ ] |
| I-14 |
docker-compose.yml — Gitea + Caddy on Gitea VPS; Gitea data volume; SQLite (no external DB) |
[ ] |
| I-15 |
gitea-config/app.ini — Gitea configuration: container registry enabled, Gitea Actions enabled, organisation bot-standards-foundation, domain gitea.botstandards.org |
[ ] |
| I-16 |
GitHub push mirror setup — configure Gitea push mirror to github.com/bot-standards-foundation/* for all repositories; triggered on every push to main |
[ ] |
| I-17 |
act_runner JVM install — Gitea Actions runner on Gitea VPS; Java 21 + Maven; handles Stage 1 (fast cycle) and Stage 3 (deploy) CI jobs |
[ ] |
| I-18 |
act_runner GraalVM install — Gitea Actions runner on Gitea VPS (or apix-app VPS off-hours); GraalVM 21; handles Stage 2 (native image build); configured to run max 1 concurrent native job |
[ ] |
| I-19 |
DNS setup notes — A record gitea.botstandards.org → Gitea VPS IP |
[ ] |
CI/CD Pipelines — .gitea/workflows/
| # |
Deliverable |
Trigger |
Status |
| I-20 |
ci-fast.yml — Stage 1: mvn verify (JVM); all module unit tests + @QuarkusTest; WireMock-based verification tests; ~3–5 min |
Every push |
[ ] |
| I-21 |
ci-native.yml — Stage 2: mvn package -Pnative per Quarkus module; Docker multi-stage build; @QuarkusIntegrationTest against native container; push to Gitea registry as <module>:main-<sha> |
Merge to main |
[ ] |
| I-22 |
deploy.yml — Stage 3: SSH to apix-app VPS; docker service update --image <new> apix_<service> for registry + spider + portal; poll /q/health until UP; fail + alert on timeout (Swarm auto-rollbacks) |
Git tag v* |
[ ] |
Local Developer Scripts — scripts/
| # |
Deliverable |
Status |
| I-23 |
setup-dev.sh — idempotent local setup: check/install Java 21 (SDKMAN prompt), Maven, Docker; copy .env.example → .env if missing; start PostgreSQL container; run Liquibase migrations via Maven; print next steps |
[ ] |
| I-24 |
dev.sh — start all three Quarkus modules in dev mode concurrently; each in its own named tmux pane (or background with log files if tmux absent); prints URLs on start |
[ ] |
| I-25 |
`restart.sh [registry |
spider |
| I-26 |
stop.sh — stop all dev-mode Quarkus processes; stop PostgreSQL container; clean PID files |
[ ] |
| I-27 |
reset.sh — stop everything; drop and recreate local DB; re-run migrations; restart dev mode |
[ ] |
| I-28 |
`logs.sh [registry |
spider |
Build Sequence
Develop in JVM mode (quarkus dev) throughout — fast reload, continuous testing. Native build only in Block 5 for the production image.
Block 1 — Multi-Module Foundation (Weeks 1–2)
- C-00 (parent
pom.xml — BOM, module declarations, plugin config)
- Create five Maven modules:
apix-common, apix-verification, apix-registry, apix-spider, apix-portal
- Generate three Quarkus modules via code.quarkus.io (or
mvn quarkus:create):
apix-registry: RESTEasy Reactive, Hibernate ORM + Panache, PostgreSQL JDBC, Liquibase, SmallRye Health, Quarkus Security
apix-spider: Quarkus Scheduler, Hibernate ORM + Panache, PostgreSQL JDBC, REST Client Reactive, SmallRye Health
apix-portal: RESTEasy Reactive, Qute, REST Client Reactive, SmallRye Health
- C-01 to C-05 (apix-common: enums + DTOs)
- C-20 to C-24 (Liquibase changelogs + registry application.properties)
- I-01, I-07 (docker-compose skeleton + .env.example)
- D-05 to D-08, D-34 to D-47 (constraints + ADRs)
Block 2 — Core Registry API (Weeks 3–4)
- C-06 to C-12 (apix-verification: all verifiers + pipeline + config)
- C-13 to C-19 (apix-registry: resources, services, model, repository)
- C-43 to C-48 (apix-verification tests — plain JUnit, no Quarkus context needed)
- C-49 to C-51 (apix-registry tests — @QuarkusTest + Testcontainers)
- D-09 to D-11 (context + technical diagrams)
Block 3 — Spider Module (Weeks 5–6)
- C-25 to C-32 (apix-spider: scheduler, fetcher, evaluator, parsers, entity, repository, config)
- C-52 (SpiderSchedulerTest)
- I-05 (spider Dockerfile — multi-stage native)
- D-20 to D-23 (runtime view sequence diagrams)
Block 4 — Portal Module + i18n + Help System + Human-Readable Service Detail (Weeks 7–9)
- C-33 to C-42 (apix-portal: resources, REST client, Qute templates, CSS, config)
- C-65 to C-67 (ServiceDetailViewModel, PortalResource update, ServiceDetailViewModelFactory)
- C-54 to C-58 (i18n: Messages interface with O/S-level description keys, EN/DE properties files, LocaleResolver, LocaleResource)
- C-59 to C-62 (help system: help.js, base layout, tour model records, HelpContentService with 5 tours)
- Update C-36 to C-40 (Qute templates: extend layout.html; replace hardcoded strings with
{inject:msg.*}; add <body data-page-id="..."> attribute; inject PAGE_TOURS + PAGE_HELP script block via HelpContentService)
- C-38 service.html: 7-section human-friendly layout (identity hero, trust card, liveness card, capabilities, pricing, contact, integration collapsible)
- C-53 (PortalResourceTest)
- C-63, C-64, C-68 (LocaleResolverTest, HelpContentServiceTest, ServiceDetailViewModelFactoryTest)
- I-03 (Caddyfile)
- I-06 (portal Dockerfile — multi-stage native)
- D-52 to D-53 (ADR-013 i18n, ADR-014 help system)
Block 5 — Gitea + CI/CD Infrastructure (Weeks 9–10)
- I-13 to I-19 (provision Gitea VPS, install Gitea + Caddy, configure container registry, GitHub mirror, act_runners)
- I-20 (ci-fast.yml — Stage 1 pipeline; verify it passes on first push)
- I-21 (ci-native.yml — Stage 2; first successful native build + push to Gitea registry)
- I-23 to I-28 (local dev scripts: setup-dev, dev, restart, stop, reset, logs)
- I-02 (docker-compose.override.yml — JVM dev mode for all three Quarkus modules)
Block 6 — Production Deployment + Zero Downtime (Weeks 10–11)
- I-08 to I-12 (provision apix-app VPS as Swarm node, backup, sanctions download, docker-stack.yml)
- I-04 to I-06 (Dockerfiles for all three Quarkus modules — multi-stage native)
- I-22 (deploy.yml — Stage 3; first tagged release deployed with zero-downtime verification)
- D-24 to D-27 (deployment view — two-VPS diagram + Swarm rolling update sequence)
- Fix any GraalVM reflection hints discovered during native integration tests
Block 7 — Arc42 Completion + Real Services (Weeks 12)
- Remaining arc42 docs (D-01 to D-04, D-40 to D-46)
- Register RS-01 to RS-08 (self + Lexnexum + reference registrations)
- Manual outreach to founding member candidates (RS-09)
Real Services Target List
At least 5 live registered services required for a credible PoC. Candidates:
| # |
Service |
Type |
Source |
| RS-01 |
APIX index itself (/health, /services) |
Self-registration |
Day 1 |
| RS-02 |
Public OpenAPI test service (Petstore or equivalent) |
Demo |
Day 1 |
| RS-03 |
BSF website bot endpoint |
Self-registration |
Week 4 |
| RS-04 |
Lexnexum — legal search / legal portal bot endpoint |
innoit.de / lexnexum.ai — controlled registrant |
Week 4 |
| RS-05 |
GLEIF API — legal-entity.lookup capability |
Reference registration by BSF; invite GLEIF to self-upgrade |
Week 5 |
| RS-06 |
OpenCorporates API — company.lookup capability |
Reference registration by BSF |
Week 5 |
| RS-07 |
EU Sanctions API (eu-sanctions.io or direct list endpoint) — sanctions.screen capability |
Reference registration by BSF |
Week 5 |
| RS-08 |
Companies House UK API — org.verify.uk capability |
Reference registration by BSF |
Week 5 |
| RS-09 |
Early adopter from founding member outreach |
Real |
Week 8+ |
| RS-10 |
Developer community volunteer (HN / LangChain) |
Community |
Week 10+ |
Open Questions
| # |
Question |
Owner |
Status |
| OQ-MVP-01 |
Domain name for public PoC — registry.apix.dev, index.botstandards.org, other? |
Carsten |
[ ] |
| OQ-MVP-02 |
GitHub organisation for open source repo — bot-standards-foundation or personal? |
Carsten |
[ ] |
| OQ-MVP-03 |
API key distribution for write access during PoC — how are early registrants onboarded? |
Carsten |
[ ] |
| OQ-MVP-04 |
Spider check frequency — every 15 min is reasonable for PoC; confirm acceptable load on registrant services |
Carsten |
[ ] |
| OQ-MVP-05 |
Hetzner server size — CX22 (2 vCPU, 4GB) sufficient for PoC; upgrade path if needed |
Carsten |
[ ] |
| OQ-MVP-06 |
OpenCorporates API key — free tier sufficient for PoC volume? Confirm rate limits before build |
Carsten |
[ ] |
| OQ-MVP-07 |
Sanctions list update cadence — weekly download of OFAC/EU/UN lists acceptable? Check if any list requires a licence for automated use |
Carsten |
[ ] |
| OQ-MVP-08 |
GLEIF API — confirm no API key required for public LEI lookup (currently free/open); check terms of use for automated batch use |
Carsten |
[ ] |
| OQ-MVP-09 |
Reference registrations (RS-05 to RS-08): BSF registers third-party APIs at O-0. Confirm BSF ToS allows this. Prepare outreach template inviting self-registration upgrade. |
Carsten |
[ ] |
| OQ-MVP-10 |
Gitea domain — gitea.botstandards.org or separate domain (e.g. code.apix.dev)? Confirm before provisioning Caddy TLS cert |
Carsten |
[ ] |
| OQ-MVP-11 |
GitHub org name — bot-standards-foundation exists? Create before configuring push mirror |
Carsten |
[ ] |
| OQ-MVP-12 |
Native build runner placement — on Gitea VPS or apix-app VPS? apix-app VPS risks resource contention during native build; Gitea VPS is cleaner but requires GraalVM installed there |
Carsten |
[ ] |
| OQ-MVP-13 |
Zero-downtime validation — how to confirm health check passes before declaring deploy successful in CI? Poll /q/health with retry loop or use Swarm service inspection |
Carsten |
[ ] |