Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:

jobs:
build:
permissions:
contents: read
runs-on: ubuntu-latest

steps:
Expand All @@ -24,5 +26,5 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build and Test
run: ./gradlew build
- name: Build, Test, and Verify Release Candidate
run: ./gradlew build verifyReleaseCandidate
Comment thread
coderabbitai[bot] marked this conversation as resolved.
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,17 @@ Verify the published Spring AI adapter API and the starter with one explicitly s
./gradlew verifyPublishedIntegrationConsumer
```

Run the complete release-candidate gate, including module-local staging and
binary/source/Javadoc/POM/module-metadata artifact checks:

```bash
./gradlew verifyReleaseCandidate
```

Use `-PprojectVersion=0.1.0` for the signed release-candidate path. Non-snapshot
staging requires signing material; the default `0.0.1-SNAPSHOT` path is
secretless.

Run sample app after implementation work:

```bash
Expand Down Expand Up @@ -436,6 +447,12 @@ Stage and deploy a Central release:

## Update History

### 2026-08-26

- Added root publication aggregation and a release-candidate verification gate
that stages every public module and checks the complete artifact/signature
set for the selected version.

### 2026-08-25

- Connected supported Spring AI 2.0 non-streaming `ChatClient` requests to text-scope adaptation, model/output resolution, conservative preflight, atomic reservation, in-flight tracking, provider usage extraction, and `ReservationAccounting` reconciliation in one around-call lifecycle.
Expand Down
82 changes: 82 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,87 @@ subprojects {
}
}

def stagingPublicationTasks = publishedModuleDescriptions.keySet().collect {
":${it}:publishAllPublicationsToStagingRepository"
}

tasks.register('publishAllPublicationsToStagingRepository') {
group = 'publishing'
description = 'Publishes every public Token Pilot module to its module-local staging repository.'
dependsOn stagingPublicationTasks
}

tasks.register('verifyReleaseCandidate') {
group = 'verification'
description = 'Runs release checks, stages every public module, and verifies the complete artifact set.'
dependsOn subprojects.collect { "${it.path}:test" }
dependsOn tasks.named('check')
dependsOn tasks.named('publishAllPublicationsToStagingRepository')

doLast {
def version = project.version.toString()
def requiredArtifacts = [
'binary JAR': { String name ->
name.endsWith('.jar') &&
!name.endsWith('-sources.jar') &&
!name.endsWith('-javadoc.jar')
},
'sources JAR': { String name -> name.endsWith('-sources.jar') },
'Javadoc JAR': { String name -> name.endsWith('-javadoc.jar') },
'POM': { String name -> name.endsWith('.pom') },
'Gradle module metadata': { String name -> name.endsWith('.module') }
]
def requiredSignatures = [
'binary JAR signature': { String name ->
name.endsWith('.jar.asc') &&
!name.endsWith('-sources.jar.asc') &&
!name.endsWith('-javadoc.jar.asc')
},
'sources JAR signature': { String name -> name.endsWith('-sources.jar.asc') },
'Javadoc JAR signature': { String name -> name.endsWith('-javadoc.jar.asc') },
'POM signature': { String name -> name.endsWith('.pom.asc') },
'Gradle module metadata signature': { String name -> name.endsWith('.module.asc') }
]

publishedModuleDescriptions.keySet().each { moduleName ->
def moduleProject = project(":${moduleName}")
def versionDirectory = moduleProject.file(
"build/staging-deploy/cloud/token-pilot/${moduleName}/${version}"
)
if (!versionDirectory.isDirectory()) {
throw new GradleException(
"${moduleName} staging directory is missing for version ${version}: ${versionDirectory}"
)
}

def artifactNames = versionDirectory.listFiles()
?.findAll { it.isFile() }
?.collect { it.name } ?: []
def missingArtifacts = requiredArtifacts.findAll { label, matcher ->
!artifactNames.any { artifactName -> matcher(artifactName) }
}.keySet()
if (!missingArtifacts.isEmpty()) {
throw new GradleException(
"${moduleName}:${version} is missing published artifacts: ${missingArtifacts}"
)
}

if (!version.endsWith('-SNAPSHOT')) {
def missingSignatures = requiredSignatures.findAll { label, matcher ->
!artifactNames.any { artifactName -> matcher(artifactName) }
}.keySet()
if (!missingSignatures.isEmpty()) {
throw new GradleException(
"${moduleName}:${version} is missing release signatures: ${missingSignatures}"
)
}
}
}

println "Verified ${publishedModuleDescriptions.size()} Token Pilot release candidates at ${version}."
}
}

def publicationPomTasks = publishedModuleDescriptions.keySet().collect {
":${it}:generatePomFileForMavenJavaPublication"
}
Expand Down Expand Up @@ -346,6 +427,7 @@ tasks.register('verifyPublicationMetadata') {
.asFile
def pom = new XmlSlurper(false, false).parse(pomFile)

requirePomValue(moduleName, 'version', pom.version.text(), project.version.toString())
requirePomValue(moduleName, 'name', pom.name.text(), moduleName)
requirePomValue(moduleName, 'description', pom.description.text(), moduleDescription)
requirePomValue(moduleName, 'url', pom.url.text(), 'https://github.com/tokenpliot/tokenpilot')
Expand Down
Loading