Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
94 changes: 94 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Check

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
build:
name: Build and check the schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- name: Install Bikeshed and Ruff
run: pip install -r requirements.txt && bikeshed update
- run: npm ci
- name: Lint the tools
run: ruff check tools/
- name: Test the tools
run: python3 -m unittest discover -s tools/tests
- name: Build every version
run: tools/build.py
- name: Check every version's fixtures against its schema
run: |
shopt -s nullglob
for dir in v*/ releases/*/; do
node tools/schema-check.cjs "$(basename "$dir")"
done
- name: Keep the site for publishing
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v7
with:
name: site
path: site
retention-days: 1

openlint:
name: Conformance, OpenLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Check out OpenLint at the commit the conformance tests were last run against
uses: actions/checkout@v7
with:
repository: openlint/openlint
ref: 344dc7ea11d52a40d97361aa1e931743596b29bc
path: .linter/openlint
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- uses: actions/setup-node@v7
with:
node-version-file: .linter/openlint/.nvmrc
- name: Build OpenLint
working-directory: .linter/openlint
run: |
npm install -g corepack --force
corepack enable
yarn install --immutable
yarn build
- name: Run the conformance tests of every draft
run: |
for dir in v*/; do
tools/conformance.py "${dir%/}" --linter "node $GITHUB_WORKSPACE/.linter/openlint/packages/cli/dist/index.js lint -r {ruleset} -f json {document}"
done

spectral:
name: Conformance, Spectral 6.17.0
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- uses: actions/setup-node@v7
with:
node-version: 24
- name: Install Spectral
run: npm install --prefix "$RUNNER_TEMP/spectral" @stoplight/spectral-cli@6.17.0
- name: Run the conformance tests of every draft
run: |
for dir in v*/; do
tools/conformance.py "${dir%/}" --linter "node $RUNNER_TEMP/spectral/node_modules/@stoplight/spectral-cli/dist/index.js lint -r {ruleset} -f json {document}"
done
39 changes: 39 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish

on:
workflow_run:
workflows: [Check]
types: [completed]
branches: [main]

permissions:
contents: read

jobs:
publish:
name: Publish to GitHub Pages
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push'
runs-on: ubuntu-latest
permissions:
actions: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Fetch the site the Check run built
uses: actions/download-artifact@v8
with:
name: site
path: site
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-pages-artifact@v5
with:
path: site
- id: deployment
uses: actions/deploy-pages@v5
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
site/
.build/
node_modules/
.venv/
__pycache__/
330 changes: 330 additions & 0 deletions CONTRIBUTING.md

Large diffs are not rendered by default.

73 changes: 35 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,67 @@

**The specification and JSON Schema for OpenLint rulesets: the Spectral ruleset format, specified on its own.**

A ruleset says where to look in a JSON or YAML document and what must hold there. Organizations publish their API style guides, security baselines and design standards as rulesets, then run them in CI against every OpenAPI, AsyncAPI, Arazzo and JSON Schema document they ship.
A ruleset is a set of rules for checking JSON and YAML documents. Each rule selects values in a document with a JSONPath expression and checks each selected value with a function. Organizations publish their API style guides, security baselines and design standards as rulesets, then run them in CI against every OpenAPI, AsyncAPI, Arazzo and JSON Schema document they ship.

The format has never been specified. It existed only as configuration for one linter, versioned with that tool. This repository treats the ruleset as an artifact in its own right: an independently versioned specification with a portable JSON Schema, so rules can be written, validated, shared and run by any tool.
The format has never been specified. It existed only as configuration for one linter, versioned with that tool. This repository treats the ruleset as an artifact in its own right: an independently versioned specification with a portable JSON Schema and conformance tests, so rules can be written, validated, shared and run by any linter.

