Skip to content

Add draft API design guide and conformance tooling - #17

Open
jeremi wants to merge 19 commits into
GovStackWorkingGroup:mainfrom
jeremi:feat/api-design-guide-linter
Open

Add draft API design guide and conformance tooling#17
jeremi wants to merge 19 commits into
GovStackWorkingGroup:mainfrom
jeremi:feat/api-design-guide-linter

Conversation

@jeremi

@jeremi jeremi commented Aug 10, 2026

Copy link
Copy Markdown

Summary

This PR introduces a reviewable draft of the GovStack API design guide and its supporting conformance tooling.

It:

  • adds the standalone API design guide, navigation, rule source, and generated rule index
  • adds a Spectral-based OpenAPI and AsyncAPI linter with explicit coverage classifications and fixtures
  • adds pinned, reusable OpenAPI and AsyncAPI common components
  • adds an explicit API inventory model for OpenAPI, AsyncAPI, normative protocol-standard, and no-API Building Blocks
  • makes event signing optional and treats recommended wire conventions as guidance instead of mandatory conformance
  • aligns GovStack namespace and documentation references with govstack.global
  • updates only the API-facing template guidance and preserves the existing specification structure and content

The template repository declares noApi because it does not itself define a Building Block API. Authors replace that declaration when creating a Building Block with one or more API surfaces.

Why

GovStack needs a shared API design baseline that is practical to adopt and honest about what automated validation can prove. Earlier iterations imposed premature requirements and allowed validation results to imply more coverage than the linter actually provided.

This draft separates normative requirements from preferred conventions, records where automated checks are only partial proxies, and keeps context-dependent requirements available for human review.

The related CFR discussion is tracked in cfr-architecture issue 7.

Template impact

The existing specification content, version history, attribution, functional requirements, workflows, and test plan remain unchanged.

The focused template integration:

  • adds api/index.yaml with an explicit noApi declaration
  • removes empty legacy api/swagger.* placeholders
  • removes an embedded Scheduler OpenAPI copy from GitBook assets
  • updates spec/8-service-apis.md to describe the inventory, coverage, and validation workflow
  • adds the validation workflow inherited by Building Block repositories

Complete reference OpenAPI and AsyncAPI documents live with the linter tests instead of turning this template into a fictional reference Building Block.

Review focus

Reviewers are especially invited to assess:

  • whether each normative requirement is necessary for interoperability
  • whether protocol-standard and non-API surfaces are represented clearly
  • whether linter coverage classifications accurately distinguish full checks, partial proxies, and context-dependent review
  • whether the resulting workflow is practical for Building Block authors

Validation

  • npm test in api-design-guide/linter: 204 tests passed
  • repository conformance check: passed with the explicit noApi declaration
  • AsyncAPI CLI 6.0.2 validation: passed with one informational latest-version notice
  • OpenAPI Spec Validator 0.9.0 validation: passed
  • python3 tools/build_rules_index.py --check: 171 rules, generated index current
  • python3 tools/check_links.py: 30 files, 496 links, and 188 anchors checked
  • git diff --check origin/main...HEAD: passed

jeremi added 19 commits July 10, 2026 17:14
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Rule 20.2 mandates a GovStack Spectral ruleset; this ships its draft
in-repo so BB spec editors and agents get mechanical verification
instead of a checklist. 125 default rules plus 8 opt-in strict
heuristics across OpenAPI 3.1 and AsyncAPI 3.0, a driver adding the
20.1 base validators, file-layout checks and 20.3 exception handling,
per-rule coverage for all 166 guide rules in coverage.yaml
(machine-checked), golden reference specs, and a composite GitHub
Action with a guarded template workflow. Guide pages updated to point
at the ruleset.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
v0.2 is no longer a pure restructuring of v0.1: this adds new normative rules
and strengthens existing ones, so version-history now says so plainly rather
than claiming presentation-only changes.

