code-foundry 1.28.9 → 1.28.11

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.
@@ -53,6 +53,7 @@ jobs:
53
53
  name: Format
54
54
  if: vars.CI_BILLING_PAUSED != 'true'
55
55
  runs-on: ${{ inputs.runner }}
56
+ timeout-minutes: 30
56
57
  steps:
57
58
  - name: Checkout
58
59
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -112,6 +113,7 @@ jobs:
112
113
  name: Lint
113
114
  if: vars.CI_BILLING_PAUSED != 'true'
114
115
  runs-on: ${{ inputs.runner }}
116
+ timeout-minutes: 30
115
117
  steps:
116
118
  - name: Checkout
117
119
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -166,6 +168,7 @@ jobs:
166
168
  name: Type-Check
167
169
  if: vars.CI_BILLING_PAUSED != 'true'
168
170
  runs-on: ${{ inputs.runner }}
171
+ timeout-minutes: 30
169
172
  steps:
170
173
  - name: Checkout
171
174
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -220,6 +223,7 @@ jobs:
220
223
  name: Build
221
224
  if: vars.CI_BILLING_PAUSED != 'true'
222
225
  runs-on: ${{ inputs.runner }}
226
+ timeout-minutes: 30
223
227
  env:
224
228
  # Framework builds may validate server-side configuration at build time.
225
229
  # Consumers can provide the real value; the fallback is intentionally
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Draft PR
2
2
 
3
3
  on:
4
4
  push:
@@ -50,6 +50,7 @@ jobs:
50
50
  name: Eval
51
51
  if: vars.CI_BILLING_PAUSED != 'true'
52
52
  runs-on: ${{ inputs.runner }}
53
+ timeout-minutes: 30
53
54
  steps:
54
55
  - name: Checkout
55
56
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Security
2
2
 
3
3
  on:
4
4
  pull_request:
@@ -16,6 +16,7 @@ jobs:
16
16
  name: OpenCode Security / Detect
17
17
  if: vars.CI_BILLING_PAUSED != 'true' && (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false))
18
18
  runs-on: ubuntu-slim
19
+ timeout-minutes: 10
19
20
  outputs:
20
21
  enabled: ${{ steps.detect.outputs.enabled }}
21
22
  token: ${{ steps.detect.outputs.token }}
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Promotion
2
2
 
3
3
  on:
4
4
  push:
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Release
2
2
 
3
3
  on:
4
4
  push:
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Validation
2
2
 
3
3
  on:
