code-foundry 1.28.2 → 1.28.4
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/code-foundry.yml +1 -1
- package/.github/workflows/release-pr.yml +12 -12
- package/.github/workflows/release.yml +18 -11
- package/CHANGELOG.md +14 -0
- package/docs/CONFIGURATION.md +3 -1
- package/docs/RELEASES.md +8 -3
- package/docs/fleet-rollouts.md +3 -2
- package/package.json +1 -1
- package/src/commands/fleet-core.mjs +5 -1
- package/src/commands/sync.mjs +16 -6
package/.github/code-foundry.yml
CHANGED
|
@@ -41,9 +41,9 @@ jobs:
|
|
|
41
41
|
# The partial clone (filter: blob:none) defers blob downloads, so the
|
|
42
42
|
# merge-base/diff comparison below can trigger a promisor fetch. With
|
|
43
43
|
# persist-credentials disabled, git has no auth for that fetch; configure
|
|
44
|
-
# the credential helper with the short-lived workflow token.
|
|
45
|
-
#
|
|
46
|
-
# on REST, so all read-only operations use github.token. It is used
|
|
44
|
+
# the credential helper with the short-lived workflow token. Some
|
|
45
|
+
# organizations reject long-lived fine-grained automation tokens with
|
|
46
|
+
# HTTP 403 even on REST, so all read-only operations use github.token. It is used
|
|
47
47
|
# only for GitHub operations owned by this workflow. The job's
|
|
48
48
|
# contents: write permission is required later for the isolated,
|
|
49
49
|
# deterministic promotion ref; protected main and staging are never
|
|
@@ -59,11 +59,11 @@ jobs:
|
|
|
59
59
|
run: |
|
|
60
60
|
set -euo pipefail
|
|
61
61
|
# Every lookup in this step is read-only and uses the short-lived
|
|
62
|
-
# workflow token
|
|
63
|
-
# automation tokens with HTTP 403 even on REST. The
|
|
62
|
+
# workflow token because some organizations reject long-lived
|
|
63
|
+
# fine-grained automation tokens with HTTP 403 even on REST. The
|
|
64
64
|
# existing-promotion-PR lookup uses authenticated REST (gh api)
|
|
65
|
-
# rather than gh pr list, which issues a GraphQL query that
|
|
66
|
-
#
|
|
65
|
+
# rather than gh pr list, which issues a GraphQL query that an
|
|
66
|
+
# organization's long-lived fine-grained token policy rejects. The
|
|
67
67
|
# REST query qualifies head as owner:branch; GitHub otherwise ignores
|
|
68
68
|
# the incomplete head filter and can count an unrelated open PR. The
|
|
69
69
|
# exact base/head/state filter and per_page=1 cap keep existing at 0
|
|
@@ -247,8 +247,8 @@ jobs:
|
|
|
247
247
|
EOF
|
|
248
248
|
|
|
249
249
|
# Creation uses authenticated REST (gh api POST) rather than
|
|
250
|
-
# `gh pr create`, which issues a GraphQL mutation that
|
|
251
|
-
#
|
|
250
|
+
# `gh pr create`, which issues a GraphQL mutation that an
|
|
251
|
+
# organization's long-lived fine-grained token policy rejects. The
|
|
252
252
|
# REST payload keeps the exact base/head/title/body fields, always
|
|
253
253
|
# opens the automated promotion as a draft, and stays fail-closed
|
|
254
254
|
# through the create-race fallback below.
|
|
@@ -263,8 +263,8 @@ jobs:
|
|
|
263
263
|
)
|
|
264
264
|
echo "::notice title=Manual PR readiness required::Created a draft promotion PR. Mark it ready for review to start validation."
|
|
265
265
|
|
|
266
|
-
# Prefer the configured automation token for the write, but
|
|
267
|
-
#
|
|
266
|
+
# Prefer the configured automation token for the write, but some
|
|
267
|
+
# organizations reject long-lived fine-grained tokens with HTTP 403
|
|
268
268
|
# even on REST, so retry the POST once with the short-lived workflow
|
|
269
269
|
# token before the create-race fallback. With no automation token
|
|
270
270
|
# configured, the first attempt already runs as github.token, so the
|
|
@@ -286,7 +286,7 @@ jobs:
|
|
|
286
286
|
# the first attempt already ran as github.token. The lookup uses
|
|
287
287
|
# the same authenticated REST query as the Check step, always with
|
|
288
288
|
# the short-lived workflow token; gh pr list issues a GraphQL query
|
|
289
|
-
# that
|
|
289
|
+
# that an organization's long-lived fine-grained token policy rejects.
|
|
290
290
|
EXISTING_NUMBER=$(GH_TOKEN="${{ github.token }}" gh api --method GET \
|
|
291
291
|
"repos/${GITHUB_REPOSITORY}/pulls" \
|
|
292
292
|
--field base=main \
|
|
@@ -18,6 +18,11 @@ on:
|
|
|
18
18
|
required: false
|
|
19
19
|
type: string
|
|
20
20
|
default: v1.0.5
|
|
21
|
+
git-workflow:
|
|
22
|
+
description: Repository topology; staging reconciliation is opt-in for staging-release.
|
|
23
|
+
required: false
|
|
24
|
+
type: string
|
|
25
|
+
default: direct
|
|
21
26
|
billing-pause-bypass:
|
|
22
27
|
description: Allow this explicitly dispatched release to run while other CI remains paused.
|
|
23
28
|
required: false
|
|
@@ -73,7 +78,7 @@ jobs:
|
|
|
73
78
|
name: Version
|
|
74
79
|
if: vars.CI_BILLING_PAUSED != 'true' || inputs['billing-pause-bypass'] == true
|
|
75
80
|
runs-on: ${{ inputs.runner }}
|
|
76
|
-
timeout-minutes:
|
|
81
|
+
timeout-minutes: 45
|
|
77
82
|
concurrency:
|
|
78
83
|
group: code-foundry-release-${{ github.repository }}-${{ github.ref }}
|
|
79
84
|
cancel-in-progress: false
|
|
@@ -172,11 +177,11 @@ jobs:
|
|
|
172
177
|
GH_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN }}
|
|
173
178
|
run: |
|
|
174
179
|
set -euo pipefail
|
|
175
|
-
#
|
|
176
|
-
# HTTP 403 even on REST
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
+
# Some organizations reject long-lived fine-grained automation tokens
|
|
181
|
+
# with HTTP 403 even on REST, so the configured automation token is
|
|
182
|
+
# validated against the current repository before any write. gh api
|
|
183
|
+
# authenticates through the GH_TOKEN environment variable and never
|
|
184
|
+
# prints the token; the
|
|
180
185
|
# response body is discarded and only the exit status selects the
|
|
181
186
|
# credential for the rest of the job.
|
|
182
187
|
if [ "$HAS_AUTOMATION_TOKEN" = true ] && gh api "repos/${GITHUB_REPOSITORY}" --jq '.full_name' >/dev/null 2>&1; then
|
|
@@ -315,7 +320,7 @@ jobs:
|
|
|
315
320
|
echo "Waiting for release PR #$pr to become mergeable under branch policy"
|
|
316
321
|
merge_state=UNKNOWN
|
|
317
322
|
mergeable=UNKNOWN
|
|
318
|
-
for attempt in $(seq 1
|
|
323
|
+
for attempt in $(seq 1 90); do
|
|
319
324
|
: "$attempt"
|
|
320
325
|
if read -r merge_state mergeable < <(
|
|
321
326
|
gh pr view "$pr" \
|
|
@@ -372,16 +377,18 @@ jobs:
|
|
|
372
377
|
# creating a release. Reconciliation is only meaningful after a release
|
|
373
378
|
# commit/tag is actually created; otherwise the promotion's source changes
|
|
374
379
|
# are incorrectly rejected as non-metadata commits.
|
|
375
|
-
if: inputs['defer-publication'] != true && (vars.CI_BILLING_PAUSED != 'true' || inputs['billing-pause-bypass'] == true) && needs.release.result == 'success' && needs.release.outputs.release_created == 'true'
|
|
380
|
+
if: inputs['git-workflow'] == 'staging-release' && inputs['defer-publication'] != true && (vars.CI_BILLING_PAUSED != 'true' || inputs['billing-pause-bypass'] == true) && needs.release.result == 'success' && needs.release.outputs.release_created == 'true'
|
|
376
381
|
runs-on: ${{ inputs.runner }}
|
|
377
382
|
timeout-minutes: 15
|
|
378
383
|
permissions:
|
|
379
384
|
contents: write
|
|
380
385
|
pull-requests: write
|
|
381
386
|
env:
|
|
382
|
-
# Reconciliation is the sole staging-ruleset bypass
|
|
383
|
-
#
|
|
384
|
-
#
|
|
387
|
+
# Reconciliation is the sole staging-ruleset bypass and is enabled only
|
|
388
|
+
# for repositories that explicitly opt into the staging-release topology.
|
|
389
|
+
# Keep GH_TOKEN scoped to the repository's own GitHub Actions integration
|
|
390
|
+
# token; deploy key use is optional and only activated when
|
|
391
|
+
# STAGING_DEPLOY_KEY is provided.
|
|
385
392
|
GH_TOKEN: ${{ github.token }}
|
|
386
393
|
STAGING_DEPLOY_KEY_PRESENT: ${{ secrets.STAGING_DEPLOY_KEY != '' }}
|
|
387
394
|
steps:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.28.4](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.3...v1.28.4) (2026-09-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **release:** scope reconciliation to staging topology ([bfba0c1](https://github.com/0xPlayerOne/code-foundry/commit/bfba0c147ea6281c47754938d6711d3e5e0335a2))
|
|
9
|
+
|
|
10
|
+
## [1.28.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.2...v1.28.3) (2026-09-10)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Maintenance
|
|
14
|
+
|
|
15
|
+
* **config:** pin Code Foundry runtime to v1.28.2 ([#599](https://github.com/0xPlayerOne/code-foundry/issues/599)) ([ed61358](https://github.com/0xPlayerOne/code-foundry/commit/ed6135813e12e1f5410066a92183f2f2d18dcc8c))
|
|
16
|
+
|
|
3
17
|
## [1.28.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.1...v1.28.2) (2026-09-10)
|
|
4
18
|
|
|
5
19
|
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -165,7 +165,9 @@ release_merge_strategy: rebase
|
|
|
165
165
|
Use `simple` with `version.txt` when no package manifest exists. Use `none` to
|
|
166
166
|
skip automated releases. `npm_publish` affects generated consumer release
|
|
167
167
|
callers; Code Foundry's own repository uses the qualified publication path
|
|
168
|
-
described in [Qualified publication](qualified-publication.md).
|
|
168
|
+
described in [Qualified publication](qualified-publication.md). A self-sync of
|
|
169
|
+
the Code Foundry source does not advance its own `runtime_ref`; this prevents a
|
|
170
|
+
self-referencing runtime pin from creating a release loop.
|
|
169
171
|
|
|
170
172
|
## Synchronization and extensions
|
|
171
173
|
|
package/docs/RELEASES.md
CHANGED
|
@@ -74,8 +74,11 @@ The generated commit preserves the staged tip's conventional subject so
|
|
|
74
74
|
Release Please still detects the appropriate release type after promotion.
|
|
75
75
|
|
|
76
76
|
The `staging` → `main` reconciliation exists only in the `staging-release`
|
|
77
|
-
topology.
|
|
78
|
-
|
|
77
|
+
topology. The reusable release workflow defaults to `direct` and requires its
|
|
78
|
+
caller to opt into `staging-release`, so a stale remote `staging` branch cannot
|
|
79
|
+
activate reconciliation in a direct repository. Patch-equivalent divergence
|
|
80
|
+
between `main` and `staging` is treated as aligned. When `staging` has pending
|
|
81
|
+
commits that are not yet represented on
|
|
79
82
|
`main`, the release workflow replays those staging-only commits in order onto a
|
|
80
83
|
detached worktree rooted at `main`, and then updates `staging` with an exact
|
|
81
84
|
`--force-with-lease` to prevent
|
|
@@ -144,6 +147,8 @@ already passed).
|
|
|
144
147
|
topology method: squash for `direct`, rebase for `staging-release`.
|
|
145
148
|
3. For a generated consumer caller, confirm the GitHub Release and any package
|
|
146
149
|
publication.
|
|
147
|
-
4. For Code Foundry itself,
|
|
150
|
+
4. For Code Foundry itself, do not advance the self-referencing runtime pin as
|
|
151
|
+
part of a release; self-sync preserves that compatibility pin and avoids a
|
|
152
|
+
self-update release loop. Confirm qualification, draft staging, immutable
|
|
148
153
|
publication, and the retained identity receipts.
|
|
149
154
|
5. In `staging-release`, synchronize `staging` with the new `main` release commit.
|
package/docs/fleet-rollouts.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Declarative fleet inventory and staged rollouts
|
|
2
2
|
|
|
3
3
|
Place `code-foundry-fleet.json` in the directory passed to `--root`. Its presence
|
|
4
|
-
opts that fleet into inventory-based discovery and controlled upgrades
|
|
5
|
-
|
|
4
|
+
opts that fleet into inventory-based discovery and controlled upgrades. The
|
|
5
|
+
runtime has no organization-specific fleet inventory; legacy discovery is limited
|
|
6
|
+
to the explicitly supplied root and its immediate children.
|
|
6
7
|
|
|
7
8
|
```json
|
|
8
9
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.4",
|
|
4
4
|
"description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
|
|
5
5
|
"homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -18,7 +18,11 @@ export function discoverRepositories(root) {
|
|
|
18
18
|
if (hasFleetManifest(root)) return discoverManifestRepositories(root)
|
|
19
19
|
/** @type {FleetRepository[]} */
|
|
20
20
|
const result = []
|
|
21
|
-
|
|
21
|
+
// Legacy discovery is intentionally limited to the explicitly supplied root
|
|
22
|
+
// and its immediate children. Consumer-fleet inventories belong in an
|
|
23
|
+
// explicit code-foundry-fleet.json manifest; the runtime must not know about
|
|
24
|
+
// any organization's repositories or directory names.
|
|
25
|
+
const candidates = [root, ...children(root)]
|
|
22
26
|
for (const candidate of new Set(candidates)) {
|
|
23
27
|
if (!existsSync(join(candidate, '.git'))) continue
|
|
24
28
|
const configured = existsSync(join(candidate, '.github/code-foundry.yml'))
|
package/src/commands/sync.mjs
CHANGED
|
@@ -119,6 +119,7 @@ const legacyFiles = [
|
|
|
119
119
|
function synchronize(options) {
|
|
120
120
|
const target = resolve(options.target)
|
|
121
121
|
const source = resolve(options.source)
|
|
122
|
+
const selfRepository = target === source
|
|
122
123
|
const dryRun = options.dryRun ?? false
|
|
123
124
|
const force = options.force ?? false
|
|
124
125
|
const configPath = join(target, '.github/code-foundry.yml')
|
|
@@ -212,14 +213,17 @@ function synchronize(options) {
|
|
|
212
213
|
)
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
|
-
// Keep normal semver pins current during
|
|
216
|
-
// refs such as `main`, `staging`, or a custom immutable SHA. An
|
|
217
|
-
// runtime ref (fleet upgrade) is authoritative and overrides even
|
|
218
|
-
// the rendered callers and the config pin land on the same runtime.
|
|
216
|
+
// Keep normal semver pins current during consumer syncs while preserving
|
|
217
|
+
// intentional refs such as `main`, `staging`, or a custom immutable SHA. An
|
|
218
|
+
// explicit runtime ref (fleet upgrade) is authoritative and overrides even
|
|
219
|
+
// those so the rendered callers and the config pin land on the same runtime.
|
|
220
|
+
// The runtime source itself is excluded from automatic pin advancement so a
|
|
221
|
+
// self-referencing config cannot create a release loop.
|
|
219
222
|
if (
|
|
220
223
|
existingConfig.runtime_ref &&
|
|
221
224
|
existingConfig.runtime_ref !== targetRuntimeRef &&
|
|
222
|
-
(options.runtimeRef !== undefined ||
|
|
225
|
+
(options.runtimeRef !== undefined ||
|
|
226
|
+
(!selfRepository && /^v\d+\.\d+\.\d+$/.test(existingConfig.runtime_ref)))
|
|
223
227
|
) {
|
|
224
228
|
runtimeRef = targetRuntimeRef
|
|
225
229
|
const current = readFileSync(configPath, 'utf8')
|
|
@@ -260,7 +264,7 @@ function synchronize(options) {
|
|
|
260
264
|
runtimeRef,
|
|
261
265
|
rustCodeql,
|
|
262
266
|
file,
|
|
263
|
-
|
|
267
|
+
selfRepository
|
|
264
268
|
)
|
|
265
269
|
)
|
|
266
270
|
}
|
|
@@ -636,6 +640,12 @@ function renderWorkflow(content, config, repository, ref, rustCodeql, file, self
|
|
|
636
640
|
release: config.release_runner ?? config.runner,
|
|
637
641
|
}
|
|
638
642
|
const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
|
|
643
|
+
if (workflow === 'release' && !selfRepository) {
|
|
644
|
+
rendered = rendered.replace(
|
|
645
|
+
/^(\s+runtime-ref:)\s+.*$/m,
|
|
646
|
+
`$1 ${ref}\n git-workflow: ${isStagingRelease(config.git_workflow) ? 'staging-release' : 'direct'}`
|
|
647
|
+
)
|
|
648
|
+
}
|
|
639
649
|
// Generated PR callers protect drafts by default. Consumers that intentionally
|
|
640
650
|
// run gates while a PR is still draft can opt out in code-foundry.yml.
|
|
641
651
|
if (configured(config.draft_protection, 'true') === 'false') {
|