Substantive additions: api/index.yaml surface inventory and coverage
dispositions (4.5, 4.6); 201-vs-202 creation semantics and mandatory response
schemas (7.21); W3C Trace Context in place of X-Request-Id (8.4); the
revision-11 Structured Field RateLimit contract (8.7); transport-neutral
GovStackAsyncError (11.8); the RFC 9700 OAuth baseline with the password grant
forbidden outright (13); a required Idempotency-Key on non-idempotent POSTs
(14.1); and detached JWS ES256 with RFC 8785 canonicalization, dropping the
v0.1 HMAC fallback (16.8).

Resolves OPEN-7-A, OPEN-7-B, OPEN-15-C, OPEN-15-D and OPEN-20-A. OPEN-15-A is
resolved in-draft but is marked v1.0-blocking and still needs committee
ratification.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Gives the template a worked contract instead of the stale swagger.json and
swagger.yaml stubs, which are removed. api/index.yaml is the canonical API
registry, api/coverage.yaml maps normative interface requirements, and
api/common/ carries the shared OpenAPI and AsyncAPI component files that rules
2.8 and 3.8 pin.

The common files are drafts vendored from an incubation repository and are not
ratified GovStack artifacts yet.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Adds --mode and api/index.yaml discovery to the driver so a run covers every
declared surface rather than a single file, and extends coverage to the rules
added in this cycle.

New functions cover baseline and creation responses (7.14), successful response
schemas (7.21), W3C Trace Context (8.4) and logical channel IDs (17.2).
s08-requestIdCorrelation.js is removed with the X-Request-Id rule it enforced.

Every rule keeps a fail/pass fixture pair and a coverage.yaml entry; the golden
reference specs still lint clean.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Replaces the prose spec template with a generic reference domain carrying
stable BB-TPL-* requirement identifiers, so the traceability and conformance
structure is demonstrated rather than described.

This commit is deliberately last and touches only committee-owned template
content. Drop it to ship the API design guide work on its own.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…stack.global

The guide used org.govstack.* as the reverse-DNS root for event types,
channel ids and error identifiers, and docs.govstack.org as the error
registry host. Neither matches the project's actual domain: govstack.org
is not owned by GovStack and docs.govstack.org does not resolve.

Reverse-DNS of govstack.global is global.govstack. docs.govstack.global
resolves and serves the documentation site.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Seven passages cited "the 2026 cross-BB audit of all 15 Building Blocks"
as a citable artifact and attached counts to it ("at least 9 BBs"). No
such document is published, so a reader cannot check any of it.

Each passage now states the provenance a reader can verify for themselves
(the published BB API specifications) and drops the unverifiable counts.
The substantive observations are unchanged.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
3.1 pinned asyncapi to exactly 3.0.0, rejecting 3.1.0, the current published
AsyncAPI 3 version. Linting the golden AsyncAPI spec re-declared as 3.1.0
produces exactly one finding, govstack-3.1 itself: every other rule in the
ruleset passes unchanged, so the pin was the only obstacle.

3.1 now mirrors 2.1's forward-qualification wording, naming the versions this
guide and ruleset version qualify and requiring a later version to be
qualified explicitly before use. The govstack-3.1 pass fixture moves to 3.1.0;
3.0.0 stays covered by tests/golden/asyncapi-golden.yaml.

Also narrows govstack-18.2-openapi to oas3_1 and govstack-18.2-asyncapi to
aas3. Both are substantive rules, not version gates, so the broad formats they
declared did not match the house style used by every other rule of that kind.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
17.4 required each channel parameter's "schema" to be documented. The
AsyncAPI 3 Parameter Object has exactly default, description, enum, examples
and location, with additionalProperties: false, in both 3.0.0 and 3.1.0. A
parameter carrying a schema field is therefore not a valid AsyncAPI document,
so the rule as written could not be satisfied.

17.4 now requires what the format can express: a non-empty description for
routing semantics, enum for closed value sets, examples otherwise. The linter
already enforced description as a stand-in; that is now a faithful
implementation of the rule rather than a proxy, and the comments saying
otherwise are updated.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
9.7 required every enum value to be SCREAMING_SNAKE_CASE, which forbade six
value sets the guide itself mandates: 17.11 delivery guarantees, 17.13
capability values, 11.5 error codes, 16.3 event types, 10.9 BCP 47 tags and
12.7 sort keys. A BB modelling any of them as an enum could not satisfy both
rules.

