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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The fleet-wide conventions - Conventional Commit titles, squash merges, the `all
- `bun run build:openapi` (`.github/scripts/gen-openapi.ts`) renders the files the descriptor dictates, the invitation role enums in `src/generated/spec-roles.ts`, the ruleset rule rows in `src/generated/spec-rules.ts`, and the passthrough vocabularies and bodies in `src/generated/spec-enums.ts`; a descriptor bump regenerates them and `test/scripts/gen-openapi.test.ts` fails on a committed copy that drifted.
- `bun run test` and `bun run fuzz` run `bun run build:schema` first: the tests and the fuzzer load the built, gitignored `lib/settings.schema.json`.
- Committed generated output is the table in `.github/scripts/generated.ts`: the whole files under `src/generated/` and the generated regions of `action.yml` and the docs pages.
- `bun run build:check` regenerates every table entry and fails on drift.
- `bun run build:check` regenerates every table entry and fails on drift. Before that, it fails on a GENERATED marker, in a file the table lists, that names a region no generator registers.
- `lib/index.js` (the action bundle), `lib/settings.schema.json` (the published schema), and `lib/pkg/` (the npm library) are built where they are needed and never committed on `main`.
- The action bundle compiles every runtime dependency in. The library keeps them external: they are the `dependencies` of `package.json`, installed by the consumer's package manager.
- [docs/reference/coverage.md](docs/reference/coverage.md) is the inventory of the supported API surface, one link per call. A change that adds or extends a section keeps its `<key>.docs.yml` rows and `.github/scripts/endpoint-docs.yml` in step.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Apply declarative repository settings from `.github/settings.yml`: a loud, state
color: "d73a4a"
```

