diff --git a/.github/workflows/rc-adopter-gate.yml b/.github/workflows/rc-adopter-gate.yml deleted file mode 100644 index c076863..0000000 --- a/.github/workflows/rc-adopter-gate.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: RC Adopter Gate - -on: - workflow_dispatch: - inputs: - version: - description: Published Codes RC version - required: true - default: 0.4.0-RC1 - -permissions: - contents: read - -jobs: - adopters: - runs-on: ubuntu-latest - timeout-minutes: 45 - - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - ref: v${{ inputs.version }} - persist-credentials: false - - - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 - with: - distribution: temurin - java-version: "21" - - - name: Verify external adopter gate - run: >- - bash scripts/verify-rc-adopters.sh - "${{ inputs.version }}" - build/reports/rc1-adopters.md - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a - if: always() - with: - name: rc1-adopter-gate-manual - path: | - build/reports/rc1-adopters.md - build/rc-adopters/*.log - if-no-files-found: warn - retention-days: 30 diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index b17515c..8e8d678 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -106,7 +106,7 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} - real-adopters: + external-integrations: needs: publish runs-on: ubuntu-latest timeout-minutes: 45 @@ -121,24 +121,24 @@ jobs: distribution: temurin java-version: "21" - - name: Verify two external adopters from Maven Central + - name: Verify external integrations from Maven Central run: >- - bash scripts/verify-rc-adopters.sh + bash scripts/verify-rc-integrations.sh "${{ needs.publish.outputs.version }}" - build/reports/rc1-adopters.md + build/reports/rc-integrations.md - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a if: always() with: - name: rc1-adopter-gate + name: rc-external-integrations path: | - build/reports/rc1-adopters.md - build/rc-adopters/*.log + build/reports/rc-integrations.md + build/rc-integrations/*.log if-no-files-found: warn retention-days: 30 github-prerelease: - needs: [publish, real-adopters] + needs: [publish, external-integrations] runs-on: ubuntu-latest timeout-minutes: 10 permissions: diff --git a/.gitignore b/.gitignore index 0705a1b..a30689a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ out/ .DS_Store **/target/ +__pycache__/ +*.py[cod] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5047050..c2ae6ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,69 +6,43 @@ ### Added -* Added Maven publications for `codes-spring` and `codes-grpc-java`. -* Added adapter API snapshots and exact published dependency budgets. -* Added clean Gradle and Maven consumer checks that resolve all three artifacts from isolated publication repositories. -* Added application-owned Spring problem-type URI mappings. -* Added a thin failed-`Outcome` to Spring `ErrorResponseException` bridge. -* Added Spring RFC 9457 golden response contracts plus MVC and WebFlux compatibility checks. -* Added decoded gRPC wire contracts covering safe, public, and explicitly exposed `google.rpc.Status` payloads. -* Added gRPC `StatusRuntimeException` trailer round-trip verification. -* Added a production compatibility matrix for Spring 6/7, minimum/current gRPC, Java 17/21/25, Java/Kotlin consumers, Gradle/Maven, and Linux/Windows/macOS. -* Added adapter JSpecify consumer verification and adapter coverage gates. -* Added published-POM verification against clean isolated Maven repositories. -* Added ten-minute Spring and gRPC boundary examples. -* Added a reproducible release-candidate adopter gate against two pinned external codebases. -* Added a dedicated release-candidate workflow that publishes to Maven Central before running the external adopter gate. -* Added a final-release promotion gate that requires a successful same-line RC and allows only version substitution between the accepted RC and final release. +* Added `codes-spring` for mapping failed outcomes to Spring RFC 9457 `ProblemDetail`. +* Added application-owned problem-type URI mappings and explicit Spring exposure policies. +* Added conversion from failed outcomes to Spring `ErrorResponseException`. +* Added `codes-grpc-java` for mapping failed outcomes to `google.rpc.Status` with stable identity in `ErrorInfo`. +* Added conversion to gRPC `StatusRuntimeException` and structured request-field issues through `BadRequest` for `INVALID_ARGUMENT` and `OUT_OF_RANGE`. +* Added Spring and gRPC integration guides. ### Changed -* Set all publishable modules to `0.4.0-RC1`. -* Corrected Spring problem details so reusable outcome messages are titles for explicitly mapped problem types and occurrence details use RFC `detail`. -* Kept stable Codes identity in the Spring `code` extension for every mapped failure. -* Reworked the Spring orders reference to consume `codes-spring` instead of duplicating adapter behavior. -* Enforced lossless Codes identity compatibility with `google.rpc.ErrorInfo.domain` and `ErrorInfo.reason`. -* Restricted exposed `google.rpc.BadRequest` issues to `INVALID_ARGUMENT` and `OUT_OF_RANGE`, requiring request-field paths and rejecting incompatible representations. -* Reworked the gRPC orders reference to consume `codes-grpc-java` instead of constructing rich error details manually. -* Moved publication consumer verification to freshly emptied Maven repositories so compatibility checks cannot be satisfied by stale local artifacts. -* Documented cases where Codes should not be introduced. +* Set all published artifacts to `0.4.0-RC1`. +* Spring problem details preserve Codes identity in the `code` extension, use the reusable outcome message as the title for explicitly mapped problem types, and keep occurrence detail in RFC `detail`. +* gRPC mappings reject lossy `ErrorInfo` identities and incompatible `BadRequest` issue representations instead of normalizing or coercing them. ## 0.3.1 ### Changed * Restored the published core to zero runtime dependencies. -* Kept JSpecify as a compile-time-only dependency for nullability metadata. +* Kept JSpecify as compile-time-only nullability metadata. * Preserved Kotlin nullability semantics across the supported Kotlin compiler matrix. -### Added - -* Added publication verification that prevents runtime dependencies from being introduced. -* Added verification that the published Maven POM remains dependency-free. - ## 0.3.0 ### Changed * Reimplemented the published core in Java 17. -* Removed the Kotlin standard library from the core dependency graph. -* Added `org.jspecify:jspecify:1.0.0` as the core's only dependency for nullability annotations. -* Replaced Kotlin-specific ABI compatibility with a deterministic public Java API snapshot. +* Removed the Kotlin standard library from the core runtime dependency graph. +* Added JSpecify nullability metadata for Java and Kotlin consumers. * Preserved the 17 standard outcomes, `OutcomeState` assignments, and built-in HTTP and gRPC mappings from `0.2.0`. -### Added - -* Added Gradle and Maven consumer verification for Java and Kotlin. -* Added Kotlin compiler compatibility and JSpecify nullability contract verification. -* Added publication checks for the expected runtime and Maven dependency contract. - ### Removed * Removed Kotlin compiler-generated API such as `Companion`, default-argument bridges, and `OutcomeState.entries`. * Removed the Kotlin `Outcome.toException` extension; use `OutcomeExceptions.toException(...)` or `new OutcomeException(...)`. ### Breaking + `0.3.0` is source and binary incompatible with `0.2.x` for callers that depend on the removed Kotlin-generated API or `Outcome.toException`. The standard outcome identities, states, and built-in protocol mappings are unchanged from `0.2.0`. @@ -85,10 +59,6 @@ The standard outcome identities, states, and built-in protocol mappings are unch * Removed `StandardOutcomes.CREATED`, `ACCEPTED`, `NO_CONTENT`, and `PAYLOAD_TOO_LARGE`. -### Added - -* Added semantic compatibility checks for standard outcome codes, states, and built-in mappings. - ## 0.1.0 * Initial public release. diff --git a/README.md b/README.md index c630b0d..ca08864 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,23 @@ [![CI](https://github.com/aalsanie/codes/actions/workflows/ci.yml/badge.svg)](https://github.com/aalsanie/codes/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) -Codes provides stable application outcome identities and explicit boundary mappings for JVM applications. Applications keep their own domain result or error model and use Codes where multiple parts of a system need to agree on outcome meaning without coupling that meaning to HTTP, gRPC, serialization, or a framework. - -A domain outcome can keep the same identity across boundaries: +Codes provides stable application outcome identities and explicit boundary mappings for JVM applications. Applications keep their existing domain result or error model; Codes gives the system one machine identity for an outcome while HTTP and gRPC remain boundary decisions. ```text -com.example.payments:PAYMENT_DECLINED - | - +-- HTTP 422 - +-- gRPC FAILED_PRECONDITION - +-- logs/metrics keep PAYMENT_DECLINED +com.example.orders:ORDER_NOT_FOUND + -> HTTP 404 + -> gRPC NOT_FOUND + -> metric label + -> log identity + -> test assertion ``` +The same `OutcomeCode` can be used wherever application code, observability, and protocol boundaries need to agree on the outcome. + ## Install +`0.4.0-RC1` is the current pre-release on Maven Central. + Core: ```kotlin @@ -120,7 +123,7 @@ ValidationResult validation = ValidationResult.invalid( Outcome outcome = validation.toOutcome(StandardOutcomes.INVALID_ARGUMENT); ``` -`ValidationResult` is a small convenience for aggregating issues. It is not intended to replace an application's result, validation, or functional programming model. +`ValidationResult` is a small convenience for aggregating issues and converting them into an outcome. ## HTTP @@ -158,29 +161,11 @@ check(outcome.code == StandardOutcomes.NOT_FOUND.code) check(status?.value == 404) ``` -## When not to use Codes - -Do not add Codes only to standardize a single controller's error body. Framework-native errors are usually enough for a small application with one boundary. - -Codes is also the wrong tool when: - -* the application does not need a stable outcome identity outside one protocol boundary; -* you want a `Result`, `Either`, validation framework, exception hierarchy, or business workflow engine; -* you want Spring Boot auto-configuration, exception scanning, annotations, or hidden mapping conventions; -* an existing public error schema is fixed and migration cost is larger than the value of cross-boundary identity; -* you need protocol adapters beyond the ones Codes actually provides and do not want to own that adapter; -* you need a central outcome registry, governance service, code generator, or schema distribution system; -* the application has not yet decided which domain outcomes are stable enough to become machine identities. - -Codes is useful when the identity itself matters independently of HTTP or gRPC. If that is not true, another abstraction is probably unnecessary. - ## Reference * [Semantic contract](docs/semantic-contract.md) * [HTTP and gRPC mappings](docs/protocol-mappings.md) * [Compatibility policy](docs/compatibility-policy.md) -* [Artifact contracts](docs/artifact-contracts.md) -* [RC1 real-adopter gate](docs/rc1-adopter-gate.md) ## License diff --git a/adoption/rc1/manifest.json b/adoption/rc1/manifest.json deleted file mode 100644 index 12a6dbf..0000000 --- a/adoption/rc1/manifest.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": "0.4.0-RC1", - "pilots": [ - { - "id": "integration-reliability-platform", - "repository": "https://github.com/aalsanie/integration-reliability-platform.git", - "commit": "1f99ad8ec4b0b7da2a20ab20101a6adb677cb735", - "patch": "adoption/rc1/integration-reliability-platform.patch", - "boundary": "Spring MVC", - "differentiator": "incremental replacement of a handwritten HTTP error mapping", - "manual_mapping_removed": "1 handwritten duplicate-event HTTP status/payload mapping", - "dependency_conflicts": "None expected; the application already uses Spring Boot 4.1.0 and owns Spring dependency management.", - "missing_api": "None identified before execution.", - "expected_identity": "io.github.aalsanie.codes.standard:ALREADY_EXISTS" - }, - { - "id": "patient-mgmt-microservices", - "repository": "https://github.com/pratham2402/patient-mgmt-microservices.git", - "commit": "26645990986a4f17b755ec17ed390c9e90112d36", - "patch": "adoption/rc1/patient-mgmt-microservices.patch", - "boundary": "Spring MVC + gRPC Java", - "differentiator": "the same INVALID_ARGUMENT identity is exercised through two real protocol boundaries", - "manual_mapping_removed": "1 handwritten Spring validation Map response; the gRPC validation path did not previously exist", - "dependency_conflicts": "The application pins gRPC 1.69.0, below the Codes 1.75.0 floor. The pilot upgrades the application gRPC line and protoc gRPC plugin to 1.75.0 and imports the 1.75.0 gRPC BOM.", - "missing_api": "None identified before execution.", - "expected_identity": "io.github.aalsanie.codes.standard:INVALID_ARGUMENT" - } - ] -} diff --git a/adoption/rc1/integration-reliability-platform.patch b/compatibility/external/integration-reliability-platform.patch similarity index 96% rename from adoption/rc1/integration-reliability-platform.patch rename to compatibility/external/integration-reliability-platform.patch index e979e18..505165b 100644 --- a/adoption/rc1/integration-reliability-platform.patch +++ b/compatibility/external/integration-reliability-platform.patch @@ -71,7 +71,7 @@ @ExceptionHandler(value = {InvalidEventProcessingStatus.class}) --- /dev/null -+++ b/src/test/java/io/github/aalsanie/irp/CodesSpringAdoptionTest.java ++++ b/src/test/java/io/github/aalsanie/irp/CodesSpringIntegrationTest.java @@ -0,0 +1,84 @@ +package io.github.aalsanie.irp; + @@ -99,7 +99,7 @@ +@SpringBootTest +@AutoConfigureMockMvc +@Import(TestcontainersConfiguration.class) -+class CodesSpringAdoptionTest { ++class CodesSpringIntegrationTest { + + @Autowired + private MockMvc mockMvc; @@ -122,7 +122,7 @@ + IntegrationConnection connection = connectionRepository.saveAndFlush( + new IntegrationConnection( + UUID.randomUUID(), -+ "pilot", ++ "integration-test", + "stripe", + ConnectionStatus.ACTIVE, + Instant.now() @@ -131,10 +131,10 @@ + + String request = """ + { -+ "externalEventId": "evt_rc1", ++ "externalEventId": "evt_codes", + "eventType": "payment.succeeded", + "payload": { -+ "paymentId": "pay_rc1" ++ "paymentId": "pay_codes" + } + } + """; diff --git a/compatibility/external/manifest.json b/compatibility/external/manifest.json new file mode 100644 index 0000000..d05a599 --- /dev/null +++ b/compatibility/external/manifest.json @@ -0,0 +1,24 @@ +{ + "integrations": [ + { + "id": "integration-reliability-platform", + "repository": "https://github.com/aalsanie/integration-reliability-platform.git", + "commit": "1f99ad8ec4b0b7da2a20ab20101a6adb677cb735", + "patch": "compatibility/external/integration-reliability-platform.patch", + "boundary": "Spring MVC", + "scope": "replace one handwritten duplicate-event HTTP mapping with codes-spring", + "dependency_notes": "The application uses Spring Boot 4.1.0 and owns Spring dependency management.", + "expected_identity": "io.github.aalsanie.codes.standard:ALREADY_EXISTS" + }, + { + "id": "patient-mgmt-microservices", + "repository": "https://github.com/pratham2402/patient-mgmt-microservices.git", + "commit": "26645990986a4f17b755ec17ed390c9e90112d36", + "patch": "compatibility/external/patient-mgmt-microservices.patch", + "boundary": "Spring MVC + gRPC Java", + "scope": "exercise INVALID_ARGUMENT through Spring MVC and gRPC Java", + "dependency_notes": "The repository pins gRPC 1.69.0; the integration patch updates that line to the Codes 1.75.0 compatibility floor.", + "expected_identity": "io.github.aalsanie.codes.standard:INVALID_ARGUMENT" + } + ] +} diff --git a/adoption/rc1/patient-mgmt-microservices.patch b/compatibility/external/patient-mgmt-microservices.patch similarity index 97% rename from adoption/rc1/patient-mgmt-microservices.patch rename to compatibility/external/patient-mgmt-microservices.patch index d292568..7f78791 100644 --- a/adoption/rc1/patient-mgmt-microservices.patch +++ b/compatibility/external/patient-mgmt-microservices.patch @@ -103,7 +103,7 @@ +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + -+@WebMvcTest(controllers = CodesValidationBoundaryTest.PilotController.class) ++@WebMvcTest(controllers = CodesValidationBoundaryTest.TestController.class) +@Import(GlobalExceptionHandler.class) +class CodesValidationBoundaryTest { + @@ -113,7 +113,7 @@ + @Test + void validationKeepsCodesIdentityAndStructuredIssuesAtHttpBoundary() + throws Exception { -+ mockMvc.perform(post("/codes-pilot/validate") ++ mockMvc.perform(post("/codes-test/validate") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { @@ -130,13 +130,13 @@ + } + + @RestController -+ static class PilotController { -+ @PostMapping("/codes-pilot/validate") -+ void validate(@Valid @RequestBody PilotRequest request) { ++ static class TestController { ++ @PostMapping("/codes-test/validate") ++ void validate(@Valid @RequestBody TestRequest request) { + } + } + -+ record PilotRequest(@NotBlank String email) { ++ record TestRequest(@NotBlank String email) { + } +} --- a/billing-service/pom.xml diff --git a/docs/artifact-contracts.md b/docs/artifact-contracts.md deleted file mode 100644 index ce7e3f0..0000000 --- a/docs/artifact-contracts.md +++ /dev/null @@ -1,15 +0,0 @@ -# Artifact contracts - -Codes `0.4.0` has three independently consumable Maven artifacts with one shared version. - -| Artifact | Purpose | Direct published dependency budget | -| --- | --- | --- | -| `io.github.aalsanie:codes` | Framework-independent outcome model and protocol mappings | No dependencies | -| `io.github.aalsanie:codes-spring` | Spring HTTP boundary mapping | `codes` and `spring-web`, both compile scope | -| `io.github.aalsanie:codes-grpc-java` | gRPC Java and `google.rpc` boundary mapping | `codes`, `grpc-api`, and `proto-google-common-protos` at compile scope; `grpc-protobuf` at runtime scope | - -The adapter budgets are exact, not maximums. A direct dependency addition or scope change must update the relevant enforced POM contract and explain why it belongs in the public artifact. - -The core remains framework-independent and dependency-free. JSpecify is compile-only metadata and must not appear in the published core POM or runtime classpath. - -Public API snapshots live under `api/`. Semantic fixtures for the core catalog and built-in mappings live under `compatibility/`. Both are reviewed contracts rather than generated files that CI silently updates. diff --git a/docs/compatibility-policy.md b/docs/compatibility-policy.md index b7e7faa..3251359 100644 --- a/docs/compatibility-policy.md +++ b/docs/compatibility-policy.md @@ -4,26 +4,35 @@ Codes is pre-1.0. Minor releases may contain source or binary breaking changes. All published Codes artifacts target Java 17 and support Java and Kotlin consumers. -The published `codes` core artifact has zero runtime dependencies. `codes-spring` and `codes-grpc-java` depend only on the boundary libraries documented in their artifact contracts and verified published POM budgets. +The `codes` core artifact has zero runtime dependencies. `codes-spring` and `codes-grpc-java` depend on the boundary libraries they adapt. -The following are part of the semantic contract: +## 0.4.x compatibility -* standard outcome codes; -* standard `OutcomeState` assignments; -* built-in HTTP mappings; -* built-in gRPC mappings. +The `0.4.x` line is verified with: + +* Java 17, 21, and 25; +* Spring Framework 6.0.0 and 7.0.9; +* gRPC Java 1.75.0 and 1.83.1; +* Kotlin 1.9.24, 2.0.21, 2.1.21, 2.2.20, and 2.4.10; +* Gradle and Maven consumers; +* Linux, Windows, and macOS. + +Spring Framework 6.0.0 is the Codes compatibility floor for the Spring 6 generation. Spring 7.0.9 is the current Spring 7 verification baseline. -Public Java API compatibility is checked independently for: +gRPC Java 1.75.0 is the Codes compatibility floor. gRPC Java 1.83.1 is the current verification baseline. -* `api/codes.api`; -* `api/codes-spring.api`; -* `api/codes-grpc-java.api`. +These are Codes compatibility statements, not upstream maintenance or security-support declarations. -Boundary wire contracts are checked independently from Java API compatibility: +## Public contract -* rendered Spring RFC 9457 problem responses are frozen by `compatibility/spring-http-problems.snapshot`; -* decoded `google.rpc.Status` payloads are frozen by `compatibility/grpc-google-rpc-status.snapshot`. +The machine-readable contract includes: + +* `OutcomeCode` identity; +* standard outcome membership; +* standard `OutcomeState` assignments; +* built-in HTTP mappings; +* built-in gRPC mappings. -The executable compatibility matrix under `compatibility/` covers the supported Spring and gRPC baselines, Java runtimes, Kotlin compilers, Gradle and Maven consumers, and supported CI operating systems. Published POM checks protect the dependency contract of each artifact. +The Spring adapter preserves Codes identity in the RFC 9457 `code` extension. The gRPC adapter preserves identity in `google.rpc.ErrorInfo.domain` and `ErrorInfo.reason`. Human-readable messages are not machine identity and may change without changing `OutcomeCode`. diff --git a/docs/rc1-adopter-gate.md b/docs/rc1-adopter-gate.md deleted file mode 100644 index 36a2bb6..0000000 --- a/docs/rc1-adopter-gate.md +++ /dev/null @@ -1,89 +0,0 @@ -# 0.4.0-RC1 real-adopter gate - -The RC is not accepted because it has downloads, stars, or because the Codes repository's own reference applications compile. - -The gate uses two independent codebases outside this repository at pinned commits and applies small application patches that consume the published Maven Central artifacts. - -## Pilot 1 — Integration Reliability Platform - -Repository: `aalsanie/integration-reliability-platform` - -Pinned commit: `1f99ad8ec4b0b7da2a20ab20101a6adb677cb735` - -Environment: - -* Java 21 -* Spring Boot 4.1.0 -* Gradle -* existing Spring MVC error boundary -* existing Testcontainers integration suite - -The pilot replaces one existing handwritten duplicate-event HTTP mapping with `codes-spring`. The existing application remains responsible for deciding that `DuplicateInboundEventException` means `ALREADY_EXISTS`. - -Expected identity: - -```text -io.github.aalsanie.codes.standard:ALREADY_EXISTS -``` - -The pilot includes a real MockMvc integration test against the application boundary. - -## Pilot 2 — Patient Management Microservices - -Repository: `pratham2402/patient-mgmt-microservices` - -Pinned commit: `26645990986a4f17b755ec17ed390c9e90112d36` - -Environment: - -* Java 21 -* Spring Boot 3.5.5 -* Maven -* REST patient service -* gRPC billing service - -This pilot exercises the actual differentiator. - -The patient service replaces its handwritten validation `Map` response with `codes-spring`. The billing service adds validation through `codes-grpc-java`. - -Both boundaries use: - -```text -io.github.aalsanie.codes.standard:INVALID_ARGUMENT -``` - -The HTTP side verifies the RFC 9457 `code` property and structured issues. The gRPC side decodes a real `StatusRuntimeException` and verifies `ErrorInfo` plus `BadRequest`. - -The repository currently pins gRPC 1.69.0. Codes declares 1.75.0 as its `0.4.x` compatibility floor, so the pilot upgrades the application's gRPC dependency line and protoc gRPC plugin to 1.75.0. That is recorded as an adopter dependency conflict; it is not hidden with a Codes patch. - -## What the automated report records - -For each pilot the gate records: - -* repository and pinned commit; -* exact patch; -* pass/fail result; -* automated clone + patch + clean build/test elapsed time; -* changed-line statistics; -* manual boundary mapping removed; -* dependency conflicts; -* missing Codes API observed by the successful integration; -* whether any custom Codes library patch was used. - -The elapsed time is machine integration time from a clean CI workspace. It is not presented as human coding time. - -A successful report may state `Missing API: None observed`. A failed build is not converted into that claim; it fails the gate for investigation. - -## What counts as success - -Both external repositories must: - -1. clone at the pinned commit; -2. accept the stored application patch with `git apply --check`; -3. resolve `0.4.0-RC1` from Maven Central; -4. build and test without a source or binary patch to Codes; -5. verify the intended boundary identity. - -Only then is the Step 5 exit gate green. - -The patches are reproducible adopter experiments. They are not claims that the upstream maintainers have merged Codes. diff --git a/docs/semantic-contract.md b/docs/semantic-contract.md index 7c491f3..d315017 100644 --- a/docs/semantic-contract.md +++ b/docs/semantic-contract.md @@ -115,8 +115,6 @@ validation.toOutcome(StandardOutcomes.INVALID_ARGUMENT); A valid result produces `StandardOutcomes.OK`. An invalid result produces the supplied failed definition. The supplied definition is validated even for a valid result so invalid conversion policy fails immediately. -`ValidationResult` is a convenience type. It is not the application result model and does not define control flow. - ## Protocol mappings `OutcomeMapper` maps an application outcome definition to a boundary representation. @@ -163,4 +161,4 @@ Human-readable text may evolve without changing outcome identity: Consumers must use `OutcomeCode`, not human-readable text, when behavior depends on identity. -`api/codes.api` protects the public Java API shape. `compatibility/` protects semantic catalog and mapping behavior. +Runtime, framework, language, and build-tool support is documented in the [compatibility policy](compatibility-policy.md). diff --git a/docs/ten-minute-grpc.md b/docs/ten-minute-grpc.md index d6ff967..c3c8fa6 100644 --- a/docs/ten-minute-grpc.md +++ b/docs/ten-minute-grpc.md @@ -1,6 +1,6 @@ # gRPC Java in ten minutes -This example adds Codes at a gRPC server boundary. It does not replace the service's domain model. +This example adds Codes at a gRPC server boundary while the service keeps its existing domain model. ## 1. Add the adapter diff --git a/docs/ten-minute-spring.md b/docs/ten-minute-spring.md index 679e781..adcbd1a 100644 --- a/docs/ten-minute-spring.md +++ b/docs/ten-minute-spring.md @@ -1,6 +1,6 @@ # Spring in ten minutes -This example adds Codes at the HTTP boundary only. It does not replace the application's exception or domain model. +This example adds Codes at the HTTP boundary while the application keeps its existing exception and domain model. ## 1. Add the adapter @@ -75,7 +75,7 @@ final class PaymentExceptionHandler { } ``` -Codes is not discovering exceptions. The application still decides which exception means which outcome. +The application decides which exception maps to which outcome. ## Result diff --git a/scripts/__pycache__/verify-release-promotion.cpython-311.pyc b/scripts/__pycache__/verify-release-promotion.cpython-311.pyc deleted file mode 100644 index 83f4f55..0000000 Binary files a/scripts/__pycache__/verify-release-promotion.cpython-311.pyc and /dev/null differ diff --git a/scripts/verify-rc-adopters.sh b/scripts/verify-rc-integrations.sh similarity index 50% rename from scripts/verify-rc-adopters.sh rename to scripts/verify-rc-integrations.sh index 7deb9ff..5e2be7d 100644 --- a/scripts/verify-rc-adopters.sh +++ b/scripts/verify-rc-integrations.sh @@ -2,21 +2,13 @@ set -euo pipefail root_dir="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" -manifest="$root_dir/adoption/rc1/manifest.json" -version="${1:-0.4.0-RC1}" -report="${2:-$root_dir/build/reports/rc1-adopters.md}" -work_dir="${RC_ADOPTER_WORK_DIR:-$root_dir/build/rc-adopters}" +manifest="$root_dir/compatibility/external/manifest.json" +version="${1:?usage: verify-rc-integrations.sh [report]}" +report="${2:-$root_dir/build/reports/rc-integrations.md}" +work_dir="${RC_INTEGRATION_WORK_DIR:-$root_dir/build/rc-integrations}" -manifest_version="$(python - "$manifest" <<'PY' -import json -import sys -from pathlib import Path -print(json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))["version"]) -PY -)" - -if [[ "$version" != "$manifest_version" ]]; then - echo "RC adopter manifest is for $manifest_version, not $version." >&2 +if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-RC[1-9][0-9]*$ ]]; then + echo "Invalid release-candidate version: $version" >&2 exit 1 fi @@ -43,53 +35,50 @@ rm -rf "$work_dir" mkdir -p "$work_dir" "$(dirname "$report")" { - echo "# Codes $version real-adopter gate" + echo "# Codes $version external integration verification" echo echo "Generated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" echo - echo "Codes library patches used by pilots: **0**" - echo - echo "Both pilots resolve Codes from Maven Central. No composite build, Maven Local repository, or source substitution is used." - echo "Integration time below is automated clean clone + patch + build/test time, not human coding time." + echo "Each integration uses a pinned external repository, applies an application-only patch, resolves Codes from Maven Central, and runs the relevant build and tests." echo } > "$report" overall=0 -pilot_value() { - local pilot_id="$1" +integration_value() { + local integration_id="$1" local field="$2" - python - "$manifest" "$pilot_id" "$field" <<'PY' + python - "$manifest" "$integration_id" "$field" <<'PY' import json import sys from pathlib import Path manifest = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) -pilot = next(p for p in manifest["pilots"] if p["id"] == sys.argv[2]) -print(pilot[sys.argv[3]]) +integration = next(item for item in manifest["integrations"] if item["id"] == sys.argv[2]) +print(integration[sys.argv[3]]) PY } run_irp() { local id="integration-reliability-platform" - local repo commit patch_dir patch_file start end elapsed status log + local repo commit checkout patch_file start end elapsed status log - repo="$(pilot_value "$id" repository)" - commit="$(pilot_value "$id" commit)" - patch_file="$root_dir/$(pilot_value "$id" patch)" - patch_dir="$work_dir/$id" + repo="$(integration_value "$id" repository)" + commit="$(integration_value "$id" commit)" + patch_file="$root_dir/$(integration_value "$id" patch)" + checkout="$work_dir/$id" log="$work_dir/$id.log" - git clone --quiet --filter=blob:none --no-checkout "$repo" "$patch_dir" - git -C "$patch_dir" checkout --quiet --detach "$commit" - git -C "$patch_dir" apply --check "$patch_file" - git -C "$patch_dir" apply "$patch_file" + git clone --quiet --filter=blob:none --no-checkout "$repo" "$checkout" + git -C "$checkout" checkout --quiet --detach "$commit" + git -C "$checkout" apply --check "$patch_file" + git -C "$checkout" apply "$patch_file" start="$(date +%s)" status="PASS" if ! ( - cd "$patch_dir" + cd "$checkout" bash ./gradlew \ "-PcodesVersion=$version" \ clean test \ @@ -115,24 +104,17 @@ run_irp() { echo echo "- Repository: \`$repo\`" echo "- Commit: \`$commit\`" - echo "- Boundary: $(pilot_value "$id" boundary)" - echo "- Differentiator: $(pilot_value "$id" differentiator)" + echo "- Boundary: $(integration_value "$id" boundary)" + echo "- Scope: $(integration_value "$id" scope)" echo "- Result: **$status**" - echo "- Automated clean integration/build time: ${elapsed}s" - echo "- Manual mapping removed: $(pilot_value "$id" manual_mapping_removed)" - echo "- Dependency conflicts: $(pilot_value "$id" dependency_conflicts)" - if [[ "$status" == "PASS" ]]; then - echo "- Missing Codes API: None observed." - else - echo "- Missing Codes API: unresolved; inspect the failed build before classifying." - fi - echo "- Expected identity: \`$(pilot_value "$id" expected_identity)\`" - echo "- Custom Codes library patches: 0" + echo "- Clean build/test time: ${elapsed}s" + echo "- Dependency notes: $(integration_value "$id" dependency_notes)" + echo "- Expected identity: \`$(integration_value "$id" expected_identity)\`" echo echo "Patch statistics:" echo echo '```text' - git -C "$patch_dir" apply --stat "$patch_file" + git -C "$checkout" apply --stat "$patch_file" echo '```' echo echo "Build log: \`$(basename "$log")\`" @@ -142,24 +124,24 @@ run_irp() { run_patient() { local id="patient-mgmt-microservices" - local repo commit patch_dir patch_file start end elapsed status log + local repo commit checkout patch_file start end elapsed status log - repo="$(pilot_value "$id" repository)" - commit="$(pilot_value "$id" commit)" - patch_file="$root_dir/$(pilot_value "$id" patch)" - patch_dir="$work_dir/$id" + repo="$(integration_value "$id" repository)" + commit="$(integration_value "$id" commit)" + patch_file="$root_dir/$(integration_value "$id" patch)" + checkout="$work_dir/$id" log="$work_dir/$id.log" - git clone --quiet --filter=blob:none --no-checkout "$repo" "$patch_dir" - git -C "$patch_dir" checkout --quiet --detach "$commit" - git -C "$patch_dir" apply --check "$patch_file" - git -C "$patch_dir" apply "$patch_file" + git clone --quiet --filter=blob:none --no-checkout "$repo" "$checkout" + git -C "$checkout" checkout --quiet --detach "$commit" + git -C "$checkout" apply --check "$patch_file" + git -C "$checkout" apply "$patch_file" start="$(date +%s)" status="PASS" if ! ( - cd "$patch_dir/patient-service" + cd "$checkout/patient-service" bash ./mvnw \ --batch-mode \ --no-transfer-progress \ @@ -173,7 +155,7 @@ run_patient() { dependency:tree \ "-Dincludes=io.github.aalsanie:*" - cd "$patch_dir/billing-service" + cd "$checkout/billing-service" bash ./mvnw \ --batch-mode \ --no-transfer-progress \ @@ -199,24 +181,17 @@ run_patient() { echo echo "- Repository: \`$repo\`" echo "- Commit: \`$commit\`" - echo "- Boundary: $(pilot_value "$id" boundary)" - echo "- Differentiator: $(pilot_value "$id" differentiator)" + echo "- Boundary: $(integration_value "$id" boundary)" + echo "- Scope: $(integration_value "$id" scope)" echo "- Result: **$status**" - echo "- Automated clean integration/build time: ${elapsed}s" - echo "- Manual mapping removed: $(pilot_value "$id" manual_mapping_removed)" - echo "- Dependency conflicts: $(pilot_value "$id" dependency_conflicts)" - if [[ "$status" == "PASS" ]]; then - echo "- Missing Codes API: None observed." - else - echo "- Missing Codes API: unresolved; inspect the failed build before classifying." - fi - echo "- Expected identity: \`$(pilot_value "$id" expected_identity)\`" - echo "- Custom Codes library patches: 0" + echo "- Clean build/test time: ${elapsed}s" + echo "- Dependency notes: $(integration_value "$id" dependency_notes)" + echo "- Expected identity: \`$(integration_value "$id" expected_identity)\`" echo echo "Patch statistics:" echo echo '```text' - git -C "$patch_dir" apply --stat "$patch_file" + git -C "$checkout" apply --stat "$patch_file" echo '```' echo echo "Build log: \`$(basename "$log")\`" @@ -228,10 +203,10 @@ run_irp run_patient { - echo "## Gate" + echo "## Result" echo if [[ "$overall" -eq 0 ]]; then - echo "**PASS** — both independent external integrations succeeded from Maven Central without custom Codes library patches." + echo "**PASS** — both external integrations built successfully and verified the expected boundary identity from Maven Central." else echo "**FAIL** — at least one external integration failed." fi