4
4
  push:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.28.11](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.10...v1.28.11) (2026-09-11)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **fleet:** bound rendered jobs with timeouts and refresh stale runtime pins ([#616](https://github.com/0xPlayerOne/code-foundry/issues/616)) ([f8d20b4](https://github.com/0xPlayerOne/code-foundry/commit/f8d20b4ad48cddd20630ff9b7c1c21d0c5ac9520))
9
+
10
+ ## [1.28.10](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.9...v1.28.10) (2026-09-11)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **ci:** dedupe caller names and keep CodeQL Rust shards aligned ([#613](https://github.com/0xPlayerOne/code-foundry/issues/613)) ([8000e5e](https://github.com/0xPlayerOne/code-foundry/commit/8000e5e152ccc7e657933b55eb5b82a278762176))
16
+
3
17
  ## [1.28.9](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.8...v1.28.9) (2026-09-11)
4
18
 
5
19
 
@@ -215,6 +215,24 @@ Each shard must contain tracked Rust source. Absolute paths, parent traversal,
215
215
  duplicates, empty scopes, and more than eight shards are rejected. Use
216
216
  `["all"]` when complete, non-overlapping source scopes are not available.
217
217
 
218
+ Every lane rendered from this configuration (pull-request validation, the
219
+ default-branch scan, the scheduled audit, and merge queues) receives the same
220
+ shard list, and the CodeQL Detect job fails closed when caller workflows drift
221
+ apart. Keep them aligned: each shard uploads a SARIF category keyed by a hash
222
+ of the shard scope, GitHub code scanning baselines every category it has seen
223
+ on the default branch, and the code-scanning merge gate waits for results in
224
+ every tracked category. Two practical consequences:
225
+
226
+ - Treat `codeql_rust_shards` as add-only while the gate tracks categories.
227
+ Removing a shard orphans its category on the default branch, and pull
228
+ requests then stall on "Code scanning is still expecting N results" until
229
+ the category is covered again.
230
+ - To retire a category for real, delete its code-scanning analyses (Code
231
+ security → Code scanning → analyses, or the code-scanning analysis
232
+ deletion API) after removing the shard, then re-run `sync`. Covering retired
233
+ categories in the shard list keeps the gate green but re-runs their analysis
234
+ on every pull request and is not a substitute for cleanup.
235
+
218
236
  ## Cloudflare Workers
219
237
 
220
238
  Repositories that deploy to Cloudflare Workers can use the opt-in verified
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.28.9",
3
+ "version": "1.28.11",
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": {
@@ -1,6 +1,14 @@
1
1
  // @ts-check
2
2
 
3
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import {
4
+ chmodSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from 'node:fs'
4
12
  import { dirname, join, resolve } from 'node:path'
5
13
  import { spawnSync } from 'node:child_process'
6
14
  import {
@@ -321,6 +329,37 @@ function synchronize(options) {
321
329
  }
322
330
  }
323
331
 
332
+ // Preserved (repository-owned) workflows may reference the runtime directly,
333
+ // for example a hand-maintained Cloudflare preview caller. Advance their
334
+ // plain semver pins to the synced runtime ref so a stale hand-added pin
335
+ // cannot keep an old runtime alive after a fleet upgrade. Intentional refs
336
+ // (branch names, immutable SHAs) and other repositories' actions are left
337
+ // untouched, mirroring the config pin policy above.
338
+ if (!selfRepository && /^v\d+\.\d+\.\d+$/.test(runtimeRef)) {
339
+ const standard = new Set(standardFiles)
340
+ const workflowsDir = join(target, '.github/workflows')
341
+ if (existsSync(workflowsDir)) {
342
+ const runtimePin = new RegExp(
343
+ `(uses:\\s*${escapeRegExp(runtimeRepository)}/\\.github/(?:workflows|actions)/[^\\s@]+)@v\\d+\\.\\d+\\.\\d+`,
344
+ 'g'
345
+ )
346
+ for (const entry of readdirSync(workflowsDir).toSorted()) {
347
+ if (!entry.endsWith('.yml') || standard.has(`.github/workflows/${entry}`)) continue
348
+ const destination = join(workflowsDir, entry)
349
+ const original = readFileSync(destination, 'utf8')
350
+ const updated = original.replace(runtimePin, `$1@${runtimeRef}`)
351
+ if (updated !== original) {
352
+ changed.push(`.github/workflows/${entry}`)
353
+ if (dryRun)
354
+ console.log(
355
+ `Would refresh the runtime pin in .github/workflows/${entry} to ${runtimeRef}.`
356
+ )
357
+ else writeOrReport(destination, updated, dryRun)
358
+ }
359
+ }
360
+ }
361
+ }
362
+
324
363
  // A repository that no longer opts into the staging-release topology must
325
364
  // not keep a generated staging promotion caller that would otherwise
326
365
  // linger dormant (it triggers on pushes to a branch that does not exist).
@@ -1003,7 +1042,12 @@ function escapeRegExp(value) {
1003
1042
  */
1004
1043
  export function isGeneratedEventCaller(content, stem, runtimeRepository) {
1005
1044
  const text = Buffer.isBuffer(content) ? content.toString('utf8') : String(content)
1006
- if (!/^name:\s*Code Foundry\s*$/m.test(text)) return false
1045
+ // Generated callers carried the shared display name "Code Foundry" before
1046
+ // issue 609 gave each caller a unique name; both renders must stay
1047
+ // recognizable so a topology switch can still prune stale promotion callers.
1048
+ const names = ['Code Foundry', ...(stem === 'release-pr' ? ['Code Foundry Promotion'] : [])]
1049
+ if (!names.some((name) => new RegExp(`^name:\\s*${escapeRegExp(name)}\\s*$`, 'm').test(text)))
1050
+ return false
1007
1051
  if (/^\s*(runs-on|steps):/m.test(text)) return false
1008
1052
  // Older generated staging-promotion callers predate the explicit runtime
1009
1053
  // repository input. They are still safe to identify structurally by their
@@ -1,7 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-check
3
3
 
4
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
4
+ import {
5
+ appendFileSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readdirSync,
9
+ readFileSync,
10
+ writeFileSync,
11
+ } from 'node:fs'
5
12
  import { resolve } from 'node:path'
6
13
  import { spawnSync } from 'node:child_process'
7
14
  import { detectPackageManager, resolveProfile } from './lib/profile.mjs'
@@ -880,6 +887,75 @@ function isPublicRepository() {
880
887
  return visibility ? visibility === 'public' : process.env.REPO_FOUNDRY_PRIVATE !== 'true'
881
888
  }
882
889
 
890
+ /** Split a workflow file's `jobs:` entries into [job id, block text] pairs.
891
+ * Caller files are re-parsed here instead of trusting the sync renderer so
892
+ * hand-edited or stale generated callers are still validated at run time.
893
+ * @param {string} text @returns {Array<[string, string]>}
894
+ */
895
+ function workflowJobBlocks(text) {
896
+ const lines = text.split('\n')
897
+ /** @type {Array<[string, string]>} */
898
+ const blocks = []
899
+ let index = 0
900
+ while (index < lines.length) {
901
+ const match = lines[index].match(/^ ([A-Za-z0-9_-]+):\s*$/)
902
+ if (!match) {
903
+ index += 1
904
+ continue
905
+ }
906
+ const start = index
907
+ index += 1
908
+ while (index < lines.length && !/^ [A-Za-z0-9_-]+:\s*$/.test(lines[index])) index += 1
909
+ blocks.push([match[1], lines.slice(start, index).join('\n')])
910
+ }
911
+ return blocks
912
+ }
913
+
914
+ /**
915
+ * The Rust SARIF category embeds a hash of each shard scope, and GitHub code
916
+ * scanning baselines every category it has seen on the default branch: when
917
+ * two lanes calling the CodeQL workflow report different shard sets, each
918
+ * lane uploads categories the other never produces and the code-scanning
919
+ * merge gate waits forever (issue 609). Fail closed before any analysis runs.
920
+ */
921
+ function assertRustShardsAligned() {
922
+ const directory = resolve(root, '.github/workflows')
923
+ if (!existsSync(directory)) return
924
+ /** @type {Map<string, string>} */
925
+ const lanes = new Map()
926
+ for (const file of readdirSync(directory)
927
+ .filter((name) => name.endsWith('.yml'))
928
+ .toSorted()) {
929
+ for (const [job, block] of workflowJobBlocks(readFileSync(resolve(directory, file), 'utf8'))) {
930
+ // Every lane that ultimately runs CodeQL analysis counts: direct
931
+ // codeql.yml callers and callers of the validation orchestrators whose
932
+ // rust-shards input flows through to the analysis matrix.
933
+ if (!/^\s+uses:\s*\S*\/(codeql|validation|validation-no-codeql)\.yml(@\S+)?\s*$/m.test(block))
934
+ continue
935
+ const raw = block.match(/^\s+rust-shards:\s*(.+?)\s*$/m)?.[1] ?? '["all"]'
936
+ // Unresolved caller expressions cannot be evaluated here; the literal
937
+ // shard list of the workflow that passes them is what gets compared.
938
+ if (raw.includes('${{')) continue
939
+ let shards = raw.replace(/^['"]|['"]$/g, '')
940
+ try {
941
+ shards = JSON.stringify(JSON.parse(shards).toSorted())
942
+ } catch {
943
+ // Compare unparsable values verbatim; alignment still fails loudly.
944
+ }
945
+ lanes.set(`${file} (${job})`, shards)
946
+ }
947
+ }
948
+ const distinct = new Set(lanes.values())
949
+ if (distinct.size <= 1) return
950
+ const detail = [...lanes].map(([lane, shards]) => `${lane}: ${shards}`).join(', ')
951
+ throw new Error(
952
+ `Rust CodeQL shard sets drift between lanes calling codeql.yml (${detail}). ` +
953
+ 'GitHub code scanning tracks every uploaded category, so every lane must pass the same rust-shards list. ' +
954
+ 'Re-render the callers with `npx code-foundry sync`, keep codeql_rust_shards aligned across callers, and retire ' +
955
+ 'removed categories by deleting their code-scanning analyses (see docs/CONFIGURATION.md).'
956
+ )
957
+ }
958
+
883
959
  function codeql() {
884
960
  const enabled =
885
961
  featureEnabled('codeql') &&
@@ -894,6 +970,9 @@ function codeql() {
894
970
  }
895
971
  return
896
972
  }
973
+ // Detect lane drift before any analysis uploads a category the other lanes
974
+ // never produce; a stall surfaces only later as a blocked merge gate.
975
+ assertRustShardsAligned()
897
976
  const available = []
898
977
  if (existsSync(resolve(root, '.github/workflows'))) available.push('actions')
899
978
  if (hasLanguage('typescript') || hasLanguage('javascript'))