Implement apix-registry with IoT sunset/decommission lifecycle and full BDD suite

- REST API: register, patch, O-level, replacements, history, search endpoints
- IoT lifecycle validations: future sunset, lock-before-release, sunset-passed-before-decommission
- DB schema: Liquibase changesets 001–008 (services, versions, replacements, sunset-at column)
- @ColumnTransformer(write="?::jsonb") on bsm_payload fields to avoid JDBC varchar→jsonb rejection
- Jandex plugin on apix-common + quarkus.index-dependency so @NotBlank validators resolve at runtime
- quarkus-logging-json extension added; quarkus.log.console.json=false is now a recognised key
- Fix requireSunsetBeforeLockRelease: Boolean.TRUE.equals instead of !Boolean.FALSE.equals (null guard)
- BDD suite: 27 scenarios / 213 steps across 5 feature files (sunset-lock, decommission, replacement, discovery, anonymity)
- Test infrastructure: JDBC TRUNCATE in @Before for DB isolation, Arc.container() for clock control — no test endpoints in production code
- sunsetAt truncated to microseconds in BDD steps to match Postgres timestamptz precision
- Cucumber step fixes: singular/plural candidate(s), lastResponse propagation in replacementsReturnsNCandidates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carsten Rehfeld
2026-05-08 09:13:26 +02:00
commit b2a16a8be7
71 changed files with 5480 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.botstandards</groupId>
<artifactId>apix-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>apix-common</artifactId>
<name>APIX :: Common</name>
<description>Shared enums, DTOs, and value types. No Quarkus dependency.</description>
<build>
<plugins>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Bean Validation API + @URL from Hibernate Validator annotations -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- Jackson annotations for JSON field naming / null-handling hints -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,46 @@
package org.botstandards.apix.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import org.hibernate.validator.constraints.URL;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
@JsonInclude(JsonInclude.Include.NON_NULL)
public record BsmPayload(
@NotBlank String name,
@NotBlank String description,
@NotBlank @URL String endpoint,
@NotEmpty List<@NotBlank String> capabilities,
@NotBlank @Email String registrantEmail,
@NotBlank String registrantName,
@NotBlank String registrantJurisdiction,
OrgType registrantOrgType,
String registrantLei,
@URL String openApiSpecUrl,
@URL String mcpSpecUrl,
@URL String policyUrl,
@URL String securityContactUrl,
@Valid Pricing pricing,
@NotBlank String bsmVersion,
ServiceStage serviceStage,
// IoT transition fields — null for non-IoT services
Boolean locked,
Instant sunsetAt,
@URL String migrationGuideUrl,
List<UUID> replacesServiceIds
) {
@JsonInclude(JsonInclude.Include.NON_NULL)
public record Pricing(
String billingModel,
BigDecimal pricePerCall,
String currency,
String billingUnit
) {}
}
@@ -0,0 +1,14 @@
package org.botstandards.apix.common;
public enum ChangeType {
REGISTERED,
BSM_UPDATED,
ORG_TYPE_CHANGED,
OLEVEL_CHANGED,
STAGE_CHANGED,
OWNERSHIP_TRANSFERRED,
REGISTRY_STATUS_CHANGED,
SUNSET_DECLARED,
LOCK_RELEASED,
REPLACEMENT_DECLARED
}
@@ -0,0 +1,8 @@
package org.botstandards.apix.common;
public enum LivenessStatus {
PENDING,
LIVE,
DEGRADED,
UNREACHABLE
}
@@ -0,0 +1,10 @@
package org.botstandards.apix.common;
public enum OLevel {
UNVERIFIED,
IDENTITY_VERIFIED,
LEGAL_ENTITY_VERIFIED,
HYGIENE_VERIFIED,
OPERATIONALLY_VERIFIED,
AUDITED
}
@@ -0,0 +1,9 @@
package org.botstandards.apix.common;
public enum OrgType {
INDIVIDUAL,
COMMERCIAL,
NON_PROFIT,
GOVERNMENT,
ACADEMIC
}
@@ -0,0 +1,7 @@
package org.botstandards.apix.common;
public enum RegistryStatus {
ACTIVE,
SUSPENDED,
ARCHIVED
}
@@ -0,0 +1,9 @@
package org.botstandards.apix.common;
public enum ServiceStage {
DEVELOPMENT,
BETA,
PRODUCTION,
DEPRECATED,
DECOMMISSIONED
}
@@ -0,0 +1,17 @@
package org.botstandards.apix.common;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
public record ServiceSummaryDto(
UUID id,
String name,
List<String> capabilities,
OLevel oLevel,
LivenessStatus livenessStatus,
ServiceStage serviceStage,
RegistryStatus registryStatus,
String endpoint,
Instant lastCheckedAt
) {}
@@ -0,0 +1,7 @@
package org.botstandards.apix.common;
public record VerificationResult(
OLevel oLevelAchieved,
String blockedAtStep,
String message
) {}