Skip to content

refactor(sections)!: environments plan through the secrets and variables engines; the sealing key is read at apply - #207

Merged
Vivswan merged 4 commits into
mainfrom
refactor/sections-environments-engines
Sep 13, 2026
Merged

Vivswan merged 4 commits into
mainfrom
refactor/sections-environments-engines

Conversation

@Vivswan

@Vivswan Vivswan commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

Before / after

before   $ gsac check   # actions_secrets with one declared secret
         GET /repos/o/r/actions/secrets?per_page=100&page=1
         GET /repos/o/r/actions/secrets/public-key          <- read in check mode; a malformed key failed the plan here
         Actions secret values cannot be read back from GitHub, so check mode verifies only that each declared secret exists; apply re-seals and rewrites every declared value on each run

after    $ gsac check
         GET /repos/o/r/actions/secrets?per_page=100&page=1
         actions_secrets: Actions secret values cannot be read back from GitHub, so check mode cannot verify them, only that each declared secret exists; apply re-seals and rewrites every declared value on every run

         $ gsac apply   # the key is read once per scope, by the first sealed PUT's thunk
         GET /repos/o/r/actions/secrets?per_page=100&page=1
         GET /repos/o/r/actions/secrets/public-key
         PUT /repos/o/r/actions/secrets/DEPLOY_TOKEN

         environments/nested.ts: planVariables (88 lines) and planEnvironmentSecrets (83 lines) re-implementing the engines
         -> two scope builders (60 lines) hand the engines one scope per environment

What changed

Environments plan their nested secrets and variables through the shared secrets and variables engines, whose sealing key is read late (at apply, by the first sealed PUT) so one engine serves the repo families and the nested scopes alike.

Behavior changes

Each has a numbered entry in docs/upgrading/v2-to-v3.md (sections 19-20, after #206's 16-18):

  • Check mode no longer reads the sealing key; a malformed key fails the first PUT at apply, before its request leaves.
  • The secrets cannot-verify note carries the section label and the shared template, and appears for an environment the plan creates too.
  • Environment variable lines use the engine's wording (not on environment "prod" on a missing one, in environment "prod" on deletes; a kept undeclared variable still names its environment); the nested duplicate-entry error is the shared one.
  • Repository variable operations gain a describe line in failure prose.

Proof

  • Tests and gates: 3468 unit tests green (bun run check clean), bun run test:e2e 260/260 after bun .github/scripts/trim-openapi.ts; the harness below classifies every main-vs-this-head difference (0 unclassified).
  • Line accounting: production src +276 -338 = -62, tests +45, docs +23 (nested.ts -118 as the two hand-written planners become scope builders; the engines +62 for the late-bound key, where/suffix, describe, plainData; errors.ts +3 for the denial wording).
Technical details

Base: main at 05e6d41 (#206); #202 landed as 2cb5c8a, the squash of 79a0851.

How

  • Late-bound sealing key: SecretsPlanScope.publicKey(exec, describe) is called from the first sealed PUT's payload thunk, once per scope (a memoized promise): an environment's key exists only after the PUT that creates the environment. The four repo families declare the key endpoint phase: "execution" (the port is gated; check mode cannot issue it), which needed GatedReadDecl.phase?: "execution" for the Codespaces write-gated read.
  • Scope prose: both engines take where (the home: "the environment", or environment "prod" for variables so a kept note names it) and suffix ( in environment "prod") and render through the shared helpers (valueDrift, cannotVerifyNote); the variables engine's ops carry describe and prove every payload plain (plainData).
  • Environments plan nested variables and secrets through the engines; the hand-rolled duplicate rejecters in nested.ts, branch-policies.ts, and protection-rules.ts become rejectDuplicates with a resource noun (variable of the "prod" environment).

Harness (main at 05e6d41 vs this head, same inputs, same matrix as #202): 9233 leaves compared, 211 differing, 0 unclassified.

intended difference leaves
secrets cannot-verify note through the template, labelled by scope 141
variables operations carry a describe line 24
environments: missing-environment notes reorder around the added secrets note 23
a missing variable's drift names its environment (not on environment "prod") 8
a denied request names its operation in parentheses 15

Against main before #202 (cumulative): 9869 leaves, 773 differing, 0 unclassified. Baseline for the table above: a git archive of main at 05e6d41 (#206 changed teams, so the earlier #202-head baseline no longer matches main).

Line accounting (production src = src/** minus *.test.ts, mock.ts, scenarios, docs.yml; against main at 05e6d41):

file net
src/sections/environments/nested.ts -118
src/sections/environments/branch-policies.ts, protection-rules.ts -5, -5
src/sections/shared/repo-variables.ts -3
src/sections/environments/index.ts 0
src/sections/contract/endpoints.ts, errors.ts +1, +3
src/sections/shared/repo-secrets.ts +3
src/sections/shared/secrets-engine.ts +24 (the late-bound key, where/suffix)
src/sections/shared/variables-engine.ts +38 (where/suffix, describe, plainData, rejectDuplicateVariableNames)
total +276 -338 = -62

Tests +45 (the late-bound key, the plainData regression, the engine wording pins).

Codex rubber-duck review, 4 rounds: round 1: 1 blocking (the variables engine lost nested.ts's plainData guard, so extra: NaN reached the wire as null), fixed in the engine for both consumers with a regression test in src/sections/actions_variables/actions_variables.test.ts; round 2: 0/0; round 3 (the rebase resolution below): 0/0; round 4 (the three gate-fold commits): 0/0.

Rebases: four git rebase --onto replays. The first (onto #202's trim commit) differed in one place outside docs/upgrading/v2-to-v3.md: #202 trimmed missingDrift, so the two engines render the missing-drift line inline (... but not on ${scope.where ?? "the repo"}; apply will create it); the rendered text is unchanged, and #202's secretValuesOf conversion of listSecretValues no longer appears here because it landed there. The second (onto #202 over main at d92b738, #205) replayed without conflict: the interdiff of added and removed lines outside the doc is empty, the doc's sections sit at 16-17 after #202's 11-15, and git grep for the #205 renames over src and test is empty. The third (onto main at 69fc7c4 after #202 landed as the squash 2cb5c8a) replayed without conflict, the range-diff shows the one commit =, and the added and removed lines are identical to the previous head's including the doc. The fourth (onto main at 05e6d41, #206) conflicted only in the doc, whose sections now sit at 19-20 after #206's 16-18; outside the doc the replay is identical.

Recorded, not built: a test proving several payload thunks share one rejected sealing-key read; the engines do not call the duplicate guard themselves (a fourth scope must remember rejectDuplicate*Names as a separate call), routed to the contract-tightening task.

Lead gate review (b8e4a1a), folded as three commits: blocking 1 (two environments holding the same undeclared variable emitted one byte-identical kept note; nestedProse names the environment again, the secrets noun already did); non-blocking (the four secret check-drift descriptions said check reads the sealing key; a denied request read "denied ... failed" with two verbs and now names its operation in parentheses, the token was denied GET /repos/o/r/actions/secrets/public-key (reading the actions_secrets sealing key): 403 ..., seven pins moved; this body). Confirmed clean by the review: one planner each, no key read in check mode, one key read per environment at apply, a denied key fails with zero PUTs, placeholders through the one helper.

Out-of-territory edits: none.

BEGIN_COMMIT_OVERRIDE
refactor(sections)!: environments plan through the secrets and variables engines; the sealing key is read at apply

BREAKING CHANGE: check mode no longer reads a secret family's sealing key (a malformed key fails the first PUT at apply); the environment secret and variable lines use the engines' wording (docs/upgrading/v2-to-v3.md sections 19-20).
END_COMMIT_OVERRIDE

@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/sections-shared-engines branch 3 times, most recently from 710c209 to bb6fd9d Compare September 13, 2026 10:24
@Vivswan
Vivswan force-pushed the refactor/sections-environments-engines branch from a83fecd to 3b57545 Compare September 13, 2026 10:28
@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.

@Vivswan
Vivswan force-pushed the refactor/sections-environments-engines branch from 3b57545 to 3ebc7ec Compare September 13, 2026 10:49
Base automatically changed from refactor/sections-shared-engines to main September 13, 2026 10:54
@Vivswan
Vivswan force-pushed the refactor/sections-environments-engines branch from 3ebc7ec to b8e4a1a Compare September 13, 2026 11:01
@Vivswan
Vivswan marked this pull request as ready for review September 13, 2026 11:05
Copilot AI balanced review requested due to automatic review settings September 13, 2026 11:05

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.

🟢 Approval recommended

The refactor is internally consistent and has targeted coverage for its changed behavior.

Pull request overview

Refactors environment secrets and variables to use shared engines while deferring sealing-key reads until apply time.

Changes:

  • Reuses shared engines for environment resources.
  • Makes sealing-key reads execution-only and memoized per scope.
  • Updates validation, tests, and v3 migration documentation.
File summaries
File Description
test/sections/secrets-engine.test.ts Tests deferred, shared key reads.
test/sections/registry.test.ts Pins execution-phase endpoints.
src/sections/shared/variables-engine.ts Adds scoped prose, descriptions, and payload validation.
src/sections/shared/secrets-engine.ts Defers and memoizes sealing-key reads.
src/sections/shared/repo-variables.ts Adopts shared validation and descriptions.
src/sections/shared/repo-secrets.ts Marks key endpoints execution-only.
src/sections/environments/protection-rules.ts Uses shared duplicate validation.
src/sections/environments/nested.ts Replaces bespoke nested planners with shared engines.
src/sections/environments/index.ts Passes section metadata into validation.
src/sections/environments/environments.test.ts Updates nested behavior coverage.
src/sections/environments/branch-policies.ts Uses shared duplicate validation.
src/sections/contract/endpoints.ts Allows execution-phase gated reads.
src/sections/actions_variables/actions_variables.test.ts Covers descriptions and plain payload validation.
src/sections/actions_secrets/actions_secrets.test.ts Covers apply-time sealing-key behavior.
docs/upgrading/v2-to-v3.md Documents the two breaking changes.
Review details
  • Files reviewed: 15/15 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…les engines; the sealing key is read at apply

The secrets engine reads the scope's sealing key from the first sealed PUT's thunk (once per scope), so the four repo families declare the key endpoint execution-phase and environments' nested secrets share the engine; the variables engine serves environments' nested variables too. Both engines take the scope's home noun and change-line suffix and render through the shared prose helpers; the variables engine proves every payload plain. The per-list duplicate rejecters in environments give way to rejectDuplicates with a resource noun.

BREAKING CHANGE: check mode no longer reads the sealing key (a malformed key fails the first PUT at apply); the environment secret and variable lines use the engines' wording. docs/upgrading/v2-to-v3.md sections 10-11.
Copilot AI review requested due to automatic review settings September 13, 2026 11:33
@Vivswan
Vivswan force-pushed the refactor/sections-environments-engines branch from b8e4a1a to 2e71b66 Compare September 13, 2026 11:33

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.

🟢 Approval recommended

The shared-engine refactor is consistent with existing contracts and has focused coverage for deferred reads, payload validation, and environment behavior.

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

@Vivswan
Vivswan merged commit 5dfc10a into main Sep 13, 2026
36 checks passed
@Vivswan
Vivswan deleted the refactor/sections-environments-engines branch September 13, 2026 11:38
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