Skip to content

fix(settings)!: unknown underscore keys fail everywhere; teams gets the _undeclared knob; GSAC_RETRY_BASE_MS - #206

Merged
Vivswan merged 3 commits into
mainfrom
refactor/small-rules
Sep 13, 2026
Merged

Vivswan merged 3 commits into
mainfrom
refactor/small-rules

Conversation

@Vivswan

@Vivswan Vivswan commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

Before

$ cat settings.yml
_owner: platform-team, see runbook RB-112
labels:
  - name: bug
    color: d73a4a
$ gsac validate settings.yml
settings.yml is valid: 1 section(s) declared (labels)

After

$ gsac validate settings.yml
error: unknown underscore key(s) in settings.yml: _owner. The underscore marks this action's directives, "_layering" (a file's top level or a list section's {entries} wrapper) and "_undeclared" (a wrapper), and nothing else; there are no private-note keys. Remove the key, or keep the note as a YAML comment

What changed

Four v3 rule decisions, each one small change.

  • Underscore rule: _layering and _undeclared are the whole underscore vocabulary; any other _x fails at the root as it already did on a wrapper. A note goes in a YAML comment.
  • teams knob: teams takes {_undeclared, entries}, default keep; delete revokes undeclared direct grants. Snapshots write the wrapper form.
  • GSAC_RETRY_BASE_MS: the retry-timing knob, renamed from RETRY_BASE_MS and documented; the old name is ignored.
  • Scenario names: the 12 underscore-keyed files are dashed and name themselves as their file stem; a corpus test pins both.

Proof

bun run check green (3467 unit tests); bun run test:e2e 260/260; bun run fuzz -- --sections teams 80/80 (seeds 3503126389 and 2617668430 included) and 40/40 over every section; build:check zero drift; build:lib + package smoke ok.

Technical details

Based on main 69fc7c4. Upgrade entries: docs/upgrading/v2-to-v3.md sections 16, 17, 18.

The teams knob in use

teams:
  _undeclared: delete        # new knob; default keep, as every run behaved before
  entries:
    - name: platform
      permission: push
teams: REVOKED undeclared team "legacy"        # apply, under delete
team "legacy" has access but is not declared in the settings file; kept under "_undeclared: keep" - ...   # note, under keep

Decisions

Item Decision
Underscore rule One rule everywhere: any unknown _x fails at the root (settings-unknown-directives) and on a wrapper, under every sections allowlist; the error names the two directives. A wrapper line holding an underscore key beside a plain typo or the pre-v3 undeclared key names every fix at once
teams knob knobbed(TeamConfig), default keep (conservative: a team is often granted by the org; collaborators stays delete). delete revokes direct grants through DELETE /orgs/{org}/teams/{slug}/repos/{owner}/{repo}; org- or enterprise-level access is noted under delete, never touched
teams list read GET /repos/{owner}/{repo}/teams is gated at repository Administration (read) alone, as GitHub grades it, and carries the section's 404 posture (denied); the public org probe keeps its personal-account 404. A token with Administration but no org Members still reads the list and meets the probes' 404 as "no access", as before this PR
GSAC_RETRY_BASE_MS Documented in the inputs reference; the old name is ignored (an env var has no loud-error channel; the upgrade page says so)
Scenario names test/e2e/foundation.test.ts pins [a-z0-9-]+.yml and name == file stem over the whole corpus
Drift prose Dropped here: #202 landed valueDrift (declared X != live Y) for collaborators and teams first, so this PR keeps main's lines

How

  • Root: validateSettingsDoc splits the strangers into underscore keys and plain unknown sections (unchanged, the allowlist warning included).
  • Wrapper: wrapperKeyError in schema-helpers.ts joins one clause per kind: the undeclared rename, the directives line for the underscore keys (named when the list holds anything else); a plain typo keeps zod's own words.
  • teams: plan() reads the org probe, then the team list, then each declared team's probe; the undeclared walk runs after the declared one over the same list. snapshot() emits the wrapper through knobbedSnapshot.
  • Fuzz oracle: sectionGrade is the repository grade; orgGateDenied is the org gate's own switch. A shut gate with a held repository grant predicts grade none under the absent posture (reads pass, probes answer 404, writes denied); the prediction carries its effective posture, which the preflight fold reads. The route-identity pin skips primaryRead, which is the section's, not the route's.

Proof detail

  • Loud errors pinned: root underscore key under no allowlist and under one; wrapper underscore key on a top-level and a nested wrapper, beside a typo, and beside the pre-v3 key; the fuzz catalog draws unknown-underscore-key and asserts the rejection names the key.
  • teams: keep note, delete revocation with an empty re-plan, org-level access noted under delete, snapshot wrapper form; teams-undeclared-delete and teams-undeclared-keep-note converge on a check re-run; endpoint coverage 163/163 (the new DELETE is hit).
  • Oracle: the org-gate model is pinned at the section and run level (a shut gate never arms the preflight barrier under fine-grained; a missing repository grant does; the 403 style does).