9.7 now scopes the requirement to BB-defined states and lists the carve-outs
with links. The linter follows for the five that are distinguishable by shape.
Sort keys are lowerCamelCase field names, indistinguishable from the
mis-cased state names the rule must keep catching, so they stay a false
positive recorded in the rule comment and the coverage note rather than a
hole in the pattern.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
5.1 required a /v{N}/ prefix on every path and 18.2 required a major version
in every path key, but 5.9 mandates an unversioned /health. A BB satisfying
5.9 broke both. The linter already carved out /health and /ready with a
comment saying why, and 18.2's function only ever checked paths that already
carry a prefix, so the guide text was the only thing asserting the
contradiction. Both rules now state the exemption.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ements

The guide restated three CFR-derived constraints more loosely than the
CFRs themselves state them. A specification that extends the CFRs may
tighten or elaborate a requirement but must not weaken one, and all
three of these are classified IMMUTABLE, so the looser wording was not
a permissible variation.

- 1.3 replaces the "precedence is unsettled" punt with the framework's
  actual rule, and points at the citation convention used below.
- 10.2 requires UTC with the Z designator rather than any RFC 3339
  offset, per govstack-cfr-data#req-2.
- 13.7 requires negotiated TLS 1.3 or higher, per
  govstack-cfr-security#req-1.
- 10.11 is new: UTF-8 text encoding, per govstack-cfr-data#req-1, with
  govstack-10.11-openapi and govstack-10.11-asyncapi enforcing that no
  declared media type or contentType names another charset.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Seventeen ruleset fragments cited <SCRATCHPAD>/linter-conventions.md, a
document that exists nowhere in the repository. The conventions it
described are the ones the linter README already documents, so the
references now point there.

ruleset.yaml claimed s03-s20 were empty skeletons and named the guide
version without its -draft suffix; both were true during the initial
build and neither is now. strict.yaml loses the same build-scaffolding
prose.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Two rules a reviewer hits early could not be satisfied as written.

11.7 requires a common error catalogue "defined in
govstack-openapi-common.yaml and reused", but that file carried the codes
only as literals inside response examples, seven of the nine, with
nothing referenceable. It now defines CommonErrorCode, including the
missing alreadyExists and unimplemented, and 11.7 names the schema so
"reused" has something to point at.

16.8 signs the complete structured CloudEvent and has verifiers
reconstruct it from the received body, but nothing required structured
content mode on the webhooks surface. In CloudEvents binary mode the
body is only `data`, so the signature was unverifiable. 16.2 now
requires application/cloudevents+json there, matching what 17.6 already
requires of AsyncAPI, and govstack-16.2-structured enforces it.

The two remaining verification inputs, key discovery on transports with
no subscription control plane and the replay-window bound, are genuine
committee decisions rather than drafting errors, so they are declared as
OPEN-15-H and marked inline at 16.8 and 16.9 instead of being invented
here.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
An outside team applied the guide to a non-BB project and reported eight
issues. Six were real: rules that could not be satisfied as written, or
carve-outs the linter did not honour.

- 5.9: health is carried by the status code. Declare both 200 and 503; the
  200 body is plain application/json, minimal, and must not expose
  system-internal detail. Drops draft-inadarei-api-health-check, an expired
  Internet-Draft that never became an RFC (resolves OPEN-4-B).
- 5.10: the standard unversioned endpoints are read-only, so they cannot be
  used to smuggle a business resource outside /v{N}/.
- 9.7: name the real enum carve-out, values drawn from an IANA registry
  (JOSE and COSE algorithms and curves, media types), not health vocabulary.
- 12.1: a collection that bounds every array it returns with maxItems may be
  returned unpaginated.
- 13.2: accept an http bearer JWT scheme. A resource server that validates
  tokens issued elsewhere would misdescribe itself declaring an oauth2 flow.

The 5.10 path set now lives in one module, functions/lib/standardEndpoints.js,
replacing three inline copies that had already drifted apart.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi marked this pull request as ready for review August 10, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant