Skip to content
Open
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
21 changes: 20 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
node-version: "20"
Comment thread
kuudori marked this conversation as resolved.

- name: Install dependencies
run: npm ci
Expand All @@ -39,7 +41,24 @@ jobs:
run: |
npx spectral lint schemas/core/openapi.yaml --format github-actions --fail-severity warn

- name: Check for contract changes
id: contract
run: |
BASE_REF="${{ github.event.pull_request.base.sha || github.event.before }}"
if [ -z "$BASE_REF" ] || ! git cat-file -e "$BASE_REF" 2>/dev/null; then
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED=$(git diff --name-only "$BASE_REF" HEAD -- '*.tsp')
if [ -n "$CHANGED" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No contract files changed — skipping version bump check"
fi

- name: Check version bump
if: steps.contract.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand Down
106 changes: 106 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# HyperFleet API Spec

TypeSpec sources that generate the HyperFleet core OpenAPI specification. Published as the `hyperfleet` npm package for provider-specific repos (e.g., `hyperfleet-api-spec-gcp`). Also consumed as a Go module via `schemas/schemas.go` (`embed.FS`).

## Critical path

```bash
npm install # Install TypeSpec compiler + deps
./build-schema.sh # Build core OpenAPI spec
npx spectral lint schemas/core/openapi.yaml --fail-severity warn # Lint (matches CI strictness)
```

**IMPORTANT:** `schemas/core/openapi.yaml` is committed. CI runs `git diff --exit-code schemas/` — if you change TypeSpec sources, rebuild and commit the schema in the same PR.

**IMPORTANT:** Every PR must bump the version in `main.tsp` (the `version` field inside the `@info` decorator). CI compares against the latest release tag and blocks if unchanged or lower.

When bumping version:
1. Edit the `version` field inside the `@info` decorator in `main.tsp`
2. Run `./build-schema.sh` (auto-syncs `package.json` version — never edit `package.json` manually)
3. Update `CHANGELOG.md`: add new `## [X.Y.Z] - YYYY-MM-DD` section, update comparison links

## Source of truth

| Topic | File |
|-------|------|
| Build process | `build-schema.sh` |
| CI checks | `.github/workflows/ci.yml` |
| Release automation | `.github/workflows/release.yml`, `RELEASING.md` |
| Contributing guide | `CONTRIBUTING.md` |
| Changelog format | `CHANGELOG.md` (Keep a Changelog) |
| Spectral rules | `.spectral.yaml` |
| TypeSpec config | `tspconfig.yaml` |
| Go module embed | `schemas/schemas.go` |

## Architecture: shared vs core

```
shared/ → Cross-provider models and services (npm package)
core/ → Core-only models and internal-only services
main.tsp → Entry point (imports shared + core)
schemas/core/ → Generated output (committed)
```

**Where to put new code:**

| What | Where | Why |
|------|-------|-----|
| Models used by all providers | `shared/models/{resource}/model.tsp` | Published as npm package |
| Endpoints for external clients | `shared/services/{resource}.tsp` | Shared across provider contracts |
| Internal-only endpoints (adapters) | `core/services/{resource}-internal.tsp` | Core contract only |
| Core-specific model overrides | `core/models/{resource}/model.tsp` | Not shared |
| Provider-specific models | Separate repo (e.g., `hyperfleet-api-spec-gcp`) | Own contract |

**Directory naming:** `shared/models/` uses plural names (`clusters/`, `nodepools/`, `statuses/`) except `resource/` and `common/`. `core/models/` uses singular names (`cluster/`, `nodepool/`). Follow existing convention per directory.

## TypeSpec conventions

**IMPORTANT: Required decorators on every interface:** `@useAuth(HyperFleet.BearerAuth)`, `@tag("ResourceName")`. Every operation must have `@operationId("operationName")` and `@summary("...")`. Missing `@useAuth` causes the generated spec to omit security requirements — this was a real bug (commit `89b9f9b`).

**Service file boilerplate:**
```tsp
import "@typespec/http";
import "@typespec/openapi";
import "@typespec/openapi3";
// ... model imports ...

using Http;
using OpenAPI;

namespace HyperFleet;

@tag("Resources")
@route("/resources")
@useAuth(HyperFleet.BearerAuth)
interface Resources {
@get
@summary("List resources")
@operationId("getResources")
getResources(...QueryParams): Body<ResourceList> | Error | BadRequestResponse;
}
```

**Model files** do not declare a namespace or `using` statements — just imports and model definitions.

**Naming:**
- Resources: `Cluster`, `NodePool`, `Resource` (singular)
- Create payloads: `ClusterCreateRequest`, `ResourceCreateRequest`
- Patch payloads: `ClusterPatchRequest`, `ResourcePatchRequest`
- Lists: `ClusterList`, `ResourceList`

**Import order:** TypeSpec library imports first, then relative model/service imports.

**Example files:** Each resource has `example_*.tsp` files for `@example` decorators. Example files in `shared/models/` are imported from their resource's `model.tsp`. Example files in `core/models/` are imported from `main.tsp`. Example files do not declare a namespace.

## Boundaries

- **IMPORTANT:** Never edit files in `schemas/` or `tsp-output-core/` directly — they are generated
- `package.json` version is auto-synced by `build-schema.sh` — do not edit manually
- New service files must be imported in `main.tsp` or they won't compile into the schema

## Gotchas

- `@typespec/rest` and `@typespec/versioning` are in `package.json` but not imported in any source file — they may be transitive requirements. Don't remove without testing.
- Spectral linting in CI uses `--fail-severity warn` — all warnings are treated as errors.
- The `go.mod` at repo root exists so downstream Go services can `go get` this module and read schemas via `embed.FS`. Don't remove it.
- The `BearerAuth` model in `main.tsp` uses lowercase `"bearer"` as a workaround for `kin-openapi` library requirements.
235 changes: 1 addition & 234 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,234 +1 @@
# HyperFleet API Spec - AI Agent Context

This repository generates the HyperFleet core OpenAPI specification from TypeSpec definitions. The GCP-specific contract lives in [hyperfleet-api-spec-gcp](https://github.com/openshift-hyperfleet/hyperfleet-api-spec-gcp), which imports shared models from this repo as the `hyperfleet` npm package.

## Quick Reference

**Build commands:**
```bash
npm run build # Generate core OpenAPI spec
./build-schema.sh # Same, via script directly
```

**Validation workflow:**
```bash
npm install # Install dependencies
./build-schema.sh # Build core OpenAPI 3.0
ls -l schemas/core/openapi.yaml # Confirm output exists
```

## Key Concepts

### Repository Layout

```
shared/ # Cross-provider models and services (published as the `hyperfleet` npm package)
core/ # Core-only models and internal services
main.tsp # Entry point — imports shared + core
schemas/core/ # Generated output (committed)
```

**When adding new models:**
- Cross-provider models → `shared/models/`
- Core-only models → `core/models/`
- GCP-specific → separate repo (`hyperfleet-api-spec-gcp`)

### Public vs Internal APIs

Status endpoints are split:
- `shared/services/statuses.tsp` - GET operations (external clients)
- `core/services/statuses-internal.tsp` - PUT operations (internal adapters only) and resource force-delete

The split allows generating different API contracts per audience. Only `statuses.tsp` is imported by default.

## Code Style

### TypeSpec Conventions

**Imports first, namespace second** (applies to service and model files; example `const` files do not declare a namespace):
```typescript
import "@typespec/http";
import "../models/common/model.tsp";

namespace HyperFleet;
```

**Use decorators for HTTP semantics:**
```typescript
@route("/clusters")
interface Clusters {
@get list(): Cluster[] | Error;
@post create(@body cluster: ClusterInput): Cluster | Error;
}
```

**Model naming:**
- Resources: `Cluster`, `NodePool` (singular)
- Inputs: `ClusterInput`, `NodePoolInput`
- Provider-specific: `GCPClusterSpec`, `AWSClusterSpec`

### File Organization

```
shared/models/{resource}/
├── model.tsp # Shared model definitions
└── interfaces.tsp # Optional: shared interfaces

shared/services/
└── {resource}.tsp # Shared service endpoints

core/models/{resource}/
└── model.tsp # Core-specific models

core/services/
└── {resource}-internal.tsp # Internal-only endpoints
```

## Boundaries

**DO NOT:**
- Modify generated files in `schemas/` or `tsp-output-core/` directly
- Add dependencies without checking TypeSpec version compatibility
- Auto-generate documentation - it degrades agent performance per research
- Commit `node_modules/` or build artifacts

**DO:**
- Run `./build-schema.sh` and commit `schemas/core/openapi.yaml` with your changes
- Keep TypeSpec files focused (one resource per service file)
- Use semantic versioning for releases (automated on merge to main)

## Common Tasks

### Add a new endpoint to existing service

```typescript
// shared/services/clusters.tsp
namespace HyperFleet;

@route("/clusters")
interface Clusters {
// ... existing endpoints ...

@get
@route("/{id}/health")
getHealth(@path id: string): HealthStatus | Error;
}
```

### Add a new resource

1. Create model:
```typescript
// shared/models/health/model.tsp
import "@typespec/http";

model HealthStatus {
id: string;
state: "healthy" | "degraded" | "critical";
lastChecked: utcDateTime;
}
```

2. Create service:
```typescript
// shared/services/health.tsp
import "@typespec/http";
import "../models/health/model.tsp";
import "../models/common/model.tsp";

namespace HyperFleet;

@route("/health")
interface Health {
@get check(): HealthStatus | Error;
}
```

3. Import in `main.tsp`:
```typescript
import "./shared/services/health.tsp";
```

4. Build: `npm run build`

### Add provider-specific fields

Provider-specific models live in the provider's own repository (e.g., `hyperfleet-api-spec-gcp`). See that repo for examples of how to extend core shared models.

## Version Bump and Changelog

When bumping the version in `main.tsp`, always update `CHANGELOG.md`:

1. Keep `## [Unreleased]` at the top, then add a new version section as `## [X.Y.Z] - YYYY-MM-DD`
2. List changes under appropriate headings (`Added`, `Changed`, `Fixed`, `Removed`)
3. Update the comparison links at the bottom of the file
4. Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format

## Validation Checklist

Before submitting changes:

- [ ] Dependencies installed: `npm install`
- [ ] Core schema builds: `./build-schema.sh`
- [ ] Schema file generated: `ls schemas/core/openapi.yaml`
- [ ] No TypeSpec compilation errors (check output)
- [ ] Schema passes linting: `spectral lint schemas/core/openapi.yaml`
- [ ] Changes committed including schema update
- [ ] PR description references related issue

## Build System Details

**The build-schema.sh script:**
1. Extracts the version from `main.tsp` and syncs it into `package.json`
2. Runs `node_modules/.bin/tsp compile main.tsp --output-dir tsp-output-core`
3. Moves output to `schemas/core/openapi.yaml`

**Output locations:**
- TypeSpec temp: `tsp-output-core/schema/openapi.yaml` (auto-deleted)
- Final: `schemas/core/openapi.yaml` (committed)

**Version sync:** `package.json` version is kept in lockstep with `main.tsp` automatically on every build. The CI consistency check (`git diff --exit-code`) enforces that both are committed together.

## VS Code Extension Notes

The TypeSpec extension may show false errors for models resolved only at compile time. Both the CLI and "Emit from TypeSpec" command work correctly.

## Dependencies

All TypeSpec libraries use version `^1.6.0` for consistency:
- `@typespec/compiler` - Core compiler
- `@typespec/http` - HTTP semantics
- `@typespec/openapi` - OpenAPI decorators
- `@typespec/openapi3` - OpenAPI 3.0 emitter
- `@typespec/rest` - REST conventions
- `@typespec/versioning` - API versioning support

**Adding new TypeSpec libraries:**
```bash
npm install --save-dev @typespec/library-name@^1.6.0
```

Match the version range to existing dependencies.

## Release Process

Releases are **fully automated** via GitHub Actions (`.github/workflows/release.yml`).

On every push to `main`, the release workflow:
1. Extracts the version from the `@info` decorator in `main.tsp`
2. Skips if a tag for that version already exists
3. Builds the core OpenAPI schema
4. Creates an annotated Git tag (`vX.Y.Z`)
5. Publishes a GitHub Release with `core-openapi.yaml` attached

The CI workflow (`.github/workflows/ci.yml`) enforces that the version in `main.tsp` is bumped from the latest release tag before a PR can be merged.

To release a new version, simply bump the version in `main.tsp` and merge to `main`.

## Architecture Context

This repository is part of the HyperFleet project. For broader context:
- Architecture repo: https://github.com/openshift-hyperfleet/architecture
- Main API implementation: https://github.com/openshift-hyperfleet/hyperfleet-api

The API implementation consumes the generated OpenAPI specs for validation and documentation.
@AGENTS.md