CONTRACT-API-GOVERNANCE-BASELINE-012: Aggregate OpenAPI Spec & SDK Generation
Status: Published (process spec — freeze not yet executed) Version: 1.0.0 Published: 2025-12-05 Owners: API Governance Guild, SDK Generator Guild Unblocks: SDKGEN-63-001, SDKGEN-63-002, SDKGEN-63-003, SDKGEN-63-004, SDKGEN-64-001, SDKGEN-64-002
Reconciliation note (verified against source 2026-05-30): This document describes a proposed freeze/versioning process. As of verification, no
api/*git tag and nostella.yaml.sha256exist, the aggregatestella.yamlis still atversion: 0.0.1and is flaggedx-stellaops-contract-status: contains-draft-stubs, the SDK generators live undersrc/Tools/StellaOps.Sdk.Generator/(notsrc/Sdk/...), and several referenced helper scripts and thetools/cosign/key set are not present. Items that are aspirational are marked inline as NOT IMPLEMENTED or proposed.
Overview
This contract defines the aggregate OpenAPI specification freeze process, versioning rules, and SHA-256 commitment mechanism that enable deterministic SDK generation across TypeScript, Python, Go, and Java targets.
Audience: the API Governance and SDK Generator guilds, plus anyone wiring a downstream SDK to the StellaOps API. This is a process spec; the freeze it describes has not yet been executed, so several steps below are marked NOT IMPLEMENTED or proposed — treat those as the intended design, not current behaviour.
Aggregate Specification
Source Location
src/Api/StellaOps.Api.OpenApi/stella.yaml
Composition Process
The aggregate spec is generated by compose.mjs (readServiceSpecs() / mergeSpecs()) from per-service specs. The composer discovers every <dir>/openapi.yaml under src/Api/StellaOps.Api.OpenApi/, sorts the directories, and namespaces each service’s paths as /<dir>{originalPath} and each component schema as <dir>.<SchemaName>. The namespace prefix is therefore the directory name, not a separately configured tag.
| Service | Source Spec | Path / Schema Namespace | Contract status |
|---|---|---|---|
| Authority | authority/openapi.yaml | /authority…, authority.* | implemented |
| Scanner | scanner/openapi.yaml | /scanner…, scanner.* | implemented |
| Export Center | export-center/openapi.yaml | /export-center…, export-center.* | draft stub |
| Graph | graph/openapi.yaml | /graph…, graph.* | draft stub |
| Orchestrator | jobengine/openapi.yaml | /jobengine…, jobengine.* | draft stub |
| Policy | policy/openapi.yaml (+ policy/exceptions.yaml) | /policy…, policy.* | draft stub |
| Scheduler | scheduler/openapi.yaml | /scheduler…, scheduler.* | draft stub |
Note (OAS-TRUTH-001, 2026-04-27): The composer detects scaffolded/stub specs (
isDraftStubSpec()— title contains(stub)or description containsscaffold) and records them on the aggregate asx-stellaops-contract-status: contains-draft-stubsplus anx-stellaops-draft-serviceslist. Draft stubs are not authoritative shipped contracts. Only the services whoseopenapi.yamlis a full contract (currently Authority and Scanner) are excluded from that list. SDK consumers must treat the draft-stub services as unstable.
Current Version
The composer emits this header (verbatim from mergeSpecs() / committed stella.yaml):
openapi: 3.1.0
info:
title: StellaOps Aggregate API
version: 0.0.1
description: Composed OpenAPI from per-service specs. This file is generated by
compose.mjs and includes draft stub service specs that are not authoritative
shipped contracts.
contact:
name: StellaOps API Guild
email: api@stella-ops.local
x-stellaops-contract-status: contains-draft-stubs
x-stellaops-draft-services:
- export-center
- graph
- jobengine
- policy
- scheduler
When no draft stubs are present the composer switches the description to the shorter “generated by compose.mjs.” form and sets x-stellaops-contract-status: implemented with an empty draft list.
Freeze Process
1. Version Tagging
When freezing for SDK generation:
# Compute SHA256 of aggregate spec
sha256sum src/Api/StellaOps.Api.OpenApi/stella.yaml > stella.yaml.sha256
# Tag the commit
git tag -a api/v0.1.0-alpha -m "API freeze for SDK Wave B generation"
Status (NOT YET DONE): No
api/*git tag exists in the repository yet (git tag -l 'api/*'is empty), andstella.yaml.sha256is not committed. This freeze step is the proposed process, not a completed action. See Current Freeze Status below.
2. SHA256 Commitment
SDK generators validate the spec hash before generation. Each generate-<lang>.sh reads the spec path from STELLA_OAS_FILE (required), computes its SHA256 (via sha256sum or shasum -a 256), and, when STELLA_OAS_EXPECTED_SHA256 is set, aborts on mismatch:
# Required: path to the spec to generate from
export STELLA_OAS_FILE=src/Api/StellaOps.Api.OpenApi/stella.yaml
# Optional hash guard (enforced only when set)
export STELLA_OAS_EXPECTED_SHA256="<sha256-hash>"
# Generator logic (from ts/generate-ts.sh, mirrored in python/go/java):
# spec_hash=$(sha256sum "$STELLA_OAS_FILE" | awk '{print $1}')
# if [ -n "$STELLA_OAS_EXPECTED_SHA256" ] \
# && [ "$STELLA_OAS_EXPECTED_SHA256" != "$spec_hash" ]; then
# echo "Spec hash mismatch: expected ... but got ..." >&2
# exit 1
# fi
The output directory defaults to <generator-root>/out/<language> and is overridable via STELLA_SDK_OUT.
3. Published Artifacts
On freeze, publish:
| Artifact | Location | Purpose |
|---|---|---|
| Tagged spec | api/v{version} git tag (proposed — not yet created) | Version reference |
| SHA256 file | stella.yaml.sha256 (proposed — not yet committed) | Hash verification |
| Changelog | src/Api/StellaOps.Api.OpenApi/CHANGELOG.md | Additive / breaking operations |
| Compat baseline | src/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml | Frozen reference for breaking-change diffs |
The changelog committed today lists Additive Operations and Breaking Operations (currently “None”) rather than the SemVer-style entries shown in the Changelog section at the foot of this document. There is no root-level
CHANGELOG-api.md.
SDK Generation Contract
All four language generators live under src/Tools/StellaOps.Sdk.Generator/<lang>/ (config + generate-<lang>.sh + test_generate_<lang>.sh):
| Language | Config | Generator | Output |
|---|---|---|---|
| TypeScript | ts/config.yaml | ts/generate-ts.sh (typescript-fetch) | ESM/CJS with typed errors |
| Python | python/config.yaml | python/generate-python.sh | sync/async clients, type hints |
| Go | go/config.yaml | go/generate-go.sh | context-first API |
| Java | java/config.yaml | java/generate-java.sh | builder pattern, OkHttp |
Path correction: The generator tree is
src/Tools/StellaOps.Sdk.Generator/, notsrc/Sdk/StellaOps.Sdk.Generator/. The latter does not exist on disk. Note that the rootpackage.jsonsdk:smoke:*scripts still reference the stalesrc/Sdk/StellaOps.Sdk.Generator/...path, so they will not resolve as written — track this as a repo inconsistency, not a doc claim to copy.
Toolchain Lock
The actual content-addressed lock is src/Tools/StellaOps.Sdk.Generator/toolchain.lock.yaml. It pins SHA256-verified artifacts and per-language template bundles (not loose version ranges):
# toolchain.lock.yaml (artifacts)
artifacts:
- name: openapi-generator-cli # version 7.4.0, tools/openapi-generator-cli-7.4.0.jar
- name: temurin-jdk # version 21.0.1, tools/jdk-21.0.1.tar.gz
- name: node # version 20.11.1, optional (post-process hooks only)
templates: # typescript / python / go / java, each SHA256-pinned
repro:
timezone: "UTC"
locale: "C"
line_endings: "LF"
file_mode: "0644"
Corrections vs. earlier drafts: Node is pinned to
20.11.1and marked optional (used only for post-process hooks), not22.x. There are no Python or Go version entries in the lock — those runtimes are not content-addressed here. The generator JAR (7.4.0) and Temurin JDK (21.0.1) pins are correct. Every artifact and template bundle carries a recorded SHA256 that is asserted before each run; seesrc/Tools/StellaOps.Sdk.Generator/TOOLCHAIN.md.
Hash Guard Implementation
Each generator emits a .oas.sha256 provenance file into its output directory. The hash written is the actually computed spec hash (spec_hash), in sha256sum-compatible <hash> <basename> form — not the expected-hash env var:
# From ts/generate-ts.sh (output_dir defaults to out/typescript, override via STELLA_SDK_OUT):
printf "%s %s\n" "$spec_hash" "$(basename "$STELLA_OAS_FILE")" > "$output_dir/.oas.sha256"
Versioning Rules
Semantic Versioning
MAJOR.MINOR.PATCH[-PRERELEASE]
- MAJOR: Breaking API changes
- MINOR: New endpoints/fields (backwards compatible)
- PATCH: Bug fixes, documentation
- PRERELEASE: alpha, beta, rc
Breaking Change Detection
The root package.json defines api:compat as node scripts/api-compat-diff.mjs and api:compat:test as node scripts/api-compat-diff.test.mjs:
# Run API compatibility check
npm run api:compat
Flag/path corrections: The earlier
--old scripts/__fixtures__/api-compat/old.yaml --new src/Api/.../stella.yamlinvocation is not valid — neither thescripts/__fixtures__/api-compat/fixtures directory nor those flags exist. The compatibility diff is driven against the committed baselinesrc/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml.NOT IMPLEMENTED:
scripts/api-compat-diff.mjs(and the siblingapi-compat-diff.test.mjs,api-changelog.mjs,api-example-coverage.mjs) referenced bypackage.jsonare absent fromscripts/, sonpm run api:compat/api:changelog/api:examplescurrently fail. Onlyapi:compose(the composer) andapi:lint(Spectral) resolve to real files.
Version Matrix
| API Version | SDK Versions | Status |
|---|---|---|
| 0.0.1 | - | Current (unfrozen) |
| 0.1.0-alpha | TS/Py/Go/Java alpha | Target freeze |
Freeze Checklist
Before SDK generation can proceed:
- [ ] All per-service specs pass
npm run api:lint(Spectral) - [ ] Aggregate composition succeeds (
npm run api:compose/node src/Api/StellaOps.Api.OpenApi/compose.mjs) - [ ] Breaking change review completed (against
baselines/stella-baseline.yaml) - [ ] SHA256 computed and committed (
stella.yaml.sha256— not yet present) - [ ] Git tag created (
api/v{version}— not yet present) - [ ] Changelog entry added (
src/Api/StellaOps.Api.OpenApi/CHANGELOG.md) - [ ] SDK generator configs updated with hash
Current Freeze Status
Pending Actions
| Action | Owner | Due | Status |
|---|---|---|---|
| Compute SHA256 for stella.yaml | API Governance Guild | 2025-12-06 | TODO |
| Create api/v0.1.0-alpha tag | API Governance Guild | 2025-12-06 | TODO |
| Update SDKGEN configs with hash | SDK Generator Guild | 2025-12-06 | TODO |
Immediate Unblock Path
To immediately unblock SDK generation:
# 1. Compute current spec hash
cd src/Api/StellaOps.Api.OpenApi
SHA=$(sha256sum stella.yaml | cut -d' ' -f1)
echo "Current SHA256: $SHA"
# 2. Create hash file
echo "$SHA stella.yaml" > stella.yaml.sha256
# 3. Tag for SDK generation
git add stella.yaml.sha256
git commit -m "chore(api): freeze aggregate spec for SDK Wave B"
git tag -a api/v0.1.0-alpha -m "API freeze for SDK generation"
# 4. Set environment for generators
export STELLA_OAS_EXPECTED_SHA256="$SHA"
SDK Generation Commands
Once freeze is complete:
# Set the spec (required by every generate-*.sh)
export STELLA_OAS_FILE="$PWD/src/Api/StellaOps.Api.OpenApi/stella.yaml"
# TypeScript
cd src/Tools/StellaOps.Sdk.Generator/ts
./generate-ts.sh
# Python
cd ../python
./generate-python.sh
# Go
cd ../go
./generate-go.sh
# Java
cd ../java
./generate-java.sh
# Run all smoke tests (api:compat / sdk:smoke wiring in package.json points at
# the stale src/Sdk/... path — invoke the test_generate_*.sh scripts directly
# under src/Tools/StellaOps.Sdk.Generator/<lang>/ until that is corrected)
npm run sdk:smoke
Governance
Change Process
- Propose: Open PR with spec changes
- Review: API Governance Guild reviews for breaking changes
- Test: Run
api:lintandapi:compat - Merge: Merge to main
- Freeze: Tag and compute SHA256 when ready for SDK
Stakeholders
- API Governance Guild: Spec ownership, breaking change review
- SDK Generator Guild: Generation toolchain, language packs
- Platform Security: Signing key provisioning (SDKREL-63-001)
Signing Keys
Development Key (Proposed — NOT present in repo)
NOT IMPLEMENTED: The
tools/cosign/directory does not exist in this repository (tools/currently holds onlyslntools/; the elk-stress harness now lives atsrc/Workflow/__Libraries/StellaOps.Workflow.Renderer.ElkJs/tools/elk-stress/). The files and environment variables below describe the intended cosign signing convention; the dev key, public key, and production drop-in must be provisioned before any of these commands will work. Treat this section as a forward design spec, not an “available now” capability.
A development signing key is intended for staging/testing:
| File | Purpose |
|---|---|
tools/cosign/cosign.dev.key | Private key (password: stellaops-dev) |
tools/cosign/cosign.dev.pub | Public key for verification |
Usage for SDK staging:
# Set environment for SDK signing
export COSIGN_KEY_FILE=tools/cosign/cosign.dev.key
export COSIGN_PASSWORD=stellaops-dev
export COSIGN_ALLOW_DEV_KEY=1
# Or use CI workflow with allow_dev_key=1
Production Keys (Pending)
Production signing requires:
- Sovereign crypto key provisioning (Action #7)
COSIGN_PRIVATE_KEY_B64CI secret- Optional
COSIGN_PASSWORDfor encrypted keys
Key Resolution Order
COSIGN_KEY_FILEenvironment variableCOSIGN_PRIVATE_KEY_B64(decoded to temp file)tools/cosign/cosign.key(production drop-in)tools/cosign/cosign.dev.key(only ifCOSIGN_ALLOW_DEV_KEY=1)
Reference
- Aggregate spec:
src/Api/StellaOps.Api.OpenApi/stella.yaml - Composition script:
src/Api/StellaOps.Api.OpenApi/compose.mjs - API changelog:
src/Api/StellaOps.Api.OpenApi/CHANGELOG.md - Compat baseline:
src/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml - Toolchain doc + lock:
src/Tools/StellaOps.Sdk.Generator/TOOLCHAIN.md,src/Tools/StellaOps.Sdk.Generator/toolchain.lock.yaml - SDK generators:
src/Tools/StellaOps.Sdk.Generator/{ts,python,go,java}/
Changelog
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2025-12-05 | API Governance Guild | Initial contract |
| 1.0.1 | 2026-05-30 | Doc Reconciliation | Verified against source. Added Scanner to the composition table and corrected the “Tag Prefix” column to the directory-based path/schema namespace (jobengine.*, not orchestrator.*). Documented x-stellaops-contract-status/x-stellaops-draft-services draft-stub markers (OAS-TRUTH-001). Corrected SDK generator base path src/Sdk/... → src/Tools/StellaOps.Sdk.Generator/. Corrected toolchain lock pins (node 20.11.1 optional; no python/go entries; SHA256-pinned artifacts/templates). Corrected hash-guard mechanics (STELLA_OAS_FILE + computed .oas.sha256). Corrected changelog location and compat baseline. Flagged missing scripts (api-compat-diff.mjs, api-changelog.mjs, api-example-coverage.mjs), absent api/* tag / stella.yaml.sha256, and absent tools/cosign/ keys as NOT IMPLEMENTED. |