3. Add the workflow and run it from the Actions tab. Keep `mode: check` for the first run: the drift report lists everything an apply would change or delete, and nothing is written. Read the report: an apply deletes undeclared labels, autolinks, collaborators, Actions variables, and Copilot agents variables. Drop the `mode: check` line once the report says what you expect; [Run check mode first](docs/start/getting-started.md#4-run-check-mode-first) explains the drift output.
3. Add the workflow and run it from the Actions tab. Keep `mode: check` for the first run: the drift report lists what an apply would change or delete, and nothing is written. Read the report: an apply deletes undeclared labels, autolinks, collaborators, Actions variables, and Copilot agents variables. Drop the `mode: check` line once the report says what you expect; [Run check mode first](docs/start/getting-started.md#4-run-check-mode-first) explains the drift output.

The report cannot list the value of a declared secret that already exists, which GitHub never reads back: every apply rewrites it, and a check shows a [cannot verify note](docs/operate/check-mode.md#what-a-cannot-verify-note-means) for it, not drift.

```yaml
# .github/workflows/settings.yml
Expand Down
4 changes: 2 additions & 2 deletions docs/operate/layering.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ The rendered file is exactly what apply runs, so it is worth knowing its shape:
An environment's variables, secrets, deployment branch policies, and deployment protection rules are in their `{_undeclared, entries}` wrapper form too, each policy resolved; its reviewers take no knob and stay as declared.
- No `_layering` and no top-level `_undeclared` anywhere: both directives are consumed before the file is written, and YAML comments do not survive the fold.
- Every `null` that won its key is there, at the top level (`pages: null`, `interaction_limits: null`) or below: the fold reads no `null` as a marker, and validation refused every one a key does not admit.
- No `_remove` anywhere: each removal was consumed at its own layer's step, along with the entry it named.
- No `_remove` on a keyed entry: each removal was consumed at its own layer's step, along with the entry it named. One on an entry of a list that is not keyed removes nothing and stays, a field [under a warning](#four-knobs).
- Every layer was validated on its own before the fold, and the result is validated again before it is written.

## The two-step workflow
Expand Down Expand Up @@ -440,7 +440,7 @@ That guarantee covers the fold alone. The per-layer validation prints the same m
- A `branches` control declared as a scalar where a mapping belongs, on a literal branch and a wildcard entry alike, naming the key and nothing of the value:
`branches[0].protection.required_status_checks: required_status_checks must be a mapping of its keys (strict, then contexts or checks), or null to turn the requirement off`.
- A YAML syntax error, quoting the offending source line with a caret under the column; an unresolved alias names the alias instead, and an anchor whose name carries whitespace or a control character prints that name after `Anchor must not contain whitespace or control characters:`.
- A YAML alias inside the node its own anchor opens (`repository: &loop {self: *loop}`), which would parse to a cyclic object, names the anchor when the file is read, before any validation: `alias *loop refers back into the node its anchor &loop opens (a YAML alias cycle), which no settings document can carry. An alias may only reference a completed node: write the value out again, or move the anchor to a sibling`.
- A YAML alias cycle (`repository: &loop {self: *loop}`), which would parse to a cyclic object, names the anchor when the file is read, before any validation: `alias *loop refers back into the node its anchor &loop opens (a YAML alias cycle), which no settings document can carry. An alias may only reference a completed node: write the value out again, or move the anchor to a sibling`.
- A YAML parser warning (an unresolved tag, an unknown directive, an ambiguous anchor, a collection used as a key) prints nothing: the parser runs with its warnings off, so such a document parses silently to the same object it always did, and only a parse failure reaches the log, printing what the bullet above describes.

A render-mode log can therefore show your settings file's structure and, through these messages, a value from it: treat it like any log that prints a parse error for a file the runner holds.
Expand Down
8 changes: 4 additions & 4 deletions docs/operate/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ Their notes are in their file header; there is no report channel in snapshot mod

A denial is a read the token's grants refuse. Where it lands decides what `on-missing-permission` does with it:

| Denied read | `fail` (default) | `warn` |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| A section's main read (the labels list, the Actions permissions) | The section fails; the run fails with exit 1 and writes no file | The section is skipped with a warning and listed in `skipped-sections`; the rest is written |
| A key the section reads on its own (an `actions` key such as the OIDC template or a cache limit; an environment's branch policies or protection rules) | The same: the section fails and no file is written | That key alone is left out under a header line; the rest of its section reads back |
| Denied read | `fail` (default) | `warn` |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| A section's main read (the labels list, the Actions permissions) | The section fails; the run fails with exit 1 and writes no file | The section is skipped with a warning and listed in `skipped-sections`; the rest is written |
| A key the section reads on its own (an `actions` key such as the OIDC template or a cache limit; an environment's branch policies or protection rules; the `interaction_limits` creation cap or bypass list) | The same: the section fails and no file is written | That key alone is left out under a header line; the rest of its section reads back |

Under both policies the error or the header line names the grant to add.

Expand Down
6 changes: 4 additions & 2 deletions docs/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ In a multi-repo run the `defaults-file` document is validated before target reso
- Two document-wide walks: a value that is not plain YAML data (a tagged mapping, a list with a hole) and a passthrough number that is not finite.
Their proof is the `ProvedPlain` brand on the walk's output, the only input the `ValidatedSettings` mint accepts.

Every issue the phase finds lands in one list: unknown directives, unknown sections, a single document's `_remove` markers, then each section's issues in apply order. Zero section requests reach that repository, and the run exits 1; in a multi-repo run only that target fails. One downgrade: an unknown section outside a non-empty `sections` allowlist is a warning, so an older action can run a file written for a newer one.
Every issue the phase finds lands in one list: unknown directives, unknown sections, a single document's `_remove` markers, then each section's issues in apply order. Zero section requests reach that repository, and the run exits 1; in a multi-repo run only that target fails.

Three downgrades, each a warning in place of an issue. An unknown section outside a non-empty `sections` allowlist, so an older action can run a file written for a newer one. And, in a document that otherwise validates, until the next major refuses them: a single document's top-level `_layering` outside its three values, and a `_remove` on an entry of a list that is not keyed.

Two limits. A shape's own issues are capped at five per section, with a count of the rest; and a section's hook runs once its shape parsed, so a shape error in an entry can hide a duplicate until it is fixed.

Expand Down Expand Up @@ -168,7 +170,7 @@ flowchart TD
whole --> out
```

The fold is a cascade: the higher layer's value wins at every depth, and `null` is a value. Every list section is keyed by what its planner matches entries by, so a fleet `Bug` and a repository `bug` are one label. Three directives say how a keyed list meets the list below it; here the highest layer, in a run with `layering: replace`:
The fold is a cascade: the higher layer's value wins at every depth (a label's `name` aside: [the rename rule](../operate/layering.md#the-rules)), and `null` is a value. Every list section is keyed by what its planner matches entries by, so a fleet `Bug` and a repository `bug` are one label. Three directives say how a keyed list meets the list below it; here the highest layer, in a run with `layering: replace`:

```yaml layer
_layering: shallow # every list section of this file, unless it says otherwise
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ The side-by-side comparison with the Probot Settings app lives in the migration
- GET/POST /repos/{owner}/{repo}/actions/variables and PATCH/DELETE /repos/{owner}/{repo}/actions/variables/{name}: plain-text repository variables, upserted by name.
- Names are case-insensitive (GitHub stores them uppercased regardless of how they are entered), so matching and duplicate rejection compare uppercased names.
- Values read back in full, which is what makes exact check-mode diffing possible; undeclared variables are DELETED by default, kept as notes under the wrapped `_undeclared: keep` form.
- A declared created_at or updated_at, which the GET reports and neither write takes, is named by a note in check and in apply, whatever the live variable holds.
- The list endpoint caps per_page at 30 (not the usual 100), which the page loop honors so a large inventory is never truncated.
- Secrets are write-only material and deliberately NOT this section: they live in the `actions_secrets` section above.
- Snapshot reads the variables back as name and value entries under the delete default.
Expand All @@ -485,6 +486,7 @@ The side-by-side comparison with the Probot Settings app lives in the migration
- GET/POST /repos/{owner}/{repo}/agents/variables and PATCH/DELETE /repos/{owner}/{repo}/agents/variables/{name}: same upsert-by-uppercase-name semantics, exact check-mode diffing, and 30-item per_page cap as `actions_variables`, over the Copilot agents variable store.
- GET .../agents/organization-variables is the read-only org-inherited view, out of scope.
- Undeclared variables are DELETED by default, kept as notes under the wrapped `_undeclared: keep` form.
- A declared created_at or updated_at, which the GET reports and neither write takes, is named by a note in check and in apply, whatever the live variable holds.
- CAVEAT: GitHub auto-migrated values from the older `copilot` Actions environment into this store, so a repository can hold agents variables nobody created here - run `mode: check` first (it lists them as deletion drift) before the first apply, or declare `_undeclared: keep`.
- Snapshot reads the variables back as name and value entries under the delete default.

Expand Down
Loading
Loading