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 no stella.yaml.sha256 exist, the aggregate stella.yaml is still at version: 0.0.1 and is flagged x-stellaops-contract-status: contains-draft-stubs, the SDK generators live under src/Tools/StellaOps.Sdk.Generator/ (not src/Sdk/...), and several referenced helper scripts and the tools/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.

ServiceSource SpecPath / Schema NamespaceContract status
Authorityauthority/openapi.yaml/authority…, authority.*implemented
Scannerscanner/openapi.yaml/scanner…, scanner.*implemented
Export Centerexport-center/openapi.yaml/export-center…, export-center.*draft stub
Graphgraph/openapi.yaml/graph…, graph.*draft stub
Orchestratorjobengine/openapi.yaml/jobengine…, jobengine.*draft stub
Policypolicy/openapi.yaml (+ policy/exceptions.yaml)/policy…, policy.*draft stub
Schedulerscheduler/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 contains scaffold) and records them on the aggregate as x-stellaops-contract-status: contains-draft-stubs plus an x-stellaops-draft-services list. Draft stubs are not authoritative shipped contracts. Only the services whose openapi.yaml is 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), and stella.yaml.sha256 is 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:

ArtifactLocationPurpose
Tagged specapi/v{version} git tag (proposed — not yet created)Version reference
SHA256 filestella.yaml.sha256 (proposed — not yet committed)Hash verification
Changelogsrc/Api/StellaOps.Api.OpenApi/CHANGELOG.mdAdditive / breaking operations
Compat baselinesrc/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yamlFrozen 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):

LanguageConfigGeneratorOutput
TypeScriptts/config.yamlts/generate-ts.sh (typescript-fetch)ESM/CJS with typed errors
Pythonpython/config.yamlpython/generate-python.shsync/async clients, type hints
Gogo/config.yamlgo/generate-go.shcontext-first API
Javajava/config.yamljava/generate-java.shbuilder pattern, OkHttp

Path correction: The generator tree is src/Tools/StellaOps.Sdk.Generator/, not src/Sdk/StellaOps.Sdk.Generator/. The latter does not exist on disk. Note that the root package.json sdk:smoke:* scripts still reference the stale src/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.1 and marked optional (used only for post-process hooks), not 22.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; see src/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.yaml invocation is not valid — neither the scripts/__fixtures__/api-compat/ fixtures directory nor those flags exist. The compatibility diff is driven against the committed baseline src/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml.

NOT IMPLEMENTED: scripts/api-compat-diff.mjs (and the sibling api-compat-diff.test.mjs, api-changelog.mjs, api-example-coverage.mjs) referenced by package.json are absent from scripts/, so npm run api:compat / api:changelog / api:examples currently fail. Only api:compose (the composer) and api:lint (Spectral) resolve to real files.

Version Matrix

API VersionSDK VersionsStatus
0.0.1-Current (unfrozen)
0.1.0-alphaTS/Py/Go/Java alphaTarget freeze

Freeze Checklist

Before SDK generation can proceed:


Current Freeze Status

Pending Actions

ActionOwnerDueStatus
Compute SHA256 for stella.yamlAPI Governance Guild2025-12-06TODO
Create api/v0.1.0-alpha tagAPI Governance Guild2025-12-06TODO
Update SDKGEN configs with hashSDK Generator Guild2025-12-06TODO

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

  1. Propose: Open PR with spec changes
  2. Review: API Governance Guild reviews for breaking changes
  3. Test: Run api:lint and api:compat
  4. Merge: Merge to main
  5. Freeze: Tag and compute SHA256 when ready for SDK

Stakeholders


Signing Keys

Development Key (Proposed — NOT present in repo)

NOT IMPLEMENTED: The tools/cosign/ directory does not exist in this repository (tools/ currently holds only slntools/; the elk-stress harness now lives at src/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:

FilePurpose
tools/cosign/cosign.dev.keyPrivate key (password: stellaops-dev)
tools/cosign/cosign.dev.pubPublic 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:

Key Resolution Order

  1. COSIGN_KEY_FILE environment variable
  2. COSIGN_PRIVATE_KEY_B64 (decoded to temp file)
  3. tools/cosign/cosign.key (production drop-in)
  4. tools/cosign/cosign.dev.key (only if COSIGN_ALLOW_DEV_KEY=1)

Reference


Changelog

VersionDateAuthorChanges
1.0.02025-12-05API Governance GuildInitial contract
1.0.12026-05-30Doc ReconciliationVerified 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.