IMPORTANT: Developer documentation for the current development branch. This content is unreleased, may change without notice, and must not be treated as Buildish release documentation.
Maintenance
Gradle signing-key rotation
When Gradle publishes a new signing key at https://gradle.org/keys/:
- Verify the new key’s fingerprint matches the published fingerprint on that page.
- Add the new key to the
GRADLE_TRUSTED_SIGNING_KEY_ALLOWLISTarray insrc/build-tool/gradle/wrapper/signature.tsalongside the current key. - After the old key is no longer used to sign any wrapper version you support, remove it from the allowlist.
Keep old and new keys in the allowlist concurrently during the rotation window to avoid breaking builds that pin an older Gradle version.
The allowlist is validated at action load time — unrecognized key material in the allowlist causes a startup error rather than silently broken signature verification.
Cache schema version
CACHE_SCHEMA_VERSION in src/config/types.ts is part of every action-owned cache family. Bump it
whenever a change to the cache content or partition layout would make an existing base cache entry
invalid or unsafe to reuse on the next run.
Schema version bumps create a new cache family: existing lineages from the previous version are effectively abandoned and a cold start occurs for all users.
Partition fingerprint changes
The partitionFingerprint value is a 16-character hex prefix of the SHA-256 of the full ordered
partition layout (including IDs, includes, excludes, and hard excludes). Any change to the active
partition set automatically produces a new fingerprint — and therefore a new cache family — without
requiring a manual schema version bump. This covers:
- Enabling or disabling a built-in partition.
- Changing include or exclude globs for any active partition.
- Adding or removing a custom partition.
- Changing the
HARD_CACHE_EXCLUDE_GLOBSlist.
Static analysis and security scanning
ESLint and eslint-plugin-regexp
npm run lint runs ESLint with @typescript-eslint and eslint-plugin-regexp. The regexp
plugin enforces correctness rules across all regex literals in the codebase, including
regexp/strict (no unescaped {/} in patterns) and regexp/no-unused-capturing-group
(change to non-capturing groups when the match result is discarded). Three style rules are
intentionally disabled in eslint.config.mjs — regexp/prefer-d, regexp/prefer-w, and
regexp/use-ignore-case — because the codebase uses explicit character classes for clarity and
to avoid unintentional match widening.
npm audit
npm run verify runs npm audit --audit-level=high as its first step. This catches known
high-severity CVEs in the dependency tree (which includes large transitive dependencies such as
@azure/storage-blob via @actions/cache). Run npm audit manually at any time to see the
full report at all severity levels.
CodeQL
The codeql job in .github/workflows/ci.yml is currently disabled with if: false during the
private-to-public preparation period. This is intentional preparatory configuration only: it does
not currently analyse source or upload SARIF results, and the required-check aggregator accepts
the resulting skipped state.
As part of the public cutover, replace the temporary condition with the intended repository scope
and validate the job before documenting it as an active check. The prepared job uses
github/codeql-action with build-mode: none to analyse TypeScript source directly and scopes
security-events: write to that job.
CI Node.js and npm setup helper
.github/actions/setup-node-npm is an internal composite action for workflows in this repository.
Callers must check out the repository first because the helper resolves the npm version through
scripts/resolve-npm-version.mjs in the current workspace.
The cache input is explicit: a package-manager name such as npm enables setup-node caching, while
the default blank value disables both requested caching and setup-node’s automatic inference from
package.json#packageManager. Keep both setup-node branches aligned and update
test/setup-node-npm-action.test.ts whenever the helper contract changes.
Public action contract
src/config/public-contract.ts is the typed source of truth for public input names, applicability,
defaults, config-file permission and value representation, descriptions, and prepare outputs.
src/config/inputs.ts reads and overlays raw inputs through that contract, while
src/config/normalize.ts owns normalization shared by all build tools. Tool adapters retain only
tool-specific normalization.
When adding or changing an input or output:
- Update the typed contract.
- Select the config-file value representation when the input is file-configurable, then update shared or tool-specific normalization as needed.
- Refresh the matching action descriptor and the generated contract block in
docs/user/configuration.md. - Add detailed prose or examples when the compact contract description is not sufficient.
test/config/public-contract.test.ts compares both action descriptors and the documentation block
to the typed contract exactly. The test fails on undeclared reads/outputs, declared-but-unused
metadata fields, default drift, description drift, tool-applicability drift, and stale reference
rows.
Gradle build-result capture
src/build-tool/gradle/build-results.ts owns capture-file installation and cleanup, bounded JSON
loading and validation, report correlation, and Gradle summary rendering. The executable Groovy
init script and build-service source are generated by
src/build-tool/gradle/build-result-capture-scripts.ts.
Keep environment-path validation and non-interpolating Groovy string encoding beside those source generators. Both generated files must reject suspicious capture roots independently so future callers cannot bypass the executable-source boundary. The build-results module re-exports the existing path-embedding helpers as its stable facade.
Manifest performance benchmark
npm run benchmark:manifest -- 10000 --shape=broad,deep measures manifest capture, delta
computation, and serialization against repeatable synthetic broad and deep cache trees. Use the same
file count and shape list for before/after comparisons, and record Node version, elapsed capture
time, peak heap, and peak RSS. The fixture and local filesystem dominate parts of the result, so
treat the numbers as directional rather than as a production service-level objective.
On POSIX systems, a constrained-descriptor check can be run in a subshell without changing machine configuration:
1ulimit -n 64
2npm run benchmark:manifest -- 10000 --shape=broad,deep
Manifest and metadata traversal use a shared default concurrency bound of 32. If that implementation constant changes, rerun both normal and constrained-descriptor scenarios and verify that canonical manifest digests remain unchanged across concurrency limits.
Adding a new CI provider
See CI Abstraction Layer for the interfaces a new provider adapter must implement and the rules that keep provider-specific logic out of the shared core.
Per-provider implementation notes: