Skip to content
Closed
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
52 changes: 43 additions & 9 deletions .github/workflows/contract.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
name: Disposable API contract

on:
workflow_call:
workflow_dispatch:
pull_request:
branches:
- main
schedule:
- cron: "41 3 * * 0"

Expand All @@ -15,6 +19,7 @@ concurrency:
jobs:
contract:
name: Official Postman and SDK contract
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 55
env:
Expand Down Expand Up @@ -85,15 +90,6 @@ jobs:
done
echo "Disposable API did not become healthy." >&2
exit 1
- name: Run both locked official Postman collections
uses: ./.github/actions/postman-contract
with:
collections: |
Fleetbase API
Fleetbase Core API
postman-ref: ${{ env.POSTMAN_REF }}
postman-api-key: ${{ env.POSTMAN_API_KEY }}
github-token: ${{ env.SOURCE_TOKEN }}
- name: Mint a fresh SDK smoke credential
env:
CI_CUSTOMER_IDENTITY: ci-customer@fleetbase.local
Expand Down Expand Up @@ -137,10 +133,48 @@ jobs:
tools: composer:v2
- name: Install SDK dependencies
run: composer install --working-dir=.sdk --no-interaction --prefer-dist
- name: Start the PHP SDK contract bridge
run: |
mkdir -p .sdk/build/live-contract
FLEETBASE_CONTRACT_TARGET_URL=http://127.0.0.1:8000 \
FLEETBASE_CONTRACT_NAMESPACE=v1 \
FLEETBASE_CONTRACT_STATE="$GITHUB_WORKSPACE/.sdk/build/live-contract/state.json" \
php -S 127.0.0.1:9000 .sdk/tools/live-sdk-contract-router.php \
> .sdk/build/live-contract/bridge.log 2>&1 &
echo $! > .sdk/build/live-contract/bridge.pid
for attempt in $(seq 1 20); do
if curl --fail --silent http://127.0.0.1:9000/__sdk_contract_health | grep -q '"status":"ok"'; then
exit 0
fi
sleep 1
done
echo "The PHP SDK contract bridge did not start." >&2
exit 1
- name: Run both official Postman collections through the SDK
uses: ./.github/actions/postman-contract
with:
collections: |
Fleetbase API
Fleetbase Core API
base-url: http://127.0.0.1:9000
postman-ref: ${{ env.POSTMAN_REF }}
postman-api-key: ${{ env.POSTMAN_API_KEY }}
github-token: ${{ env.SOURCE_TOKEN }}
- name: Prove every locked request invoked its SDK method
if: always()
run: php .sdk/tools/check-live-sdk-contract.php --state=.sdk/build/live-contract/state.json
- name: Run SDK cases against the same stack
run: php .sdk/tools/run-live-contract-smoke.php
env:
FLEETBASE_CONTRACT_BASE_URL: http://localhost:8000
- name: Upload SDK live-contract evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: php-sdk-live-contract
path: .sdk/build/live-contract/
if-no-files-found: error
retention-days: 30
- name: Dump stack logs on failure
if: failure()
run: docker compose logs --no-color --tail=300
Expand Down
95 changes: 67 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,71 @@
name: Release

on:
workflow_dispatch:
inputs:
version:
description: Semantic version without a v prefix
required: true
type: string
publish:
description: Publish the tag and GitHub Release after approval
required: true
default: false
type: boolean
push:
branches:
- main

permissions:
contents: read
pull-requests: read

concurrency:
group: release-${{ github.sha }}
cancel-in-progress: false

jobs:
prepare:
name: Detect merged release branch
runs-on: ubuntu-latest
outputs:
eligible: ${{ steps.release.outputs.eligible }}
version: ${{ steps.release.outputs.version }}
pull-request: ${{ steps.release.outputs.pull-request }}
steps:
- name: Resolve the merged release pull request
id: release
env:
GH_TOKEN: ${{ github.token }}
run: |
pull_request="$(gh api \
-H "Accept: application/vnd.github+json" \
"/repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" \
--jq '[.[] | select(.merged_at != null and .base.ref == "main" and (.head.ref | startswith("release/")))] | first')"
branch="$(printf '%s' "$pull_request" | jq -r '.head.ref // empty')"
number="$(printf '%s' "$pull_request" | jq -r '.number // empty')"
if [ -z "$branch" ]; then
echo "eligible=false" >> "$GITHUB_OUTPUT"
echo "No merged release/* pull request targets this main commit."
exit 0
fi
version="${branch#release/}"
version="${version#v}"
if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Release branch '$branch' must end in a semantic version." >&2
exit 1
fi
echo "eligible=true" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "pull-request=$number" >> "$GITHUB_OUTPUT"
echo "Release $version resolved from PR #$number ($branch)."

live-contract:
name: Full live SDK contract
needs: prepare
if: needs.prepare.outputs.eligible == 'true'
uses: ./.github/workflows/contract.yml
secrets: inherit

