8.8 KiB
Integrating a project with Trace receipts
Trace records two different facts:
- a build receipt says what CI successfully produced;
- a deployment receipt says what a host actually started and verified.
Keeping those separate prevents a green image build from being presented as a successful production deployment.
source commit → shared runner → immutable image digests → build receipt
↓
deploy host → pull exact images → start → health checks → deployment receipt
↓
Trace public projection / private history
The goonk integration is the reference implementation. Trace does not need access to any production Git repository.
1. Register the project
Add ops/project.json to the producer repository:
{
"schemaVersion": 1,
"project": "retur",
"displayName": "Retur",
"repository": "explewd/retur",
"components": ["web"],
"environments": ["production"],
"publicUrl": "https://retur.games.goonk.se"
}
project is the stable machine ID. Use the same value in CI, Trace's token mapping, deployment receipts, and public API requests.
2. Create a project-specific credential
Generate a separate random token for every producer:
openssl rand -hex 32
Configure the Trace runtime through keep:
TRACE_RECEIPT_TOKENS=goonk:<goonk-token>,retur:<retur-token>,wisp:<wisp-token>
Configure the producer repository's Gitea Actions secrets:
TRACE_EVENTS_URL=https://trace.dev.xplwd.com/api/v1/events
TRACE_RECEIPT_TOKEN=<only this project's raw token>
TRACE_EVENTS_URL may be an organization-level secret. TRACE_RECEIPT_TOKEN must remain per-project. Do not include the project: prefix in the repository secret.
3. Generate a build receipt on the shared runner
Generate the receipt after images have been pushed, when their immutable digests are available:
{
"schemaVersion": 1,
"eventType": "build",
"eventId": "retur-build-713-17b6b042e751",
"project": "retur",
"occurredAt": "2026-07-20T19:35:13.180Z",
"result": "success",
"source": {
"repository": "explewd/retur",
"commit": "17b6b042e751b3158e4ab54fc3b318d104b581a1",
"branch": "main"
},
"ci": {
"system": "gitea-actions",
"runId": "713"
},
"artifacts": [
{
"component": "web",
"digest": "sha256:<immutable-image-digest>"
}
],
"verification": {
"build": "success"
}
}
Event IDs must be deterministic for a single CI run. Trace treats repeat submissions of the same event ID as successful duplicates.
The goonk helper at scripts/generate-build-receipt.mjs demonstrates deriving source and runner identity. It currently assumes web and optional api components; adapt the component list for the producer rather than falsely reporting nonexistent artifacts.
4. Preserve and submit the receipt
Use upload-artifact@v3 with the current Gitea Actions installation. Version 4 requires GitHub's newer artifact service and fails here.
- name: Preserve build receipt
uses: actions/upload-artifact@v3
with:
name: operational-build-receipt
path: artifacts/build-receipt.json
- name: Submit build receipt to Trace
if: always()
continue-on-error: true
env:
TRACE_EVENTS_URL: ${{ secrets.TRACE_EVENTS_URL }}
TRACE_RECEIPT_TOKEN: ${{ secrets.TRACE_RECEIPT_TOKEN }}
run: |
if [[ ! -f artifacts/build-receipt.json ]]; then
echo "No build receipt was generated."
exit 0
fi
curl --fail-with-body --retry 2 \
-H "Authorization: Bearer ${TRACE_RECEIPT_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @artifacts/build-receipt.json \
"${TRACE_EVENTS_URL}"
Trace being unavailable should not invalidate a good application image. Preserve the artifact and retry later. Receipt generation failing because its inputs are malformed is different and should be visible in CI.
5. Embed build identity when useful
Web applications may bake a safe manifest into the image before building:
{
"schemaVersion": 1,
"project": "retur",
"commit": "17b6b042e751b3158e4ab54fc3b318d104b581a1",
"branch": "main",
"builtAt": "2026-07-20T19:34:03.496Z",
"runId": "713"
}
This proves which source the running artifact identifies as. It normally cannot contain final image digests because those do not exist until after the image is built; the finalized Trace receipt remains the authority for digests.
Never embed credentials, private registry locations, runner identities, or raw receipt payloads in a public manifest.
6. Emit a deployment receipt from the deploy host
The deploy host should:
- pull the intended immutable image or known SHA tag;
- reconcile Compose;
- run real application health checks;
- submit a deployment receipt only after the result is known.
The deployment host does not need a Git checkout. It may retain or download the build receipt and change only the deployment fields:
{
"schemaVersion": 1,
"eventType": "deployment",
"eventId": "retur-production-17b6b04-20260720T194200Z",
"project": "retur",
"environment": "production",
"occurredAt": "2026-07-20T19:42:00Z",
"result": "success",
"source": {
"repository": "explewd/retur",
"commit": "17b6b042e751b3158e4ab54fc3b318d104b581a1",
"branch": "main"
},
"artifacts": [
{
"component": "web",
"digest": "sha256:<same-digest-as-build>"
}
],
"verification": {
"health": "healthy"
}
}
Submit it to the same /api/v1/events endpoint using the same project-scoped bearer token. A failed deployment or rollback should also create an event with an honest result; absence of a receipt means unknown, never success.
For now this last POST belongs in the actual deployment wrapper. Do not require an application repository or ad-hoc Node script on a Compose-only production host.
7. Read the result
Private operational history is visible at:
https://trace.dev.xplwd.com/events
It uses Trace's Basic Auth and includes raw private receipts.
The public allowlisted projection is:
curl https://trace.dev.xplwd.com/api/v1/public/projects/retur/latest
Example:
{
"project": "retur",
"deployment": {
"schemaVersion": 1,
"eventType": "deployment",
"project": "retur",
"environment": "production",
"commit": "17b6b042e751b3158e4ab54fc3b318d104b581a1",
"result": "success",
"health": "healthy",
"occurredAt": "2026-07-20T19:42:00Z"
}
}
The projection never includes the stored raw payload, tokens, internal hostnames, logs, or full registry references.
Recommended health checks
Choose checks that prove the deployed application shape, not merely that a container process exists.
| Project shape | Minimum checks |
|---|---|
| Static site/game | homepage, essential asset, embedded commit |
| Web + API | homepage, API health, embedded commit |
| Stateful service | HTTP health, database access, safe read/write probe if appropriate |
| CLI release | tests, packaged artifact checksum; use a release event rather than deployment |
Keep detailed responses private. The public receipt needs only a bounded result such as healthy, degraded, or failed.
Verification checklist
After onboarding a project:
- The workflow artifact contains the correct commit and immutable digests.
- Reposting the same build returns
duplicate: true. /eventsshows the build separately from deployments.- Before deployment, the public endpoint returns
deployment: null. - After deployment, the public endpoint reports the deployed commit and health.
- The embedded build identity matches the deployment receipt's commit.
- Removing Trace connectivity does not stop the application from serving its static/core experience.
Security boundaries
- One token per project; a
returtoken cannot submit asgoonk. - Store runtime token mappings in
keep, not Git. - Gitea secrets exist only in runner jobs; deploy hosts need their own
keep-supplied token. - Trace's Basic Auth password is unrelated to bearer-token ingestion.
TRACE_REPOSis optional and should be empty in production when no deliberate read-only mirrors exist.- Publication to
/failuresremains a separate human review and redaction process.
Onboarding summary
□ add ops/project.json
□ create project token and add it to Trace through keep
□ add the raw token to that repository's Gitea secrets
□ generate a versioned build receipt after image push
□ preserve it with upload-artifact@v3
□ submit it best-effort to Trace
□ embed safe build identity if the application can expose it
□ add post-deploy health checks
□ submit deployment receipts from the real deploy mechanism
□ verify private history and public projection