nvcf / bdd
AGENTS.md261 starsChanged 9 days agoApache-2.0View on GitHub
Scope: everything under tests/bdd/. This directory is the strict-DSL replacement for the legacy tests/bdd runner. The whole point is a Gherkin vocabulary that an AI can extend without inventing opaque domain helpers. Read PLAN.md before touching code. Steps describe what an operator types: copy files, edit YAML, run a shell command, assert against exit codes / file contents / JSON output. Step handlers in steps/ are thin wrappers around helpers in dsl/ and around harness.CommandRunner. Domain validation lives in Gherkin…
- Reads credentials
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository133 other files it gives its agents.
- AGENTS.mdAGENTS.md
- ai-tooling/AGENTS.mdAGENTS.md
- deploy/helm/cassandra/AGENTS.mdAGENTS.md
- deploy/helm/cert-manager/AGENTS.mdAGENTS.md
- deploy/helm/cloud-functions/AGENTS.mdAGENTS.md
- deploy/helm/cloud-tasks/AGENTS.mdAGENTS.md
- deploy/helm/encrypted-secret-store/AGENTS.mdAGENTS.md
- deploy/helm/icms/AGENTS.mdAGENTS.md
- deploy/helm/llm-request-router/AGENTS.mdAGENTS.md
- deploy/helm/nats/AGENTS.mdAGENTS.md
- deploy/helm/notary/AGENTS.mdAGENTS.md
- deploy/helm/nvcf-pki/AGENTS.mdAGENTS.md
- deploy/helm/openbao/AGENTS.mdAGENTS.md
- deploy/helm/vanity-gateway/AGENTS.mdAGENTS.md
- deploy/releases/AGENTS.mdAGENTS.md
- deploy/stacks/AGENTS.mdAGENTS.md
- deploy/stacks/observability/AGENTS.mdAGENTS.md
- deploy/stacks/stargate-dev/AGENTS.mdAGENTS.md
- deploy/stacks/stargate-dev/benchmark/AGENTS.mdAGENTS.md
- docs/AGENTS.mdAGENTS.md
- docs/overview/samples/scripts/AGENTS.mdAGENTS.md
- examples/AGENTS.mdAGENTS.md
- infra/cassandra/AGENTS.mdAGENTS.md
- infra/openbao/AGENTS.mdAGENTS.md
- infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.mdAGENTS.md
- infra/openbao/tests/AGENTS.mdAGENTS.md
- migrations/cassandra/AGENTS.mdAGENTS.md
- migrations/openbao/AGENTS.mdAGENTS.md
- src/compute-plane-services/byoo-otel-collector/AGENTS.mdAGENTS.md
- src/compute-plane-services/ess-agent/AGENTS.mdAGENTS.md
- src/compute-plane-services/image-credential-helper/AGENTS.mdAGENTS.md
- src/compute-plane-services/nvca/AGENTS.mdAGENTS.md
- src/compute-plane-services/nvsnap/docs/AGENTS.mdAGENTS.md
- src/compute-plane-services/nvstor/AGENTS.mdAGENTS.md
- src/compute-plane-services/request-trace-uploader/AGENTS.mdAGENTS.md
- src/control-plane-services/admin-token-issuer-proxy/AGENTS.mdAGENTS.md
- src/control-plane-services/api-keys/AGENTS.mdAGENTS.md
- src/control-plane-services/cloud-functions/AGENTS.mdAGENTS.md
- src/control-plane-services/cloud-tasks/AGENTS.mdAGENTS.md
- src/control-plane-services/encrypted-secret-store/AGENTS.mdAGENTS.md
- src/control-plane-services/function-autoscaler/AGENTS.mdAGENTS.md
- src/control-plane-services/function-autoscaler/local_env/AGENTS.mdAGENTS.md
- src/control-plane-services/instance-cluster-management/AGENTS.mdAGENTS.md
- src/control-plane-services/nats-auth-callout/AGENTS.mdAGENTS.md
- src/control-plane-services/notary/AGENTS.mdAGENTS.md
- src/invocation-plane-services/grpc-proxy/AGENTS.mdAGENTS.md
- src/invocation-plane-services/http-invocation/AGENTS.mdAGENTS.md
- src/invocation-plane-services/llm-api-gateway/AGENTS.mdAGENTS.md
- src/invocation-plane-services/ratelimiter/AGENTS.mdAGENTS.md
- src/invocation-plane-services/vanity-gateway/AGENTS.mdAGENTS.md
- src/libraries/go/lib/AGENTS.mdAGENTS.md
- src/libraries/go/worker/AGENTS.mdAGENTS.md
- src/libraries/java/nv-boot-parent/AGENTS.mdAGENTS.md
- src/libraries/python/nv-cloud-function-helpers/AGENTS.mdAGENTS.md
- src/libraries/rust/nvcf-info/AGENTS.mdAGENTS.md
- src/libraries/rust/stargate/AGENTS.mdAGENTS.md
- src/uis/nvcf-ui/AGENTS.mdAGENTS.md
- src/uis/nvcf-ui/helm/AGENTS.mdAGENTS.md
- tools/AGENTS.mdAGENTS.md
- tools/docs-version-sync/AGENTS.mdAGENTS.md
- The first 60 shown.
The file
We split this AGENTS.md into the rules it gives an agent
12 rules. About 0 are common, 3 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 18
Use a shared step when a repeated operator action or observable keeps every meaningful target, value, context, and timeout visible while …
- Very specific: line 189
Do not introduce blanket helm list-based uninstall or namespace deletion that catches topology infrastructure (eg in envoy-gateway-system, …
- Very specific: line 265
Every Go file under tests/bdd/ carries the SPDX header in .goheader.tmpl.
- Distinctive: line 267
Run the non-live tests before pushing:
- Distinctive: line 302
Avoid trivial forwarding helpers.
- Very specific: line 314
Read PLAN.md and confirm every step in your draft is in the catalog.
- Very specific: line 317
Write the feature file in features/.
- Very specific: line 326
Do not add opaque composite steps such as Given the stack is installed.
- Very specific: line 328
Add the row to PLAN.md first: regex, table/docstring shape, one sentence of behavior.
- Very specific: line 332
If the handler needs a pure operation, put it in dsl/ with a unit test.
- Very specific: line 334
Add a positive unit test in steps/steps_test.go driving the handler against a fake CommandRunner.
- Very specific: line 339
Every live run writes a fresh directory under tests/bdd/out/:
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md - tests/bdd
Scope: everything under tests/bdd/.
This directory is the strict-DSL replacement for the legacy tests/bdd
runner. The whole point is a Gherkin vocabulary that an AI can extend
without inventing opaque domain helpers. Read PLAN.md before touching code.
The strict-DSL contract
Steps describe what an operator types: copy files, edit YAML, run a
shell command, assert against exit codes / file contents / JSON
output. Step handlers in steps/ are thin wrappers around helpers in
dsl/ and around harness.CommandRunner. Domain validation lives in
Gherkin via When I run command plus an output assertion, never inside
a handler.
Use a shared step when a repeated operator action or observable keeps every
meaningful target, value, context, and timeout visible while hiding only
command or output-format mechanics. Keep When I run command plus an
assertion as the escape hatch for uncommon or command-specific behavior.
Function and task lifecycle steps, plus API key generation for their backing
services and Event Ledger, are a narrow command-adapter exception. They
store only the selected nvcf-cli config, pass visible arguments to one CLI
command, and preserve the real command result. They do not store function or
task identity, apply defaults, parse or normalize product values, or enforce
product preconditions. The task status assertion is a bounded test-runner
poll of task get --json that compares .task.status with an exact string and
classifies no status as terminal. Their successfully wording asserts exit code 0 to keep happy
paths compact. Use raw command steps for negative and exit-code-specific cases
so nvcf-cli and the NVCF API remain the product-validation boundary.
Layering
harness/owns suite lifecycle:Config,CommandRunner,Ledger,CommandCache,Suite. Step handlers depend on these; nothing else does.dsl/owns pure helpers:${VAR}interpolation, dotted-path YAML upsert and read, YAML subtree match/contain, self-managed secrets rendering, kubectl manifest builders, JSON row matching. Every helper is unit-testable in isolation. No I/O coordination, no Godog dependency.steps/owns Godog step handlers andScenarioContext. Each handler is one or two lines plus a delegate to adslhelper orSuite.Runner.godog_test.goowns the live entry points and the fake-runner wiring tests.
A step handler that does anything beyond argv assembly, ledger
snapshot, runner invocation, and result capture is a smell. Move the
logic into dsl/.
Vocabulary rules
- Feature assertions must not hard-code released component versions. Verify artifact identity without the tag, or derive the expected version from the authoritative stack or chart configuration.
${VAR}interpolation is the only env-var form the DSL recognizes; a bare$wordis left literal. Implementations must not useos.ExpandEnv. Expansion lives indsl.Interpolate.- Function and task lifecycle CLI option tables preserve row order, repeated options,
empty values, and product-invalid values. Only the
option | valuetable structure is validated before the command runs. - The Helm function lifecycle uses
SAMPLE_HELM_FUNCTION_CHART. The chart must be publicly reachable by ReVal, expose anentrypointService on port 8000, answer/health, and echo the request message from/echo. Keep the chart reference outside the feature so release versions are not hard-coded. - The LLM field-fidelity scenario uses
SAMPLE_LLM_ECHO_IMAGE(default: the released in-repo sample), an OpenAI-compatible sample that replies with the request it received whenx_load_tester_echois true. Do not point it at the:localsample, which returns a fixed reply other scenarios assert. - The decision route scenarios (
@llm-decision-routes) useSAMPLE_LLM_DECISIONS_IMAGE, an in-repo sample image that serves/v1/decisionsand/v1/systemonewith a header echo and themock_errorquery controls. The scenarios are opt-in:TestSingleClusterHelmfileexcludes@llm-decision-routesunless the variable is set, andTestDecisionRouteScenariosSkipWithoutDecisionsImagecovers that. Do not default it ingodog_test.go. The scenarios sit after the field-fidelity scenario under a# Beta decision routescomment, whichTestLLMRoutingFeaturesRejectIncompleteAndMisroutedResponsesuses to stop its slice; their wiring test isTestDecisionRouteScenariosRejectBrokenGateways. - The BYOO feature reads its task sample from
SAMPLE_BYOO_TASK_IMAGEand its function sample fromSAMPLE_BYOO_FUNCTION_IMAGE. Tag every BYOO scenario that installs, upgrades, or removes a release with@stack-changesoTestBYOOExistingInstallnever changes an existing install. - The Vanity Gateway shadow sampling scenario reads shadow decisions from the gateway's metrics, because a client cannot see whether a request was shadowed. Its session keys and first messages are chosen for fixed hash buckets on either side of 50 percent; if the sampling hash or canonical first-message form changes, pick new values and update the comment.
- Gateway API route readiness tables expose each route's kind, name,
namespace, and intended Gateway parent plus the shared context and timeout.
The step requires
Accepted=TrueandResolvedRefs=Truefor that parent but does not allowlist route kinds or duplicate Gateway API validation. - File-mutating steps (
I copy the file,I write yaml file,I update yaml file,I prepare self-managed secrets file,I substitute a block) snapshot the destination throughSuite.Ledgerbefore the first write. Suite teardown restores every snapshotted path. CommandRunnertokenizes command text with shlex and execs the argv directly. There is no shell, so heredocs, pipes, and redirections only work inside an explicit/bin/bash -c '...'wrapper. Apply inline YAML throughKubernetes manifest ... is:plusI successfully apply Kubernetes manifest ... using contexts:instead ofkubectl apply -f -with a heredoc.Kubernetes manifest ... is:stores the raw docstring inScenarioContext.Manifests; the apply step interpolates${VAR}and writes the rendered body underout/<run-id>/before each explicit-contextkubectl apply. Declare manifests with the scenario's Givens even when they reference an env var exported later, and never interpolate at declaration time.Given command has succeeded:keys on the fully resolved command text. Two scenarios whose pre-interpolation text matches but whose env vars differ must miss the cache. The cache lives inSuite.Cache.- Bootstrap Givens (
a single-cluster ncp-local cluster is running,multi-cluster ncp-local compute clusters are running:,Helm is authenticated to OCI registry ...,the ... image pull secret exists in namespaces:) each wrap exactly one Make target or one Helm invocation. The image pull secret Given applies one namespace manifest and one docker-registry secret manifest per row. Caching is idempotent per suite; the underlying bootstrap runs at most once even if multiple scenarios name the Given. - The Helm OCI registry authentication Given reads
NGC_API_KEYfrom the process environment and passes it only throughCommandRunner.RunWithSensitiveStdin. The key must never be interpolated into command text, argv, command logs, captured output, or failure messages. - Features that bring up a
tools/ncp-local-clustertopology must include a conflict precheck in their Background before the bootstrap Given, asserting the OTHER topology is absent. UseI run command "k3d cluster get <conflicting-cluster>"followed bythe command exit code should be 1(k3d v5 exits 1 on "not found"). Single-cluster features (CLI and Helmfile) check forncp-local-cp; multi-cluster features check forncp-local. The Gherkin comment above the precheck must call out the exactmake destroycommand an operator runs to remediate. Both single- and multi-cluster control-plane k3d serverlbs claim overlapping host ports, including 8080, 8443, 10081, and NATS on 4222. The multi-cluster control plane also claims 10086 for the gRPC worker callback path. Leaving the wrong topology running causes the bootstrap Make target to fail deep inside k3d with a generic port-bind error; the precheck surfaces this immediately. harness.NewSuitesnapshots~/.nvcf-cli.nvcf-cli-local.statethrough the Ledger so the admin JWTnvcf-cli initwrites during a live run is restored (or removed) at suite teardown. HOME is intentionally NOT isolated: k3d, kubectl, docker, and helm all resolve their config under$HOME, and pointing HOME at an empty per-run directory breaks the bootstrap Givens. Subsequent self-hosted commands read the JWT back from the state file, so the token never appears in argv or per-command logs. Do not introduce step handlers that capture secrets into env vars; relying on the state file keeps the JWT out of<seq>.cmdlines.- The live runner installs SIGINT and SIGTERM cleanup before scenarios run. Interrupt cleanup cancels the active step and its Unix process group, waits for that step to stop writing, then restores the same file and environment ledgers while preventing later steps from starting. Ledger-backed generated registry credentials must not remain after an interrupted run.
- Pre-suite destructive cleanup is governed by the single env var
BDD_CLEANUP_MODE. Valid values:stack-single,stack-multi,topology-single,topology-multi, or unset. Unknown values fail the suite at start; the harness never silently downgrades to no-cleanup. The mode maps to one make target inharness/cleanup.goviacleanupCommandFor; that map is the single source of truth. Both the env-var path and the Make targets intools/ncp-local-cluster/Makefileanddeploy/stacks/self-managed/Makefileare intentionally maintained so an operator can clean by hand without involvinggo test. - Cleanup belongs in
harness/cleanup.go, never insteps/. Do not introduce aGiven the cluster is freshly destroyedGiven or similar; the conflict precheck inside every feature Background is the in-band assertion that cleanup worked. - Cleanup runs BEFORE the CLI state-file snapshot in
NewSuite. The snapshot captures the post-cleanup baseline (typically empty for destructive modes); teardown restores to that baseline. For destructive modes the operator's pre-suite JWT is intentionally not preserved because the cluster it pointed at is gone. - Governing rule for cleanup: topology cleanup may delete topology
resources, stack cleanup may only delete stack-owned resources
and stack artifacts. Topology cleanup is implemented as Make
targets in
tools/ncp-local-cluster/Makefile(destroy,destroy-all-ncp-local) because k3d cluster lifecycle belongs to the cluster-build tooling. Stack cleanup is implemented as a bash script attests/bdd/scripts/destroy-stack.shso the BDD-specific allow-lists, namespace lists, and kubectl/helm context plumbing stay co-located with the harness that owns them. Stack-owned releases and namespaces are explicit allow-lists at the top of the script (STACK_RELEASES_CP,STACK_RELEASES_WORKER,STACK_NAMESPACES_*,STACK_CRS_WORKER). Do not introduce blankethelm list-based uninstall or namespace deletion that catches topology infrastructure (eginenvoy-gateway-system, the namespace itself,cert-manager). Multi-cluster stack cleanup must also apply the worker release and namespace allow-lists onk3d-ncp-local-cp: feature Backgrounds createnvca-operator(and its pull secret) on the control-plane context, not only on compute clusters. Artifact cleanup must remove Helmfile render trees under each stackout/directory and generated values underdeploy/stacks/nvcf-compute-plane/registration/, not only root-levelout/*.yaml.
CLI vs Helmfile install paths
The suite exercises two operator workflows that share a stack but differ in how the control plane is installed. Future changes must keep the CLI install path independent of authored Helmfile values. They must also preserve the exported-profile handoff from a Helmfile control-plane install into compute registration.
- CLI path (
single-cluster-up.feature,multi-cluster-up.feature) is profile-driven.nvcf-cli self-hosted install --control-planewritesout/control-plane-profile.yamlwith both endpoint layers (inClusterpluscomputeReachable). The follow-upcompute-plane register --control-plane-profile <path> --kube-context <ctx>picks the right URL block based on the kube-context, probes JWKS, and emits a values file with the right URLs already baked in. The profile is the single source of truth. - Helmfile control-plane install is values-driven. The operator authors
environments/<env>.yaml, andmake installruns Helmfile sync. Before compute registration, export the selected installed environment withself-hosted --control-plane-stack <path> --env <env> control-plane profile export. The compute-planemake register-clustertarget consumes that file throughCONTROL_PLANE_PROFILEand passes it toself-hosted compute-plane register. Runnvcf-cli initexplicitly before registration.NVCF_CLI_CONFIGis optional and only selects the config and state files the registration command reads; the Make target does not runinit. In a local single-cluster flow, omit both persistent CLI context flags during profile export because the CLI requires either a valid split-cluster pair or neither. PassCOMPUTE_KUBE_CONTEXT=k3d-ncp-localto the compute target so registration probes the intended cluster.
For multi-cluster Helmfile the BDD fixture
fixtures/self-managed-local-bdd-multi.yaml carries the same
service-DNS URL shape as the single-cluster local fixture. In the
multi-cluster ncp-local topology, those names resolve to alias
Services in the compute cluster and the alias Endpoints point at the
control-plane LB. If those hostnames or ports ever change, keep the
multi fixture's selfManaged block, the local stack values, and the CLI
feature's profile assertion. A follow-up drift-detection check is
tracked separately (see commit history).
Two recurring failure modes worth remembering when editing either multi-cluster feature:
- Single-cluster URLs in a multi-cluster fixture. In-cluster DNS
(
api.sis.svc.cluster.localetc.) only resolves inside the control-plane k3d cluster. The NVCA agent on a separate compute cluster cannot dial those addresses. Symptom: NVCA agent inCrashLoopBackOffwithdial tcp ... connect: connection refusedagainst an in-cluster hostname. - Wrong kubectl context when
make register-clusterruns. Theself-hosted compute-plane registercommand discovers OIDC issuer and JWKS from its selected compute context, then registers that identity with ICMS. If the context is the cp cluster, ICMS records the cp cluster's JWKS for the compute cluster's row. The compute cluster's NVCA agent then 401s against ICMS at runtime ("Signed JWT rejected: ... no matching key(s) found"). Switch the context to the compute context explicitly throughCOMPUTE_KUBE_CONTEXT(or a compute-scoped kubeconfig) beforemake register-cluster, not after.
Tests
- Every Go file under
tests/bdd/carries the SPDX header in.goheader.tmpl.golangci-lintenforces this. Run the non-live tests before pushing:
cd tests/bdd go test -short ./... golangci-lint run --config .golangci.yml ./...tests/bddcarries its owngo.mod, so the lint invocation MUST run from insidetests/bdd/. Runninggolangci-lintagainst./tests/bdd/...from the repo root produces a confusing "no go files to analyze" or "directory prefix does not contain main module" error. Thetests/bdd/scripts/lint.shwrapper handles the cd internally and works from any cwd:tests/bdd/scripts/lint.sh- Wiring tests in
godog_test.goexercise feature files against a fakeCommandRunner. They assertstatus == 0plus one substring check that a destructive command was issued. Do not deep-equality the recorder; consolidating equivalent steps in the future must not break these tests. - Live entry points (
TestSingleClusterUp,TestMultiClusterUp,TestSingleClusterHelmfile) skip under-short. They build the CLI and exercise realmake/kubectl/helmagainst k3d.
Style
- Plain ASCII only in committed text. No bold, no em dashes, no smart quotes.
- Lower-case any identifier used only inside its own package. The interface satisfaction Godog enforces is not a reason to export a step handler.
- Avoid trivial forwarding helpers. Inline anything that is a single expression wrapped in a one-liner.
- Comments above important exported functions should say what the contract is, not restate the implementation.
Commit messages
- Conventional Commits. Common scopes:
bdd,dsl,harness,steps.
Adding a feature file
- Read
PLAN.mdand confirm every step in your draft is in the catalog. If something cannot be expressed, prefer rawWhen I run command+ an output assertion. - Write the feature file in
features/. - Seed any handoff artifacts in the matching wiring test inside
godog_test.go. - Confirm
go test -short ./...is green.
Adding a step
Adding a step is deliberate. Add one only for a repeated action or observable
that keeps meaningful inputs visible and has no hidden workflow branching.
Do not add opaque composite steps such as Given the stack is installed.
- Add the row to
PLAN.mdfirst: regex, table/docstring shape, one sentence of behavior. - Implement the handler in the matching
steps/*_steps.go. Keep it thin. - If the handler needs a pure operation, put it in
dsl/with a unit test. - Add a positive unit test in
steps/steps_test.godriving the handler against a fake CommandRunner.
Live-run output
Every live run writes a fresh directory under tests/bdd/out/:
out/<run-id>/logs/<seq>.{cmd,stdout,stderr}for every command the runner executed.out/<run-id>/originals/is reserved for an on-disk ledger variant if very large fixtures ever push memory limits.out/<run-id>/diagnostics/is reserved for Kubernetes diagnostics collection once the integration with the existing collector lands.
Restore happens automatically at suite teardown; the working tree should be clean after a green run.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