validate:
name: Validate release candidate
needs:
- prepare
- live-contract
if: needs.prepare.outputs.eligible == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
env:
RELEASE_VERSION: ${{ inputs.version }}
PUBLISH_RELEASE: ${{ inputs.publish }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Check out complete history
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -40,12 +82,7 @@ jobs:
- name: Audit locked dependencies
run: composer audit --locked --no-interaction
- name: Validate version, branch, changelog, and notes
run: |
if [ "$PUBLISH_RELEASE" = "true" ]; then
php tools/check-release-version.php "$RELEASE_VERSION"
else
php tools/check-release-version.php "$RELEASE_VERSION" --allow-non-main
fi
run: php tools/check-release-version.php "$RELEASE_VERSION"
- name: Run authoritative quality and contract gates
run: composer check
- name: Generate and enforce fresh coverage
Expand All @@ -64,7 +101,7 @@ jobs:
with:
path: .
format: cyclonedx-json
output-file: build/dist/fleetbase-php-${{ inputs.version }}.sbom.cdx.json
output-file: build/dist/fleetbase-php-${{ needs.prepare.outputs.version }}.sbom.cdx.json
upload-artifact: false
dependency-snapshot: false
- name: Generate checksums and collect evidence
Expand All @@ -76,15 +113,16 @@ jobs:
- name: Upload release-candidate artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fleetbase-php-${{ inputs.version }}-release-candidate
name: fleetbase-php-${{ needs.prepare.outputs.version }}-release-candidate
path: build/dist/
if-no-files-found: error
retention-days: 30

publish:
name: Publish approved release
if: inputs.publish == true
needs: validate
needs:
- prepare
- validate
runs-on: ubuntu-latest
timeout-minutes: 15
environment: release
Expand All @@ -94,7 +132,7 @@ jobs:
attestations: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Check out reviewed commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -103,7 +141,7 @@ jobs:
- name: Download validated release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: fleetbase-php-${{ inputs.version }}-release-candidate
name: fleetbase-php-${{ needs.prepare.outputs.version }}-release-candidate
path: build/dist
- name: Recheck release identity
run: php tools/check-release-version.php "$RELEASE_VERSION"
Expand All @@ -120,12 +158,13 @@ jobs:

verify-package:
name: Verify published Packagist install
if: inputs.publish == true
needs: publish
needs:
- prepare
- publish
runs-on: ubuntu-latest
timeout-minutes: 20
env:
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Set up PHP and Composer
uses: shivammathur/setup-php@db91e1a0e48e84637d325a7ee4d2677e146ceec4 # 2.36.0
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

- PHP 8.0 through 8.5 compatibility and PSR-18/PSR-7 transport injection.
- Typed SDK exception hierarchy, defensive list-response hydration, deterministic contract/API snapshots, hermetic tests, and modern repository guidance.
- Disposable-stack CI that executes all 220 locked Postman requests through their exact PHP SDK methods.
- Automatic validation and publication when a semantic `release/` branch is merged into `main`.

### Changed

- Relicensed unreleased 1.1.0 code to `AGPL-3.0-or-later`; released 1.0.x tags remain MIT licensed.
- Preserved the 1.0.x facade while rebuilding request, response, resource, and service internals.
- Modernized Composer dependencies and development tooling.
- Normalized order dispatch calls so `dispatch($orderId)`, `dispatchOrder($orderId)`, and the legacy parameter-array form use the same official `PATCH` endpoint.

### Deprecated

Expand All @@ -34,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Tracking-status resources resolve with the correct spelling.
- Legacy resource lifecycle hooks execute in the documented order and retain caller options.
- Order services can access the transport, and QR/signature subject paths are generated correctly.
- Order dispatch and destination actions use the HTTP verbs defined by the official API contract.
- HTTP status, malformed JSON, empty body, transport, and Fleetbase error responses are handled consistently without leaking credentials.

### Security
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ $fleetbase = new Fleetbase($_ENV['FLEETBASE_API_KEY'], [
Standard resource services retain `create()`, `update()`, `findRecord()`, `findAll()`, `query()`, `queryRecord()`, and `destroy()`. Dedicated actions use discoverable methods named after the official Postman request.

```php
$order = $fleetbase->orders->dispatchOrder([
'id' => 'order_123',
]);
$order = $fleetbase->orders->dispatch('order_123');

// The contract-named form is equivalent.
$order = $fleetbase->orders->dispatchOrder('order_123');

$response = $fleetbase->client->request('GET', 'future-endpoint', [
'limit' => 10,
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"@php tools/generate-endpoint-services.php",
"@php tools/generate-api-coverage.php",
"@php tools/generate-api-examples.php",
"git diff --exit-code -- contracts/postman-manifest.json contracts/service-map.json docs/api-coverage.md docs/api-examples.md src/Services tests/Contract"
"git diff --exit-code -- contracts/php-sdk-examples.json contracts/postman-manifest.json contracts/service-map.json docs/api-coverage.md docs/api-examples.md src/Services tests/Contract"
],
"api:snapshot": "@php tools/generate-api-snapshot.php --autoload=vendor/autoload.php --label=working-tree --output=build/contracts/public-api-current.json",
"api:compatibility": [
Expand Down
Loading
Loading