This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
Tools
Tools tree
This tree contains developer tools and reusable Bazel rule modules. All tracked
content follows the repository’s public-source policy. Standalone rules_*
modules retain their own Bzlmod names and public build APIs, with documentation
on the main repository site. They do not own separate landing sites.
Requirements
- Bazel targets MUST use repository-internal visibility except for standalone
Bazel rule modules and toolchain types whose owner explicitly exposes a public
build API.
- Tool artifacts MUST NOT be published as first-party product artifacts.
- Standalone Bazel rule modules MAY publish their reusable build APIs through
their explicit module release workflow.
- Production build targets MAY consume the public build APIs of standalone
Bazel rule modules and toolchains. Other tool targets MUST NOT be dependencies
of production build targets.
- Tool targets intended for repository-wide use MUST use
visibility = ["//:__subpackages__"].
- Tool targets MAY be used in tests and explicit source-generation/update
targets.
An explicit source-generation/update target is a developer workflow, not part
of the production build graph. It may depend on a tool from this tree and use
write_source_file to update a checked-in source artifact. Normal production
and documentation targets must consume that checked-in artifact directly;
they must not depend on the generator, its tool, or an action-generated copy.
1 - Agents
Repository-wide reusable agent skills
Repository agent skills
This project owns the reusable cross-repository agent skills. Maintained
improvement work lives in this project’s OpenSpec workspace. It
does not centralize component facts or runtime state: each fact remains
canonical at its natural owner.
The main site publishes this project’s repository documentation at
/docs/tools/agents/ through //tools:docs.
Start here
Dated audits remain in
migrated history.
Ownership boundaries
openspec/ owns agent-system requirements, changes, and evidence;
the migration map locates historical records.
- Other components own their specifications and changes under their own
openspec/ directory. infra/src/openspec/ describes repository evolution.
skills/ owns reusable cross-product, repository-wide agent procedures and
their development-time evaluations. A product-specific procedure belongs
with its product; a procedure that no narrower project genuinely owns lives
here rather than in a contrived local home.
- Product-specific skills remain with their project at
projects/<project>/skills/<name>. Do not split one skill across owners.
Skill packaging and discovery
Every canonical skill is packaged as a skill_library in its owning
directory.
The repository discovery directory .agents/skills/ contains one relative
symlink per skill. Each link points directly to its canonical project-owned
directory. .agents/BUILD.bazel declares the complete discovery set and
generates those links with //.agents:write_skills; its generated
exact-state test verifies them. Bazel ignores the discovery directory and
builds only canonical targets, preventing duplicate packages while allowing
skills from more than one owning project. Each skill grants
//.agents:skill_discovery read access so the owning declaration can reach it.
Skill evaluation data is not part of the runtime skill_library unless a
skill explicitly declares otherwise. Every new or updated skill includes an
offline Promptfoo configuration-validation target. That target checks the eval
harness, not answer correctness. Live behavioral evaluations are manual,
networked tests and must not be included in ordinary wildcard test runs; they
may be omitted when representative coverage requires tool calls or external
state that cannot be provided safely and reproducibly, with the gap documented
beside the eval configuration.
2 - Android
Android
packages.txt owns the SDK and NDK package selection used by :install and
:installer_tar. The latter packages the installer and the pinned Linux x86-64
command-line tools for Ansible deployment without bootstrapping Bazel on the
remote host. It requires a host Java runtime; downloading SDK packages happens
only when the installed command runs, never in a Bazel build action.
3 - Ansible
Bazel rules for ansible
ansible_lint runs ansible-lint from Bazel-managed Python
dependencies. A Go test runner exposes the CLI binaries under their
hyphenated names, configures writable Ansible paths, and invokes the
linter against workspace-relative sources.
Python dependencies are declared in pyproject.toml and locked in uv.lock.
Update the lock with //tools/ansible:uv_lock.update and review its
diff. Build //tools/ansible:uv_lock to generate it without source writes.
These use the shared uv locking workflow, whose sandboxed resolution action
requires network access to PyPI.
Molecule is not integrated yet. The likely path is to add Molecule to
these Python requirements and invoke it through a Bazel-native Go runner,
but practical scenarios require choosing and provisioning a Molecule
driver before any scenarios can run.
4 - Bazel configs
Bazel config targets
Harbor
Targets that pull from the private Harbor registry are disabled by default so
repository-wide builds work while Harbor is unavailable. Enable them with
--config=harbor when the registry is reachable.
5 - Bazel session cleanup
The repository’s .codex/config.toml queues asynchronous Bazel expunge when a
Codex session ends. Review and trust the hook with /hooks in Codex; project
hooks do not run until trusted. A completed assistant turn does not trigger
cleanup.
If /hooks is empty in a linked Git worktree, check the primary checkout’s
.codex/config.toml. In Codex CLI 0.153.4, observed on 2026-09-06, config/read
returned the primary checkout’s configuration even though its project-layer
metadata named the linked worktree. That primary configuration had no hooks,
so hooks/list returned an empty list. The exact worktree TOML listed both
hooks in an isolated normal checkout; separate user-config probes also accepted
both TOML and JSON. Changing formats is therefore not an established fix.
Merge the hook configuration and update the primary checkout, then start a new
CLI session and check /hooks to trust it. Recheck configuration resolution
after upgrading Codex; this is an observed version-specific behavior, not a
guarantee for every release.
The shell hook launches a transient user systemd service, which runs
bazel --noblock_for_lock clean --config=agent --expunge_async in each built,
tracked Bazel workspace in the linked Git worktree. It skips the primary
checkout, submodules, workspaces without a bazel-out symlink, and busy Bazel
instances. Source files, Git worktrees, and shared disk caches are preserved.
Nested workspaces are discovered from tracked MODULE.bazel, WORKSPACE, and
WORKSPACE.bazel files. Custom convenience-symlink prefixes are not supported.
This Linux hook requires Bash, Git, Bazelisk’s bazel on PATH, and a running
user systemd manager. The service inherits PATH, not the session’s full
environment. KillMode=process allows Bazel’s asynchronous deletion child to
finish after the service exits. Failures appear in the user journal; failure
to enqueue is reported by Codex as a hook failure. There is no automatic retry
or orphan-cache sweep. Concurrent sessions sharing a worktree may lose warm
build state when one ends; the Bazel lock protects running builds, not idle
sessions. Resuming a cleaned session rebuilds its outputs normally.
The hook uses shell because it must run from a fresh checkout without first
building a helper. It invokes Bazel directly to supply the startup lock flag,
which bazel_agent does not currently accept. Tests replace Git, systemd, and
Bazel with fixtures and never clean real caches.
Codex’s SessionEnd hook is synchronous even with async: true and permits
at most three seconds. Systemd owns the longer-running cleanup; the hook does
not wait for it. See the Codex hook documentation.
6 - Bazel shell worker
Bazel worker that runs shell commands
6.1.1 - al_genrule
al_genrule
load("@com_alwaldend_src//tools/shell_worker/main/bzl:al_genrule.bzl", "al_genrule")
al_genrule(test, executable, **kwargs)
Generate al_genrule target
PARAMETERS
| Name |
Description |
Default Value |
| test |
If set, use al_genrule_test |
False |
| executable |
if set, use al_genrule_executable |
False |
| kwargs |
kwargs for the rule |
none |
6.1.2 - al_genrule_rule
al_genrule_executable
load("@com_alwaldend_src//tools/shell_worker/main/bzl:al_genrule_rule.bzl", "al_genrule_executable")
al_genrule_executable(name, srcs, data, outs, cmd, set_flags, shell, tools, worker)
Build executable using shell worker
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Sources, will not be added to runfiles |
List of labels |
optional |
[] |
| data |
Data, will be added to runfiles |
List of labels |
optional |
[] |
| outs |
Outputs |
List of labels; nonconfigurable |
required |
|
| cmd |
Script to execute |
String |
required |
|
| set_flags |
set flags |
List of strings |
optional |
["-eux"] |
| shell |
shell to use |
String |
optional |
"/bin/sh" |
| tools |
Tools, will be added to runfiles |
List of labels |
optional |
[] |
| worker |
Worker binary |
Label |
optional |
"@com_alwaldend_src//tools/shell_worker" |
al_genrule_regular
load("@com_alwaldend_src//tools/shell_worker/main/bzl:al_genrule_rule.bzl", "al_genrule_regular")
al_genrule_regular(name, srcs, data, outs, cmd, set_flags, shell, tools, worker)
Build shell worker rule
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Sources, will not be added to runfiles |
List of labels |
optional |
[] |
| data |
Data, will be added to runfiles |
List of labels |
optional |
[] |
| outs |
Outputs |
List of labels; nonconfigurable |
required |
|
| cmd |
Script to execute |
String |
required |
|
| set_flags |
set flags |
List of strings |
optional |
["-eux"] |
| shell |
shell to use |
String |
optional |
"/bin/sh" |
| tools |
Tools, will be added to runfiles |
List of labels |
optional |
[] |
| worker |
Worker binary |
Label |
optional |
"@com_alwaldend_src//tools/shell_worker" |
al_genrule_test
load("@com_alwaldend_src//tools/shell_worker/main/bzl:al_genrule_rule.bzl", "al_genrule_test")
al_genrule_test(name, srcs, data, outs, cmd, set_flags, shell, tools, worker)
Test using shell worker
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Sources, will not be added to runfiles |
List of labels |
optional |
[] |
| data |
Data, will be added to runfiles |
List of labels |
optional |
[] |
| outs |
Outputs |
List of labels; nonconfigurable |
required |
|
| cmd |
Script to execute |
String |
required |
|
| set_flags |
set flags |
List of strings |
optional |
["-eux"] |
| shell |
shell to use |
String |
optional |
"/bin/sh" |
| tools |
Tools, will be added to runfiles |
List of labels |
optional |
[] |
| worker |
Worker binary |
Label |
optional |
"@com_alwaldend_src//tools/shell_worker" |
7 - Bazelrc
Bazelrc files
Usage
bazel run //tools/bazelrc:preset.update
The agent profile must not propagate ambient TEMP, TMP, or TMPDIR
values into repository rules, actions, host actions, or tests. Those execution
contexts use Bazel-managed temporary storage; host tools own any explicit
task/run scratch they require.
Agent tests use Bazel’s normal result cache. Tests that inspect undeclared
workspace files must opt out with the no-cache tag.
Agent commands use Bazel’s default lockfile update mode. Review and commit
generated lockfile changes with dependency changes. CI retains strict lockfile
checking; use --lockfile_mode=error for an explicit reproducibility check.
The shared CI profile works in root and nested workspaces. The root-only
//tools/bazel_configs:ci_flag belongs in root.bazelrc; nested workspaces
import the common CI flags without resolving that root package.
In the root workspace, lint actions fail on violations. Their failure setting
is identical across normal and lint configurations to preserve the analysis
cache when switching modes. --config=lint enables the linter aspects and
requests only lint reports and skill validation, not ordinary target outputs.
Links
The generated preset omits the optional Cloudflare module mirror because it
slow-stalls on the CI network. Bazel uses the source URLs and mirrors declared
by each registry entry. Configure this omission in BUILD.bazel, then regenerate
preset.bazelrc; a later empty flag does not clear an accumulated mirror.
9 - Buildifier
Buildifier
Links
10 - Bzl
Bazel rules related to Bazel itself
10.1.1 - al_alias_map
al_alias_map
load("@com_alwaldend_src//tools/bzl/main/bzl:al_alias_map.bzl", "al_alias_map")
al_alias_map(aliases, visibility)
Generate aliases from an alias map
PARAMETERS
| Name |
Description |
Default Value |
| aliases |
alias map, keys are names, values are alias arguments |
none |
| visibility |
default visibility |
["//:__subpackages__"] |
10.1.2 - al_bzl_generate_repository
al_bzl_generate_repository
load("@com_alwaldend_src//tools/bzl/main/bzl:al_bzl_generate_repository.bzl", "al_bzl_generate_repository")
al_bzl_generate_repository(name, files, repo_mapping)
Generate a repository
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this repository. |
Name |
required |
|
| files |
Files to generate, keys are paths, values are file contents |
Dictionary: String -> String |
optional |
{} |
| repo_mapping |
In WORKSPACE context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.
For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target).
This attribute is not supported in MODULE.bazel context (when invoking a repository rule inside a module extension’s implementation function). |
Dictionary: String -> String |
optional |
|
10.1.3 - al_bzl_library_map
al_bzl_library_map
load("@com_alwaldend_src//tools/bzl/main/bzl:al_bzl_library_map.bzl", "al_bzl_library_map")
al_bzl_library_map(name, visibility, libs, deps, **kwargs)
Create bzl_library targets from a map
PARAMETERS
| Name |
Description |
Default Value |
| name |
combined bzl_library target name |
none |
| visibility |
- |
None |
| libs |
bzl_library names |
{} |
| deps |
other al_bzl_library_map targets |
[] |
| kwargs |
bzl_library kwargs |
none |
10.1.4 - al_bzl_target_doc
al_bzl_target_doc
load("@com_alwaldend_src//tools/bzl/main/bzl:al_bzl_target_doc.bzl", "al_bzl_target_doc")
al_bzl_target_doc(name, visibility, subpackages)
Document bazel targets
PARAMETERS
| Name |
Description |
Default Value |
| name |
target name |
none |
| visibility |
- |
none |
| subpackages |
list of subpackages |
[] |
10.1.5 - al_genquery_write_to_source_file
al_genquery_write_to_source_file
load("@com_alwaldend_src//tools/bzl/main/bzl:al_genquery_write_to_source_file.bzl", "al_genquery_write_to_source_file")
al_genquery_write_to_source_file(name, expression, scope, var_name, out_file)
Write genquery result to a bzl file
Example:
al_genquery_write_to_source_file(
name = "al_bzl_libs",
expression = """
filter(
"^//",
attr(
"srcs",
".{3,}",
kind(
"bzl_library",
deps("//bzl")
)
)
)
""",
out_file = "al_bzl_libs.bzl",
scope = ["//bzl"],
var_name = "AL_BZL_LIBS",
)
PARAMETERS
| Name |
Description |
Default Value |
| name |
name prefix |
none |
| expression |
genquery expression |
none |
| scope |
genquery scope |
none |
| var_name |
variable name in the generated .bzl file |
none |
| out_file |
output bzl file |
none |
11 - Bzlenv
Setup bazel environment
Rules to create a bazel environment which functions similar to venv
Features
- Adds bazel-built tools to
${PATH}
- Exports .env
Usage
Create env and activate:
. "$(bazel run //tools/bzlenv)"
Activate existing env:
Deactivate env:
11.1.1 - al_bzlenv_binary
al_bzlenv_binary
load("@com_alwaldend_src//tools/bzlenv/main/bzl:al_bzlenv_binary.bzl", "al_bzlenv_binary")
al_bzlenv_binary(name, activate, tools)
Dev shell binary
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| activate |
Activation script |
Label |
optional |
"@com_alwaldend_src//tools/bzlenv/main/sh:activate" |
| tools |
Tools, keys are tool names, values are tool labels |
Dictionary: String -> Label |
optional |
{} |
12 - Cc
Cc
//:refresh_compile_commands uses the pinned Hedron extractor. Its archive is
declared through use_repo_rule and fetched when the extractor’s package is
loaded, rather than during module resolution. LLVM toolchain registration
remains automatic.
Links
13 - Cloc
Cloc
Links
The pinned upstream binary is owned by
third_party/com_github_aldanial_cloc.
14 - Cmctl
Cert Manager cmctl
Links
15 - Consul
Consul
Links
The pinned upstream binary is owned by
third_party/com_hashicorp_consul.
15.1 - Gen gossip key
Generate a gossip key and write it to a kv secret
16 - Drawio
Bazel rules for drawio
Links
Reproducible SVGs
drawio_svg in defs.bzl renders named pages from a .drawio file with the
pinned Drawio web exporter and the existing pinned Chrome-for-Testing browser.
The extraction action unpacks the web application from the Drawio AppImage;
rendering uses a small Puppeteer bridge for its export messages. Neither action
uses a host display server or accesses the network. JavaScript keeps this
adapter close to Drawio’s browser API and the existing Puppeteer dependency.
Use the rule from an explicit source-update target with write_source_files,
when a consumer requires checked-in SVGs. Normal documentation consumes the
maintained SVGs, not the rendering tool. The generated freshness tests catch stale output.
Exports use a light theme with an opaque white canvas, keeping labels and
connectors readable in both light and dark documentation themes and standalone
image viewers. The renderer adds this canvas on every export.
Chrome’s inner sandbox is disabled because it cannot nest within the Bazel
Linux sandbox used for these actions. The Bazel sandbox remains enabled;
requests outside local files and embedded data are rejected. The renderer is
intended for checked-in, reviewed diagram sources and propagates export failures.
The repeat-render test checks byte equality, including stable SVG identifiers.
Rendering uses only the declared Liberation Fonts 2.1.5
through an isolated Fontconfig configuration. Helvetica/Arial map to Liberation
Sans, Times to Liberation Serif, and Courier to Liberation Mono. The fonts are
licensed under the SIL Open Font License 1.1; their archive retains LICENSE.
16.1.1 - al_drawio_run_binary
al_drawio_run_binary
load("@com_alwaldend_src//tools/drawio/main/bzl:al_drawio_run_binary.bzl", "al_drawio_run_binary")
al_drawio_run_binary(name, srcs, out, arguments, cmd_timeout)
Run drawio a a build action
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Sources |
List of labels |
optional |
[] |
| out |
Output |
Label; nonconfigurable |
required |
|
| arguments |
Arguments, location statements are expanded |
List of strings |
required |
|
| cmd_timeout |
Drawio command timeout |
String |
optional |
"1m" |
16.1.2 - al_drawio_toolchain
al_drawio_toolchain
load("@com_alwaldend_src//tools/drawio/main/bzl:al_drawio_toolchain.bzl", "al_drawio_toolchain")
al_drawio_toolchain(name, drawio)
Drawio toolchain
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| drawio |
Drawio binary |
Label |
required |
|
17 - F-Droid
Pinned fdroidserver environment for local F-Droid build testing
Pinned fdroidserver environment for local F-Droid builds. The wrapper
provisions an isolated Python virtualenv and delegates to fdroid. Use
tools/gradle/gradle-wrapper for the Android app’s Gradle build; this tool
only drives F-Droid metadata and local builds.
Usage
tools/fdroid/fdroid-wrapper <fdroid-subcommand>
Run from an F-Droid data directory containing config.yml and metadata/.
Caches go under the workspace’s ignored out/ tree.
18 - File installer
CLI tool to install files
19 - fj
Forgejo CLI
Links
22 - Flux operator
Flux operator
Links
23 - Forgejo
Forgejo
Links
24 - Gazelle
Repository BUILD file generation
//tools/gazelle:gazelle runs the repository’s Gazelle language plugins.
Repository-wide directives remain in root BUILD.bazel, where Gazelle discovers
them for all packages. Root //:gazelle and //:gazelle_bin remain compatibility
entry points.
//tools/gazelle:gazelle_python_manifest.update refreshes root
gazelle_python.yaml; //tools/gazelle:gazelle_python_manifest.test validates
its integrity against tools/py/uv.lock. The manifest stays at the
repository root for default ancestor discovery. It can live elsewhere if the
root BUILD file sets gazelle:python_manifest_file_name to its relative path.
25.1.1 - al_git_binary
al_git_binary
load("@com_alwaldend_src//tools/git/main/bzl:al_git_binary.bzl", "al_git_binary")
al_git_binary(name, arguments)
Run git binary
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| arguments |
Git arguments (templated) |
List of strings |
optional |
[] |
25.1.2 - al_git_changelog
al_git_changelog
load("@com_alwaldend_src//tools/git/main/bzl:al_git_changelog.bzl", "al_git_changelog")
al_git_changelog(name, visibility, git_binary, subpackages)
Create changelog target
PARAMETERS
| Name |
Description |
Default Value |
| name |
target name prefix |
none |
| visibility |
visibility |
none |
| git_binary |
- |
"@git//:git" |
| subpackages |
subpackages |
[] |
25.1.3 - al_git_current_state
25.1.4 - al_git_extension
al_git_extension
al_git_extension = use_extension("@com_alwaldend_src//tools/git/main/bzl:al_git_extension.bzl", "al_git_extension")
al_git_extension.local_git(name)
Create git repos
TAG CLASSES
local_git
Attributes
| Name |
Description |
Type |
Mandatory |
Default |
| name |
Name |
Name |
required |
|
25.1.5 - al_git_info_file
al_git_info_file
load("@com_alwaldend_src//tools/git/main/bzl:al_git_info_file.bzl", "al_git_info_file")
al_git_info_file(name, git_state, git_tool, timeout)
Generate git info file
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| git_state |
Files that should invalidate the cache on new commit |
List of labels |
optional |
[] |
| git_tool |
Git tool to use |
Label |
optional |
"@com_alwaldend_src//tools/git/main/go" |
| timeout |
Timeout in seconds |
Integer |
optional |
60 |
25.1.6 - al_git_repo
al_git_repo
load("@com_alwaldend_src//tools/git/main/bzl:al_git_repo.bzl", "al_git_repo")
al_git_repo(name, repo_mapping)
Git repo
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this repository. |
Name |
required |
|
| repo_mapping |
In WORKSPACE context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.
For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target).
This attribute is not supported in MODULE.bazel context (when invoking a repository rule inside a module extension’s implementation function). |
Dictionary: String -> String |
optional |
|
25.1.7 - al_git_resolved_toolchain
al_git_resolved_toolchain
load("@com_alwaldend_src//tools/git/main/bzl:al_git_resolved_toolchain.bzl", "al_git_resolved_toolchain")
al_git_resolved_toolchain(name)
Resolved git toolchain
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
25.1.8 - al_git_run_binary
al_git_run_binary
load("@com_alwaldend_src//tools/git/main/bzl:al_git_run_binary.bzl", "al_git_run_binary")
al_git_run_binary(name, srcs, outs, arguments, git)
Run a git binary as a build action
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Files to be made available |
List of labels |
optional |
[] |
| outs |
Outputs |
List of labels; nonconfigurable |
required |
|
| arguments |
Arguments (Location is expanded) |
List of strings |
optional |
[] |
| git |
Git binary |
Label |
required |
|
25.1.9 - al_git_toolchain
al_git_toolchain
load("@com_alwaldend_src//tools/git/main/bzl:al_git_toolchain.bzl", "al_git_toolchain")
al_git_toolchain(name, git_dir, git_path, git_root, invalidation)
Local git toolchain
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| git_dir |
Git directory path |
String |
required |
|
| git_path |
Git binary path |
String |
required |
|
| git_root |
Git workspace path |
String |
required |
|
| invalidation |
Files that should invalidate git actions |
List of labels |
optional |
[] |
25.2 - Proto
Protobuf contracts
25.2.1 - contracts
Proto docs for contracts.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/git/main/proto/contracts:contracts_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/git/main/proto/contracts:contracts",
],
)
Proto
syntax = "proto3";
package git;
option go_package = "git.alwaldend.com/alwaldend/src/tools/git/main/proto/contracts";
message GitSignature {
string name = 1;
string email = 2;
int64 when = 3;
}
message GitCommit {
string hash = 1;
GitSignature author = 2;
GitSignature committer = 3;
string merge_tag = 4;
string pgp_signature = 5;
string message = 6;
repeated string tags = 7;
map<string, bool> changed_files = 8;
}
message GitTagAnnotated {
GitSignature tagger = 1;
string hash = 2;
string message = 3;
string pgp_signature = 4;
}
message GitTag {
string name = 1;
string target = 2;
GitTagAnnotated annotated = 3;
}
message GitRemote {
string name = 1;
repeated string urls = 2;
}
message GitInfo {
map<string, GitCommit> commits = 1;
repeated string commits_order = 2;
map<string, GitTag> tags = 3;
repeated GitRemote remotes = 4;
}
26 - Git filter repo
Git filter repo
27 - Gitea
Gitea
Links
The pinned upstream binary is owned by
third_party/com_gitea_gitea.
28.1.1 - al_go_repository
al_go_repository
al_go_repository = use_extension("@com_alwaldend_src//tools/go/main/bzl:al_go_repository.bzl", "al_go_repository")
al_go_repository.go_repository(name, importpath, sum, version)
Extension wrapper around go_repository (useless because you can just call use_repo_rule)
TAG CLASSES
go_repository
Attributes
| Name |
Description |
Type |
Mandatory |
Default |
| name |
Name |
Name |
required |
|
| importpath |
importpath |
String |
required |
|
| sum |
checksum |
String |
optional |
"" |
| version |
- |
String |
required |
|
29 - go_mod
Keep go.mod files on one Go version
go_mod
go_mod keeps every tracked go.mod file on the Go version configured in
tools/go_mod/cmd/go_mod/BUILD.bazel. It discovers files with Git, so ignored
scratch files are excluded.
Update all module files:
bazel run //tools/go_mod/cmd/go_mod:update
Check without writing:
bazel test //tools/go_mod/cmd/go_mod:test
The check target is part of //:repo_quality_test.
30 - Gradle wrapper
//tools/gradle:gradle-wrapper runs the pinned Gradle distribution from
third_party/net_gradle_gradle with the Java runtime selected by the existing
rules_java toolchain. It does not download anything at run time.
bazel_agent bazel run //tools/gradle:gradle-wrapper -- --version
Gradle caches remain under ignored out/gradle/. Set GRADLE_CACHE_ROOT to
redirect them for a task.
31.1.1 - al_gzip_extension
al_gzip_extension
al_gzip_extension = use_extension("@com_alwaldend_src//tools/gzip/main/bzl:al_gzip_extension.bzl", "al_gzip_extension")
al_gzip_extension.download(name, integrity, url)
Extension for gzip repos
TAG CLASSES
download
Attributes
| Name |
Description |
Type |
Mandatory |
Default |
| name |
Name |
Name |
required |
|
| integrity |
Integrity |
String |
optional |
"" |
| url |
Url |
String |
required |
|
31.1.2 - al_gzip_repo
al_gzip_repo
load("@com_alwaldend_src//tools/gzip/main/bzl:al_gzip_repo.bzl", "al_gzip_repo")
al_gzip_repo(name, integrity, repo_mapping, url)
Gzip repo
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this repository. |
Name |
required |
|
| integrity |
Integrity |
String |
optional |
"" |
| repo_mapping |
In WORKSPACE context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.
For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target).
This attribute is not supported in MODULE.bazel context (when invoking a repository rule inside a module extension’s implementation function). |
Dictionary: String -> String |
optional |
|
| url |
Url |
String |
required |
|
32 - Harbor
Harbor CLI
Links
33 - Helm
Bazel rules for helm
Links
33.1.1 - al_helm_binary
al_helm_binary
load("@com_alwaldend_src//tools/helm/main/bzl:al_helm_binary.bzl", "al_helm_binary")
al_helm_binary(name, data, arguments, cd)
Helm binary
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| data |
Helm chart |
List of labels |
optional |
[] |
| arguments |
Helm arguments |
List of strings |
optional |
[] |
| cd |
Cd to a directory before running bazel |
String |
optional |
"." |
33.1.2 - al_helm_chart
al_helm_chart
load("@com_alwaldend_src//tools/helm/main/bzl:al_helm_chart.bzl", "al_helm_chart")
al_helm_chart(name, deps, package, source)
Helm chart
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| deps |
Helm chart deps |
List of labels |
optional |
[] |
| package |
Helm chart package |
Label |
optional |
None |
| source |
Helm chart source |
Label |
optional |
None |
33.1.3 - al_helm_chart_info
AlHelmChartInfo
load("@com_alwaldend_src//tools/helm/main/bzl:al_helm_chart_info.bzl", "AlHelmChartInfo")
AlHelmChartInfo(source, package, deps, files_info)
Information about a helm chart
FIELDS
| Name |
Description |
| source |
Chart sources (PackageFilegroupInfo, optional) |
| package |
Chart package (tgz file) |
| deps |
Chart deps (depset of AlHelmChartInfo) |
| files_info |
Chart file structure (PackageFilesInfo) |
33.1.4 - al_helm_chart_lock
al_helm_chart_lock
load("@com_alwaldend_src//tools/helm/main/bzl:al_helm_chart_lock.bzl", "al_helm_chart_lock")
al_helm_chart_lock(name, lock, lock_out)
Generate targets for a chart lock
PARAMETERS
| Name |
Description |
Default Value |
| name |
name |
none |
| lock |
lock label |
none |
| lock_out |
parsed lock filename |
none |
33.1.5 - al_helm_cmds
33.1.6 - al_helm_deps
al_helm_deps
al_helm_deps = use_extension("@com_alwaldend_src//tools/helm/main/bzl:al_helm_deps.bzl", "al_helm_deps")
al_helm_deps.from_locks(name, integrity, locks)
Extension to download helm dependencies
TAG CLASSES
from_locks
Attributes
| Name |
Description |
Type |
Mandatory |
Default |
| name |
Repo name |
Name |
required |
|
| integrity |
Intergrity for locks, keys are packages, values are integrity |
Dictionary: String -> String |
optional |
{} |
| locks |
Helm lock labels |
List of labels |
optional |
[] |
33.1.7 - al_helm_deps_repo
al_helm_deps_repo
load("@com_alwaldend_src//tools/helm/main/bzl:al_helm_deps_repo.bzl", "al_helm_deps_repo")
al_helm_deps_repo(name, integrity, locks, repo_mapping)
Helm deps repo
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this repository. |
Name |
required |
|
| integrity |
Intergrity for locks, keys are packages, values are integrity |
Dictionary: String -> String |
optional |
{} |
| locks |
Lock labels to parse |
List of labels |
optional |
[] |
| repo_mapping |
In WORKSPACE context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.
For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target).
This attribute is not supported in MODULE.bazel context (when invoking a repository rule inside a module extension’s implementation function). |
Dictionary: String -> String |
optional |
|
33.1.8 - al_helm_toolchain
al_helm_toolchain
load("@com_alwaldend_src//tools/helm/main/bzl:al_helm_toolchain.bzl", "al_helm_toolchain")
al_helm_toolchain(name, helm)
Helm toolchain
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| helm |
Helm binary |
Label |
required |
|
34 - Hermes install
Run the Hermes Agent install script
Links
Usage
Run the pinned Hermes Agent install script:
bazel run //tools/hermes_install
35 - Hooks
Git hooks
The hook installer resolves the repository’s effective hooks directory through
Git. This supports linked worktrees and repositories that configure
core.hooksPath.
The installed pre-commit hook requires bazel_agent. Bootstrap it before
installing the hook:
bazel run --config=agent //projects/bazel_agent:install
Install or update the checked-in hooks:
bazel_agent bazel run //:write_git_hooks
Verify that every hook is current and executable without changing it:
bazel_agent bazel run //:write_git_hooks -- test
36 - Http server
Bazel rules for running http.server
36.1.1 - al_http_server_binary
al_http_server_binary
load("@com_alwaldend_src//tools/http_server/main/bzl:al_http_server_binary.bzl", "al_http_server_binary")
al_http_server_binary(name, srcs, arguments)
Run a http server
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Files to symlink |
List of labels |
optional |
[] |
| arguments |
Arguments |
List of strings |
optional |
[] |
37 - Install file
Bazel rules to install files
37.1.1 - al_install_file
al_install_file
load("@com_alwaldend_src//tools/install_file/main/bzl:al_install_file.bzl", "al_install_file")
al_install_file(name, args, install_file_label, visibility, **py_binary_kwargs)
Create py_binary target to install file
PARAMETERS
| Name |
Description |
Default Value |
| name |
target name |
none |
| args |
install-file args |
[] |
| install_file_label |
- |
"//tools/install_file/main/py:install_file_lib" |
| visibility |
- |
["//:__subpackages__"] |
| py_binary_kwargs |
- |
none |
39 - Js
Js
Repository JavaScript tooling dependencies belong to the pnpm workspace in
tools/. Its package.json and pnpm-lock.yaml own their requested and resolved
versions. //tools:node_modules owns the Bazel package store and links; loading
the repository root package does not load the npm extension’s generated rules.
Run pnpm with //tools/pnpm, passing --dir with the absolute path to tools/
for dependency updates. Keep lifecycle scripts disabled (--ignore-scripts)
when updating the lockfile.
41 - kubectl
Kubectl
Links
42.1.1 - al_lua_library
al_lua_library
load("@com_alwaldend_src//tools/lua/main/bzl:al_lua_library.bzl", "al_lua_library")
al_lua_library(name, srcs, check, stylua_config_label, stylua_label, pkg_kwargs, visibility)
Generate targets for a lua library
PARAMETERS
| Name |
Description |
Default Value |
| name |
library name |
none |
| srcs |
library sources |
none |
| check |
if set, only these files will be checked |
[] |
| stylua_config_label |
- |
"//tools/stylua:stylua_config" |
| stylua_label |
- |
"//tools/stylua" |
| pkg_kwargs |
- |
{} |
| visibility |
visibility |
["//:__subpackages__"] |
43 - Make install
Rules to create tar archives that can be installed with make
43.1.1 - al_make_install
al_make_install
load("@com_alwaldend_src//tools/make_install/main/bzl:al_make_install.bzl", "al_make_install")
al_make_install(name, srcs)
Create the make install executable and a filegroup
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Make install filegroups |
List of labels |
optional |
[] |
43.1.2 - al_make_install_binary
al_make_install_binary
load("@com_alwaldend_src//tools/make_install/main/bzl:al_make_install_binary.bzl", "al_make_install_binary")
al_make_install_binary(name, src, arguments)
Create a binary target for a make install
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| src |
Make install target |
Label |
required |
|
| arguments |
Arugments for the executable |
List of strings |
optional |
[] |
43.1.3 - al_make_install_filegroup
al_make_install_filegroup
load("@com_alwaldend_src//tools/make_install/main/bzl:al_make_install_filegroup.bzl", "al_make_install_filegroup")
al_make_install_filegroup(name, deps, srcs, diff_args, install_args, install_dir, pkg_prefix)
Create a make install filegroup
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| deps |
Deps |
List of labels |
optional |
[] |
| srcs |
Sources to install |
List of labels |
optional |
[] |
| diff_args |
Diff args |
List of strings |
optional |
[] |
| install_args |
Install args |
List of strings |
optional |
["--compare", "-D"] |
| install_dir |
Install directory |
String |
optional |
"${HOME}" |
| pkg_prefix |
Ignore that prefix for srcs |
String |
required |
|
43.1.4 - al_make_install_filegroup_info
AlMakeInstallFilegroupInfo
load("@com_alwaldend_src//tools/make_install/main/bzl:al_make_install_filegroup_info.bzl", "AlMakeInstallFilegroupInfo")
AlMakeInstallFilegroupInfo(srcs, deps, install_dir, origin, install_args, diff_args, pkg_prefix)
Describe install info for a single filegrop
FIELDS
| Name |
Description |
| srcs |
depset of PackageFilegroupInfo |
| deps |
depset of AlMakeInstallFilegroupInfo |
| install_dir |
Install directory |
| origin |
Rule label |
| install_args |
Args for the install command |
| diff_args |
Args for the diff command |
| pkg_prefix |
Ignore that prefix for srcs |
44 - Maven Install Gradle Converter
Converts rules_jvm_external maven_install.json locks to Gradle build inputs
Converts rules_jvm_external maven_install.json lock files into Gradle
version catalogs and dependency verification metadata so Bazel-built Android
apps can maintain the parallel Gradle build required for F-Droid.
Usage
Use the al_gradle_lock Starlark macro from
main/bzl/gradle_lock.bzl in your app’s gradle/BUILD.bazel:
load("//tools/maven_install_gradle_converter:defs.bzl", "al_gradle_lock")
al_gradle_lock(
name = "update",
lock_file = "//projects/<app>:maven_lock.json",
tool = "//tools/maven_install_gradle_converter/cmd/gradle_lock_gen",
)
Run bazel run :update_toml_write to regenerate the checked-in version
catalog. The corresponding *_write_test target fails when it goes stale.
Gradle owns verification-metadata.xml because it must record the plugin
classpath in addition to the Bazel lock. Regenerate it with:
projects/<app>/../../tools/gradle/gradle-wrapper --project-cache-dir out/gradle-wrapper/project-cache --write-verification-metadata sha256
45.1.1 - al_md_data
al_md_data
load("@com_alwaldend_src//tools/md/main/bzl:al_md_data.bzl", "al_md_data")
al_md_data(name, srcs, deps, **kwargs)
Markdown data backed by a filegroup
Targets:
PARAMETERS
| Name |
Description |
Default Value |
| name |
filegroup name |
none |
| srcs |
markdown files |
none |
| deps |
deps |
[] |
| kwargs |
filegroup kwargs |
none |
46 - Mermaid
Bazel-managed Mermaid diagram renderer
This package exposes the Mermaid CLI as a repository-wide Bazel tool. Bazel
provisions the pinned Node toolchain, JavaScript dependency graph, Mermaid CLI,
and Chrome-for-Testing browser used by Puppeteer. Rendering runs as a Bazel
target or action; it does not use a host browser, download one during an npm
lifecycle hook, or fetch scripts or fonts from a CDN.
theme.json owns the diagram appearance. It applies a
documentation palette, typography, layout, and the handDrawn look to every
diagram, so rendered work shares one visual contract instead of Mermaid’s
built-in default. Shapes stay semantic: () and other inherently rounded
shapes render rounded, while [] boxes render as sketched rectangles. Nothing
forces every node into a rounded outline.
The mermaid_svg rule and the mmdc target both apply the theme by default.
A diagram can override any part of it with its own Mermaid directive, which
Mermaid merges over the file config:
%%{init: {"look": "classic"}}%%
flowchart LR
a[Square] --> b(Rounded)
Node label paragraphs have 2px of horizontal padding. The pinned handwriting
font can paint beyond a string’s measured advance: openai.com measures 78px
but its final glyph reaches 79px. Mermaid clips HTML labels to their measured
foreignObject, so the padding reserves room for that overhang before layout.
Label foreignObject elements also use overflow: visible: if an SVG viewer
falls back to a wider font, its glyphs can extend into the node’s existing
padding instead of being cut off at the narrower measured frame. A fallback
Arial render reproduces right-edge clipping on the hostname without this rule.
The rule selects .label > * because Mermaid lowercases element selectors;
foreignobject would not match the case-sensitive SVG foreignObject name.
themeCSS boxes every edge label and every cluster title, so an annotation
reads as a caption instead of sitting on the edge line. The rule styles the
<p> inside a real label and leaves .labelBkg transparent; styling
.labelBkg directly would draw a stray box wherever an edge carries no text.
Mermaid emits an empty label plate for unlabelled edges under the dagre
renderer but omits it under ELK, so the rule is what the test asserts rather
than any particular empty element.
Each boxed label carries a 3px margin. Under the dagre renderer Mermaid sizes
a label’s <p> to the text only, so the frame clips the outer half of the 1px
border and a label appears to have a missing right edge; the margin keeps the
border inside the clip rect. Under ELK the frame already includes the margin,
so the value there is breathing room rather than a correctness fix.
The cluster title plate pins font-size: 13px. ELK sizes a container from the
width of its own title plate, so a child container whose title is wider than
its parent’s escapes the parent on both sides. Nesting a hostname title inside
a short parent title is the case that exposes this: at the inherited 16px
the child measured 333px against a 302px parent and escaped 20px per side,
while 13px fits it with 7px of clearance per side. The plate is a container
label, not body text, so the smaller size also keeps it visually subordinate.
flowchart.defaultRenderer selects dagre-wrapper, Mermaid’s Dagre renderer.
Diagrams use left-to-right flow by default. Dagre’s nodeSpacing and
rankSpacing control spacing between nodes and ranks, while the title styling
below keeps nested containers readable.
flowchart.subGraphTitleMargin.top is negative on purpose. Under dagre a
nested cluster keeps a fixed 20px inset from its parent that does not grow with
nodeSpacing, rankSpacing, padding, or this margin, so the parent title
would be overlapped by the container nested inside it, and the negative margin
is the only thing that lifts it clear. ELK does not need the lift, but both
renderers share this one config, so the value stays and keeps the title on the
cluster border. Remove it only after re-checking a diagram with nested
subgraphs.
The cluster title additionally forces line-height: 1, which keeps the plate
short enough to sit on the border consistently instead of spilling past it.
handDrawnSeed is fixed, so the sketch jitter is stable rather than different
on every render. Pass theme on the rule or a later -c argument to the CLI
to replace the whole config for one diagram; because a second -c file
replaces rather than merges, override only the specific keys you need with a
directive.
Paint order
Mermaid emits a container title in <g class="cluster-label"> inside
<g class="clusters">, and emits that group before the edges, the edge labels,
and the nodes. SVG paints in document order and offers no other layer control:
z-index needs a CSS-positioned box and does not reorder SVG elements, and
paint-order only orders fill, stroke, and markers within a single element.
An edge that crosses a container border therefore paints over the title plate
and appears to run through the title text. Mermaid exposes no z-order, layer,
or paint-order configuration key, so this is fixed by rewriting the SVG rather
than by a theme setting.
The current theme already does everything CSS can do here, so the remaining
crossing is the one case CSS cannot reach. The title plate is the topmost paint
at its own plate coordinates, and on the host_bot diagram one edge crosses the
External services plate and two cross the LLM BM plate. A rasterised check
draws a white plate and then a red line over it and reads the pixel at the
crossing: reordering the document turns that pixel white, while z-index,
z-index with position:relative or position:absolute, isolation:isolate,
paint-order, transform:translateZ(0), will-change:transform, a
filter:drop-shadow stacking context, and opacity:0.999 all leave it red.
Only document order moves an SVG group above a later sibling, which is why the
lift is a rewrite and not a rule.
cmd/render/paint_order.mjs moves every title group to the end of the outer
root group after Mermaid writes the file. Dagre places nested containers in
translated groups; the lift composes their ancestor transforms onto each title
so its position stays unchanged. Empty, self-closing paint layers do not affect
the group scan. A browser test checks both title coordinates and visible
stacking. Both entry points apply the lift, so a diagram rendered through
mermaid_svg and the same diagram rendered through mmdc paint identically.
mmdc rewrites the file named by -o or --output; an SVG printed to stdout
is not rewritten.
Diagram direction
Write flowchart LR for left-to-right flow. Direction belongs to the diagram
source; the shared theme selects Dagre but does not override an explicit
diagram direction. Use another direction when the diagram or the user requires
it.
Subgraphs can declare direction TB, but Mermaid ignores that direction when
their internal nodes connect outside the group. Both Dagre and ELK exhibit
this limitation on the T3 Code graph, so verify the rendered arrangement rather
than assuming a direction declaration took effect.
Actors
An actor is an ordinary rounded node marked with the actor class. The theme
owns the figure: a .actor div rule paints a hand-drawn stick figure as a
background-image and reserves room with padding-left. A diagram therefore
carries only its label text:
remote("Actor")
classDef actor font-style:normal;
class remote,intranet actor
The classDef is required even though the theme supplies the appearance.
Mermaid emits a node’s class attribute only when a classDef declares it, so a
bare class statement leaves the node unstyled and the figure does not paint.
Keeping the figure in the theme rather than in the diagram means the ~1.2 KB of
base64 SVG is written once, every actor node shares it, and the diagram source
states topology only. Sizing is fixed by the theme, so all actors match: the
figure plus the renderer’s padding lands an actor node at the same height as a
single-line service node.
The rule rides in every rendered document, including diagrams with no actor
node, where it adds about 1.3 KB to the inline stylesheet. That is the cost of
owning appearance centrally; it is acceptable because the rule is inert without
an actor node and every diagram already carries a much larger stylesheet. The wobble is baked into the path coordinates with a
fixed seed rather than applied through an SVG filter, so the shared figure
carries no <defs> id that would collide when the background repeats.
Branching edges
Use a visible junction node when several outgoing edges share one label:
codex ---|"Inference"| inference@{ shape: junction }
inference --> openai
inference --> openrouter
The incoming segment carries the shared label without an arrowhead, and the
outgoing segments point to their respective targets. The junction is a real
flowchart node and affects layout, so inspect the rendered spacing. This is
the form used by the maintained T3 Code diagram. The separate
architecture-beta diagram type has its own junction declaration syntax.
Layout width
Dagre uses nodeSpacing between nodes in a rank and rankSpacing between
ranks. For an LR diagram, rank spacing contributes to width and node spacing
contributes to height. These settings do not impose a maximum canvas width;
labels, topology, and nested groups also affect the result. Check the rendered
diagram at its intended display size before adjusting spacing or direction.
Plain renders
mermaid_svg(..., plain = True) and MERMAID_PLAIN=1 render with Mermaid’s own
defaults instead of the repository appearance: no theme, no paint-order
post-processing, and no pinned fonts. The output is deliberately neither themed
nor hermetic, and exists to compare a diagram against the upstream appearance.
A plain action declares neither the theme nor the fonts as inputs, so it cannot
depend on them by accident; //users/simeonwarren/host_bot:rendered_plain is a
worked example. Plain output is not published and not committed.
upstream-theme.json is the themed counterpart to that
opt-out. It selects Mermaid’s built-in default theme and pins the label family
to a font the renderer already carries, so the result is hermetic like any other
render and shows the upstream appearance rather than a host-dependent one. Use
it when a consumer has to show the contrast between Mermaid’s own look and the
repository theme, as the blog post on this work does:
mermaid_svg(
name = "example_default",
src = "example.mmd",
out = "rendered/example-default.svg",
theme = "//tools/mermaid:upstream-theme.json",
)
This is a comparison input, not a second appearance the repository maintains. It
exists so a before-and-after pair renders from one source through the same
pipeline; a diagram’s own look still comes from theme.json.
Native site diagrams
The main site’s mermaid_site_svg macro lives in
projects/alwaldend.com/pkg/mermaid/defs.bzl.
It selects mermaid_svg(native = True) with the site’s own configuration,
compiled palette, and pinned Liberation Sans font. It does not inherit the
historical theme or actor decoration. Its own preset restores rounded caption
plates using Mermaid’s themeCSS and flowchart settings. The site’s compact
preset uses basis curves and flowchart.edgeSpacing; the
pinned dependency patch
passes the latter through to Dagre before layout, including nested subgraphs.
Native mode requires a palette CSS input. mermaid_svg also requires a
dark_out SVG output; mermaid_webp encodes the selected color_scheme
(light by default, or dark). The
stylesheet exposes --mermaid-* custom properties named after Mermaid theme
variables. Chrome resolves them in light and dark media, and the renderer
passes the resulting values to Mermaid’s native configuration and exposes the
same --mermaid-* properties on its diagram root for input themeCSS. Font embedding
uses the CLI’s CSS input. The returned SVG bytes are written directly: there
is no color rewriting, paint-order change, or stylesheet insertion afterwards.
Both outputs are packaged beside each other. The site selects the .dark.svg
sibling using its existing theme attribute and always prints the light image.
A diagram used by documentation and a historical blog post can expose both
render targets from one authoritative .mmd source. Blog consumers keep their
original mermaid_svg target and its existing appearance.
Raster renders
mermaid_webp renders the same diagram source to a WebP image for a consumer
that has to upload the file to a service. Some upload endpoints accept only a
fixed set of raster media types and reject image/svg+xml, so such a consumer
cannot use the SVG rule at all.
load("//tools/mermaid:defs.bzl", "mermaid_webp")
mermaid_webp(
name = "rendered",
src = "architecture.mmd",
out = "rendered/architecture.webp",
)
The rule projects the mermaid_svg document to a raster. By default it renders
the historical themed SVG first — same shared theme, same pinned browser and
fonts, same paint-order pass — then has the pinned browser encode that document
to WebP. Set native = True and pass the same palette, theme, and
label_font as a native SVG target to encode that target’s appearance.
Set color_scheme = "dark" for its dark appearance; the default remains
"light". Native rendering reads the palette’s selected media values, embeds
the selected font through Mermaid’s CSS input, and sends the returned SVG straight to the
existing encoder without SVG post-processing. native and palette must be
set together; selecting "dark" requires native rendering. The site macro
mermaid_site_webp accepts the same color_scheme option. A WebP carries one
fixed appearance for page embeds, social cards, and service uploads.
Encoding stays inside the hermetic graph and adds no image-conversion
dependency. The raster is authored at the diagram’s
natural size and scaled by the rule’s scale (default 1). Set scale = 2
when a consumer needs higher pixel density for text or re-encoding. A raster render
always uses the maintained appearance; it has no plain opt-out, because a
plain raster could not show the contrast a comparison needs any more reliably
than the SVG rule’s.
WebP canvases default to aspect_ratio = [5, 2]. The
encoder reduces the two positive integer terms and adds the selected theme’s
background around the scaled diagram. It uses the smallest whole-pixel canvas
with that exact ratio that contains the original image, centers the diagram,
and never crops or stretches it. SVG output is unaffected. Set aspect_ratio = []
for the natural raster canvas, or select another positive integer pair. The CLI
uses the same default; --aspect-ratio natural selects natural output, and two
repeated numeric --aspect-ratio flags select another ratio.
scale must be a positive integer, and the
final image must fit WebP’s 16383-pixel limit in each dimension.
Consumers that must keep the image as a checked-in asset obtain it from the
rule’s output rather than by hand, so the asset can be regenerated and compared
against a fresh render. The .mmd source stays authoritative. Blog posts keep
the raster render beside index.md by copying it through write_source_files,
as the diagram skill describes.
Tests
test/renderer/renderer_test.go renders fixture diagrams through the
mermaid_svg rule and asserts the contract of the theme: the documentation
palette and hand-drawn node class survive, the built-in lavender colors do not,
labels are measured through the pinned fonts, and the label plates keep their
border slack. It also asserts that every container title is written after the
nodes, which is the paint order cmd/render/paint_order.mjs produces and the
property that keeps an edge from drawing over a title. The same package renders
a WebP fixture and asserts the raster reproduces the SVG canvas at the rule’s
scale, so a raster that stopped following the maintained document fails there.
test/renderer/native_raster_test.mjs compares light and dark native WebPs with
their separately built SVGs, checks each selected site palette, visible label
pixels, and the embedded sans-serif font, and verifies unchanged scaled canvas
geometry. The test saves each reference and actual image and a JSON measurement
report in its undeclared outputs for repeatable inspection.
test/renderer/aspect_ratio_test.mjs checks horizontal and vertical padding,
minimal exact-ratio dimensions, unchanged content scale, and theme backgrounds
against independently rendered SVG references. It also exercises invalid size
and ratio inputs, and saves reference rasters and measurement reports.
cmd/mmdc/mmdc_test.go
drives the interactive CLI and re-renders under a Fontconfig pointed at a
monospace-only pool; a byte-identical result shows the CLI resolved text
through the pinned fonts.
Hermetic rendering
Mermaid measures label text through Fontconfig, so a renderer that follows the
host would produce different canvas geometry on every machine. Both entry
points instead expose only the repository’s pinned fonts to Chrome:
cmd/render/fontconfig.mjs writes a Fontconfig file whose sole <dir> values
are the directory holding the pinned handwriting face and the directory holding
the pinned Liberation set, and whose aliases map the families the theme requests
onto them. The same file is applied by the build action and by an interactive
mmdc run, so both resolve identical text metrics.
The generated Fontconfig must use absolute paths. Chrome is a separate process
from the launcher, and a relative <dir> resolves against whatever working
directory it inherits rather than the action’s execroot. Passing a caller’s own
-p/--puppeteerConfigFile hands full control of the browser launch, and
including the font aliases in it, to that caller.
Pinned fonts fix the metrics a render measures with, but not the metrics a
consumer displays those glyphs with. A documentation page embeds an SVG through
<img>, and an SVG loaded that way is an isolated document: it cannot see the
page’s webfonts, loads no network font, and falls back to whatever the viewer’s
system provides. A host font is wider than the pinned handwriting face, so
github.com overflowed the label box Mermaid sized with the pinned font and
appeared clipped, even though the file was correct. cmd/render/embed_font.mjs
therefore inlines the pinned face as a @font-face data URI in every themed
document, so the glyphs travel with the SVG and the label metrics match
everywhere, including in a viewer that can load no webfont at all. The rule
adds about 50 KB to a rendered diagram; that is the cost of an image that is
correct wherever it is displayed. Its family name is read from the font’s own
name table rather than hard-coded, so a font swap cannot silently desync it
from the theme.
The JavaScript launcher runs from Bazel’s output tree. Pass absolute paths when
rendering source-tree files directly:
repo_root="$PWD"
bazel_agent bazel run //tools/mermaid:mmdc -- \
-i "${repo_root}/path/to/diagram.mmd" \
-o "${repo_root}/path/to/diagram.svg"
Declare a maintained diagram as a mermaid_svg action and feed the output to
its consumer, which uses declared Bazel paths and needs no absolute-path
handling. The diagram skill defines direct
documentation consumption and checked-in publication assets for blog posts.
The .mmd remains the authoritative diagram source in both cases.
The pinned upstream binary is owned by
third_party/com_google_chrome_headless_shell.
46.1 - OpenSpec
46.1.1 - Specifications
46.1.1.1 - Mermaid rendering
46.1.1.1.1 - mermaid-rendering Specification
Purpose
Provide hermetic SVG and raster diagram outputs that share their selected
theme, font, and canvas geometry, with reproducible publication assets.
Requirements
Requirement: Raster diagram renders
The tool SHALL expose a rule that renders a Mermaid source to a raster image in
a media type a service upload endpoint can accept, beginning with WebP. The
raster render SHALL reuse the selected SVG render contract rather than defining a
second appearance: the same theme, the same pinned browser and fonts, and
the same post-processing policy as the SVG rule. Native rendering SHALL use
the consumer’s palette and font and encode the selected light or dark SVG without post-processing;
the default SHALL retain the historical theme and paint-order pass.
Encoding MUST go through the pinned
browser, so the rule introduces no image-conversion dependency. The render SHALL
be a projection of the SVG render — the same maintained document, encoded to a
raster — rather than a second layout. The render SHALL be hermetic — no host
browser, host font, or network fetch — and deterministic for a given source,
theme, and configuration.
Scenario: Render a diagram to a raster image
- WHEN a diagram source is rendered through the raster rule
- THEN it produces an image in a service-acceptable media type
- AND the render uses the shared theme, the pinned browser, and the pinned
fonts rather than host or network inputs
- WHEN the same diagram source is rendered to SVG and to the raster format
- THEN both renders apply the same selected theme and post-processing policy
- AND neither render introduces an appearance of its own
- AND the raster canvas reproduces the SVG render’s geometry at the rule’s scale
Scenario: Render a native site diagram
- WHEN the raster rule selects native mode with a palette and label font
- THEN it encodes the selected native SVG with the same palette and font
- AND it performs no historical SVG post-processing
- AND native mode and a palette must be selected together
Scenario: Reproduce the natural canvas
- WHEN a diagram’s natural canvas differs from the browser’s default
replaced-element size
- THEN the raster is captured at the SVG’s own
viewBox geometry rather than
the default size
- AND its canvas equals the SVG render’s geometry at the rule’s scale
- WHEN the raster rule’s inputs are inspected
- THEN it uses the pinned browser to encode the raster format
- AND it declares no image-conversion tool or library
Scenario: Select a native raster color scheme
- WHEN a native raster selects
color_scheme as light or dark
- THEN it uses the corresponding consumer palette media values
- AND an omitted color scheme retains the light default
- AND both schemes preserve SVG geometry and label visibility
- AND unsupported schemes and dark selection without native mode are rejected
Requirement: Raster renders belong to their source
A raster render SHALL be reproducible from the diagram source, with the source
remaining authoritative. A consumer that must keep the image as a checked-in
asset SHALL obtain it from the rule’s output rather than by hand, so the asset
can be regenerated and compared against a fresh render.
Scenario: Keep a checked-in raster asset current
- WHEN a consumer checks in a diagram’s raster render
- THEN the asset is the rule’s output for that source
- AND the asset can be compared against a fresh render of the same source
Scenario: Update the source
- WHEN a diagram source changes
- THEN its raster render changes when regenerated from the new source
- AND the source remains the artifact an author edits
50 - Nogo
Golang static analyser
Links
51 - Nping
Nping - Network packet generation tool & ping utility
Links
53 - Opencode
Opencode - The open source AI coding agent
Links
54 - OpenSpec
Pinned OpenSpec CLI and repository specification validation
This package runs the upstream OpenSpec CLI with
Bazel’s pinned Node toolchain. tools/package.json pins
@fission-ai/openspec to 1.11.0; tools/pnpm-lock.yaml records the package
integrities and transitive dependency versions. Package lifecycle scripts are
disabled. No global npm installation is needed.
Each component owns an OpenSpec workspace at <owner>/openspec/. The
repository’s evolution belongs to infra/src/openspec/,
which is the default for the runnable target. Run commands from the enclosing
repository root; OPENSPEC_PROJECT selects a different owner directory:
bazel_agent bazel run //tools/openspec -- --version
bazel_agent bazel run //tools/openspec -- list --specs --json
bazel_agent bazel run //tools/openspec -- status --change <change-name> --json
bazel_agent bazel run //tools/openspec -- validate --all --strict --no-interactive
OPENSPEC_PROJECT=tools/agents bazel_agent bazel run //tools/openspec -- list --specs --json
OPENSPEC_PROJECT=infra/vault bazel_agent bazel run //tools/openspec -- list --specs --json
OPENSPEC_PROJECT is a repository launcher setting, not an upstream CLI flag.
It is relative to the enclosing repository root, and OpenSpec reads and writes
that owner’s source artifacts directly. Standalone nested projects use the
same root invocation, for example OPENSPEC_PROJECT=tools/rules_skills.
Run upstream validation for all 51 owner workspaces in sandboxed tests against
declared source inputs:
bazel_agent bazel test //infra/src/openspec/validation:validate_test //infra/src/openspec/validation:archive_test
validate_test aggregates one strict test per owner for current specs and
active change deltas. archive_test covers the repository, agent-system, and
MCP workspaces and checks that archived changes have completed task
checkboxes; upstream does not reapply historical deltas during that check.
Both are structural checks. Requirement scenarios still need evidence from
their owning implementation and validation workflow.
The source-to-test mapping lives in
infra/src/openspec/validation/BUILD.bazel. Each owner’s
openspec/BUILD.bazel exposes config, baseline specs, and complete native
change artifacts. Standalone projects expose this source through their
existing module repository, without taking an OpenSpec dependency. The shared
openspec_validation macro retains declared source runfiles through a
js_library with no_copy_to_bin: files keep their owning package paths,
including across module boundaries. Tests select that owner inside their
sandbox and receive only declared inputs.
When adding an owner workspace, add its source label to _WORKSPACE_SOURCES.
When it first archives a change, also add it to _ARCHIVE_WORKSPACES. Keep
these aggregate memberships aligned with the checked-in workspaces.
The launcher disables telemetry and update checks. OpenSpec’s XDG config and
data paths are isolated to ignored out/openspec/ in the source workspace for
bazel run, and to Bazel’s test temporary directory for tests. These targets
do not read or change user-global OpenSpec configuration. Commands that create,
edit, or archive changes still modify source artifacts as documented by the
upstream CLI.
Update the version in tools/package.json, then use the owning pnpm workflow
described in tools/js to regenerate its lock with lifecycle
scripts disabled. Re-run the version command and both validation tests after
upgrading.
54.1 - OpenSpec
54.1.1 - project-rules-openspec Specification
Purpose
Define reusable Bazel validation and execution behavior for owner-local OpenSpec workspaces.
Requirements
OpenSpec validation
The system SHALL validate one owner-local OpenSpec workspace in a sandbox using the pinned OpenSpec CLI.
Scenario: current validation
- WHEN a caller runs the validation target
- THEN OpenSpec validates current specifications and active changes with strict mode
Scenario: archived validation
- WHEN a caller requests archived validation
- THEN OpenSpec validates completed archived changes with strict mode
55 - Ops
Ops CLI binary
Links
56.1.1 - al_apply_patches
al_apply_patches
load("@com_alwaldend_src//tools/patch/main/bzl:al_apply_patches.bzl", "al_apply_patches")
al_apply_patches(name, src, patches, visibility, **kwargs)
Create a genrule applying patches
PARAMETERS
| Name |
Description |
Default Value |
| name |
genrule name |
none |
| src |
source archive label |
none |
| patches |
patches label |
none |
| visibility |
visibility |
["//:__subpackages__"] |
| kwargs |
other genrule kwargs |
none |
57.1.1 - al_genrule_src
al_genrule_src
load("@com_alwaldend_src//tools/pkg/main/bzl:al_genrule_src.bzl", "al_genrule_src")
al_genrule_src(name, srcs, visibility)
Create a filegroup and a genrule generating a tar archive
PARAMETERS
| Name |
Description |
Default Value |
| name |
genrule name |
none |
| srcs |
source labels |
[] |
| visibility |
- |
["//:__subpackages"] |
57.1.2 - al_pkg_basic_naming
al_pkg_basic_naming
load("@com_alwaldend_src//tools/pkg/main/bzl:al_pkg_basic_naming.bzl", "al_pkg_basic_naming")
al_pkg_basic_naming(name, deps)
Variables for @rules_pkg
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| deps |
Deps to merge |
List of labels |
optional |
[] |
57.1.3 - al_pkg_extract_dir
al_pkg_extract_dir
load("@com_alwaldend_src//tools/pkg/main/bzl:al_pkg_extract_dir.bzl", "al_pkg_extract_dir")
al_pkg_extract_dir(name, src, out, arguments)
Extract an archive into a TreeArtifact
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| src |
Archive to unpack |
Label |
required |
|
| out |
Output directory name |
String |
optional |
"" |
| arguments |
Additional arguments |
List of strings |
optional |
[] |
57.1.4 - al_pkg_tar_combined
al_pkg_tar_combined
load("@com_alwaldend_src//tools/pkg/main/bzl:al_pkg_tar_combined.bzl", "al_pkg_tar_combined")
al_pkg_tar_combined(name, srcs, strip_components, **kwargs)
Create a genrule combining several tars into one
PARAMETERS
| Name |
Description |
Default Value |
| name |
genrule name |
none |
| srcs |
dicts tar archives ({“label”: “tar label”, “dir”: “target dir”}) |
[] |
| strip_components |
value of –stip-components |
2 |
| kwargs |
other genrule kwargs |
none |
57.1.5 - al_unpack_archives
al_unpack_archives
load("@com_alwaldend_src//tools/pkg/main/bzl:al_unpack_archives.bzl", "al_unpack_archives")
al_unpack_archives(name, srcs, out)
Unpack several archives using tar into a directory
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
- |
List of labels |
required |
|
| out |
- |
String |
optional |
"" |
58 - Pkgsite
Pkgsite
Links
60 - Postcss
Postcss
Links
61 - Prettier
Prettier
Links
62 - Print deps
Aspect to print deps
62.1.1 - al_print_deps
al_print_deps
load("@com_alwaldend_src//tools/print_deps/main/bzl:al_print_deps.bzl", "al_print_deps")
al_print_deps()
ASPECT ATTRIBUTES
ATTRIBUTES
63 - Proto
Protobuf rules
63.1.1 - al_proto_docs
al_proto_docs
load("@com_alwaldend_src//tools/proto/main/bzl:al_proto_docs.bzl", "al_proto_docs")
al_proto_docs(name, src, prefix, visibility, renames)
Generate protobuf documentation
PARAMETERS
| Name |
Description |
Default Value |
| name |
name |
none |
| src |
protobuf source file |
none |
| prefix |
- |
None |
| visibility |
visibility |
None |
| renames |
- |
None |
64 - Py
Python rules
This package owns the repository Python dependency manifest pyproject.toml
and native uv.lock. Update it with //tools/py:uv_lock.update; root
//:uv_lock.update is an alias. Bazel’s pip.parse reads uv.lock directly,
and Gazelle uses that same lock to validate its import manifest. There are no
requirements-file exports or separately maintained pip locks.
BUILD files load lock directly from @rules_python//python/uv:lock.bzl with
out = "uv.lock", which selects native uv lock. Build uv_lock to regenerate
the lock without modifying source. Run uv_lock.update to replace the checked-in
file, then review package versions, hashes and platform/Python markers. Extras
are declared in pyproject.toml and retained in the native lock.
The same workflow owns component-local manifests and locks in //tools/ansible,
//tools/ykman, //projects/autoscroll and //projects/infinitime. Update each
through its own uv_lock.update target. Lock generation needs PyPI access, so
only the upstream resolution action carries requires-network; it remains
sandboxed. All locking targets are manual. Normal consumers use checked-in
locks without running resolution, and there is no custom lock freshness test.
include.MODULE.bazel pins uv and Python 3.14.4, the newest stable runtime in
rules_python 2.4.2’s bundled version catalog. This updates Bazel-managed Python
without changing the host interpreter or adding a custom runtime archive.
The root pyproject.toml contains only shared tool settings. Ruff and other
Python tools discover it from source packages, and the repository checker
targets use //tools/py:pyproject to select those settings explicitly. The
manifest in this package contains only project metadata and dependencies.
The repo-python skill owns the repository Python
implementation and review procedure, including mandatory type hints. Its
discovery link is generated by //.agents:write_skills.
64.1.1 - al_genrule_with_wheels
al_genrule_with_wheels
load("@com_alwaldend_src//tools/py/main/bzl:al_genrule_with_wheels.bzl", "al_genrule_with_wheels")
al_genrule_with_wheels(name, wheels, srcs, cmd, **kwargs)
Regular genrule with wheels added to ${PYTHONPATH}
PARAMETERS
| Name |
Description |
Default Value |
| name |
genrule name |
none |
| wheels |
list of wheel labels |
none |
| srcs |
srcs for the genrule |
[] |
| cmd |
genrule cmd |
[] |
| kwargs |
other genrule kwargs |
none |
64.1.2 - al_py_binary_shell
al_py_binary_shell
load("@com_alwaldend_src//tools/py/main/bzl:al_py_binary_shell.bzl", "al_py_binary_shell")
al_py_binary_shell(name, deps, srcs, shell_type, shell_label, **kwargs)
Create a py_binary target that allows you to run commands in proper python environment
PARAMETERS
| Name |
Description |
Default Value |
| name |
target name |
none |
| deps |
py_binary deps |
[] |
| srcs |
py_binary srcs |
[] |
| shell_type |
${BAZEL_PYTHON_SHELL_TYPE} |
"python" |
| shell_label |
- |
"//tools/py/main/py:bazel_python_shell_lib" |
| kwargs |
other py_binary kwargs |
none |
64.1.3 - al_py_checker
al_py_checker
load("@com_alwaldend_src//tools/py/main/bzl:al_py_checker.bzl", "al_py_checker")
al_py_checker(name, tool, args_bin, args_test, test_size, disable_fix, **kwargs)
Create -fix and -test targets for a python checker
PARAMETERS
| Name |
Description |
Default Value |
| name |
Name prefix |
None |
| tool |
Tool label |
None |
| args_bin |
Args for the binary target |
None |
| args_test |
Args for the test |
None |
| test_size |
- |
"small" |
| disable_fix |
If set, do not create fix target |
False |
| kwargs |
Kwargs for both targets |
none |
64.1.4 - al_py_checkers
al_py_checkers
load("@com_alwaldend_src//tools/py/main/bzl:al_py_checkers.bzl", "al_py_checkers")
al_py_checkers(name, srcs, isort_label, black_label, mypy_label, flake8_label, pyproject_label)
Generate -fix and -test targets for python checkers
PARAMETERS
| Name |
Description |
Default Value |
| name |
- |
none |
| srcs |
list of source file labels |
none |
| isort_label |
- |
"//tools/isort" |
| black_label |
- |
"//tools/black" |
| mypy_label |
- |
"//tools/mypy" |
| flake8_label |
- |
"//tools/flake8" |
| pyproject_label |
- |
"//tools/py:pyproject" |
65 - Qt
Qt rules
Links
Hermetic Qt
The module configuration downloads SHA-256-pinned Qt 6.8.3 distributions via
rules_qt and registers its build tools. Bazel builds do not require Qt under
/opt or another host installation.
66 - Readme tree
Tool to parse README.md files
Example
bazel run tools/readme_tree -- parse -g -C "${PWD}" .
67 - Release
Release rules
The release tool generates release manifests/pages and deploys their files.
OCI deployment uses Soras. SSH deployment uses the operator’s OpenSSH and
rsync executables and requires rsync on the destination; it needs neither
Soras nor Ansible. No custom receiver or upload service is installed.
Build a portable release bundle
al_release_bundle packages declared payloads as release.json and
files/<filename> in a Bazel directory output. It reuses the manifest
generator for file sizes and hashes and reads STABLE_VERSION from the
versioning owner’s workspace status. Run its build through
tools/versioning/cmd/versioning/versioning.sh bazel -- build --config=release.
It does not contact deployment destinations or calculate another version.
The CLI equivalent is generate --project projects/<name> --version_file <status-file> --add_file <payload> --output_dir <empty-directory>. Repeat
--add_file for multiple payloads. Missing or duplicate version values,
unsafe path components, missing payloads, colliding filenames, and empty
bundles fail. The output directory must be absent or empty. A completed
manifest is written only after payload copying finishes.
The website’s //projects/alwaldend.com:release target consumes this rule;
its owner documents the complete build and SSH publication commands.
Publish over SSH
A release directory contains release.json and files/<filename>. The
manifest owns the version, project, and payload names. Pass an explicit
SSH environment and destination, or a deployment JSON containing an ssh
object. The download component provides preconfigured targets. These default
to the existing ansible administrator and use sudo -n -u download for
content operations. Host configuration from merged
PR #102 provides the content account,
storage, and server rsync package. Configure the selected host before publishing.
bazel_agent bazel run //infra/download:publish.local -- \
--release_dir "$PWD/out/release"
bazel_agent bazel run //infra/download:publish.yandex -- \
--release_dir "$PWD/out/site-release" --site_archive website.tar.gz \
--steps upload,extract,link
For other SSH destinations:
bazel_agent bazel run //tools/release -- deploy \
--release_dir "$PWD/out/release" --environment local \
--ssh_host download-host --ssh_user admin --publish_user download \
--ssh_root /srv/download
--ssh_deployment or --ssh_host publishes all manifest files through that
SSH destination, regardless of their original OCI metadata. Otherwise,
--environment selects matching SSH metadata attached to individual items.
Omitting the environment selects existing OCI deployments and requires
--soras_path. An empty selection fails.
Host trust and authentication use normal OpenSSH configuration, with strict
host-key checks and noninteractive authentication. --ssh_config,
--ssh_identity, --ssh_known_hosts, --ssh_port, and --ssh_user allow
explicit local overrides. Credentials are not written into manifests.
--ssh_path and --rsync_path select installed executables. The default
deployment deadline is 30 minutes; --timeout overrides it.
The public project defaults to the manifest project after removing a leading
projects/; --project overrides it. The tool validates each path component
and uses the remote layout
<root>/projects/<project>/releases/<version>/<filename>.
Rsync updates matching filenames using checksums, normalizes directories to
0755 and files to 0644, and never deletes other files. A failed multi-file
transfer can leave completed files published; rsync’s error and itemized
output describe the transfer. Files are transferred through private staging
on the same content filesystem, using rsync’s normal temporary-file rename.
Remote shell arguments use POSIX single-quote escaping to preserve literal values.
Choose publication steps
--steps selects an ordered subset of upload,extract,link; it defaults to
upload. Each step can run alone. Unknown, repeated, empty, or out-of-order
steps fail before a remote change. A release manifest still supplies the
project and version for every step.
# Upload files only; archives remain ordinary downloadable files.
bazel_agent bazel run //infra/download:publish.yandex -- \
--release_dir "$PWD/out/site-release" --steps upload
# Extract an already-published archive without changing current.
bazel_agent bazel run //infra/download:publish.yandex -- \
--release_dir "$PWD/out/site-release" --steps extract \
--site_archive website.tar.gz
# Select an existing extracted release; no local payload or archive is needed.
bazel_agent bazel run //infra/download:publish.yandex -- \
--release_dir "$PWD/out/site-release" --steps link
Extraction accepts a published .tar.gz or .zip filename. It retrieves the
archive into a private local temporary directory, validates and extracts it,
then copies the site into private remote staging and promotes the completed
directory to sites/<project>/releases/<version>. Local free space must hold
both the archive and its extracted content. No archive helper is installed on
the host. Existing completed site versions are retained without retransferring
or reextracting their content.
The extractor rejects traversal, absolute paths, links, device entries,
duplicate file names, and missing root index.html. Archives may contain at
most 100,000 entries. Archive ownership, executable bits, and extended
attributes are not preserved. Server-created content inherits the destination’s
configured SELinux labels.
Linking atomically replaces sites/<project>/current after checking the
selected directory and its root index. A short flock serializes promotion
and link changes. A failure in an earlier step leaves the current site
unchanged, although completed ordinary uploads may already be public. Use a
new version for changed website content; select an earlier retained version
with --steps link. There is no separate rollback command or automatic deletion.
al_release_deployment accepts ssh_* metadata alongside its existing OCI
variant. ssh_public_url supplies generated artifact URLs with the public
project/version layout. al_release_binary can package a fixed SSH destination
with oras = None, while taking --release_dir at runtime.
67.1 - Bzl
Bazel bindings for the release tool
67.1.1 - al_release
al_release
load("@com_alwaldend_src//tools/release/main/bzl:al_release.bzl", "al_release")
al_release(name, srcs, git_bundle, git_state, manifest, project, release_name, release_tool,
root_prefix)
Rule describing a release
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Sources |
List of labels |
optional |
[] |
| git_bundle |
Deterministic Git bundle used to generate release metadata |
Label |
optional |
"@com_alwaldend_src_tools_git//:release_git_state" |
| git_state |
Additional declared Git state used to generate release metadata |
List of labels |
optional |
[] |
| manifest |
Load manifest from a file instead of generating it from srcs |
Label |
optional |
None |
| project |
Project package_name |
String |
required |
|
| release_name |
Release name |
String |
required |
|
| release_tool |
Release tool |
Label |
optional |
"@com_alwaldend_src//tools/release/main/go" |
| root_prefix |
Root prefix |
String |
optional |
"content/docs" |
67.1.2 - al_release_binary
al_release_binary
load("@com_alwaldend_src//tools/release/main/bzl:al_release_binary.bzl", "al_release_binary")
al_release_binary(name, oras, **kwargs)
Create a release command; oras=None omits the OCI executable.
PARAMETERS
| Name |
Description |
Default Value |
| name |
Target name. |
none |
| oras |
OCI executable, or None for an SSH-only command. |
"//third_party/land_oras_oras:oras" |
| kwargs |
Remaining release rule attributes. |
none |
67.1.3 - al_release_bundle
Package declared payloads and versioning metadata for the release publisher.
al_release_bundle
load("@com_alwaldend_src//tools/release/main/bzl:al_release_bundle.bzl", "al_release_bundle")
al_release_bundle(name, srcs, project, release_tool)
Emit release.json and files/ using STABLE_VERSION from the versioning bootstrap.
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| srcs |
Payload files; basenames must be unique. |
List of labels |
required |
|
| project |
Canonical project subdirectory. |
String |
required |
|
| release_tool |
Manifest generator and bundle writer. |
Label |
optional |
"@com_alwaldend_src//tools/release/main/go" |
67.1.4 - al_release_deployment
al_release_deployment
load("@com_alwaldend_src//tools/release/main/bzl:al_release_deployment.bzl", "al_release_deployment")
al_release_deployment(name, oci_repository, ssh_environment, ssh_host, ssh_project, ssh_public_url,
ssh_publish_user, ssh_root, ssh_site_archive, ssh_user)
Deployment info
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| oci_repository |
OCI repository url |
String |
optional |
"" |
| ssh_environment |
Explicit environment selector |
String |
optional |
"" |
| ssh_host |
SSH hostname or SSH configuration alias |
String |
optional |
"" |
| ssh_project |
Public project name; empty removes projects/ from the manifest project |
String |
optional |
"" |
| ssh_public_url |
Public download origin for generated artifact links |
String |
optional |
"" |
| ssh_publish_user |
Content account selected through sudo -n -u |
String |
optional |
"" |
| ssh_root |
Provisioned content root |
String |
optional |
"/srv/download" |
| ssh_site_archive |
Optional website archive filename |
String |
optional |
"" |
| ssh_user |
Administrator login; empty uses SSH configuration |
String |
optional |
"" |
67.1.5 - al_release_deployment_info
AlReleaseDeploymentInfo
load("@com_alwaldend_src//tools/release/main/bzl:al_release_deployment_info.bzl", "AlReleaseDeploymentInfo")
AlReleaseDeploymentInfo(info, info_file)
Deployment info
FIELDS
| Name |
Description |
| info |
Deployment info struct |
| info_file |
Deployment info file |
67.1.6 - al_release_deps
al_release_deps
load("@com_alwaldend_src//tools/release/main/bzl:al_release_deps.bzl", "al_release_deps")
al_release_deps(name, srcs, visibility, **kwargs)
Generate a dependency diagram using genquery
PARAMETERS
| Name |
Description |
Default Value |
| name |
name |
none |
| srcs |
list of labels to generate deps for (should be full labels) |
none |
| visibility |
visibility |
None |
| kwargs |
kwargs for al_release_files |
none |
67.1.7 - al_release_files
al_release_files
load("@com_alwaldend_src//tools/release/main/bzl:al_release_files.bzl", "al_release_files")
al_release_files(name, deps, srcs, deployments, ignore_suffixes, release_tool)
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| deps |
Deps |
List of labels |
optional |
[] |
| srcs |
Sources |
List of labels |
optional |
[] |
| deployments |
Deployment info |
List of labels |
optional |
[] |
| ignore_suffixes |
Ignore src files ending with these suffixes |
List of strings |
optional |
[] |
| release_tool |
Release tool |
Label |
optional |
"@com_alwaldend_src//tools/release/main/go" |
67.1.8 - al_release_files_info
AlReleaseFilesInfo
load("@com_alwaldend_src//tools/release/main/bzl:al_release_files_info.bzl", "AlReleaseFilesInfo")
AlReleaseFilesInfo(files, manifest)
Release files
FIELDS
| Name |
Description |
| files |
File dict, keys are filenames, values are Files |
| manifest |
Release manifest for srcs |
67.1.9 - al_release_info
AlReleaseInfo
load("@com_alwaldend_src//tools/release/main/bzl:al_release_info.bzl", "AlReleaseInfo")
AlReleaseInfo(release_name, project, files, manifest)
Release information
FIELDS
| Name |
Description |
| release_name |
Release name (string) |
| project |
Project subdir (string) |
| files |
File dict, keys are filenames, values are Files |
| manifest |
Release manifest (File) |
67.2 - Go
Golang code for the release tool
67.3 - Proto
Protobuf contracts
67.3.1 - contracts
Proto docs for contracts.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/release/main/proto/contracts:contracts_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/release/main/proto/contracts:contracts",
],
)
Proto
syntax = "proto3";
package release;
import "tools/git/main/proto/contracts/contracts.proto";
option go_package = "git.alwaldend.com/alwaldend/src/tools/release/main/proto/contracts";
message ReleaseHash {
string algo = 1;
string content = 2;
}
message ReleaseFile {
repeated ReleaseHash hashes = 1;
string name = 2;
string safe_name = 6;
string url = 3;
int64 size = 4;
string local_path = 5;
}
message ReleaseItem {
ReleaseFile file = 1;
repeated ReleaseDeployment deployments = 2;
}
message Project {
string subdir = 1;
string safe_subdir = 2;
}
message Git {
git.GitCommit revision = 1;
repeated git.GitCommit commits = 2;
}
message ReleasePageSectionItemAttr {
string name = 1;
string content = 2;
}
message ReleasePageSectionItem {
string content = 1;
string content_url = 2;
repeated ReleasePageSectionItemAttr attrs = 3;
}
message ReleasePageSection {
string title = 1;
repeated ReleasePageSectionItem items = 2;
}
message ReleasePage {
repeated ReleasePageSection sections = 1;
}
message ReleaseDeploymentOci {
string repository = 1;
repeated string tags = 2;
}
message ReleaseDeployment {
ReleaseDeploymentOci oci = 1;
ReleaseDeploymentSsh ssh = 2;
}
message ReleaseDeploymentSsh {
string environment = 1;
string host = 2;
string user = 3;
string root = 4;
string project = 5;
string site_archive = 6;
string publish_user = 7;
string public_url = 8;
}
message Release {
repeated ReleaseItem items = 1;
string name = 2;
Project project = 3;
Git git = 4;
}
68 - Replace section
Replace sections of files
69 - Repo map
Extension to download several versions of a repository depending on a platform
69.1.1 - al_repo_map
al_repo_map
al_repo_map = use_extension("@com_alwaldend_src//tools/repo_map/main/bzl:al_repo_map.bzl", "al_repo_map")
al_repo_map.download(name, build_file_content, build_file_native_binary, download_type, executable,
repos, strip_prefix)
Extension to create several repos from a map
TAG CLASSES
download
Attributes
| Name |
Description |
Type |
Mandatory |
Default |
| name |
Name |
Name |
required |
|
| build_file_content |
Build file content |
String |
optional |
"" |
| build_file_native_binary |
Args for a native binary build file |
Dictionary: String -> String |
optional |
{} |
| download_type |
Download type |
String |
optional |
"http_archive" |
| executable |
Field executable for http_file |
Boolean |
optional |
False |
| repos |
Map of repos |
Dictionary: String -> List of strings |
required |
|
| strip_prefix |
Strip prefix |
String |
optional |
"" |
70 - Repository AL configuration
Shared configuration for repository command wrappers
//tools/al:config packages the repository’s shared AL configuration. The
root //:al label remains a compatibility alias. The Lua source files stay
at the repository root so existing CLI configuration discovery and
require("al_lib") imports retain their paths.
The //:tf.* compatibility aliases select Terraform commands declared here
with the public rules_terraform command maps and the generic AL wrapper. These
bindings use the shared config and explicitly select the repository root as
their Terraform working directory.
71 - Repository CI
Shared entry point for repository builds and tests
Every CI workflow checks out its event revision and uses the local action:
- name: Build and test the repository
uses: ./tools/ci/action
The action launches bazel run --config=ci //tools/ci
directly from Node.js with an argument array and no shell. It uses
GITHUB_WORKSPACE as the working directory, streams the command output, and
propagates failure. It needs no npm packages or generated JavaScript bundle.
The action comes from the exact revision already checked out by the workflow.
The command builds
and tests normal //... targets in the root workspace and each nested workspace
with a MODULE.bazel at a boundary declared by .bazelignore. Non-workspace
entries such as caches and dependency directories are ignored. Workspace
boundaries remain owned by the root ignore file.
Each workspace runs bazel_agent bazel build --config=ci //... followed by
bazel_agent bazel test --config=ci //.... The command continues after failures
so all workspaces and both phases are attempted, streams Bazel diagnostics,
and exits nonzero if any phase fails. It does not run deployment targets or
runner acceptance checks. Normal Bazel expansion excludes manual targets and
incompatible platforms; optional configurations are not a build matrix.
The runner needs the repository-managed Node.js, bazel, bazel_agent, and Android
SDK/NDK prerequisites. The runner deployment
installs these before CI starts. Agent invocations use
bazel_agent bazel run --config=ci //tools/ci.
The pinned Bazel releases its invocation before executing the command, allowing
child builds to reuse each workspace’s normal output base. No second cache or
source checkout is needed. Unit tests verify workspace discovery, both phases,
and propagation of failures without running nested Bazel inside a test sandbox.
The workflow’s checkout action comes from the organization-owned
Forgejo mirror,
managed by the repository catalog and Forgejo
Terraform. Its immutable commit pin selects the action implementation;
github.sha separately selects the source revision being built.
72 - Repository delivery
Guarded Git and pull-request delivery
repo_delivery implements the deterministic part of repository delivery. It
invokes native Git and a selected forge CLI; it does not infer task ownership,
choose validation commands, resolve conflicts, or judge review feedback.
Status and immutable changed-path inventories, and preparation receipts,
support complete output up to 4 MiB so large source relocations can use the
guarded workflow. Stable reads and writes enforce that
limit; other typed local records retain their 256 KiB limit. Receipt schema,
scope, integrity, and publication checks apply at either size.
Provider and delivery workflow
Run delivery commands from this Git worktree’s repository root, which owns the
root MODULE.bazel and tools/repo_delivery. This also applies when changed
files belong to a nested Bazel module: its workspace does not own the delivery
target. Use the same feature worktree, not another checkout. The baseline works
with older installed runners. Detect the provider without printing a
credential-bearing remote URL:
bazel_agent bazel run //tools/repo_delivery -- provider
The optional cached form is al tool repo_delivery -- .... An execution
failure or delivery refusal still requires diagnosis. All examples below use
the baseline.
The sanitized report distinguishes forge support from Git transport support.
adapter_available reports whether the forge has an adapter;
git_transport reports ssh, https, or mixed_or_unsupported; and
delivery_transport_available is true only when both captured endpoints are
canonical SSH endpoints. It never reports either endpoint.
For validation that invokes Bazel, use the cached entry point or generate a
task-local launcher once from the repository root. Ordinary bazel run can
retain the Bazel lock while its target runs; --script_path releases it before
the generated launcher is executed. Refresh the launcher after changing the
delivery tool. This requires no installed runner update:
bazel_agent bazel run --script_path=out/task/repo_delivery \
//tools/repo_delivery
For the supported GitHub adapter, the normal workflow is:
bazel_agent bazel run //tools/repo_delivery -- inspect --base master
bazel_agent bazel run //tools/repo_delivery -- prepare \
--base master \
--message-file out/task/commit.md \
--receipt-file out/task/prepare.json \
--path path/to/task/file \
--rewrite <inspect.local_head_oid> # omit when the range has no commit
# For an explicitly reviewed task-owned multi-commit range, use
# --consolidate <inspect.local_head_oid> instead of --rewrite.
out/task/repo_delivery validate \
--receipt-file out/task/prepare.json --plan-file out/task/checks.json
out/task/repo_delivery continue \
--receipt-file out/task/prepare.json --publish
out/task/repo_delivery continue \
--receipt-file out/task/prepare.json
checks.json is an explicitly selected validation plan. Keep it mode 0600 in
the same ignored out/<task>/ directory as the preparation receipt. For
example, a change confined to the delivery Go package can use:
{
"schema": "repo_delivery/validation_plan/v1",
"checks": [
{
"workspace": ".",
"kind": "test",
"targets": [
"//tools/repo_delivery/cmd/repo_delivery:go_test",
"//:repo_quality_test"
],
"timeout_seconds": 3600
},
{
"workspace": ".",
"kind": "lint",
"targets": ["//tools/repo_delivery/cmd/repo_delivery:all"],
"timeout_seconds": 3600
}
],
"gap_decisions": []
}
Allowed kinds are test, build, and lint (build with --config=lint).
The plan accepts 1–32 sequential checks, 1–128 explicit local labels per check,
and a 1–3600 second deadline per check. Package :all is supported outside the
root package; recursive target patterns, arbitrary run targets, shell
commands, and free-form flags are not. Workspace paths are relative to the Git
worktree root and must name real Bazel workspace roots. Nested checks execute
there; the required //:repo_quality_test check executes in .. The plan must
lint every suggested non-root affected package and resolve each validation gap
with exactly one { "path": "...", "reason": "..." } decision. The caller
still selects sufficient consumer checks and verifies representative output.
validate runs aggregate git diff --check first, then the selected checks.
It requires a fully clean worktree and ordinary index flags before and after
checks. It records exact head/tree, preparation revision, plan bytes, inherited
environment digest, check outcomes, and log digests beside the receipt, using
mode 0600 files. Each output stream is capped at 96 KiB; truncation fails the
check. The same receipt lock protects validation and continuation. Keep all
these files trusted and unedited; they are consistency evidence, not security
tokens. Tool binaries, ignored configuration, external services, and other
inputs outside the recorded Git tree/environment still require the caller’s
input-stability judgment before publication. No passing checks are reused
automatically by a new validation run.
continue reports readiness without publishing. Only continue --publish
uses the captured passing result to call the existing guarded publication and
verification path; it never substitutes the current mutable HEAD as evidence.
Changed candidate, receipt, plan, environment, incomplete results, altered
logs, and dirty inputs refuse publication. A base rebase records
revalidation_required and stops before pushing: rerun validate against the
updated receipt, then explicitly continue. An interrupted or failed publication
remains publication_attempted; further continuation only verifies its remote
postcondition and never blindly repeats a push or metadata mutation. Diagnose
an incomplete result before using the existing manual recovery API. A new
validation run cannot reset an uncertain attempt for the same candidate.
prepare derives affected_bazel_labels from the prepared aggregate changed
paths, selecting each nearest non-root Bazel package. It never infers //:all
from a root-owned file. The reported
bazel_selection_basis is nearest_non_root_package_without_dependency_analysis:
this bounds discovery but does not establish downstream impact. Shared build
inputs may need explicit consumer checks.
bazel_validation_gaps lists paths requiring a separate decision, with reasons
root_package_requires_explicit_targets, ignored_by_root_workspace,
nested_workspace, or no_bazel_package. For each gap, select appropriate
targets or record why no target check applies. Validate nested or ignored
workspaces through their owner, then return to the repository root for delivery.
The suggested commands always include the root-workspace gate
bazel_agent bazel test //:repo_quality_test; that gate does not establish
semantic correctness for BUILD, MODULE, or configuration changes. Run semantic
lint for the selected affected targets in addition to that mandatory gate.
To synchronize a prepared, task-owned, single-commit feature branch with an
advanced base before prepare, use the guarded adapter workflow:
bazel_agent bazel run //tools/repo_delivery -- rebase --base master
The command refuses dirty trees, divergent remote feature tips, multi-commit
or merge-containing ranges, and pull-request metadata changes. It fetches
fresh refs, replays the commit in an isolated worktree, preserves signature
requirements, pushes only with the captured remote feature lease, verifies
base advancement, and reports the literal resulting head. Run required
validations against that head before prepare and publish.
Remote rewrites use rewrite-authorize to write a typed, non-authorizing
authorization receipt (old remote OID, new head OID, owner root, task paths,
provider ownership), then prepare --rewrite-authorization <file> instead of
raw --replace-remote handoff. Review replies accept --goal-ref,
--delivery-ref, and --defect-id durable join references.
prepare refuses a divergent remote feature tip by default. Use
--replace-remote <literal-inspect.remote_head_oid> only after the
git-rebase-remote workflow has preserved that exact old tip and established
that it is task-owned history the user authorized rewriting. Never infer the
OID or use this escape hatch for shared, stacked, human-owned, unrelated, or
ambiguous history. A mismatched, malformed, absent, or unnecessary
authorization is refused. The final fresh snapshot and preparation receipt
bind the same old remote OID for the later exact force-with-lease push.
prepare --consolidate <literal-inspect.local_head_oid> is the explicit
ownership authorization for replacing a multi-commit feature range with one
aggregate commit. It is not inferred from author names. The adapter still
requires a merge-free linear chain, one author and committer identity across
the range, the ownership disclaimer on its oldest commit, pull-request
metadata matching the requested aggregate message, and the exact inspected
head.
It preserves any signature requirement found in the range and keeps the
fetched remote feature tip as the publication lease. --consolidate and
--rewrite are mutually exclusive.
When a clean replay onto an advanced base makes an expected aggregate path
disappear from the resulting diff, the adapter accepts that shrink only when
the prior candidate and the new base have byte-identical Git tree entries at
that path. A new path, a non-identical disappearance, or an entirely empty
aggregate remains a refusal. The derived receipt records the reduced exact
aggregate path set.
The manual publish --receipt-file <path> --validated-head <literal-head_oid>
API remains available for validations outside the structured plan and diagnosed
recovery. Never populate that flag by resolving current HEAD: carry the
literal candidate that the checks covered. A preparation receipt alone does
not establish validation. The structured continuation path supplies that
literal value from its recorded passing results instead.
For message-only amendments, deliver --message-file <path> --receipt-file <path> --owner-root <root> --task-path <path> combines inspection, any needed
rewrite authorization, and preparation. It stops before publication with a
nonzero exit and a revalidation_required report containing the exact head,
tree, receipt, affected labels, and suggested checks. Establish validation for
that candidate using validate, then use continue --publish, or use the
manual publish API and verify. Do not repeat deliver to continue, because
that prepares another candidate.
Version 1 delivery accepts only SCP-style or ssh:// Git fetch and push
endpoints. The gh CLI is still used for the GitHub forge API, but the tool
does not import mutable global credential-helper configuration installed by
gh auth setup-git. An HTTPS Git remote is therefore rejected before
inspect, preparation, or publication performs network access. Configure an
SSH remote explicitly before using delivery; the tool will not rewrite the
repository’s remote configuration for you.
prepare writes a strict versioned receipt under an ignored
out/<task>/ directory. The receipt binds the prepared head and tree to the
exact worktree and Git directories, fetch and push endpoint digests, sanitized
repository identity, forge adapter, base and head refs, fetched base, expected
remote feature ref, expected pull-request identity or absence, and immutable
aggregate path scope. The file contains no
remote URL, must remain untracked, and is installed with mode 0600 by atomic
rename. It is a consistency record, not an unforgeable authorization token.
The adjacent receipt lock serializes receipt transitions and reads performed
by cooperating repo_delivery processes. Keep the ignored receipt path
trusted and do not edit, replace, copy over, or otherwise write it while
prepare, publish, or receipt-bound verify is running. Stable byte and
revision checks detect changes visible before their checks, but operating
systems do not provide a portable atomic
pathname-content compare-and-swap. A same-user process that ignores the lock
can race the final comparison and rename; the tool does not claim to exclude
that writer. A persistent exclusive revision-claim sidecar would have the same
trust boundary, while a crash before installation could strand the receipt
without a safe automatic cleanup decision, so it is not used.
Use repeated --path flags for fully task-owned paths, or pre-stage only
task-owned hunks and use --use-index. Both modes bind the complete existing
feature diff, not merely the paths newly staged by that invocation.
--message-only --rewrite <exact-oid> preserves the tree but changes the
commit OID. Consolidation also changes the commit OID and parent structure.
Every prepare, consolidation, or message-only amendment therefore requires a
validation decision bound to its returned exact head. Run required checks
after preparation by default. For a tree-preserving amendment, prior passing
evidence may be reused only after recording its exact prior candidate, the
matching tree OID, the new OID, and unchanged inputs relevant to each check.
Those inputs include commands, tools, configuration, and environment. Rerun
checks affected by commit identity, history, or stamping, and any check whose
input stability is unknown. The caller owns and records this applicability
judgment; the receipt neither proves validation nor authorizes reuse.
When an authorized remote replacement is pending, rewrite evidence accepts an
existing pull request only if its metadata matches the exact projectable local
or fetched-remote commit projection. A legacy remote tail that lacks an
aggregate disclaimer cannot block a matching local aggregate, and unrelated
pull-request text remains a refusal.
Publish and receipt-bound verify require a clean index and reject staged,
unstaged, or untracked changes in the prepared task scope. They preserve
unrelated unstaged and untracked files when no rebase is needed. A required
rebase demands a fully clean worktree and index. Run validation from a clean
checkout at the literal head_oid whenever unrelated dirty files could affect
the check; the tool cannot infer a check’s input set.
If the base advances after preparation, publish rebases the exact captured
commit in an isolated ignored worktree. It then exits nonzero before pushing
and emits a revalidation_required JSON report containing the new exact head,
tree, and derived receipt. Validate that returned head directly, then retry
publish with the same receipt file and the new literal OID. Do not run
prepare again unless content or the commit message must change. A receipt
also supports a guarded retry when the remote already equals its prepared head
but pull-request creation or metadata synchronization stopped partway through.
Replacement pull-request identities and any state other than the exact prior
or desired projection are refused. Multi-commit consolidation likewise
requires an existing pull request to equal the requested aggregate message’s
projection, so consolidation cannot silently replace independently edited
pull-request text.
The tool creates commits with Git plumbing and pushes one exact ref with hooks
disabled. It rejects shallow, promisor, or grafted history and ignores
replacement objects. Each network operation runs in a fresh private,
config-free bare Git directory bound to the captured SSH endpoint and the
repository’s canonical object database. It does not load mutable main-repo
local/worktree URL rewriting, proxy, TLS, SSH-command, or remote
configuration. Normal DNS resolution and SSH host-key verification still
apply; the tool does not claim to pin an IP address or server key. Ordinary
local Git operations still intentionally use repository identity and signing
configuration. Fetches use isolated temporary refs without pruning, then
install exact private refs locally; pushes use an exact force-with-lease.
Repository-required hook checks remain the caller’s responsibility and must
complete before recording the validated OID.
The feature-ref lease and base-ref checks are separate because Git forges do
not offer a cross-ref compare-and-swap. If the base advances in the narrow push
window, the feature ref may be visible briefly before the tool detects the
race and attempts an exact guarded rollback. Coordinate consumers that react
immediately to branch-update webhooks.
Review workflow
Start with a bounded structured inventory:
bazel_agent bazel run //tools/repo_delivery -- review inspect
For feedback on an exact candidate with recorded passing validation that is
already published, use one command:
out/task/repo_delivery review address \
--receipt-file out/task/prepare.json \
--thread-id <thread-id> --body-file out/task/reply.md
address verifies the publication and validation evidence, derives current
provider guards, posts the reasoned reply, saves its receipt, and resolves the
thread. The default reply receipt path is derived from the preparation receipt
and thread; --reply-receipt-file selects an explicit path in the same task
directory. A durable attempt checkpoint beside that file prevents duplicate
replies after interruption, success, or an unknown reply outcome. Retry with
the same paths after an explicitly reported read-failure restoration to resume
resolution using the unchanged receipt and expiry. Expired, consumed, or
changed authority stops without reposting. Inspect the actual remote state
before explicitly selecting a fresh receipt path for recovery. Keep checkpoints
trusted and do not delete them to bypass this protection.
Thread inventory includes each thread’s first comment page in its enclosing
request. Only additional comment pages need separate queries. All pages retain
shared byte, node, request, cursor, and metadata consistency checks.
The low-level commands remain available. Carry values from the latest inspection literally. All mutations require the
pull-request node ID, expected head, and pull-request expectation digest. A
top-level reply also requires the
reported last-comment sentinel and top-level inventory digest:
bazel_agent bazel run //tools/repo_delivery -- review comment \
--pull-request-id <pull_request.id> \
--expected-head <pull_request.head_ref_oid> \
--expected-pull-request-digest <pull_request_expectation_digest> \
--expected-last-top-level-comment \
<expected_last_top_level_comment> \
--expected-top-level-comments-digest \
<expected_top_level_comments_digest> \
--body-file out/task/comment.md
Thread replies and resolutions additionally bind the thread, its last comment,
and its complete expectation digest:
bazel_agent bazel run //tools/repo_delivery -- review reply \
--pull-request-id <pull_request.id> \
--expected-head <pull_request.head_ref_oid> \
--expected-pull-request-digest <pull_request_expectation_digest> \
--thread-id <review_threads[index].id> \
--expected-last-comment-id <review_threads[index].comments[-1].id> \
--expected-thread-digest <review_threads[index].expectation_digest> \
--body-file out/task/reply.md \
--reply-receipt-file out/task/reply.json
bazel_agent bazel run //tools/repo_delivery -- review resolve \
--pull-request-id <pull_request.id> \
--expected-head <pull_request.head_ref_oid> \
--expected-pull-request-digest <pull_request_expectation_digest> \
--thread-id <review_threads[index].id> \
--expected-last-comment-id <review_threads[index].comments[-1].id> \
--expected-thread-digest <review_threads[index].expectation_digest> \
--reply-receipt-file out/task/reply.json
bazel_agent bazel run //tools/repo_delivery -- review request \
--pull-request-id <pull_request.id> \
--expected-head <pull_request.head_ref_oid> \
--expected-pull-request-digest <pull_request_expectation_digest> \
--reviewer <login>
Comment and reply bodies must be ignored files under out/<task>/; the tool
adds the exact comment disclaimer. Resolution requires the strict reply
receipt written by review reply, and the receipt must still match the exact
pull request, head, thread, reply, body, and complete bounded review inventory
projection. The parent pull-request UpdatedAt is a nondecreasing floor,
rather than an exact value, because reply side effects can advance it
asynchronously; every review, top-level comment, thread and thread comment, and
review request remains bound.
Reinspect after every mutation and use the newly reported IDs and digests for
the next one.
The one-use reply authority expires no more than five minutes after issuance.
Resolution verifies the authority window and atomically consumes the receipt
before contacting the provider. If a provider read fails or the review epoch advances before any resolution
mutation is attempted, the tool can restore the original unexpired receipt
without replacing another file or extending its authority window. Only an
explicit restoration report permits retry with that receipt; no additional
public reply is needed for that read failure.
Expiration, semantic or full-inventory mismatch, mutation failure, and an
unknown mutation outcome still consume authority. Never recreate the receipt
yourself. Reinspect the remote state and, if resolution remains appropriate,
leave a fresh reasoned reply to obtain new one-use authority.
GitHub provides neither compare-and-swap resolution nor a documented monotonic
review-thread epoch. A human resolve followed by unresolve can therefore be
unobservable if it restores identical thread and pull-request state inside the
five-minute authority window. The exact-state checks, short expiration, and
one-use consumption narrow that risk; they do not prove that such an ABA or a
change to provider metadata omitted from the bounded projection did not occur.
Do not resolve a thread that may be concurrently moderated.
GitHub does not expose atomic compare-and-swap mutations for pull-request
metadata, comments, replies, resolutions, or review requests. The adapter
checks exact expectations before each mutation and verifies the complete
result afterward, but a concurrent human change can still occur inside that
narrow provider-side window. Do not mutate an actively coedited pull request.
Post-mutation inspection failures are reported as outcome unknown; reinspect
instead of retrying blindly.
Forge adapters
GitHub is supported through the gh CLI. Forge behavior is selected behind a
Go interface and uses argument-vector subprocess calls, so another structured
CLI adapter can be added without changing delivery policy. --forge auto
selects GitHub for github.com; use
--forge github --forge-cli <gh-path> to select an explicit executable.
Unknown or unsupported forges fail before any commit rewrite or push.
Version 1 is limited to same-repository pull requests. It cannot reliably
discover or map an upstream pull request whose head lives in a fork. Treat
same-repository topology as a caller-enforced precondition: do not use the
delivery or review commands for a fork-based or otherwise cross-repository
pull request, and stop when remote or pull-request ownership is uncertain.
The adapter fails closed when it observes an unsupported or inconsistent
topology, but that refusal is not a substitute for this caller check.
The read-only provider command recognizes known Forgejo hosts, but there is
currently no Forgejo delivery adapter. Use the repository’s documented
Forgejo compatibility workflow instead of pretending GitHub operations are
portable.
73 - Repository quality
Whole-index formatting, linting and validation coordinator
This package uses upstream aspect_rules_lint formatting and linting rules to
discover Git-tracked files and inspect declared source targets with established
tools acquired by Bazel. It does not implement a formatter or linter and does
not download tools at runtime. Thin workspace adapters extend the upstream
language list with the official Go SDK for module and workspace manifests,
StyLua and Selene for Lua, and the existing Prettier XML plugin for Qt .ui
and .qrc files.
Format all safe, hand-maintained files:
bazel_agent bazel run //:format
Run the non-mutating repository check:
bazel_agent bazel test //:repo_quality_test
The suite also parses root BUILD.bazel and rejects load() statements to keep
unrelated tool dependencies out of root package loading. Run this check alone
with bazel_agent bazel test //tools/repo_quality/test/root_build:root_build_test.
Run semantic source linters over declared targets:
bazel_agent bazel build --config=lint //...
The CSS formatter bucket includes CSS, Less and SCSS. The JavaScript bucket
includes JavaScript, JSON, JSON-with-comments, JSON5, TypeScript, TSX and Vue.
Go source uses gofumpt; go.mod and go.work use the official Go SDK.
Lua checks cover every tracked file not excluded by Git attributes, using
StyLua for canonical formatting and Selene for correctness diagnostics.
Explicit templates (.j2, .tmpl, .tpl, and the Hugo layout tree), generic
application configuration, and non-Terraform HCL are deliberately excluded
from general formatting. Ordinary Markdown, YAML, HTML and other files remain
covered even when their contents include template expressions. Generated,
vendored, lock, binary, diagram and exact-content files likewise retain their
owning regeneration, validation or integrity workflow.
The integration test intentionally uses the upstream rule’s no_sandbox mode
so it can inspect files that are tracked by Git but not declared in BUILD
files. This is a local-checkout guarantee, like the root Buildifier test; tool
acquisition remains hermetic and pinned through Bazel.
The quality suite also runs pinned ast-grep against every tracked first-party
Python file not marked linguist-vendored or linguist-vendored=true, rejecting symbol imports in
favor of module imports and qualified member access. Formatting exclusions do
not exempt first-party Python from this check. Explicit public re-exports
listed in __all__ may use direct symbol imports in __init__.py; wildcard
imports remain forbidden.
Its syntax fixtures distinguish real imports from
comments and strings. Module from-imports are verified against source paths
and pinned Python module specifications without executing imported packages.
Like formatting, this complete-checkout check is local
and uncached; downloaded external repositories are outside its coverage.
74 - Resolved toolchain
Helper rule to create a resolved toolchain
Links
74.1.1 - al_resolved_toolchain
al_resolved_toolchain
load("@com_alwaldend_src//tools/resolved_toolchain/main/bzl:al_resolved_toolchain.bzl", "al_resolved_toolchain")
al_resolved_toolchain(toolchain_label, **kwargs)
Create a resolved toolchain
PARAMETERS
| Name |
Description |
Default Value |
| toolchain_label |
- |
none |
| kwargs |
rule kwargs |
none |
RETURNS
Resolved toolchain rule
75.1.1 - al_rfc_extension
al_rfc_extension
al_rfc_extension = use_extension("@com_alwaldend_src//tools/rfc/main/bzl:al_rfc_extension.bzl", "al_rfc_extension")
al_rfc_extension.download(name, integrity, rfcs)
Rfc extension
TAG CLASSES
download
Attributes
| Name |
Description |
Type |
Mandatory |
Default |
| name |
Name |
Name |
required |
|
| integrity |
Rfc integrity |
Dictionary: String -> String |
optional |
{} |
| rfcs |
Rfcs |
List of strings |
optional |
[] |
75.1.2 - al_rfc_repo
al_rfc_repo
load("@com_alwaldend_src//tools/rfc/main/bzl:al_rfc_repo.bzl", "al_rfc_repo")
al_rfc_repo(name, integrity, repo_mapping, rfcs, url)
Rfc repository
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this repository. |
Name |
required |
|
| integrity |
Rfc integrity |
Dictionary: String -> String |
optional |
{} |
| repo_mapping |
In WORKSPACE context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.
For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target).
This attribute is not supported in MODULE.bazel context (when invoking a repository rule inside a module extension’s implementation function). |
Dictionary: String -> String |
optional |
|
| rfcs |
Rfcs |
List of strings |
optional |
[] |
| url |
Url format |
String |
optional |
"https://www.rfc-editor.org/rfc/{postfix}" |
76 - Root Bazel module
Generate the root module’s owning include list
MODULE.bazel is generated directly from include.MODULE.bazel files:
bazel run //tools/bazel_module:update
Dependency declarations and pins stay in their owning include files. The small
root module(...) header lives in the Go generator. Discovery uses the existing
//tools/git host Git wrapper to include tracked and untracked files while
respecting Git ignore rules. Deleted files, symlinks, .bazelignore
directories, and standalone workspaces beneath
MODULE.bazel, WORKSPACE, or WORKSPACE.bazel boundaries are excluded.
Includes are ordered by owning directory, with third_party, tools, then
projects before other trees. This keeps the existing precedence between those
trees, including toolchain registrations. A parent directory precedes its
children. Keep any order-sensitive declarations together in one owning include.
//tools/bazel_module/cmd/update:freshness_test checks the current checkout and
is included in //:repo_quality_test. It intentionally disables sandboxing and
result caching to detect new or removed files outside declared Bazel inputs.
The generator’s unit tests remain sandboxed and cacheable.
Before deleting or moving an existing include, retain a launcher so regeneration
does not need to load the old module after its include disappears:
mkdir -p out/bazel_module
bazel run --script_path=out/bazel_module/update //tools/bazel_module:update
# Delete or move the include, then regenerate:
out/bazel_module/update
77 - Rules binary toolchain
Bazel toolchains for packaged executable binaries
rules_binary_toolchain creates Bazel toolchains and runnable targets for
packaged executable binaries. Archive entries can include runtime files,
which the generated binary targets expose through Bazel runfiles.
77.1 - Bzl
Bazel code
Archive binaries
Each archive binaries entry requires a name and path. It can also set
runtime_files to a list of Bazel glob patterns relative to the unpacked
archive. The generated <name>_native_binary exposes matching files as
runfiles; <name>_filegroup exposes the binary and those same files.
Omit runtime_files when the binary needs no extra runtime files.
77.2 - OpenSpec
77.2.1 - Specifications
77.2.1.1 - Project dns
77.2.1.1.1 - project-dns Specification
Purpose
Record the retirement of rules_binary_toolchain landing infrastructure while preserving
its reusable Bazel module.
Requirements
Requirement: Keep landing infrastructure retired
The module SHALL follow the tools boundary and SHALL
have no dedicated landing DNS declarations, Terraform root, or operational
source exports.
Scenario: Inspect the module after landing retirement
- WHEN the module is consumed from
tools/rules_binary_toolchain
- THEN its reusable Bazel rules remain available without landing infrastructure.
77.2.1.2 - Project rules binary toolchain
77.2.1.2.1 - Rules binary toolchain
Purpose
Provide Bazel toolchains and runnable targets for packaged executable binaries,
including their runtime files. This source baseline was observed on 2026-09-08
at revision 550d7e79b1f5fdbc2b6017b75178471d6914082f.
Sources: project description,
lock parsing,
module extension,
archive repositories,
and runnable wrapper.
Requirements
The module extension SHALL resolve a requested toolchain by name and version
from its JSON lock and create repositories for the matching archives.
- WHEN the requested toolchain is absent or has no archive matching its locked version
- THEN extension evaluation fails with a diagnostic instead of selecting another version.
Requirement: Verify downloaded archive integrity
Archive repository download and download-and-extract actions SHALL compare the
reported integrity against the action’s declared integrity and fail on mismatch.
Scenario: Downloaded content differs from its lock entry
- WHEN a download reports an integrity value different from the lock action
- THEN repository creation fails with the expected and observed integrity values.
Requirement: Preserve runtime files in generated binary targets
Generated native binary and filegroup targets SHALL include the files selected
by each binary’s runtime_files glob patterns, in addition to the executable.
Runnable toolchain wrappers SHALL merge the selected toolchain’s runfiles and
declared data runfiles.
Scenario: A packaged executable needs an adjacent runtime asset
- WHEN an archive declares a runtime asset through
runtime_files
- THEN the native binary carries that asset in its data, the filegroup exposes it, and a wrapper carries the toolchain runfiles into execution.
Requirement: Forward wrapper arguments and environment
The runnable wrapper SHALL expand declared location and make-variable arguments,
append invocation arguments, and expose the declared env through
RunEnvironmentInfo.
Scenario: A caller supplies additional command arguments
- WHEN a generated wrapper is invoked with arguments after its configured arguments
- THEN the selected toolchain binary receives both argument sets in that order.
78 - Rules Dnscontrol
Bazel-aware DNSControl configuration packaging
rules_dnscontrol packages a DNSControl JavaScript entrypoint together with
its record configuration files and emits a generated requires.json manifest
of relative paths matching the Bazel runfiles tree. Record symlinks retain a
.json suffix so DNSControl parses them as JSON. Load the manifest with
require() and load each listed path with another require() call;
DNSControl’s JavaScript loader does not implement CommonJS module.exports.
79 - Rules docs
Bazel documentation packaging rules
rules_docs packages Markdown documentation under a common archive prefix.
Documentation rule
Add the module dependency:
bazel_dep(name = "rules_docs", version = "<VERSION>")
Then declare the package documentation. srcs defaults to
glob(["*.md"]); visibility is optional.
load("@rules_docs//docs:defs.bzl", "docs_filegroup")
docs_filegroup(
name = "docs",
deps = ["child"],
)
Relative dependency names without a colon are normalized to the child
package’s docs target. prefix defaults to the current package beneath
content/docs/.
Files are packaged at their basename by default. When a package’s sources span
subdirectories that hold identically named files, set preserve_paths = True
so each source keeps its package-relative path and the destinations stay
distinct.
Gazelle extension
Generation support is packaged separately so consumers of the documentation
rule do not inherit Gazelle and Go dependencies. Add it as a development-only
module dependency:
bazel_dep(
name = "rules_docs_gazelle",
version = "<VERSION>",
dev_dependency = True,
)
Add the public language target to the repository’s custom Gazelle binary:
load("@gazelle//:def.bzl", "gazelle_binary")
gazelle_binary(
name = "gazelle_binary",
languages = [
"@rules_docs_gazelle//gazelle",
],
)
The extension only operates in directories where both a BUILD file and a
README.md already exist. Newly generated rules use glob(["*.md"]) and are
visible only to their nearest ancestor Bazel package. Existing srcs, deps,
visibility, and prefix attributes are preserved.
The public @rules_docs_gazelle//gazelle:gazelle_docs binary contains only
this extension and can update a nested workspace without initializing
unrelated language plugins from its parent repository.
80 - Rules docs Gazelle
Gazelle extension for Bazel documentation packaging rules
rules_docs_gazelle adds a docs_filegroup to existing Bazel packages that
contain a README.md. The generated rule loads its macro from rules_docs.
Add rules_docs as a normal dependency and this generator as a development
dependency:
bazel_dep(name = "rules_docs", version = "<VERSION>")
bazel_dep(
name = "rules_docs_gazelle",
version = "<VERSION>",
dev_dependency = True,
)
Add the public language target to the repository’s custom Gazelle binary:
load("@gazelle//:def.bzl", "gazelle_binary")
gazelle_binary(
name = "gazelle_binary",
languages = [
"@rules_docs_gazelle//gazelle",
],
)
The extension only operates in directories where both a BUILD file and a
README.md already exist. Newly generated rules use glob(["*.md"]) and are
visible only to their nearest ancestor Bazel package. Existing srcs, deps,
visibility, and prefix attributes are preserved.
The public @rules_docs_gazelle//gazelle:gazelle_docs binary contains only
this extension and can update a nested workspace without initializing
unrelated language plugins from its parent repository.
81 - Rules Hugo
Bazel rules for Hugo sites
rules_hugo builds Hugo sites with a registered Hugo toolchain. It keeps the
site archive in the target configuration and the Hugo binary in the execution
configuration so site sources are not rebuilt for the execution platform.
al_hugo_site wraps a site source archive and its PostCSS tooling.
al_hugo_run_binary builds the site with the registered Hugo toolchain and
al_hugo_binary exposes a runnable Hugo command for a site. The optional
al_hugo_worker rule runs the build through a persistent worker.
Module setup
bazel_dep(name = "rules_hugo", version = "<VERSION>")
The Hugo toolchain is generated by the al_hugo_extension module
extension, not declared in @rules_hugo//pkg/bzl. Declare the extension
in the root module, request the toolchain archives for the Hugo version,
and register the generated toolchain repository:
al_hugo_extension = use_extension(
"@rules_hugo//pkg/bzl:al_hugo_extension.bzl",
"al_hugo_extension",
)
al_hugo_extension.toolchains(
name = "my_hugo_toolchain",
version = "0.165.0",
)
use_repo(
al_hugo_extension,
"my_hugo_toolchain_os_linux_cpu_x86_64",
)
register_toolchains("@my_hugo_toolchain_os_linux_cpu_x86_64")
The generated repository name is "<name>_<os>_<cpu>" for each platform
in the requested version’s archive set. See
projects/alwaldend.com/include.MODULE.bazel for a complete example that
also wires the Hugo lock file and remote themes.
81.1 - OpenSpec
81.1.1 - Specifications
81.1.1.1 - Project dns
81.1.1.1.1 - project-dns Specification
Purpose
Record the retirement of rules_hugo landing infrastructure while preserving
its reusable Bazel module.
Requirements
Requirement: Keep landing infrastructure retired
The module SHALL follow the tools boundary and SHALL
have no dedicated landing DNS declarations, Terraform root, or operational
source exports.
Scenario: Inspect the module after landing retirement
- WHEN the module is consumed from
tools/rules_hugo
- THEN its reusable Bazel rules remain available without landing infrastructure.
81.1.1.2 - Project rules hugo
81.1.1.2.1 - Rules Hugo
Purpose
Build and run Hugo sites with registered Bazel toolchains, site source archives,
and declared PostCSS tooling. This source baseline was observed on 2026-09-08
at revision 550d7e79b1f5fdbc2b6017b75178471d6914082f.
Sources: project description,
site provider rule,
build rule,
toolchain extension,
and worker rule.
Requirements
al_hugo_site SHALL retain the .tar site archive in the target configuration
and resolve its executable PostCSS dependency in the execution configuration.
Build rules SHALL obtain Hugo from the registered Hugo toolchain.
- WHEN Bazel analyzes a Hugo site build for distinct target and execution platforms
- THEN the site archive remains a target input and Hugo and PostCSS are selected as execution tools.
Requirement: Build into a declared destination directory
al_hugo_run_binary SHALL unpack the site archive, make its PostCSS executable
available to Hugo, and invoke Hugo with --destination pointing to the declared
output directory.
Scenario: A site build specifies an output directory
- WHEN a caller supplies
out_dir and additional Hugo arguments
- THEN the action appends the declared destination to those arguments and exposes the output directory through
DefaultInfo.
The Hugo module extension SHALL create toolchain repositories from the archive
set associated with the requested version, carrying each archive’s declared
integrity and execution-platform constraints.
Scenario: A supported version has a Linux x86-64 archive
- WHEN a toolchain tag requests that version under a repository name prefix
- THEN the extension creates the corresponding
<name>_os_linux_cpu_x86_64 repository with a Hugo toolchain constrained to that platform.
Requirement: Support an explicit persistent-worker build path
al_hugo_worker SHALL serialize site inputs, arguments, tools, environment, and
output location into a flag file and invoke its worker with Bazel’s protobuf
worker protocol requirements.
Scenario: A caller chooses the worker rule
- WHEN a site is built with
al_hugo_worker
- THEN Bazel receives a worker-capable
HugoSite action whose declared output directory is <target>.dest.
82 - Rules Promptfoo
A pinned Bazel runner for Promptfoo skill evaluations
rules_promptfoo runs repository skills through a pinned Promptfoo CLI. It
keeps dependency resolution reproducible in Bazel while deliberately running
model calls only at test runtime.
Setup
Add the module and its skill-provider dependency:
bazel_dep(name = "rules_promptfoo", version = "<VERSION>")
bazel_dep(name = "rules_skills", version = "<VERSION>")
The development checkout uses sibling local_path_override declarations.
Published releases must replace those local overrides with registry versions.
Evaluate a skill
load("@rules_promptfoo//promptfoo:defs.bzl", "promptfoo_test")
promptfoo_test(
name = "answer_question_eval",
config = "promptfooconfig.yaml",
skills = ["//tools/agents/skills/answer-question:skill"],
env_inherit = [
"CODEX_HOME",
"CODEX_PATH_OVERRIDE",
],
reuse_codex_login = True,
)
The runner physically copies each selected SkillInfo.files_by_path bundle
to an isolated Bazel test-temporary
workspace/.agents/skills/<name>/ tree. Point the Codex SDK provider at the
exported workspace and, when using a compatible host Codex executable, at the
exported executable path:
prompts:
- "{{ question }}"
providers:
- id: openai:codex-sdk
config:
codex_path_override: "{{ env.CODEX_PATH_OVERRIDE }}"
working_dir: "{{ env.PROMPTFOO_SKILL_WORKSPACE }}"
cli_env:
CODEX_HOME: "{{ env.PROMPTFOO_SUBJECT_CODEX_HOME }}"
tests:
- vars:
question: Why is the sky blue?
assert:
- type: skill-used
value: answer-question
Live eval targets are tagged manual, requires-network, no-cache,
no-remote, and local. They also pass --no-cache, --no-write, and
--no-share to Promptfoo; local prevents sandboxed or remote execution when
the caller opts into host Codex credentials. The runner exports a separate
empty PROMPTFOO_JUDGE_WORKSPACE so a model judge need not discover the skill
it grades. Explicit results are preserved as the Bazel undeclared output
results.json; Promptfoo configuration, cache, isolated workspaces, and
subprocess temporary files stay in a mode-0700 directory below Bazel’s
absolute TEST_TMPDIR and are removed on exit. Bazel owns and cleans that
private parent directory as a second cleanup boundary. Callers that must
redirect test scratch can use Bazel’s --test_tmpdir option.
The runner deliberately uses Bazel-managed temporary storage instead of a
source-checkout out/ directory. Putting a no-skill control below the source
tree would expose the checkout’s .agents directory through an ancestor and
invalidate the isolation that the control is meant to measure. This is the
repository policy’s operating-system-temporary exception for a tool that
cannot safely use task-local source storage; the runner still removes its
randomized child directory on normal exit and handled signals. The runner
rejects a TEST_TMPDIR below any ancestor containing .agents, so do not
redirect --test_tmpdir into a source checkout or another agent tree.
results.json contains the evaluated prompts and model outputs. CI may collect
undeclared outputs when a manual eval is explicitly run, so treat the artifact
as potentially sensitive even though the target is no-cache and no-remote.
Run a live target explicitly and pass the absolute path to an existing Codex
login and a compatible host Codex executable through the test environment,
never credential contents through env or a checked-in config. Set the
provider’s codex_path_override from CODEX_PATH_OVERRIDE:
bazel_agent bazel test //path/to:answer_question_eval \
--test_env=CODEX_HOME=/absolute/path/to/.codex \
--test_env=CODEX_PATH_OVERRIDE=/absolute/path/to/codex \
--test_output=errors
If that Codex installation reaches OpenAI through host proxy variables, pass
only the needed names as well (for example, --test_env=HTTPS_PROXY and
--test_env=NO_PROXY) and declare the same names in the rule’s env_inherit.
Do not put proxy values in checked-in files.
With reuse_codex_login = True, the runner links only the writable
CODEX_HOME/auth.json into distinct, otherwise empty subject and judge Codex
homes inside its mode-0700 state directory. Provider configs can select them
through PROMPTFOO_SUBJECT_CODEX_HOME and PROMPTFOO_JUDGE_CODEX_HOME. The
runner holds an exclusive advisory lock on the stable login directory for the
complete test, serializing repository eval targets that use the same login.
With a compatible Codex executable, an automatic refresh writes through either
link to the persistent host file, while the host’s config.toml, skills,
plugins, memories, and MCP configuration remain unstaged and cannot contaminate
the subject, no-skill control, or judge. This reuses the caller’s existing
login state; it does not mint or export a token. An API-key-backed target may
instead inherit OPENAI_API_KEY without enabling login reuse.
For this purpose, compatible means that file-backed auth updates open and
truncate $CODEX_HOME/auth.json, preserving the symlink. The bundled Codex
0.144.0 implementation and the tested host Codex 0.150.1 do this. Do not
override the executable with an implementation that atomically replaces the
isolated auth.json path: that would replace the symlink, split subject and
judge state, and fail to persist a rotated refresh token. See the corresponding
OpenAI Codex file-storage implementations for
0.144.0
and
0.150.1.
Login reuse also forces Promptfoo’s CLI, assertion-worker, and prompt-suggestion
concurrency to one, overriding higher configuration values so subject and judge
Codex processes cannot race the same single-use refresh token within one test.
Do not run another Codex process against the same auth.json concurrently;
unrelated processes do not honor the runner’s advisory lock. ChatGPT-managed
auth can rotate its refresh token, so the refreshed file must remain persistent
and have a single serialized user. The local-login mode is unsuitable for
shared or untrusted CI; use an API key or supported workload identity there.
In this public repository it is strictly a manual, trusted-developer workflow;
never seed or persist ChatGPT-managed auth in repository CI or artifacts.
See OpenAI’s
managed-auth guidance for the
refresh and serialization requirements.
Run credentialed live targets only from a reviewed, trusted revision. The
isolated working directory and read-only provider setting reduce accidental
writes, but Codex still has whatever host read access its enforced sandbox
policy permits and can send prompt context to the configured service.
Files in data are placed in the test runfiles. args are passed literally;
they do not expand Bazel location expressions. Prefer keeping checked-in
Promptfoo prompt and dataset files beside the config and list them in data
so Promptfoo’s own config-relative path resolution remains portable.
The env and env_inherit attributes cannot override Bazel’s test temporary
directories or the runner-owned TMPDIR, TMP, and TEMP values.
Validate without a model call
load(
"@rules_promptfoo//promptfoo:defs.bzl",
"promptfoo_validate_test",
)
promptfoo_validate_test(
name = "promptfoo_config_test",
config = "promptfooconfig.yaml",
)
Validation is an ordinary offline Bazel test and does not receive the live
eval tags. It rejects env_inherit so validation cannot accidentally receive
host credentials.
Dependency boundary
The module pins Promptfoo and the Codex SDK with a dedicated pnpm lock. npm
lifecycle hooks and optional dependencies are disabled. The Codex executable
and Promptfoo’s libSQL binding are promoted to explicit Linux x86-64
dependencies. AJV is also promoted because ajv-formats declares its runtime
peer as optional, which would otherwise be removed by no_optional. The
bundled real CLI target is intentionally compatible only with glibc-based
Linux x86-64. Bazel enforces the OS and CPU constraints, but this repository
does not currently expose a libc constraint. Other Promptfoo providers that
rely on omitted optional packages or install scripts must be reviewed and
promoted explicitly before use.
The pinned Promptfoo distribution is patched so
PROMPTFOO_DISABLE_TELEMETRY=1 also suppresses its best-effort request to
r.promptfoo.app. The upstream 0.122.2 telemetry implementation otherwise
records a “telemetry disabled” event through that separate endpoint. The same
patch makes Promptfoo’s otherwise hard-coded prompt-suggestion concurrency
honor the runner’s serialization setting.
83 - Rules Promptfoo Gazelle
Gazelle extension for offline Promptfoo validation tests
rules_promptfoo_gazelle generates required, offline
promptfoo_validate_test targets for Promptfoo configurations below an
evals/ directory. It never creates an evals subpackage and never generates
live promptfoo_test targets.
Add rules_promptfoo as a normal dependency and this generator as a
development dependency:
bazel_dep(name = "rules_promptfoo", version = "<VERSION>")
bazel_dep(
name = "rules_promptfoo_gazelle",
version = "<VERSION>",
dev_dependency = True,
)
For one-pass skill package creation, also add rules_skills as a normal
dependency and rules_skill_gazelle as a development dependency:
bazel_dep(name = "rules_skills", version = "<VERSION>")
bazel_dep(
name = "rules_skill_gazelle",
version = "<VERSION>",
dev_dependency = True,
)
Add both public language targets to the repository’s custom Gazelle binary:
load("@gazelle//:def.bzl", "gazelle_binary")
gazelle_binary(
name = "gazelle_binary",
languages = [
"@rules_promptfoo_gazelle//gazelle",
"@rules_skill_gazelle//gazelle",
],
)
With both extensions installed, a fresh directory containing SKILL.md and a
conventional Promptfoo configuration receives both skill_library and
promptfoo_validate_test targets in one Gazelle run; it does not need an
existing BUILD file.
The extension recognizes these conventional configuration paths:
evals/promptfooconfig.yaml
evals/promptfooconfig.<variant>.yaml
evals/promptfooconfig.yml
evals/promptfooconfig.<variant>.json
The default file produces eval_config_test; a variant produces
eval_<sanitized_variant>_config_test. Variants are lowercased, and runs of
characters other than ASCII letters and digits become underscores. Colliding
sanitized names receive a stable path-derived __<8 lowercase hex digits>
suffix. The double underscore cannot be produced by the sanitizer, so a
variant cannot claim a collision target’s namespace.
When a non-root containing package has a SKILL.md, each ordinary validation
target stages that package’s conventional :skill target. Repository-root
skills are unsupported by rules_skills, so a root-package SKILL.md does not
infer that label. The exact no_skill variant omits skills so it remains a
control. A generic Promptfoo package without SKILL.md receives no inferred
skill label. Every non-configuration regular file under evals/, including
README.md and test cases, is added to data in sorted order. Directories
containing BUILD, BUILD.bazel, or an additional filename configured as
valid in Gazelle are package boundaries and are not traversed.
The extension reconciles config, data, and skills when a discovered
configuration matches a conventionally named validation target. Adding or
editing eval files therefore updates those attributes on the next run.
Removing or renaming a configuration does not delete the old target: a name
shape is not reliable proof that Gazelle created a rule, so stale targets must
be removed explicitly. Gazelle’s # keep semantics remain available at the
rule, attribute, and individual list-item levels for intentional overrides.
Other attributes, including args, env, tags, and visibility, remain
manual and are preserved while the generated target exists. Existing manual
or live promptfoo_test rules are also left intact.
The extension does not follow a symlink used as the package’s evals
directory, because it could escape the repository. Such a symlink, or another
unexpected filesystem scan error including an error while checking a candidate
package boundary, is a non-destructive no-op rather than a signal to remove
targets. Before scanning, it also resolves the repository root and package
directory and refuses a package whose resolved path escapes the workspace,
including a package reached through Gazelle’s symlink-following support. The
extension manages the shared load for both Promptfoo rule symbols and uses
Bzlmod’s apparent repository name.
The public @rules_promptfoo_gazelle//gazelle:gazelle_promptfoo binary
contains only this extension and can update a nested workspace without
initializing unrelated language plugins from its parent repository.
84 - Rules skill
Bazel rules and validation for Codex skills
rules_skills packages all files belonging to a Codex skill and validates its
instructions and optional OpenAI metadata with a hermetic Bazel aspect.
Getting started
Add the module and register the validation aspect:
bazel_dep(name = "rules_skills", version = "<VERSION>")
build --aspects @rules_skills//skill:defs.bzl%skill_validation_aspect
build --output_groups=+skill_validation
Declare one library in the skill’s named, non-root package:
load("@rules_skills//skill:defs.bzl", "skill_library")
skill_library(
name = "skill",
srcs = glob(
["**"],
exclude = [
"BUILD.bazel",
"BUILD",
"evals/**",
],
),
)
The package’s final path segment is the skill name, so a skill_library
cannot be declared in a repository root package. Move a root-level skill into
a named subpackage before declaring it.
Building the library materializes the skill_validation output group. The
aspect checks SKILL.md, verifies that the frontmatter name matches its
package directory, and validates agents/openai.yaml when it is present.
Consuming a skill
Rules that install or evaluate skills can require the public SkillInfo
provider:
load("@rules_skills//skill:defs.bzl", "SkillInfo")
attrs = {
"skill": attr.label(providers = [SkillInfo]),
}
SkillInfo exposes these fields:
name is the logical skill name derived from the final segment of root.
The validation aspect verifies that the SKILL.md frontmatter uses the same
name.
root is the owning Bazel package path within the skill’s repository, such
as tools/agents/skills/answer-question. It has no repository,
execution-path, or runfiles prefix. It is always non-empty because
repository root packages are unsupported.
files_by_path maps slash-separated paths relative to root to Bazel
File values. It includes SKILL.md, preserves nested paths such as
agents/openai.yaml, and has the same shape for source and generated files.
files remains the depset of all skill files. skill is the distinguished
SKILL.md file, and openai_yaml is the optional distinguished
agents/openai.yaml file.
Consumers should use files_by_path when staging a bundle instead of parsing
File.path or File.short_path. Every source must belong to the skill’s Bazel
package, and duplicate logical paths are rejected during analysis.
Packaging skills from an external archive
Use skill_archives when skills are maintained upstream and consumed as a
pinned archive. The external repository’s BUILD file derives one target per
skill directory from a wildcard, so a new upstream skill needs no edit here:
load("@rules_skills//skill:defs.bzl", "skill_archives")
skill_archives(
name = "skills",
roots = [
manifest.rsplit("/", 1)[0]
for manifest in glob(["skills/*/SKILL.md"])
],
)
skill_archives is a macro over the skill_archive rule. Each generated
target’s name is its skill directory’s final segment, root is the skill
directory inside the package, and its packaged logical paths are relative to
root. Analysis fails when a root has no SKILL.md. Declare one
skill_archive directly when a single skill needs a hand-written target.
Writing the source-tree discovery directory
Use skills_write to reconcile .agents/skills from declared skills. Each
symlinks label is installed as a direct relative symlink to its canonical
root; each archives label is copied in as regular files, which is how archive
skills are materialized into the consuming repository:
load("@rules_skills//skill:defs.bzl", "skills_write")
skills_write(
name = "write_skills",
archives = ["@org_fissionai_openspec//:openspec-propose"],
discovery_dir = ".agents/skills",
symlinks = ["//tools/agents/skills/answer-question:skill"],
workspace_marker = "//:AGENTS.md",
)
workspace_marker is a source file in the consuming repository used to
resolve the workspace root from test runfiles. Reconcile and verify:
bazel run //.agents:write_skills
bazel test //.agents:write_skills_test
The generated check requires exactly the declared names: missing, extra, or
stale entries fail, and a written entry must match its declared source
byte-for-byte. The updater holds a sibling <discovery_dir>.lock directory
while it changes entries; a process killed without running its exit trap can
leave that lock behind. After confirming no updater is active, remove the empty
lock directory and rerun the updater.
Both rules require POSIX symlinks and Bash and are limited to Linux and macOS
checkouts; native Windows checkouts are not supported.
85 - Rules skill Gazelle
Gazelle extension for Bazel skill libraries
rules_skill_gazelle adds a skill_library(name = "skill") to each named
subpackage that contains a SKILL.md. The generated rule loads its macro from
the apparent rules_skills repository, so Bzlmod repository mappings are
respected.
Add rules_skills as a normal dependency and this generator as a development
dependency:
bazel_dep(name = "rules_skills", version = "<VERSION>")
bazel_dep(
name = "rules_skill_gazelle",
version = "<VERSION>",
dev_dependency = True,
)
Add the public language target to the repository’s custom Gazelle binary:
load("@gazelle//:def.bzl", "gazelle_binary")
gazelle_binary(
name = "gazelle_binary",
languages = [
"@rules_skill_gazelle//gazelle",
],
)
The generated source bundle is:
load("@rules_skills//skill:defs.bzl", "skill_library")
skill_library(
name = "skill",
srcs = glob(
["**"],
exclude = [
"BUILD.bazel",
"BUILD",
"evals/**",
],
),
)
The generator always excludes BUILD.bazel and BUILD, plus every custom
BUILD filename configured in Gazelle. Duplicate configured names are removed.
SKILL.md is sufficient for Gazelle to create a new BUILD file below the
repository root. A root-level SKILL.md is ignored because rules_skills
requires a named package from which it can derive the skill name. Existing
attributes on a manually maintained skill_library(name = "skill") are
preserved, and a missing SKILL.md does not delete a manual rule.
The public @rules_skill_gazelle//gazelle:gazelle_skills binary contains only
this extension and can update a nested workspace without initializing
unrelated language plugins from its parent repository.
86 - Rules template
Bazel rules and a Go command for rendering template files
rules_template provides Bazel rules and a Go command for rendering template
files. A registered toolchain supplies the templating executable to build
actions.
Getting started
MODULE.bazel:
bazel_dep(name = "rules_template", version = "<VERSION>")
register_toolchains("@rules_template//main/bzl:all")
BUILD.bazel:
86.2 - OpenSpec
86.2.1 - Specifications
86.2.1.1 - Project dns
86.2.1.1.1 - project-dns Specification
Purpose
Record the retirement of rules_template landing infrastructure while preserving
its reusable Bazel module.
Requirements
Requirement: Keep landing infrastructure retired
The module SHALL follow the tools boundary and SHALL
have no dedicated landing DNS declarations, Terraform root, or operational
source exports.
Scenario: Inspect the module after landing retirement
- WHEN the module is consumed from
tools/rules_template
- THEN its reusable Bazel rules remain available without landing infrastructure.
86.2.1.2 - Project rules template
86.2.1.2.1 - Rules template
Purpose
Render Go text templates from declared data files through a command and a Bazel
toolchain rule. This source baseline was observed on 2026-09-08 at revision
550d7e79b1f5fdbc2b6017b75178471d6914082f. The command operates on one template
and output per invocation; the rule’s list attributes do not establish a
multi-template rendering guarantee.
Sources: project description,
command flags,
templater,
template functions,
and Bazel action.
Requirements
Requirement: Load supported data formats into template context
The template command SHALL load data files in argument order into DataFiles,
decode .toml, .json, .ndjson, and .yaml data, and expose .txt files as
lines without structured decoding. --extension SHALL override extension
selection for the supplied data files.
Scenario: A caller renders a template using newline-delimited JSON
- WHEN a declared
.ndjson input contains valid JSON on each data line
- THEN its template context contains a parsed sequence in
Data and the input lines in Lines.
The command SHALL return errors for unsupported data extensions, failed reads,
invalid structured data, invalid template syntax, template execution failures,
and failed output writes.
Scenario: A data file uses an unsupported extension
- WHEN a data path has an unrecognized extension and no supported override
- THEN rendering fails with an unsupported-extension diagnostic identifying the data path.
Requirement: Expose the registered template function set
Template execution SHALL use the project’s function map, including JSON
serialization, HTML escaping, path basename and dirname helpers, and first and
last element helpers.
Scenario: A template serializes structured data
- WHEN a valid template invokes
to_json on JSON-serializable context data
- THEN the function returns the JSON representation for inclusion in the output.
template_run_binary SHALL invoke the registered templater with its template
subcommand, declare source and data files as inputs, declare output files, and
forward additional rule arguments.
Scenario: A target declares one template and one output
- WHEN Bazel executes the target with its registered template toolchain
- THEN the action passes the template, ordered data flags, and output path to the templater and exposes the declared output to downstream targets.
87 - Rules Terraform
Bazel rules and pinned provider installation for Terraform
This standalone module owns reusable Terraform rules. Terraform and provider
archives are declared Bazel inputs. Provider downloads belong to the Bzlmod
extension; Terraform uses a filesystem mirror containing only the providers
selected by its target.
The extension resolves one version per provider source. Consumers select a
provider by Bazel label. Explicit version override policy can be added at this
resolution boundary later; conflicting versions currently fail resolution.
Repository authentication and backend injection are supplied by the caller.
The module does not depend on the enclosing monorepo’s AL or Vault packages.
Declare providers
Use terraform_providers from @rules_terraform//:extensions.bzl. Each
archive tag requires a unique repository name, canonical lowercase
source (hostname/namespace/type), exact version, Terraform platform
(os_arch), immutable HTTPS urls, and SHA256 SRI integrity. Verify the
integrity against the publisher’s release checksums before declaring it.
Import each generated repository with use_repo. Its public :provider
target exposes TerraformProviderInfo. Different platforms can share one
provider version; duplicate archives or conflicting versions fail before
repositories are registered. Provider downloads use Bazel’s verified downloader
and repository cache, without executing host tools or querying a registry.
This repository’s concrete declarations live in the
Terraform dependency package.
The module is currently developed with a local Bzlmod override; no registry
release is implied.
load("@rules_terraform//terraform:defs.bzl", "terraform_binary", "terraform_test")
terraform_binary(
name = "tf_plan",
srcs = glob(["*.tf"]),
data = ["//modules/example:source"],
providers = ["@my_provider_linux_amd64//:provider"],
arguments = ["plan"],
)
terraform_test(
name = "tf_fmt_test",
srcs = glob(["*.tf"]),
arguments = ["--direct", "fmt", "-check", "-recursive"],
)
arguments are fixed runner/Terraform arguments; command-line arguments are
appended. srcs and data declare configuration, local child modules, and files
read by the configuration. chdir defaults to the target’s package. The
rules package ZIPs at
HOST/NAMESPACE/TYPE/terraform-provider-TYPE_VERSION_OS_ARCH.zip beneath a
target-specific runfiles mirror, following Terraform’s
filesystem mirror contract.
Command maps and wrappers
The same terraform/defs.bzl exports terraform_binary_map,
terraform_target_binary_map, and terraform_test_map. They preserve explicit
operation names such as tf.plan, tf.apply, and tf_tests.fmt_test.
Targeted maps produce plan/show/apply commands; their apply command requires
one saved plan. No unnamed apply alias is generated.
Maps run the Terraform rules directly by default. Callers can supply a
wrapper macro and its wrapper_kwargs to compose authentication or other
command setup without adding that framework to rules_terraform. The wrapper
receives the final target name, the inner executable in args, its runfiles
in data, and common target attributes. wrapper_kwargs cannot replace those
arguments or duplicate common attributes. Wrapped tests require a test wrapper.
Repository consumers load AL’s generic al_binary_run or
al_binary_run_test and pass their configs and run_args in
wrapper_kwargs. Their plugin binaries remain in data. Provider-free tests
can use the Terraform test maps directly. Repository root command bindings
live with the shared AL configuration.
The default Terraform executable is the pinned toolchain in
terraform/binary_toolchain.json, acquired through rules_binary_toolchain.
The initial supported executable platform is Linux amd64. The optional
terraform executable attribute permits a caller-supplied Bazel tool.
@rules_terraform//terraform:cli exposes the pinned CLI for formatting and
other consumers that do not initialize providers.
Initialization and generated locks remain in runtime workspaces. No source
.terraform.lock.hcl is required. The launcher contract
describes working directories, direct commands, saved plans, and manifest-only
execution. Provider and backend operations can still use the network when
the operator invokes them; provider acquisition itself has no runtime registry
fallback. Configuration modules must be declared local inputs.
87.1 - OpenSpec
87.1.1 - Specifications
87.1.1.1 - Terraform execution
87.1.1.1.1 - terraform-execution Specification
Purpose
Provide reusable Bazel rules that acquire pinned Terraform providers and run
Terraform with declared configuration, executable, and provider inputs.
Requirements
Requirement: Verified provider acquisition
The provider module extension SHALL download immutable HTTPS archives with
mandatory SHA256 integrity through Bazel repository fetching. It SHALL select
one version for each canonical provider source across the extension graph.
Scenario: Conflicting provider versions
- WHEN declarations select different versions of the same provider source
- THEN extension resolution fails before provider repositories are created.
Scenario: Verified archive
- WHEN a provider archive is fetched
- THEN Bazel verifies the declared integrity and exposes its canonical source,
version, platform, and packed filesystem-mirror path.
Requirement: Declared provider execution
Terraform rules SHALL place selected provider archives in runfiles using the
packed filesystem-mirror layout. The runner SHALL configure only that mirror
for provider installation and SHALL preserve the declared Terraform executable.
Scenario: Offline initialization
- WHEN all required providers are declared by a target
- THEN initialization and provider schema validation succeed without registry
access or a host provider cache.
Scenario: Missing provider
- WHEN configuration requires a provider absent from the target’s mirror
- THEN initialization fails without falling back to a registry download.
Requirement: Reusable execution and repository integration
The reusable module SHALL own Terraform execution and named command maps while
remaining independent of parent repository labels. Maps SHALL support optional
caller-supplied command wrappers. Repository consumers SHALL select generic AL
wrappers explicitly to preserve configuration, plugin lifecycle, backend
injection, named operations, and the saved-plan apply guard. Shared provider
pins SHALL be owned by third_party/terraform.
Scenario: Saved-plan apply
- WHEN a targeted apply wrapper receives a missing plan or extra arguments
- THEN the runner rejects it before Terraform initialization.
Scenario: Repository migration
- WHEN repository Terraform commands and tests are analyzed
- THEN they load maps from rules_terraform, select shared provider labels,
and require neither tools/terraform nor a checked-in .terraform.lock.hcl file.
Scenario: Optional wrapper
- WHEN a command map supplies a wrapper
- THEN the wrapper receives the declared Terraform invocation and runfiles
without requiring a framework dependency in the reusable module.
Scenario: Direct tests
- WHEN a test map needs no external command setup
- THEN it runs the Terraform test rule without an AL dependency.
87.2 - Terraform launcher
Execute Terraform with providers declared by Bazel
This implementation launcher reads the generated target sidecar and resolves
Terraform, configuration inputs, and provider archives through Bazel runfiles.
Consumer targets use the public Terraform rules instead of invoking this binary
directly.
The launcher selects the target’s working directory inside the runfiles tree.
Source files can remain symlinks, so formatting retains its existing source
behavior. Initialization writes lockfiles and Terraform metadata in that runfiles
directory. Saved plans and local state retain their normal paths relative to
the working directory; the launcher never removes them.
Each invocation uses a private CLI configuration containing only the declared
filesystem provider mirror. It overrides user CLI configuration, removes inherited
provider-cache settings, disables checkpoint checks, and rejects injected CLI
arguments, provider reattachment, and provider-download commands. Missing provider
versions and platforms fail without a registry installation fallback. Temporary
CLI configuration and any materialized provider mirror are removed on exit.
Before commands that can load providers, the launcher reads Terraform’s lock
selections through version -json and checks every selected installed package
against its declared archive. Source addresses, versions, file paths, and file
contents must match; undeclared selections and extra, missing, modified, or linked
package files fail before the requested command starts. The check honors
TF_DATA_DIR, including paths relative to Terraform’s working directory. It also
runs after explicit initialization. This adds local archive decompression and
hashing work; it does not initialize the backend or contact a registry. The
working directory must not be modified concurrently during execution.
Initialization runs before the requested command unless --direct precedes it.
Its standard output goes to standard error, preserving structured command output.
AL_TF_BACKEND_CONFIG* environment values remain literal initialization
arguments; other provider and backend environment, including TF_HTTP_*, is
preserved. Terraform process exit codes are returned unchanged.
Configuration supports declared local modules. Provider installation is isolated;
the launcher does not inspect module source expressions or sandbox Terraform’s
service connections.
--require-saved-plan accepts only apply <saved-plan-file>. The file must exist
and be regular; relative paths resolve against the selected working directory.
Argument and environment validation happens before initialization.
Manifest-only tests materialize declared configuration inputs beneath
TEST_TMPDIR, retaining their workspace until Bazel removes that test directory.
Outside tests, a directory runfiles tree is required to preserve persistent
working-directory semantics. An explicit --chdir before the Terraform command
selects a caller-owned directory when that behavior is intended.
Invocation scratch uses TEST_TMPDIR, then an explicitly configured TMPDIR,
then BUILD_WORKSPACE_DIRECTORY/out/rules_terraform/runtime. It never falls back
to the system temporary directory.
88 - rules_iso
ISO image download and flash rules
Module extension that downloads ISO images with http_file and adds a
runnable iso_flash target next to each image.
89 - Run tool
Run tool rules
89.1.1 - al_run_tool
al_run_tool
load("@com_alwaldend_src//tools/run_tool/main/bzl:al_run_tool.bzl", "al_run_tool")
al_run_tool(name, tool, executable, test, **kwargs)
Generate either native_test, native_binary, or run_binary target
PARAMETERS
| Name |
Description |
Default Value |
| name |
Target name (required) |
none |
| tool |
Tool label to run (required) |
none |
| executable |
If True, generate native_binary |
False |
| test |
If True, generate native_test |
False |
| kwargs |
kwargs for rules |
none |
90 - Selene
Hermetic Selene Lua linter
Selene provides correctness-oriented linting for the repository’s
hand-maintained Lua files. The repository standard-library description permits
the intentional al configuration DSL and Neovim’s vim global while keeping
Selene’s ordinary Lua diagnostics enabled.
Links
The pinned upstream binary is owned by
third_party/com_github_kampfkarren_selene.
91.1.1 - al_sh_library
al_sh_library
load("@com_alwaldend_src//tools/sh/main/bzl:al_sh_library.bzl", "al_sh_library")
al_sh_library(name, shfmt_src, editorconfig_src, shellcheck_src, run_args_src, visibility,
test_data, **sh_kwargs)
Create targets for a shell library
Targets:
- ${name}.shfmt_fix: executable to run shfmt
- ${name}.shfmt_test: test whether the script is formatted
- ${name}.shellcheck_test: shellcheck test
PARAMETERS
| Name |
Description |
Default Value |
| name |
target name |
none |
| shfmt_src |
- |
"//tools/shfmt" |
| editorconfig_src |
- |
"//tools/shfmt:editorconfig" |
| shellcheck_src |
- |
"//third_party/com_github_koalaman_shellcheck:shellcheck" |
| run_args_src |
- |
"//tools/sh/main/sh:run_args_lib" |
| visibility |
- |
["//:__subpackages__"] |
| test_data |
- |
[] |
| sh_kwargs |
kwargs for sh targets |
none |
91.1.2 - al_write_script
al_write_script
load("@com_alwaldend_src//tools/sh/main/bzl:al_write_script.bzl", "al_write_script")
al_write_script(name, out, content, make_vars, set_flags, shebang)
Write a script and make it executable
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| out |
Output file |
Label; nonconfigurable |
required |
|
| content |
Script content |
String |
required |
|
| make_vars |
Additional make vars |
Dictionary: String -> String |
optional |
{} |
| set_flags |
Flags to pass to set |
List of strings |
optional |
["-eu"] |
| shebang |
Sheband to use |
String |
optional |
"#!/usr/bin/env sh" |
92 - Shfmt
Shfmt
Links
The pinned upstream binary is owned by
third_party/com_github_mvdan_sh.
94 - Stylua
Stylua wrapper
Links
The pinned upstream binary is owned by
third_party/com_github_johnnymorganz_stylua.
95.1.1 - al_toml_data
al_toml_data
load("@com_alwaldend_src//tools/toml/main/bzl:al_toml_data.bzl", "al_toml_data")
al_toml_data(name, deps, srcs, tomlv)
ATTRIBUTES
| Name |
Description |
Type |
Mandatory |
Default |
| name |
A unique name for this target. |
Name |
required |
|
| deps |
Toml data targets |
List of labels |
optional |
[] |
| srcs |
Toml files |
List of labels |
optional |
[] |
| tomlv |
Tomlv target to use for validation |
Label |
optional |
"@com_alwaldend_src//tools/tomlv" |
95.1.2 - al_toml_info
AlTomlInfo
load("@com_alwaldend_src//tools/toml/main/bzl:al_toml_info.bzl", "AlTomlInfo")
AlTomlInfo(srcs, deps)
Provide toml data info
FIELDS
| Name |
Description |
| srcs |
Toml files |
| deps |
Toml data targets |
95.1.3 - al_toml_validate
al_toml_validate
load("@com_alwaldend_src//tools/toml/main/bzl:al_toml_validate.bzl", "al_toml_validate")
al_toml_validate()
Aspect adding linters for toml files
ASPECT ATTRIBUTES
ATTRIBUTES
97 - Traefik
Traefik
Links
98 - Transitive sources
Provider and a rule to extract transitive sources
98.1.1 - al_transitive_sources
AlTransitiveSources
load("@com_alwaldend_src//tools/transitive_sources/main/bzl:al_transitive_sources.bzl", "AlTransitiveSources")
AlTransitiveSources(transitive_sources)
Provide transitive sources
FIELDS
| Name |
Description |
| transitive_sources |
- |
al_transitive_sources
load("@com_alwaldend_src//tools/transitive_sources/main/bzl:al_transitive_sources.bzl", "al_transitive_sources")
al_transitive_sources(srcs, deps)
Obtain the source files for a target and its transitive dependencies.
PARAMETERS
| Name |
Description |
Default Value |
| srcs |
a list of source files |
none |
| deps |
a list of targets that are direct dependencies |
none |
RETURNS
a collection of the transitive sources
99 - Trufflehog
Trufflehog
The normal //tools/trufflehog:repo_test scans the history reachable from the
checked-out HEAD and skips unrelated local refs. Run the manual
//tools/trufflehog:repo_all_refs_test target explicitly when an audit must
include every local Git ref, such as T3 checkpoint refs from other worktrees.
Links
The pinned upstream binary is owned by
third_party/com_github_trufflesecurity_trufflehog.
101.1.1 - al_combine_files
al_combine_files
load("@com_alwaldend_src//tools/txt/main/bzl:al_combine_files.bzl", "al_combine_files")
al_combine_files(name, srcs, **kwargs)
Create a genrule combining several files into one
PARAMETERS
| Name |
Description |
Default Value |
| name |
genrule target |
none |
| srcs |
list of labels to combine |
none |
| kwargs |
other genrule kwargs |
none |
101.1.2 - al_txt_data
al_txt_data
load("@com_alwaldend_src//tools/txt/main/bzl:al_txt_data.bzl", "al_txt_data")
al_txt_data(name, srcs, **kwargs)
Text data
PARAMETERS
| Name |
Description |
Default Value |
| name |
target name |
none |
| srcs |
sources |
none |
| kwargs |
filegroup kwargs |
none |
102 - Vault
Vault
Links
The root //:vault and //:vault.* labels delegate to wrappers in
//tools/vault/cmd/workspace, using //tools/al:config. //tools/vault:vault
is the standalone CLI; the wrappers add the existing AL environment injection.
The pinned upstream binary is owned by
third_party/com_hashicorp_vault.
102.1 - Backup
Run backup for Vault
102.2 - Forgejo login
Get a short-lived Forgejo token using OIDC
Plugin data supplies the default Forgejo URL, OIDC source, Vault connection,
and Vault authentication. One selected plugin_call may override nonempty
configuration fields, for example vault_auth = "forgejo_registration",
while inheriting the remaining defaults. Calls without overrides preserve
the defaults. Multiple calls with overrides and malformed configuration are
rejected before authentication; configuration values are omitted from errors.
The plugin registers token removal before issuance, using a unique token name.
Startup failure and normal shutdown attempt removal through the authenticated
Forgejo browser session, including when creation did not return a usable token.
Cleanup parses the complete applications settings page and verifies token absence
after deletion. It then logs out the invocation’s browser session and verifies
that the retained original cookie no longer accesses authenticated settings.
Cleanup failures are reported.
The parser follows the Forgejo 15.0.3 applications template: all tokens appear on
one page, each in a flex-item row with a title and a matching delete button.
It requires the applications page marker and new-token link before accepting an
empty list. Login pages, invalid token IDs, duplicates, and responses larger than
4 MiB fail closed. A changed upstream template may require a parser update.
Network requests have a ten-second timeout and honor cancellation. Forced process
termination or an unavailable Forgejo server can prevent cleanup; Forgejo tokens
do not acquire an expiry merely because this plugin created them.
102.2.1 - api
Proto docs for api.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/forgejo_login:api_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/forgejo_login:forgejo_login",
],
)
Proto
syntax = "proto3";
package com.alwaldend.src.tools.vault.forgejo_login.forgejo_login_proto;
option go_package = "git.alwaldend.com/alwaldend/src/tools/vault/forgejo_login/forgejo_login_proto";
message Config {
string forgejo_url = 1;
string forgejo_oauth_name = 2;
string vault_conn = 3;
string vault_auth = 4;
}
102.3 - Gen client cert
Generate a client certificate
102.4 - Gen root token
Generate a root token for Vault
102.5 - Harbor login
Create a harbor session using OIDC
The plugin destroys its Harbor session on normal shutdown and checks that the
original session ID is rejected by the current-user API. Cleanup uses Harbor’s
/c/oidc/logout endpoint (available in newer Harbor releases) and does not follow
the optional identity-provider logout redirect. Unsupported endpoints, network
failures, or sessions that remain valid are reported as cleanup errors. Session
expiry remains server-controlled; forced termination cannot guarantee logout.
The session destruction behavior is defined by Harbor’s
OIDC controller.
Vault tokens created by the invocation are revoked after session cleanup.
102.5.1 - api
Proto docs for api.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/harbor_login:api_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/harbor_login:harbor_login",
],
)
Proto
syntax = "proto3";
package com.alwaldend.src.tools.vault.harbor_login.harbor_login_proto;
option go_package = "git.alwaldend.com/alwaldend/src/tools/vault/harbor_login/harbor_login_proto";
message Config {
string harbor_url = 1;
string vault_conn = 2;
string vault_auth = 3;
}
102.6 - Injector
Secret injector
Shutdown first drains plugin requests, then stops and waits for resource
processes, revokes invocation-owned Vault credentials, and deletes temporary
files and SSH key directories. Failed cleanup is reported. Registration after
shutdown is rejected; a fetcher removes any unregistered temporary material.
Temporary files use mode 0600 and directories use 0700. Deletion is filesystem
unlinking, not secure erasure, and cannot run after SIGKILL or host failure.
no_auth explicitly sets the injected VAULT_TOKEN to an empty value so an
inherited token is overridden. This does not remove the user’s token-helper
file; commands that independently consult that helper may still authenticate.
Template errors and OIDC status errors omit input and response contents.
OIDC requests honor cancellation and do not follow redirects.
102.6.1 - api
Proto docs for api.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/injector:api_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/injector:injector",
],
)
Proto
syntax = "proto3";
package com.alwaldend.src.tools.vault.injector.injector_proto;
option go_package = "git.alwaldend.com/alwaldend/src/tools/vault/injector/injector_proto";
message File {
string value = 1;
string from_file = 2;
map<string, string> extra = 3;
}
message Env {
string value = 1;
}
message Kv {
// Secret path
string path = 1;
// Secret mount
string mount = 2;
}
message Op {
string method = 1;
string path = 4;
map<string, string> data = 5;
}
message VaultSsh {
string backend = 1;
int64 ttl = 2;
}
message Process {
string name = 1;
repeated string args = 2;
}
message Oidc {
string name = 1;
string scope = 2;
string client_id = 3;
string redirect_uri = 4;
}
message VaultEnv {
// Vault connection
string conn = 1;
// Vault auth
string auth = 2;
}
message Resource {
// Resource name
string name = 1;
// Vault connection for the resource
string vault_conn = 2;
// Vault auth for the resource
string vault_auth = 3;
// Dependencies
repeated string deps = 8;
oneof res {
// Vault operation
Op op = 4;
// Vault KV secret
Kv kv = 5;
// Environment variable
Env env = 6;
// File
File file = 7;
// Vault environment variables
VaultEnv vault_env = 9;
// Ssh key signed by Vautl
VaultSsh vault_ssh = 10;
// Run a cmd
Process process = 11;
// Create an OIDC token
Oidc oidc = 12;
}
}
message Config {
// Resources
repeated Resource res = 1;
}
102.7 - Login
Login to vault using the yubikey
Usage
bazel run //tools/vault/login
102.8 - PVE login
Get a login ticket using OIDC
The plugin requests an API token with a one-hour expiry and deletes that token
on shutdown using the retained login ticket and CSRF token. The token name is
registered for cleanup before creation, so cleanup is attempted even when a
creation response is lost or malformed. Failures to delete are reported; expiry
is the fallback when shutdown cannot complete. Pre-existing credentials are not
revoked. Vault tokens created by the invocation are revoked after token cleanup.
Token deletion uses the Proxmox
user token API.
102.8.1 - api
Proto docs for api.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/pve_login:api_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/pve_login:pve_login",
],
)
Proto
syntax = "proto3";
package com.alwaldend.src.tools.vault.pve_login.pve_login_proto;
option go_package = "git.alwaldend.com/alwaldend/src/tools/vault/pve_login/pve_login_proto";
message Config {
string pve_base_url = 1;
string pve_redirect_url = 2;
string pve_realm = 3;
string vault_conn = 4;
string vault_auth = 5;
}
102.9 - Tf backend
Http terraform backend backed by Vault
The plugin tracks each running backend for shutdown, including backends created
before a later call fails. Shutdown drains HTTP requests before revoking the
backend’s invocation-owned Vault credentials. If the shutdown deadline expires,
connections are forcibly closed and the timeout is reported. Normal HTTP server
closure is not an error. Vault requests use their incoming request context.
102.9.1 - api
Proto docs for api.proto
Java
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/tf_backend:api_java_library",
],
)
Golang
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "name",
deps = [
"@com_alwaldend_src//tools/vault/tf_backend:tf_backend",
],
)
Proto
syntax = "proto3";
package com.alwaldend.src.tools.vault.tf_backend.tf_backend_proto;
option go_package = "git.alwaldend.com/alwaldend/src/tools/vault/tf_backend/tf_backend_proto";
message Config {
string vault_conn = 1;
string vault_auth = 2;
string vault_secret = 3;
string vault_secret_mount = 4;
}
102.10 - Unseal
Unseal the vault
103 - Versioning
Global repository and project versioning
versioning owns versions for this repository and its first-party projects.
It deliberately does not manage versions of third-party dependencies.
The repository uses SemVer-compatible calendar versions:
- ordinary development:
0.0.0-dev;
- nightly trunk tag:
vYYYY.W.0-nightly.YYYYMMDD;
- weekly release branch:
releases/YYYY.W;
- release tag:
vYYYY.W.PATCH.
YYYY and W are the ISO week-year and week. The week is not zero-padded,
because SemVer forbids leading zeroes in numeric identifiers. Patch zero is
the release branch point. Every first-parent commit after that point advances
the calculated patch number by one.
Calculated versions and Bazel status omit the Git tag’s leading v.
A commit may carry one nightly tag and one release tag when a nightly is
promoted. Branch context selects the channel automatically. On detached HEAD,
pass --channel release or --channel nightly for that exact co-tagged
commit. For an untagged detached commit from a release branch, pass
--release YYYY.W so the tool can calculate its patch from the correct
branch-point tag.
Build and inspect the tool with:
bazel_agent bazel run //tools/versioning/cmd/versioning -- show
For a stamped build, use the bootstrap entry point. It generates a
source-current Bazel launcher under out/versioning/, then the Go tool runs
Bazel with itself as workspace status:
tools/versioning/cmd/versioning/versioning.sh bazel -- \
build --config=release //path/to:artifact
Read $versioning for the guarded nightly, release, and Bazel stamping
workflows.
Guarded release-ref publication
versioning also owns a typed, reviewed release-ref plan and a
provider-neutral guarded publisher for the generated nightly and release
tags. It never merges versioning, delivery, or goal authority; the tool
retains its own release-refs authority.
Generate a deterministic plan from the resolved version state:
bazel_agent bazel run //tools/versioning/cmd/versioning -- release-plan \
--plan out/delivery/release-plan.json
The plan records the exact version, channel, commit, tree state, target refs
(tag-only for nightly, branch-plus-tag for release), the expected remote
preconditions, and whether atomic multi-ref publication is required. It is
reviewed before consumption and is not an authorization.
Publish the reviewed plan only after explicit release scope:
bazel_agent bazel run //tools/versioning/cmd/versioning -- release-publish \
--plan out/delivery/release-plan.json \
--receipt out/delivery/release-ref-receipt.json
The guarded publisher fetches expected remote state, acquires a distinct
release-refs lease, publishes the refs (atomically when required and
supported), and verifies the remote before emitting a ReleaseRefReceipt.
An existing immutable release tag never moves; a remote that cannot guarantee
atomic multi-ref publication is an explicit refusal, never a generic success.
104 - Vial
Bazel rules for Vial
Links
104.1.1 - al_vial_configs
al_vial_configs
load("@com_alwaldend_src//tools/vial/main/bzl:al_vial_configs.bzl", "al_vial_configs")
al_vial_configs(name, srcs, visibility, **kwargs)
Generate vial config targets
PARAMETERS
| Name |
Description |
Default Value |
| name |
generated docs archive name |
none |
| srcs |
vial config |
none |
| visibility |
visibility |
None |
| kwargs |
kwargs for template_files |
none |
105 - Word lists
Word lists
Links
106 - Workspace status
Workspace status script
Links
107 - X CLI
Pinned upstream xurl command-line client for the X API
This target runs the unmodified upstream xurl CLI. The release archive and
integrity pin belong to
third_party/com_github_xdevplatform_xurl.
The current toolchain supports Linux x86-64.
bazel_agent bazel run //tools/xurl -- version
bazel_agent bazel run //tools/xurl -- --help
xurl uses its native ~/.xurl/auth.yml credential store. Startup can create or
migrate this store and import ~/.twurlrc, even for help and version commands.
There is no upstream configuration-path override. Automated use must isolate
these paths; do not place credentials in command arguments or enable verbose
logging, which prints the Authorization header.
Select --auth oauth1 and --app <name> explicitly when comparing the Article
uploader with the CLI. Otherwise xurl prefers stored OAuth 2.0 credentials.
--data accepts literal JSON, not curl-style @file input. The CLI follows
redirects and uses Go’s default transport; a single invocation does not provide
the uploader’s stricter redirect and request-replay safeguards.
This generic tool supplies neither repository credentials nor a Markdown
converter. The Article uploader
owns those operations and its Vault injection flow. Creating a draft is a
separate, quota-consuming operation from inspecting this CLI.
Links
108 - Ykman
Ykman (Yubikey Manager)
Links
Python dependencies use the shared uv locking workflow.
Dependencies are declared in pyproject.toml and locked in uv.lock.
Run //tools/ykman:uv_lock.update to update the native lock and review its
diff. Build //tools/ykman:uv_lock to generate it without source writes.
The sandboxed resolution action requires network access to PyPI.