Repository scaffolding
The environment, quality gates, service topology, and CI workflow contracts every other capability relies on.
StableImplemented, specified, and covered by tests in the repository.
This capability delivers no runtime code. It specifies the ground every other capability stands on: the reproducible developer environment, the dependency groups, the lint and type gates, the local service topology, and the CI workflow contracts.
It is written as a specification, with scenarios and tests, for the same reason the runtime capabilities are. A project's quality gates are load-bearing infrastructure — the moment they are conventions rather than requirements, they erode one exception at a time, and the erosion is invisible until something that should have been caught is not.
What it pins down
- Layout and versions. A
uv-managedsrc/layout; Python and Beam floors that match the constraints the runtime actually needs. - Dependency groups. Dev tooling is partitioned from runtime dependencies, so what a user would install is not what a contributor installs.
- Gates.
rufffor lint and format acrosssrc/andtests/;mypy --strictover the source tree;pytestwith a closed marker registry in strict mode, so a typo'd marker is an error rather than a silently unselected test. pre-commit. The gates run locally, before push, rather than only in CI.- Local services. Docker compose brings up Redpanda, Redis, and Flink for the integration tier.
Makefileas the single contract between local and CI. Every CI step invokes amaketarget, so a target passing on your machine means the same thing in the workflow.- A public API surface that starts empty and typed. Names become public deliberately, by being added, rather than by accident of module layout.
Related
- How changes get made here — the process these gates enforce.
- Testing tiers and CI — what runs where, and when.
- Contributing — the local loop in practice.
- Install — the source install this layout produces.
Published verbatim from openspec/specs/repo-scaffolding/spec.md — 11 requirements, 33 scenarios. Each scenario is the source a test is derived from and named after.
Purpose
Provides the reproducible developer environment, dependency management, code-quality gates, local service topology, and CI workflow contracts that every future beam-agents capability relies on. This capability delivers scaffolding only — no runtime application code.
Requirements
Requirement: Project layout follows uv-managed src/ convention
The repository SHALL be organised as a uv-managed Python project with a src/beam_agents/ package, a top-level tests/ directory, a protos/ directory for .proto sources with generated _pb2.py files co-located, a docker/ directory for compose assets, an openspec/ directory for spec-driven change artifacts, and a website/ directory holding the documentation site. A .python-version file MUST pin the default interpreter to a supported version. The website/ directory MUST be self-contained: no Python package or test outside tests/docs/ may import from it, and no module under src/ may depend on it.
Scenario: Bootstrap on a clean checkout
- WHEN a contributor runs
uv sync --all-groupson a clean checkout - THEN the command completes successfully, creates
.venv/, installs every declared dependency group, andpython -c "import beam_agents"exits 0 with no output
Scenario: Required top-level directories exist
- WHEN the repository is inspected after this change lands
- THEN
src/beam_agents/__init__.py,tests/conftest.py,protos/,docker/compose.yaml,openspec/,website/package.json,pyproject.toml,uv.lock, and.python-versionall exist
Scenario: Node artifacts are not tracked
- WHEN a contributor installs the site's dependencies and builds it
- THEN
git status --porcelainreports no untrackedwebsite/node_modules/orwebsite/.next/entries, and the site's lockfile is tracked
Requirement: Python and Beam version floors match project constraints
pyproject.toml SHALL declare requires-python = ">=3.11,<3.13" and MUST include apache-beam[gcp]>=2.60, httpx[http2], pydantic>=2, and protobuf in [project.dependencies]. Attempting to install on an unsupported interpreter MUST fail with a clear resolver error.
Scenario: Install rejects Python 3.13
- WHEN a contributor attempts
uv syncunder Python 3.13 - THEN
uvrefuses to resolve the environment and reports therequires-pythonconstraint
Scenario: Install rejects Python 3.10
- WHEN a contributor attempts
uv syncunder Python 3.10 - THEN
uvrefuses to resolve the environment and reports therequires-pythonconstraint
Scenario: Runtime dependencies present after minimal install
- WHEN a contributor runs
uv syncwith no group flags - THEN
apache_beam,httpx,pydantic, andgoogle.protobufare all importable in the resulting environment
Requirement: Dependency groups partition dev tooling from runtime
pyproject.toml SHALL define dependency groups dev, test, lint, typecheck, integration, bench, and docs. Runtime dependencies MUST NOT appear inside any group. Heavy or optional tooling (testcontainers, mutmut, hypothesis, benchmark rigs) MUST live in a group and MUST NOT be pulled by the base install.
Scenario: Base install omits integration tooling
- WHEN a contributor runs
uv syncwith no group flags - THEN
testcontainersandmutmutare NOT installed
Scenario: Lint job installs only lint group
- WHEN CI runs
uv sync --frozen --group lint - THEN
ruffis installed andpytestis NOT installed
Requirement: ruff enforces lint and format across src/ and tests/
ruff configuration SHALL live in [tool.ruff] inside pyproject.toml. Enabled rule selectors MUST include E, F, I, B, UP, SIM, ASYNC, PL, and RUF. Line length MUST be 100. ruff format MUST be the sole formatter. The lint make target MUST run both ruff check and ruff format --check.
Scenario: Blocking call in async function fails lint
- WHEN a contributor commits an
async deffunction that callstime.sleep(1) - THEN
make lintexits non-zero with anASYNCrule violation
Scenario: Formatter drift fails CI
- WHEN a pull request contains unformatted code
- THEN the
ci-lintjob fails at theruff format --checkstep
Requirement: mypy --strict gates the source tree
mypy MUST run with strict = true against src/. Per-module overrides MAY set ignore_missing_imports = true for apache_beam.* only. Any MUST NOT appear in the public signatures of beam_agents.__init__. The type make target MUST run the full strict pass.
Scenario: Missing type hint fails typecheck
- WHEN a contributor adds a public function to
src/beam_agents/without a return-type annotation - THEN
make typeexits non-zero with a strict-mode error
Scenario: Beam stub gaps do not fail typecheck
- WHEN
src/importsapache_beam.transformsand calls its API - THEN
make typesucceeds without stub warnings for Beam modules
Requirement: pytest uses a closed marker registry with strict mode
pytest configuration SHALL live in [tool.pytest.ini_options] and MUST set addopts to include --strict-markers and --strict-config, asyncio_mode = "auto", and a default timeout. The registered markers MUST include exactly integration, semantics, dataflow, and slow. Using an unregistered marker MUST fail the test session.
Scenario: Unregistered marker is an error
- WHEN a contributor decorates a test with
@pytest.mark.integrtaion(typo) - THEN
pytestexits non-zero reporting the unknown marker
Scenario: Default run excludes integration and dataflow tiers
- WHEN a contributor runs
make test-unitwith docker down - THEN the run completes successfully and no
integration,semantics, ordataflow-marked test executes
Requirement: pre-commit enforces gates locally before push
A .pre-commit-config.yaml SHALL wire hooks for ruff check --fix, ruff format, mypy on staged files, protobuf generation drift, and a local hook that blocks commits touching src/ when no openspec/changes/<name>/ directory is present in the working tree or referenced by the commit message. An escape-hatch environment variable BEAM_AGENTS_ALLOW_NO_CHANGE=1 MUST bypass the OpenSpec hook only.
Scenario: Committing to src/ without a change fails
- WHEN a contributor stages a change under
src/beam_agents/with noopenspec/changes/*/directory present - THEN
git commitis rejected by the pre-commit hook with a message pointing to the OpenSpec workflow
Scenario: Protobuf drift blocks commit
- WHEN a contributor edits a
.protofile but forgets to regenerate the_pb2.py - THEN the pre-commit protobuf hook exits non-zero
Requirement: Docker compose provides Redpanda, Redis, and Flink for integration tests
docker/compose.yaml SHALL define services redpanda, redis, and flink (jobmanager + taskmanager), each pinned by image digest. Ports MUST be namespaced away from defaults: Kafka on 19092, Redis on 16379, Flink JobManager on 18081. Each service MUST declare a healthcheck. make compose-up MUST bring the stack up and make compose-down MUST tear it down cleanly.
Scenario: Compose stack starts healthy
- WHEN a contributor runs
make compose-up - THEN within 60 seconds all three services report healthy via
docker compose ps
Scenario: Unit tests pass with compose down
- WHEN a contributor runs
make test-unitwith no docker services running - THEN the run completes successfully with no skipped tests attributable to missing services
Requirement: GitHub Actions workflows mirror the testing tiers
The repository SHALL define five workflows under .github/workflows/: ci.yml, integration.yml, quality.yml, nightly.yml, and website.yml. ci.yml MUST run a matrix of Python 3.11, 3.12 on ubuntu-latest and macos-latest, executing make lint type test-unit. integration.yml MUST run make compose-up test-integration test-semantics on ubuntu-latest. quality.yml MUST run make mutation plus a coverage-ratchet check against main, where the mutation step runs when src/beam_agents/core/** or tests/core/** changed and MUST fail the workflow on a surviving mutant. nightly.yml MUST run on a 0 7 * * * schedule and on workflow_dispatch, authenticating to GCP via Workload Identity Federation and running -m dataflow tests, and MUST additionally run make mutation in a job that requires no cloud credentials and is not conditioned on repository variables or secrets. website.yml MUST run make site-check on ubuntu-latest, triggered by changes to website/**, src/**, docs/**, openspec/specs/**, or its own file, with a pinned Node version and a cached dependency install. ci, integration, and quality MUST be marked required for merge into main; whether website is required is a repository setting outside this requirement.
Scenario: CI workflow runs on pull request
- WHEN a pull request targeting
mainis opened - THEN
ci.yml,integration.yml, andquality.ymlall trigger and their success is required before merge
Scenario: CI matrix excludes Python 3.10
- WHEN
ci.yml'spython-versionmatrix is inspected - THEN it contains exactly
3.11and3.12, and does not contain3.10
Scenario: Nightly workflow uses Workload Identity Federation
- WHEN
nightly.ymlexecutes - THEN authentication to GCP happens via
google-github-actions/auth@v2withworkload_identity_providerand no long-lived service-account JSON key appears in secrets
Scenario: Nightly workflow no-ops without configured GCP project
- WHEN
nightly.ymlruns andvars.GCP_PROJECT_IDis unset - THEN the dataflow job is skipped with a clear log message and the workflow exits successfully
Scenario: Quality workflow mutation step triggers on core test changes
- WHEN a pull request changes only files under
tests/core/ - THEN
quality.yml's change detection selects the mutation step rather than skipping it
Scenario: Quality workflow fails on a surviving mutant
- WHEN
quality.yml's mutation step completes with a surviving mutant incore/ - THEN the step exits non-zero and the required
qualitycheck fails
Scenario: Nightly workflow runs the mutation sweep unconditionally
- WHEN
nightly.ymlis inspected - THEN it contains a job invoking
make mutationwhose execution is not conditioned onvars.GCP_PROJECT_IDor on provider API-key secrets
Scenario: Website workflow triggers on a runtime change
- WHEN a pull request modifies a file under
src/beam_agents/ - THEN
website.ymltriggers and runsmake site-check
Scenario: Website workflow does not run for unrelated changes
- WHEN a pull request modifies only files under
docker/ - THEN
website.ymldoes not trigger
Requirement: Makefile is the single contract between local and CI
A top-level Makefile SHALL expose targets bootstrap, fmt, lint, type, test-unit, test-integration, test-semantics, mutation, compose-up, compose-down, proto, site-dev, site-build, and site-check. CI workflows MUST invoke only make <target> for their primary steps (setup steps such as uv sync or a Node setup action are exempt). A contributor running make <target> locally MUST get the same behaviour as CI for that target. The site-* targets MUST be the only targets requiring a Node toolchain, and site-check is the only site target that may additionally require the uv environment.
Scenario: CI step invokes a make target
- WHEN any CI workflow's primary build/test step is inspected
- THEN the step's shell command is of the form
make <target>(setup steps excepted)
Scenario: Local lint matches CI lint
- WHEN a contributor runs
make linton their machine and pushes the same commit - THEN the
ci-lintjob reports the same pass/fail result for that commit
Scenario: Python targets run without Node
- WHEN a contributor with no Node toolchain runs
make bootstrap lint type test-unit - THEN every target completes normally and none reports a missing Node or package-manager binary
Scenario: Site build runs without the Python environment
- WHEN a contributor with no
.venv/runsmake site-build - THEN the target completes successfully
Requirement: Public API surface is typed and governed by the 1.0 freeze
src/beam_agents/__init__.py SHALL exist and mypy --strict MUST pass on the module. The surface started empty at scaffolding time and SHALL grow only through OpenSpec changes; as of the 1.0 API freeze, the names it exposes are governed by the public-api capability and its committed surface snapshot rather than by this requirement.
Importing the package MUST NOT perform I/O, spawn threads, mutate global state, or import optional dependencies — adapter frameworks, effector transport clients, and exporter protos MUST stay out of the import graph until their feature is used. The single sanctioned piece of import-time indirection is the module-level __getattr__ that lazily resolves optional-extra adapter classes.
Scenario: Fresh import is side-effect free
- WHEN a contributor imports
beam_agentsin a clean interpreter with no optional extras installed - THEN the import succeeds without network or filesystem access, no threads are spawned, and no optional-extra module (LangGraph, effector transports, opentelemetry-proto) appears in
sys.modules
Scenario: Public surface matches the committed snapshot
- WHEN the surface test derives the root module's
__all__and public names from source - THEN they equal the
public-apisnapshot's record forbeam_agents/__init__.py, and any deviation fails the offlinecilane
What backs this page
- Specification
- openspec/specs/repo-scaffolding/spec.md
- Test
- tests/test_import.py