Line accounting

git diff origin/main...HEAD --numstat, by kind; the 13 renames are +0/-0.

Kind + - Net Why it grew
Source 176 33 +143 teams' undeclared walk, the list's grant, and the revoke endpoint (teams/index.ts); the two new loud errors (orchestrate.ts, problem.ts, schema-helpers.ts)
Tests 463 111 +352 pins for the two loud errors, the teams knob, and the org-gate oracle model (unit and oracle tests, two scenarios, the fuzz case, the corpus name rule); the rest are expectation updates
Docs 89 28 +61 the three upgrade entries, the env-var table, and the teams caveat; the private-note prose is shorter than before
Scripts 4 0 +4 the teams row of the generated policy table
Generated 53 13 +40 regeneration: schema, action.yml, sections/undeclared-policy pages, COVERAGE

This PR is the user-visible half of the task (rules and a knob), so source is additive; the one-owner deletions are #208, where source is net flat.

  • Small fixes on the way: the docs examples in incident-freeze.md, troubleshooting.md, examples.md, and migrating-from-probot.md used an underscore note and now use a comment; layering.md's cycle example moved from _notes to repository and its wrapper count points at the generated sentence; the pre-existing custom-properties.yml scenario is renamed to its name: (custom-properties-apply-converges.yml); two stale "private key" wordings in layers.ts and its test.
  • Recorded, not built: a persisted negative control for the scenario-name gate (the old names were checked to fail it during review); a presence check on the old RETRY_BASE_MS name (test-only knob, silent ignore accepted); a typestate for "listing read before the undeclared walk" in teams; the teams: [] corner of the oracle (a run that never reaches an org-gated probe can finish clean under org_members none and the 403 style; the generator never draws it); a declared child team losing access through an undeclared parent's revoked grant under delete (needs a parent lookup per team; documented in the teams section instead).
  • Not done here: swapping run.test.ts's module-level testIo for collectingIo() is not mechanical (61 sites read captured as level: message strings and mask: lines, which collectingIo shapes differently); it goes to the test-audit task.
  • Codex rubber duck: 8 rounds; on the PR commit, round 1 three non-blocking (docs cycle example, one word in the teams section, two over-long literals), round 2 one non-blocking (stale fuzz comments, replaced by the fuzz case), rounds 3 and 4 clean; on the fold commit, round 1 one blocking (the preflight fold re-read the static posture; the prediction now carries its effective one), round 2 clean; one clean round each on the two conflicted rebases (test/action/run.test.ts onto chore(test): one generator registry behind build:check, one Io capture fake, one ROOT, conventions stated #200; the drift lines and the upgrade page onto refactor(sections)!: milestones, webhooks, and rulesets join listSection; duplicate live items fail loudly everywhere #202). The lead's gate review: 1 blocking (the team list's grant) and 11 non-blocking, all folded. Copilot: 3 threads, 2 fixed, 1 recorded and documented, all resolved.
  • Reviewer note (Copilot): the teams default keep and the ignored old env var name are owner decisions; lib/settings.schema.json, action.yml, COVERAGE.md, docs/reference/sections.md, and docs/reference/undeclared-policy.md are regenerated.

BEGIN_COMMIT_OVERRIDE
fix(settings)!: unknown underscore keys fail everywhere; teams gets the _undeclared knob; GSAC_RETRY_BASE_MS

BREAKING CHANGE: an unknown underscore key at the document root fails validation naming _layering and _undeclared, where v2 dropped it as a private note; move notes into YAML comments.
teams takes the {_undeclared, entries} wrapper (default keep): every run lists the repository's teams (repository Administration read) and notes undeclared direct grants, _undeclared: delete revokes them, and snapshots write the wrapper form.
The retry-timing knob is read from GSAC_RETRY_BASE_MS; RETRY_BASE_MS is ignored.
END_COMMIT_OVERRIDE

Copilot AI balanced review requested due to automatic review settings September 13, 2026 08:48
@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

File size check

0 over a hard cap (fails), 8 warning(s).

File Size Tier Cap
.github/scripts/release-pipeline.ts 1755 lines warn 1600
.github/scripts/release-pipeline.ts:339 168 chars warn 150
.github/scripts/release-pipeline.ts:1 26 comment lines (header) warn 25
.github/scripts/release-pipeline.ts:1572 13 comment lines warn 10
.github/workflows/post-green.yml:139 11 comment lines warn 10
test/action/run.test.ts:759 151 chars warn 150
test/action/run.test.ts:780 155 chars warn 150
test/docs/guides.test.ts:468 155 chars warn 150

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

4 managed file(s) skipped; repo-platform owns them.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@Vivswan Vivswan added the merge-when-green Owner approved: merge once every gate is green label Sep 13, 2026
@Vivswan
Vivswan force-pushed the refactor/small-rules branch from e9824f4 to 305b872 Compare September 13, 2026 09:35
Copilot AI review requested due to automatic review settings September 13, 2026 09:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It combines breaking validation changes with destructive team-access revocation and therefore warrants final human verification.

Review details
  • Files reviewed: 48/60 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@Vivswan
Vivswan marked this pull request as ready for review September 13, 2026 09:47
Copilot AI review requested due to automatic review settings September 13, 2026 09:55
@Vivswan
Vivswan force-pushed the refactor/small-rules branch from 305b872 to dc7d044 Compare September 13, 2026 09:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Mixed wrapper typos currently receive misleading underscore-directive diagnostics.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 48/60 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/sections/shared/schema-helpers.ts Outdated
@Vivswan
Vivswan force-pushed the refactor/small-rules branch from dc7d044 to 8de6d62 Compare September 13, 2026 10:06
Copilot AI review requested due to automatic review settings September 13, 2026 10:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Undeclared parent-team revocation can remove access from a declared child team that was considered converged through inheritance.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 48/60 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/sections/teams/index.ts
Copilot AI review requested due to automatic review settings September 13, 2026 10:44
@Vivswan
Vivswan force-pushed the refactor/small-rules branch from 8de6d62 to 6fa7474 Compare September 13, 2026 10:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Mixed renamed and unknown underscore wrapper keys omit the new directive guidance.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 64/65 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/sections/shared/schema-helpers.ts Outdated
…he _undeclared knob; GSAC_RETRY_BASE_MS

Four v3 rule decisions, each a small change:

- Underscore rule: an unknown `_x` key is rejected at the document root as it
  already was inside a wrapper, with an error naming the two directives
  (`_layering`, `_undeclared`); a note belongs in a YAML comment. A misspelled
  `_layerin: replace` can no longer pass as a private note and merge a layer
  meant to replace.
- teams takes `knobbed(TeamConfig)` with default `keep`: every plan lists the
  repository's teams, notes each undeclared direct grant, and revokes them under
  `_undeclared: delete` (DELETE /orgs/{org}/teams/{slug}/repos/{owner}/{repo});
  access granted above the repository is noted, never touched. The snapshot
  writes the wrapper form.
- RETRY_BASE_MS becomes GSAC_RETRY_BASE_MS, documented in the inputs reference;
  the old name is ignored, as an environment variable has no loud-error channel.
- The 12 scenario files named with a section key's underscore are dashed, and
  a test over the corpus pins the rule.
- Drift lines read declared-first everywhere: collaborators and teams said
  `live role X != declared Y`, the other sections `declared X != live Y`.
…e fold of the gate review

- GET /repos/{owner}/{repo}/teams is graded by GitHub at repository Administration (read) alone, so the
  list declares that permission and carries the section's 404 posture (denied): a fine-grained token
  with administration:read and no org Members reads the list and meets the per-team probes' 404 as
  "no access", as it did before the list read existed. The fuzz oracle models the org gate as its own
  switch (orgGateDenied) instead of folding it into the section grade; the route-identity pin skips
  primaryRead, which is the section's, not the route's.
- One helper words the "granted at the X level" note plan() and snapshot() share; the snapshot comment
  no longer calls a direct team never revocable.
- The 12 renamed scenarios (and the pre-existing custom-properties.yml) name themselves as their file
  stem, and the corpus test pins name == stem.
- Docs: the env-var table names what GSAC_RETRY_BASE_MS skips versus scales and lists every variable
  the action reads; layering.md points at the generated count; the upgrade page's underscore sentence
  names its one null-valued corner; a stale "private key" comment and test name reworded.
…; teams docs name the parent-grant caveat

- A wrapper holding an underscore key beside a plain typo, or beside the pre-v3 `undeclared` spelling, now gets
  both clauses in one line: the typo stays on zod's own words, the underscore keys are named before the
  directives advice, and the rename clause is joined instead of returned alone.
- The teams docs say what `_undeclared: delete` does to a declared child team whose access comes through an
  undeclared parent's grant: declare the parent too, or keep.
Copilot AI review requested due to automatic review settings September 13, 2026 11:06
@Vivswan
Vivswan force-pushed the refactor/small-rules branch from 6fa7474 to 4225379 Compare September 13, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Migration and playbook documentation still contradict the implemented team and retry behavior.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 62/63 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread docs/reference/undeclared-policy.md
Comment thread docs/upgrading/v2-to-v3.md
Comment thread docs/upgrading/v2-to-v3.md
@Vivswan
Vivswan merged commit 05e6d41 into main Sep 13, 2026
37 checks passed
@Vivswan
Vivswan deleted the refactor/small-rules branch September 13, 2026 11:15
Vivswan added a commit that referenced this pull request Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-when-green Owner approved: merge once every gate is green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants