code-foundry 0.32.4 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CONTRIBUTING.md +17 -14
- package/.github/workflows/ci.yml +25 -8
- package/.github/workflows/codeql.yml +28 -14
- package/.github/workflows/draft-pr.yml +25 -18
- package/.github/workflows/draft-pr_self-ci.yml +3 -1
- package/.github/workflows/opencode-security_self-ci.yml +4 -2
- package/.github/workflows/release-pr.yml +31 -9
- package/.github/workflows/release-pr_self-ci.yml +4 -7
- package/.github/workflows/release.yml +136 -23
- package/.github/workflows/release_self-ci.yml +5 -1
- package/.github/workflows/security.yml +18 -11
- package/.github/workflows/test.yml +28 -9
- package/.github/workflows/validation.yml +183 -0
- package/.github/workflows/validation_self-ci.yml +82 -0
- package/CHANGELOG.md +12 -0
- package/README.md +7 -4
- package/docs/CONFIGURATION.md +2 -1
- package/docs/RELEASES.md +26 -18
- package/docs/WORKFLOWS.md +54 -4
- package/package.json +1 -1
- package/src/commands/doctor.mjs +53 -3
- package/src/commands/release.mjs +220 -68
- package/src/commands/sync.mjs +88 -4
- package/src/lib/github-doctor.mjs +10 -4
- package/src/lib/release-policy.mjs +88 -19
- package/src/lib/validation-policy.mjs +127 -0
- package/src/runtime.mjs +54 -0
- package/.github/workflows/ci_self-ci.yml +0 -21
- package/.github/workflows/codeql_self-ci.yml +0 -29
- package/.github/workflows/security_self-ci.yml +0 -21
- package/.github/workflows/test_self-ci.yml +0 -22
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
name: Code Foundry Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
mode:
|
|
7
|
+
description: Validation tier (fast, audit, or release).
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
runtime-repository:
|
|
11
|
+
description: Repository containing the Code Foundry runtime.
|
|
12
|
+
required: false
|
|
13
|
+
type: string
|
|
14
|
+
default: 0xPlayerOne/code-foundry
|
|
15
|
+
runtime-ref:
|
|
16
|
+
description: Code Foundry runtime tag or ref.
|
|
17
|
+
required: false
|
|
18
|
+
type: string
|
|
19
|
+
default: v0.32.4
|
|
20
|
+
ci-runner:
|
|
21
|
+
description: Runner used by CI jobs.
|
|
22
|
+
required: false
|
|
23
|
+
type: string
|
|
24
|
+
default: ubuntu-latest
|
|
25
|
+
test-runner:
|
|
26
|
+
description: Runner used by non-unit test jobs.
|
|
27
|
+
required: false
|
|
28
|
+
type: string
|
|
29
|
+
default: ubuntu-latest
|
|
30
|
+
unit-runner:
|
|
31
|
+
description: Runner used by unit tests.
|
|
32
|
+
required: false
|
|
33
|
+
type: string
|
|
34
|
+
default: ubuntu-slim
|
|
35
|
+
security-runner:
|
|
36
|
+
description: Runner used by Security jobs.
|
|
37
|
+
required: false
|
|
38
|
+
type: string
|
|
39
|
+
default: ubuntu-slim
|
|
40
|
+
codeql-runner:
|
|
41
|
+
description: Runner used by CodeQL jobs.
|
|
42
|
+
required: false
|
|
43
|
+
type: string
|
|
44
|
+
default: ubuntu-latest
|
|
45
|
+
rust-shards:
|
|
46
|
+
description: JSON array of Rust scope sharding values. Use ["all"] for single-pass behavior.
|
|
47
|
+
required: false
|
|
48
|
+
type: string
|
|
49
|
+
default: '["all"]'
|
|
50
|
+
rust-threads:
|
|
51
|
+
description: Threads used for Rust extraction and analysis. Values above 1 opt into local parallelism.
|
|
52
|
+
required: false
|
|
53
|
+
type: string
|
|
54
|
+
default: '1'
|
|
55
|
+
rust-max-parallel:
|
|
56
|
+
description: Maximum Rust shard jobs allowed to run concurrently.
|
|
57
|
+
required: false
|
|
58
|
+
type: number
|
|
59
|
+
default: 1
|
|
60
|
+
secrets:
|
|
61
|
+
TURBO_TOKEN:
|
|
62
|
+
required: false
|
|
63
|
+
NEXTAUTH_SECRET:
|
|
64
|
+
required: false
|
|
65
|
+
|
|
66
|
+
# The orchestrator keeps the union of permissions needed by the chain it calls
|
|
67
|
+
# (CodeQL uploads require security-events: write). Nested reusable workflows
|
|
68
|
+
# can only maintain or reduce permissions, never elevate them.
|
|
69
|
+
permissions:
|
|
70
|
+
actions: read
|
|
71
|
+
contents: read
|
|
72
|
+
packages: read
|
|
73
|
+
security-events: write
|
|
74
|
+
|
|
75
|
+
jobs:
|
|
76
|
+
ci:
|
|
77
|
+
name: CI
|
|
78
|
+
if: inputs.mode == 'fast' || inputs.mode == 'audit'
|
|
79
|
+
uses: ./.github/workflows/ci.yml
|
|
80
|
+
with:
|
|
81
|
+
runtime-repository: ${{ inputs.runtime-repository }}
|
|
82
|
+
runtime-ref: ${{ inputs.runtime-ref }}
|
|
83
|
+
runner: ${{ inputs.ci-runner }}
|
|
84
|
+
secrets:
|
|
85
|
+
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
86
|
+
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
|
87
|
+
|
|
88
|
+
test:
|
|
89
|
+
name: Test
|
|
90
|
+
if: inputs.mode == 'fast' || inputs.mode == 'audit'
|
|
91
|
+
uses: ./.github/workflows/test.yml
|
|
92
|
+
with:
|
|
93
|
+
runtime-repository: ${{ inputs.runtime-repository }}
|
|
94
|
+
runtime-ref: ${{ inputs.runtime-ref }}
|
|
95
|
+
runner: ${{ inputs.test-runner }}
|
|
96
|
+
unit-runner: ${{ inputs.unit-runner }}
|
|
97
|
+
unit-only: ${{ inputs.mode == 'fast' }}
|
|
98
|
+
secrets:
|
|
99
|
+
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
100
|
+
|
|
101
|
+
security:
|
|
102
|
+
name: Security
|
|
103
|
+
if: inputs.mode == 'audit'
|
|
104
|
+
uses: ./.github/workflows/security.yml
|
|
105
|
+
with:
|
|
106
|
+
runtime-repository: ${{ inputs.runtime-repository }}
|
|
107
|
+
runtime-ref: ${{ inputs.runtime-ref }}
|
|
108
|
+
runner: ${{ inputs.security-runner }}
|
|
109
|
+
|
|
110
|
+
codeql:
|
|
111
|
+
name: CodeQL
|
|
112
|
+
if: inputs.mode == 'audit'
|
|
113
|
+
uses: ./.github/workflows/codeql.yml
|
|
114
|
+
with:
|
|
115
|
+
runtime-repository: ${{ inputs.runtime-repository }}
|
|
116
|
+
runtime-ref: ${{ inputs.runtime-ref }}
|
|
117
|
+
runner: ${{ inputs.codeql-runner }}
|
|
118
|
+
rust-shards: ${{ inputs.rust-shards }}
|
|
119
|
+
rust-threads: ${{ inputs.rust-threads }}
|
|
120
|
+
rust-max-parallel: ${{ inputs.rust-max-parallel }}
|
|
121
|
+
|
|
122
|
+
release-policy:
|
|
123
|
+
name: Release Policy
|
|
124
|
+
if: inputs.mode == 'release'
|
|
125
|
+
runs-on: ubuntu-slim
|
|
126
|
+
timeout-minutes: 10
|
|
127
|
+
steps:
|
|
128
|
+
- name: Checkout
|
|
129
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
130
|
+
with:
|
|
131
|
+
persist-credentials: false
|
|
132
|
+
fetch-depth: 0
|
|
133
|
+
filter: blob:none
|
|
134
|
+
- name: Checkout runtime
|
|
135
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
136
|
+
with:
|
|
137
|
+
persist-credentials: false
|
|
138
|
+
repository: ${{ inputs.runtime-repository }}
|
|
139
|
+
ref: ${{ inputs.runtime-ref }}
|
|
140
|
+
path: .github/.code-foundry
|
|
141
|
+
sparse-checkout: |
|
|
142
|
+
src/lib
|
|
143
|
+
src/runtime.mjs
|
|
144
|
+
- name: Install runtime
|
|
145
|
+
run: mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
|
|
146
|
+
- name: Validate generated release diff
|
|
147
|
+
env:
|
|
148
|
+
FOUNDRY_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
149
|
+
FOUNDRY_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
150
|
+
FOUNDRY_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
|
151
|
+
FOUNDRY_REPOSITORY: ${{ github.repository }}
|
|
152
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation release_diff
|
|
153
|
+
|
|
154
|
+
# Aggregate check: "Validation / Gate" (caller job name + this job name).
|
|
155
|
+
# Always evaluates; only jobs required for the mode must have succeeded.
|
|
156
|
+
gate:
|
|
157
|
+
name: Gate
|
|
158
|
+
needs: [ci, test, security, codeql, release-policy]
|
|
159
|
+
if: always()
|
|
160
|
+
runs-on: ubuntu-slim
|
|
161
|
+
timeout-minutes: 10
|
|
162
|
+
env:
|
|
163
|
+
FOUNDRY_MODE: ${{ inputs.mode }}
|
|
164
|
+
FOUNDRY_CI: ${{ needs.ci.result }}
|
|
165
|
+
FOUNDRY_TEST: ${{ needs.test.result }}
|
|
166
|
+
FOUNDRY_SECURITY: ${{ needs.security.result }}
|
|
167
|
+
FOUNDRY_CODEQL: ${{ needs.codeql.result }}
|
|
168
|
+
FOUNDRY_RELEASE_POLICY: ${{ needs.release-policy.result }}
|
|
169
|
+
steps:
|
|
170
|
+
- name: Checkout runtime
|
|
171
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
172
|
+
with:
|
|
173
|
+
persist-credentials: false
|
|
174
|
+
repository: ${{ inputs.runtime-repository }}
|
|
175
|
+
ref: ${{ inputs.runtime-ref }}
|
|
176
|
+
path: .github/.code-foundry
|
|
177
|
+
sparse-checkout: |
|
|
178
|
+
src/lib
|
|
179
|
+
src/runtime.mjs
|
|
180
|
+
- name: Install runtime
|
|
181
|
+
run: mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
|
|
182
|
+
- name: Evaluate aggregate gate
|
|
183
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation gate
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
name: Code Foundry
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, staging]
|
|
6
|
+
types:
|
|
7
|
+
- opened
|
|
8
|
+
- synchronize
|
|
9
|
+
- reopened
|
|
10
|
+
- ready_for_review
|
|
11
|
+
schedule:
|
|
12
|
+
- cron: '31 6 * * 1'
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
# Default every caller job to no repository permissions. Individual jobs grant
|
|
16
|
+
# only the scopes their own validation path consumes.
|
|
17
|
+
permissions: {}
|
|
18
|
+
|
|
19
|
+
# Superseded PR updates cancel the previous run; schedule and dispatch runs
|
|
20
|
+
# are keyed by event so periodic audits and manual runs stay independent.
|
|
21
|
+
concurrency:
|
|
22
|
+
group: code-foundry-validation-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
|
|
23
|
+
cancel-in-progress: true
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
mode:
|
|
27
|
+
name: Mode
|
|
28
|
+
runs-on: ubuntu-slim
|
|
29
|
+
timeout-minutes: 10
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
outputs:
|
|
33
|
+
mode: ${{ steps.classify.outputs.mode }}
|
|
34
|
+
steps:
|
|
35
|
+
- name: Checkout runtime
|
|
36
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
37
|
+
with:
|
|
38
|
+
persist-credentials: false
|
|
39
|
+
repository: 0xPlayerOne/code-foundry
|
|
40
|
+
ref: ${{ github.sha }}
|
|
41
|
+
path: .github/.code-foundry
|
|
42
|
+
sparse-checkout: |
|
|
43
|
+
src/lib
|
|
44
|
+
src/runtime.mjs
|
|
45
|
+
- name: Install runtime
|
|
46
|
+
run: mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
|
|
47
|
+
- name: Classify validation mode
|
|
48
|
+
id: classify
|
|
49
|
+
env:
|
|
50
|
+
FOUNDRY_EVENT_NAME: ${{ github.event_name }}
|
|
51
|
+
FOUNDRY_BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
52
|
+
FOUNDRY_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
53
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation mode >> "$GITHUB_OUTPUT"
|
|
54
|
+
|
|
55
|
+
# The stable aggregate check name is the caller job name plus the
|
|
56
|
+
# orchestrator gate job name: "Validation / Gate". Keep both names fixed.
|
|
57
|
+
validation:
|
|
58
|
+
name: Validation
|
|
59
|
+
needs: mode
|
|
60
|
+
# Reusable workflows can only maintain or reduce the caller job's scopes.
|
|
61
|
+
# The audit tier needs security-events: write for CodeQL uploads.
|
|
62
|
+
permissions:
|
|
63
|
+
actions: read
|
|
64
|
+
contents: read
|
|
65
|
+
packages: read
|
|
66
|
+
security-events: write
|
|
67
|
+
uses: ./.github/workflows/validation.yml
|
|
68
|
+
with:
|
|
69
|
+
mode: ${{ needs.mode.outputs.mode }}
|
|
70
|
+
runtime-repository: 0xPlayerOne/code-foundry
|
|
71
|
+
runtime-ref: ${{ github.sha }}
|
|
72
|
+
ci-runner: ubuntu-latest
|
|
73
|
+
test-runner: ubuntu-latest
|
|
74
|
+
unit-runner: ubuntu-slim
|
|
75
|
+
security-runner: ubuntu-slim
|
|
76
|
+
codeql-runner: ubuntu-latest
|
|
77
|
+
rust-shards: '["all"]'
|
|
78
|
+
rust-threads: '1'
|
|
79
|
+
rust-max-parallel: 1
|
|
80
|
+
secrets:
|
|
81
|
+
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
82
|
+
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.33.0](https://github.com/0xPlayerOne/code-foundry/compare/v0.32.4...v0.33.0) (2026-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add tiered validation workflow ([#301](https://github.com/0xPlayerOne/code-foundry/issues/301)) ([dd3015e](https://github.com/0xPlayerOne/code-foundry/commit/dd3015ea22ab94b2ed3d55f6e900a04f3e968aeb))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* preserve regex escape in test assertion ([#303](https://github.com/0xPlayerOne/code-foundry/issues/303)) ([0a3c56f](https://github.com/0xPlayerOne/code-foundry/commit/0a3c56f5318baa8002b1079687797702f2c06383))
|
|
14
|
+
|
|
3
15
|
## [0.32.4](https://github.com/0xPlayerOne/code-foundry/compare/v0.32.3...v0.32.4) (2026-08-01)
|
|
4
16
|
|
|
5
17
|
|
package/README.md
CHANGED
|
@@ -88,10 +88,13 @@ GitHub Code Quality or other paid GitHub features.
|
|
|
88
88
|
See [Workflow and CI conventions](docs/WORKFLOWS.md) for triggers, required
|
|
89
89
|
checks, runners, coverage, caching, and custom workflow extensions.
|
|
90
90
|
|
|
91
|
-
The
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
The contribution policy uses the `staging-release` workflow: feature PRs squash
|
|
92
|
+
into `staging`, the promotion PR rebases into `main` (`merge_strategy: rebase`),
|
|
93
|
+
and Release Please version PRs squash into `main` (`release_merge_strategy:
|
|
94
|
+
squash`). Release automation never defaults to a merge method and never merges
|
|
95
|
+
with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on
|
|
96
|
+
any other strategy. GitHub Stacks is not part of this topology and does not
|
|
97
|
+
reduce the required workflow runs.
|
|
95
98
|
|
|
96
99
|
## Releases and publishing
|
|
97
100
|
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -48,7 +48,8 @@ repository manifests and source
|
|
|
48
48
|
| `npm_publish` | `true` or `false` | Opt into npm publication |
|
|
49
49
|
| `license` | `gpl-3.0-or-later`, `agpl-3.0-or-later`, `mit`, `preserve`, `none` | License policy; new repositories default to GPLv3 |
|
|
50
50
|
| `git_workflow` | `staging-release` | Branch/release model; the standard model promotes `staging` into `main` |
|
|
51
|
-
| `merge_strategy` | `rebase
|
|
51
|
+
| `merge_strategy` | `rebase` | Promotion merge method for `staging` → `main`; the staging-release topology requires rebase |
|
|
52
|
+
| `release_merge_strategy` | `squash` | Merge method for Release Please version PRs into `main`; release automation fails closed unless squash |
|
|
52
53
|
| `runner` fields | GitHub runner names | Per-workflow runner policy |
|
|
53
54
|
|
|
54
55
|
Supported features are `ci`, `codeql`, `security`, `test`, `draft-pr`,
|
package/docs/RELEASES.md
CHANGED
|
@@ -12,9 +12,13 @@ Keep commits Conventional Commit-shaped (`feat:`, `fix:`, `docs:`, `ci:`,
|
|
|
12
12
|
`chore:`, and so on). Release Please uses them to select patch/minor/major
|
|
13
13
|
versions and generate grouped changelog notes.
|
|
14
14
|
|
|
15
|
-
The
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
The merge audit pins one merge method per transition in this topology. Feature
|
|
16
|
+
and fix branches land on `staging` with **squash** merges, the `staging` → `main`
|
|
17
|
+
promotion PR merges with **rebase** (`merge_strategy: rebase`), and Release
|
|
18
|
+
Please version PRs merge with **squash** (`release_merge_strategy: squash`).
|
|
19
|
+
Release automation never defaults to a merge method and never merges with
|
|
20
|
+
`--admin`: the release workflow fails closed unless `release_merge_strategy` is
|
|
21
|
+
exactly `squash`. The current supported `git_workflow` is `staging-release`.
|
|
18
22
|
|
|
19
23
|
The release workflow opens or updates a versioned PR after changes reach
|
|
20
24
|
`main`. Merging that PR updates the changelog, creates the Git tag and GitHub
|
|
@@ -30,23 +34,27 @@ Set these values in `.github/code-foundry.yml`:
|
|
|
30
34
|
```yaml
|
|
31
35
|
release_type: auto # auto, node, python, rust, simple, or none
|
|
32
36
|
npm_publish: false # true only for an npm package
|
|
33
|
-
merge_strategy: rebase #
|
|
34
|
-
release_merge_strategy:
|
|
37
|
+
merge_strategy: rebase # required: staging -> main promotion PRs rebase
|
|
38
|
+
release_merge_strategy: squash # required: Release Please version PRs squash only
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
`merge_strategy` applies to promotion PRs (`staging` into `main`) and
|
|
38
|
-
`release_merge_strategy` to Release Please version PRs;
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
`release_merge_strategy` to Release Please version PRs; feature PRs into
|
|
43
|
+
`staging` use squash merges. The topology requires `merge_strategy: rebase`
|
|
44
|
+
and `release_merge_strategy: squash`; `code-foundry doctor` and
|
|
45
|
+
`code-foundry sync` reject any other value, and the release workflow fails
|
|
46
|
+
closed instead of falling back to `merge`. Both keep `main` fully linear,
|
|
47
|
+
which is what makes the post-release reconciliation possible: release-only
|
|
48
|
+
main commits cannot be discarded because they are allowed metadata-only, and
|
|
49
|
+
all non-metadata drift is rejected before mutation.
|
|
50
|
+
|
|
51
|
+
Patch-equivalent divergence between `main` and `staging` is treated as aligned.
|
|
52
|
+
When `staging` has pending commits that are not yet represented on `main`, the
|
|
53
|
+
release workflow replays those staging-only commits in order onto a detached
|
|
54
|
+
worktree rooted at `main`, and then updates `staging` with an exact
|
|
55
|
+
`--force-with-lease` to prevent
|
|
56
|
+
unintended branch rewrites. There is no unconditional mirror force-push and no
|
|
57
|
+
fallback synchronization commit path.
|
|
50
58
|
|
|
51
59
|
`auto` selects a supported manifest. Use `simple` with `version.txt` for a
|
|
52
60
|
repository without a package manifest and `none` for a repository that should
|
|
@@ -78,6 +86,6 @@ the generated version pull request before using the token to merge it.
|
|
|
78
86
|
|
|
79
87
|
1. Merge tested changes from `staging` into `main`.
|
|
80
88
|
2. Review the generated Release Please PR and changelog.
|
|
81
|
-
3. Merge the release PR with the repository
|
|
89
|
+
3. Merge the release PR with the repository's configured `release_merge_strategy` (**squash**; the release workflow fails closed on any other value).
|
|
82
90
|
4. Confirm the GitHub Release and any package publication.
|
|
83
91
|
5. Synchronize `staging` with the new `main` release commit.
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Standard triggers
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The canonical validation caller uses pull requests plus bounded audit entry
|
|
6
|
+
points; it does not run the same suites again on branch pushes:
|
|
6
7
|
|
|
7
8
|
```yaml
|
|
8
|
-
push:
|
|
9
|
-
branches: [main, staging]
|
|
10
9
|
pull_request:
|
|
11
10
|
branches: [main, staging]
|
|
11
|
+
schedule:
|
|
12
|
+
- cron: '31 6 * * 1'
|
|
13
|
+
workflow_dispatch:
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
Pull requests into `staging` run the fast tier, ordinary pull requests into
|
|
17
|
+
`main` run the full audit tier, and exact Release Please pull requests into
|
|
18
|
+
`main` run only release policy. Scheduled and manual runs select the audit
|
|
19
|
+
tier. Draft PR automation separately listens to supported topic-branch pushes,
|
|
20
|
+
promotion automation listens to `staging` pushes, and release automation
|
|
21
|
+
listens to `main` pushes.
|
|
15
22
|
Custom deployment, indexing, search, Slither, or other workflows are
|
|
16
23
|
repository-owned extensions and should keep their own triggers and permissions.
|
|
17
24
|
|
|
@@ -31,6 +38,49 @@ Use concise job names such as `CI / Format`, `Test / Unit`, and
|
|
|
31
38
|
`CodeQL / Analyze (Python)`. Required checks should match the jobs actually
|
|
32
39
|
enabled for the repository profile.
|
|
33
40
|
|
|
41
|
+
## Merge methods
|
|
42
|
+
|
|
43
|
+
The merge audit pins one merge method per transition. `code-foundry doctor`
|
|
44
|
+
and `code-foundry sync` fail closed on any other strategy, and the release
|
|
45
|
+
workflow refuses to run unless its strategy is exactly `squash`.
|
|
46
|
+
|
|
47
|
+
| Transition | Merge method | Enforcement |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Feature/fix PR into `staging` | Squash | Contribution policy; see `CONTRIBUTING.md` |
|
|
50
|
+
| `staging` → `main` promotion PR | Rebase (`merge_strategy: rebase`) | `merge_strategy` must be `rebase`; merge commits are rejected |
|
|
51
|
+
| Release Please version PR into `main` | Squash (`release_merge_strategy: squash`) | Release automation fails closed unless `squash`; never defaults to `merge`, never uses `--admin` |
|
|
52
|
+
|
|
53
|
+
Keeping `main` linear — rebase promotions and squash release PRs — is what
|
|
54
|
+
lets the post-release reconciliation fast-forward or replay `staging` safely.
|
|
55
|
+
|
|
56
|
+
Protect `staging` with the aggregate `Validation / Gate`, squash-only pull
|
|
57
|
+
requests, and a single GitHub Actions integration path. That path uses the
|
|
58
|
+
GitHub Actions integration token by default, and optionally an SSH deploy key
|
|
59
|
+
when `STAGING_DEPLOY_KEY` is configured. The deploy key is required only when
|
|
60
|
+
a personal-repository ruleset for `staging` enforces a Deploy Key bypass for
|
|
61
|
+
`release reconcile`; repositories without that bypass continue with tokenless
|
|
62
|
+
checkout + GitHub API calls.
|
|
63
|
+
|
|
64
|
+
When used, the optional `STAGING_DEPLOY_KEY` is written at runtime only for
|
|
65
|
+
reconcile, used to set `GIT_SSH_COMMAND` and trusted host settings for the
|
|
66
|
+
`release reconcile` transport, then scrubbed after the step.
|
|
67
|
+
|
|
68
|
+
When absent, the job keeps `GH_TOKEN = github.token` and runs `gh auth setup-git`
|
|
69
|
+
so repositories without a Deploy Key ruleset bypass still reconcile successfully.
|
|
70
|
+
|
|
71
|
+
For this reconciliation path, maintainer PATs and administrator roles are not
|
|
72
|
+
authorized bypasses; the job deliberately authenticates with `github.token`,
|
|
73
|
+
not `CODE_FOUNDRY_TOKEN` or `RELEASE_PLEASE_TOKEN`.
|
|
74
|
+
|
|
75
|
+
## GitHub Stacks
|
|
76
|
+
|
|
77
|
+
GitHub Stacks (stacked pull requests) is not part of this topology and does
|
|
78
|
+
not reduce required workflow runs. Every pull request in a stack still
|
|
79
|
+
triggers its own validation run, and each branch keeps its own required
|
|
80
|
+
checks; stacking never collapses or skips a required check in the tiered
|
|
81
|
+
validation gate. Land changes through the standard `staging-release` flow
|
|
82
|
+
instead.
|
|
83
|
+
|
|
34
84
|
## Language defaults
|
|
35
85
|
|
|
36
86
|
- TypeScript/JavaScript: ESLint, Prettier, and Bun's native `bun test`.
|
package/package.json
CHANGED
package/src/commands/doctor.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process'
|
|
|
6
6
|
import { includesValue, readConfig } from '../lib/config.mjs'
|
|
7
7
|
import { recommendRunners, resolveProfile } from '../lib/profile.mjs'
|
|
8
8
|
import { doctorGithub } from '../lib/github-doctor.mjs'
|
|
9
|
+
import { isGeneratedEventCaller } from './sync.mjs'
|
|
9
10
|
|
|
10
11
|
/** @param {string} root @param {{ github?: boolean }} [options] */
|
|
11
12
|
export function doctor(root, options = {}) {
|
|
@@ -68,15 +69,64 @@ export function doctor(root, options = {}) {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const features = config.features ?? 'all'
|
|
73
|
+
const mergeStrategy = config.merge_strategy ?? 'rebase'
|
|
74
|
+
if (mergeStrategy !== 'rebase') {
|
|
75
|
+
error(`merge_strategy must be "rebase" for the staging-release promotion topology; got "${mergeStrategy}".`)
|
|
73
76
|
}
|
|
74
|
-
|
|
77
|
+
const releaseMergeStrategy = config.release_merge_strategy ?? ''
|
|
78
|
+
if (includesValue(features, 'release') && releaseMergeStrategy !== 'squash') {
|
|
79
|
+
error(`release_merge_strategy must be "squash" for automated release merges; got "${releaseMergeStrategy || '(unset; release automation never defaults to merge)'}".`)
|
|
80
|
+
}
|
|
81
|
+
for (const workflow of ['validation', 'draft-pr', 'release-pr', 'release']) {
|
|
82
|
+
if (includesValue(features, workflow) && !existsSync(join(target, `.github/workflows/${workflow}.yml`))) error(`missing enabled workflow: ${workflow}.yml`)
|
|
83
|
+
}
|
|
84
|
+
const validationEnabled = includesValue(features, 'validation') || ['ci', 'test', 'security', 'codeql'].some((legacy) => includesValue(features, legacy))
|
|
85
|
+
const validationCaller = ['validation.yml', 'validation_self-ci.yml']
|
|
86
|
+
.map((file) => join(target, `.github/workflows/${file}`))
|
|
87
|
+
.find((file) => existsSync(file) && /pull_request:/.test(readFileSync(file, 'utf8')))
|
|
88
|
+
if (validationEnabled && !validationCaller) {
|
|
89
|
+
error('missing tiered validation caller; run code-foundry sync to adopt validation.yml')
|
|
90
|
+
} else if (validationCaller) {
|
|
91
|
+
const caller = readFileSync(validationCaller, 'utf8')
|
|
92
|
+
if (!/^ validation:\n name: Validation/m.test(caller)) {
|
|
93
|
+
error('validation caller is missing the Validation job; the Validation / Gate aggregate check cannot form.')
|
|
94
|
+
}
|
|
95
|
+
if (!/uses:\s+(?:\.\/\.github\/workflows\/validation\.yml|\S+\/\.github\/workflows\/validation\.yml@)/.test(caller)) {
|
|
96
|
+
error('validation caller does not reference the validation orchestrator.')
|
|
97
|
+
}
|
|
98
|
+
const runtimeRef = caller.match(/^\s+runtime-ref:\s+(.+?)\s*$/m)?.[1]
|
|
99
|
+
const checkoutRef = caller.match(/^\s+ref:\s+(.+?)\s*$/m)?.[1]
|
|
100
|
+
if (!runtimeRef || !checkoutRef) {
|
|
101
|
+
error('validation caller is missing runtime ref wiring (mode checkout or orchestrator input).')
|
|
102
|
+
} else if (runtimeRef !== checkoutRef) {
|
|
103
|
+
error(`validation caller pins mismatched runtime refs (${runtimeRef} vs ${checkoutRef}).`)
|
|
104
|
+
} else if (runtimeRef !== '${{ github.sha }}' && !/^v\d+\.\d+\.\d+$/.test(runtimeRef)) {
|
|
105
|
+
warn(`validation caller runtime ref ${runtimeRef} is not a released tag; pin a vX.Y.Z tag.`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const runtimeRepository = config.runtime_repository ?? '0xPlayerOne/code-foundry'
|
|
109
|
+
for (const stem of ['ci', 'test', 'security', 'codeql']) {
|
|
110
|
+
const legacy = join(target, `.github/workflows/${stem}.yml`)
|
|
111
|
+
if (existsSync(legacy) && isGeneratedEventCaller(readFileSync(legacy, 'utf8'), stem, runtimeRepository)) {
|
|
112
|
+
warn(`stale generated legacy caller ${stem}.yml still triggers canonical suites; run code-foundry sync to migrate to validation.yml.`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.log('Remote CI, Test, Security, CodeQL, and release runtimes are loaded by the tiered validation orchestrator.')
|
|
75
116
|
if (options.github) {
|
|
76
117
|
const github = doctorGithub(target)
|
|
118
|
+
/** @type {{ codeFoundryTokenPresent?: boolean, releasePleaseTokenPresent?: boolean, stagingDeployKeyPresent?: boolean }} */
|
|
119
|
+
const secrets = github.details.secrets ?? {}
|
|
77
120
|
for (const message of github.warnings) warn(`GitHub: ${message}`)
|
|
78
121
|
for (const message of github.errors) error(`GitHub: ${message}`)
|
|
79
122
|
console.log(`GitHub doctor inspected ${github.details.repository}.`)
|
|
123
|
+
const codeFoundryToken = secrets.codeFoundryTokenPresent ? 'present' : 'absent'
|
|
124
|
+
const releasePleaseToken = secrets.releasePleaseTokenPresent ? 'present' : 'absent'
|
|
125
|
+
const stagingDeployKey = secrets.stagingDeployKeyPresent ? 'present' : 'absent'
|
|
126
|
+
console.log(`GitHub secrets: CODE_FOUNDRY_TOKEN=${codeFoundryToken}, RELEASE_PLEASE_TOKEN=${releasePleaseToken}, STAGING_DEPLOY_KEY=${stagingDeployKey}`)
|
|
127
|
+
if (!secrets.codeFoundryTokenPresent && !secrets.releasePleaseTokenPresent) {
|
|
128
|
+
warn('GitHub token routing fallback: PR creation will remain draft and requires manual ready-for-review to trigger validation.')
|
|
129
|
+
}
|
|
80
130
|
}
|
|
81
131
|
if (errors) throw new Error(`Repository doctor found ${errors} error(s).`)
|
|
82
132
|
console.log('Repository doctor passed.')
|