1# Contributing to Gemara
2
3The project welcomes your contributions whether they be:
4
5* reporting an [issue](https://github.com/gemaraproj/gemara/issues/new/choose)
6* making a code contribution ([create a fork](https://github.com/gemaraproj/gemara/fork))
7* updating our [docs](https://github.com/gemaraproj/gemara/blob/main/README.md)
8
9## PR guidelines
10
11All changes to the repository should be made via PR ([OSPS-AC-03](https://baseline.openssf.org/#osps-ac-03)).
12
13PRs MUST meet the following criteria:
14
15* Clear title that conforms to the [Conventional Commits spec](https://www.conventionalcommits.org/)
16* Descriptive commit message
17* DCO signoff (via `git commit -s` -- [OSPS-LE-01](https://baseline.openssf.org/#osps-le-01))
18* All checks must pass ([OSPS-QA-04](https://baseline.openssf.org/#osps-qa-04))
19
20### Useful make tasks when making schema changes
21
22Make sure to update `schema-nav.yml` in the [website repository](https://github.com/gemaraproj/website), after making any changes to the schemas in the layer.cue files
23For instance:
24You have added a new schema 'newschema' in layerN.cue( where N is a number from 1 - 7), the website's `schema-nav.yml` should look like:
25```
26- title: "Layer N"
27 filename: "layer-N"
28 schemas:
29 - "SchemaA"
30 - "SchemaB"
31 - "SchemaC"
32 - newschema //new schema added
33```
34
35Use `cue fmt .` and `make cuefmtcheck` to ensure proper formatting and `make lintcue` to validate the syntax of your changes. If you forget to do this before opening a PR and your changes are invalid, the [CI workflow](.github/workflows/ci.yml) will fail and alert you.
36
37### Adding a new artifact type
38
39Adding a new artifact type requires schema changes, test data, and documentation updates. Follow the checklist below.
40
41| **Step** | **Action** |
42|:---------|:----------------------------------------------------------------------------------------------|
43| 1 | Define the new `#YourArtifact` definition in the appropriate `layer-N.cue` file |
44| 2 | Add `"YourArtifact"` to the `#ArtifactType` enum in `base.cue` |
45| 3 | Add new enum or alias types to `schema-nav.yml` in the [website repo](https://github.com/gemaraproj/website) under the correct layer |
46| 4 | Create a valid test data file in `test/test-data/` (prefix with `good-`) |
47| 5 | Add positive and/or negative test cases to `test/schema_test.go` |
48| 6 | Run `cue vet -d '#YourArtifact' . test/test-data/good-your-artifact.yaml` to validate locally |
49| 7 | Run `cue fmt .` and `make cuefmtcheck` to verify formatting |
50| 8 | Run `make lintcue` and `make test` to confirm all checks pass |
51
52## Schema compatibility
53
54The schemas form a public contract. A CI gate compares an OpenAPI projection of the current
55schema against the latest released `v1` baseline and fails the check on any backward-incompatible
56change. The gate enforces compatibility in **both directions**: it is consumer-safe (a response
57may not drop a property or a guarantee that existing readers depend on) and producer-safe (a request
58may not add a new requirement that existing writers cannot satisfy).
59
60Run the check locally before opening a PR:
61
62```
63make breaking-check
64```
65
66This generates the OpenAPI projection, fetches the latest `v1` release as the baseline, and reports
67any breaking changes with their [oasdiff](https://github.com/oasdiff/oasdiff) check IDs.
68
69### Projection directives
70
71The `cue2openapi` projection reads its Gemara-specific metadata from `@gemara(...)` attributes:
72
73| Directive | Scope | OpenAPI output |
74|:----------|:------|:---------------|
75| `@gemara(status="stable")` | File | `x-status` on its definitions |
76| `@gemara(format="date-time")` | Definition | `format` |
77| `@gemara(default=false)` | Field with the same CUE default | `default`, and the field is optional on the wire |
78
79Allowed status values are `experimental`, `stable`, and `deprecated`. A default directive must match
80the CUE default so the validation and wire contracts cannot diverge. Keep CUE doc comments as the
81source for descriptions and CUE constraints such as `time.Format` as the source for validation; these
82are projected only where the upstream encoder cannot represent them.
83
84### Making an intentional breaking change
85
86Sometimes a breaking change is deliberate. There are exactly two sanctioned ways to introduce one:
87
881. **Bump the module major version to `@v2`.** A major bump establishes a new baseline lineage, so
89 `v1` consumers are never silently broken. Use this when the breaking change is part of a broader,
90 intentional evolution of the contract.
912. **Allowlist the specific change.** Add the offending exception to a `.oasdiff-allow` file at the
92 repository root; `make breaking-check` passes it to the check via `--allow` automatically when the
93 file exists. Each entry is a schema-scoped `<check-id> <schema>` pair, one per line, so the
94 exception applies only to that one schema. The check ID and schema are both printed for every
95 reported change, so you can copy them straight from the output — e.g. a change reported as:
96
97 ```
98 ERR[request-property-enum-value-removed] ControlEvaluation: removed the enum value ...
99 ```
100
101 is allowlisted with:
102
103 ```
104 request-property-enum-value-removed ControlEvaluation
105 ```
106
107 This MUST be done in a reviewed PR, and the file MUST include a justification comment (lines
108 beginning with `#`) explaining why the change is acceptable. Use this for narrowly scoped,
109 well-understood exceptions.
110
111Do not work around the gate by any other means.
112
113## Releases
114
115Releases are automatically created when a PR is merged into `main` with the `release` label. To trigger a release:
116
1171. Add the `release` label to your PR before merging
1182. Merge the PR into `main`
119
120The release workflow will automatically:
121- Create a GitHub release with the appropriate version tag
122- Generate release notes from the PR using release-drafter
123- Publish the CUE module to the central registry
124
125**Note:** Only PRs merged into `main` with the `release` label will trigger a release. Other labels (such as `breaking`, `feature`, or `vuln`) will not trigger releases.