| | |
| -- | -- |
| **Specification** | [SPECIFICATION.md](SPECIFICATION.md) |
| **JSON Schema** | [`schema/v1/ruleset.schema.json`](schema/v1/ruleset.schema.json) (draft 2020-12). Canonical `$id`: `https://spec.openlint.org/schema/v1/ruleset.schema.json`, not served yet |
| **Reference implementation** | [openlint/openlint](https://github.com/openlint/openlint) |
| **Website** | [openlint.org/spec](https://openlint.org/spec/) |
| **Published releases** | `spec.openlint.org`, proposed in [#13](https://github.com/orgs/openlint/discussions/13) and not set up yet |
| | |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Specification** | [`v1/index.bs`](v1/index.bs), the working draft of 1.x. It will be published at `https://spec.openlint.org/v1/draft/`, which is not served yet |
| **JSON Schema** | [`v1/ruleset.schema.json`](v1/ruleset.schema.json) (draft 2020-12) |
| **Conformance tests** | [`v1/conformance/`](v1/conformance/), one directory per test |
| **Reference implementation** | [openlint/openlint](https://github.com/openlint/openlint) |
| **Website** | [openlint.org/spec](https://openlint.org/spec/) |

> **Status: draft.** The document describes the format as implemented today, faithfully and without extension. It is not yet a formal specification: it will be rewritten to the principles below. The `v1` schema identity is stable: see [Versioning](SPECIFICATION.md#versioning).
> **Status: working draft.** The specification states the format as Spectral and OpenLint behave today, as identified requirements with conformance tests that pass against both. Where that behavior could be better, an issue in the draft proposes the change. There is no release yet.

## How the specification will be written
## Versions

Proposed by Joost Farla in [discussion #13](https://github.com/orgs/openlint/discussions/13) and not yet agreed:
Each release will live at its own URL that never changes, such as `https://spec.openlint.org/1.0.0/`, with its schema at `https://spec.openlint.org/1.0.0/ruleset.schema.json`. A fix ships as a new patch release. Each major version has a working draft in a folder of its own, published at `https://spec.openlint.org/v<major>/draft/`.

1. **BCP 14 keywords.** MUST, SHOULD, MAY and the others have their [RFC 2119](https://www.rfc-editor.org/info/rfc2119/) meaning only in capitals ([RFC 8174](https://www.rfc-editor.org/rfc/rfc8174)), with the same boilerplate as OpenAPI, Arazzo and Overlay. Examples are informative.
2. **Defined terms.** A glossary of the terms requirements depend on, such as document, ruleset, rule, match, result and linter. Where a normative reference already defines a term, link to it.
3. **Exact normative references.** Every reference names an exact version, especially JSONPath, regular expressions, JSON Schema and YAML.
4. **One conformance target per requirement.** Each requirement applies to a ruleset or to a linter, never both.
5. **A permanent ID for every requirement,** such as `/req/given/no-match`, never reused. Removed requirements stay listed as removed.
6. **Conformance tests for every requirement,** listed in the spec and implemented by the conformance suite.
7. **A defined result model,** adopting the fields of Spectral's JSON output (`code`, `path`, `severity`, `message`, `range`, `source`) as normative.
8. **Immutable releases.** Each release lives at its own URL, such as `spec.openlint.org/1.0.0/`, and never changes. Work in progress lives at `spec.openlint.org/draft/`.
`https://spec.openlint.org/v1/` will serve the latest 1.x release. Editors that follow the newest release can point at `https://spec.openlint.org/v1/ruleset.schema.json`. Until 1.0.0 is released, it serves the draft's schema. Each schema's `$id` is the URL it is published at, so the schema of every release has an identity of its own.

Open questions: whether to group requirements into conformance classes, whether path-style IDs work for everyone, and what to publish with (Respec, Bikeshed or something else).
Within a major version, changes are additive and backward-compatible: new optional properties, newly registered format identifiers, clarified descriptions. A change that rejects a previously valid ruleset goes into the draft of the next major version. What counts as a breaking change is discussed in [#12](https://github.com/orgs/openlint/discussions/12).

## Validate a ruleset

```bash
npx ajv-cli validate --spec=draft2020 \
-s schema/v1/ruleset.schema.json \
-d .spectral.yaml
npx ajv-cli validate --spec=draft2020 --strict=false \
-s v1/ruleset.schema.json \
-d ruleset.yaml
```

Editor setup is in [Using the schema](SPECIFICATION.md#using-the-schema).
Editor setup is in Appendix D of the draft, Using the schema.

## Contribute

[CONTRIBUTING.md](CONTRIBUTING.md) explains the layout of this repository, how to build and check the draft, how to write requirements, issues, and tests, and how to run the conformance tests against a linter.

## What this format is for

**Governing structured documents, not only OpenAPI.** A rule selects nodes in a JSON or YAML document with a path expression and asserts something about them. Nothing in that is specific to APIs, and the format is already used on CI workflows, Kubernetes manifests and other configuration. Support for documents that aren't API descriptions is a goal, and a change that's correct only for API descriptions is a defect. Which document types to support is being discussed in [#5](https://github.com/orgs/openlint/discussions/5).

## Why this is separate from the linter

When the format ships only as part of one linter, it stops moving when the linter does. A team's governance rules usually outlast the CLI, the vendor, and often the API itself. So the rules stand on their own: specified, versioned independently, and implementable by anyone. The reference implementation follows this document, and other engines, such as [vacuum](https://github.com/daveshanley/vacuum), are welcome to implement it too. A written specification with a public conformance suite is what lets several engines agree.
When the format ships only as part of one linter, it stops moving when the linter does. A team's governance rules usually outlast the CLI, the vendor, and often the API itself. So the rules stand on their own: specified, versioned independently, and implementable by anyone. The reference implementation follows this document, and other linters, such as [vacuum](https://github.com/daveshanley/vacuum), are welcome to implement it too. A written specification with a public conformance suite is what lets several linters agree.

## Work in progress

| Topic | Discussion |
| -- | -- |
| Formalizing the specification: the principles above | [#13](https://github.com/orgs/openlint/discussions/13) |
| Backwards compatibility: what we promise, and to whom | [#12](https://github.com/orgs/openlint/discussions/12) |
| Document types ("formats") | [#5](https://github.com/orgs/openlint/discussions/5) |
| Aliases | [#2](https://github.com/orgs/openlint/discussions/2) |
| Rules testing | [#7](https://github.com/orgs/openlint/discussions/7) |
| Topic | Discussion |
| ---------------------------------------------------------------- | ------------------------------------------------------ |
| Formalizing the specification: the principles this draft applies | [#13](https://github.com/orgs/openlint/discussions/13) |
| Backwards compatibility: what we promise, and to whom | [#12](https://github.com/orgs/openlint/discussions/12) |
| Document types ("formats") | [#5](https://github.com/orgs/openlint/discussions/5) |
| Aliases | [#2](https://github.com/orgs/openlint/discussions/2) |
| Rules testing | [#7](https://github.com/orgs/openlint/discussions/7) |

## Where things go

| What | Where |
| -- | -- |
| Problems with the format, the schema or this document | [openlint/spec issues](https://github.com/openlint/spec/issues) |
| Problems with the linter | [openlint/openlint issues](https://github.com/openlint/openlint/issues) |
| Ideas, direction and questions | [OpenLint discussions](https://github.com/orgs/openlint/discussions) |
| What | Where |
| ----------------------------------------------------- | ----------------------------------------------------------------------- |
| Problems with the format, the schema or this document | [openlint/spec issues](https://github.com/openlint/spec/issues) |
| Problems with the linter | [openlint/openlint issues](https://github.com/openlint/openlint/issues) |
| Ideas, direction and questions | [OpenLint discussions](https://github.com/orgs/openlint/discussions) |

The most useful contribution is a ruleset that behaves differently from what this document says. That's a bug in the specification.

Expand Down
Loading
Loading