code-foundry 1.28.9 → 1.28.10

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.
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Draft PR
2
2
 
3
3
  on:
4
4
  push:
@@ -1,4 +1,4 @@
1
- name: Code Foundry
1
+ name: Code Foundry Security
2
2
 
3
3
  on:
4
4
  pull_request:
@@ -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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.28.10](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.9...v1.28.10) (2026-09-11)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **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))
9
+
3
10
  ## [1.28.9](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.8...v1.28.9) (2026-09-11)
4
11
 
5
12
 
@@ -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.10",
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": {
@@ -1003,7 +1003,12 @@ function escapeRegExp(value) {
1003
1003
  */
1004
1004
  export function isGeneratedEventCaller(content, stem, runtimeRepository) {
1005
1005
  const text = Buffer.isBuffer(content) ? content.toString('utf8') : String(content)
1006
- if (!/^name:\s*Code Foundry\s*$/m.test(text)) return false
1006
+ // Generated callers carried the shared display name "Code Foundry" before
1007
+ // issue 609 gave each caller a unique name; both renders must stay
1008
+ // recognizable so a topology switch can still prune stale promotion callers.
1009
+ const names = ['Code Foundry', ...(stem === 'release-pr' ? ['Code Foundry Promotion'] : [])]
1010
+ if (!names.some((name) => new RegExp(`^name:\\s*${escapeRegExp(name)}\\s*$`, 'm').test(text)))
1011
+ return false
1007
1012
  if (/^\s*(runs-on|steps):/m.test(text)) return false
1008
1013
  // Older generated staging-promotion callers predate the explicit runtime
1009
1014
  // 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'))