Skip to content

ADR-0006 — Centralized .env Scaffold with Secrets Prompted at Generation Time

Status: accepted Deciders: DeAcero AI Agent Guild Date: 2026-03-18 Commits: d271ccd (fix(template): prompt for Squit API key and scaffold .env), 486c492 (fix(env): add missing env vars to .env and .env.example)


Context and Problem Statement

Cornerstone-generated projects depend on two categories of runtime secrets:

  1. SQUIT_API_KEY — API key for the SQUIT MCP semantic SQL search service (5.7M legacy SQL objects). Without it, all discovery tools that query SQUIT (squit_client.py, sql_topology.py, etc.) fail with cryptic HTTP 401 errors.
  2. AGENTIC_TELEMETRY_URL — URL of the central Observability Service. Without it, telemetry is a no-op (by design per ADR-0003), but teams that want observability must configure it manually.

Before this ADR's resolution, the template: - Did not include a .env file — teams had to create it from scratch - Did not prompt for the API key at generation time — teams discovered the requirement only when a tool failed - Had the SQUIT API key hardcoded in squit_client.py as a default argument (security anti-pattern) - Did not include a .gitignore — teams could accidentally commit secrets - Was missing AGENTIC_TELEMETRY_DEBUG and OTEL_EXPORTER_OTLP_ENDPOINT from .env despite being referenced in .telemetry/ code


Decision Drivers

  • Secrets must never be hardcoded in source files (OWASP A02 — Cryptographic Failures)
  • Teams must not have to read documentation to know what environment variables are required
  • Generated .env must never be committed to version control
  • Teams that do not have a SQUIT API key must be able to generate a project and fill in the key later
  • All env vars referenced in the template code must have a canonical home

Considered Options

  1. Template variables + pre-filled .env — add squit_api_key and agentic_telemetry_url to cookiecutter.json; generate .env with values filled in at generation time; add .gitignore
  2. Post-generation documentation only — keep .env.example, document manual copy step in README
  3. Vault / secrets manager integration — integrate HashiCorp Vault or GCP Secret Manager; .env is auto-populated at runtime from the vault
  4. GitHub Actions secrets only — manage secrets exclusively via repo secrets, not .env; local dev uses no secrets

Decision Outcome

Chosen option: Option 1 — Template variables + pre-filled .env at generation time.

Rationale:

  • Option 2 (documentation only) relies on teams reading documentation before running tools. The SQUIT key was hardcoded precisely because teams skipped the manual copy step — this is the problem to solve.
  • Option 3 (Vault/secrets manager) is the correct enterprise solution but adds significant infrastructure complexity for a Cookiecutter template targeting teams that may not have vault access configured. It is not ruled out for the future but cannot be the baseline.
  • Option 4 (GitHub Actions only) leaves local development broken. Teams run discovery tools and ADR gate checks locally, not only in CI.
  • Option 1 surfaces the requirement at the exact moment the team is paying attention: during cookiecutter gh:deagentic/cornerstone. Empty values are valid — the team can press Enter and fill them in later. The pre-filled .env makes the variable names and their purpose self-documenting.

Positive Consequences

  • Teams know what secrets are required before writing a single line of code
  • squit_client.py fails with a clear error message instead of a silent wrong-key HTTP 401
  • .gitignore prevents accidental secret commits from day one
  • All env vars referenced in the codebase have a single canonical location (.env)
  • .env.example serves as documentation for anyone who recreates the file

Negative Consequences

  • cookiecutter.json now prompts for values that many teams may not have yet (SQUIT key requires requesting from the platform admin)
  • Teams generating a project for the first time may be confused by the prompts if they have not been onboarded
  • Cookiecutter prompts do not support masked input — the API key is visible in the terminal during generation

Implementation Details

cookiecutter.json additions

{
  "squit_api_key": "",
  "agentic_telemetry_url": "",
  "_copy_without_render": [...]
}

Empty string defaults allow skipping with Enter. The values are rendered into .env via Jinja2 at generation time.

Generated .env

SQUIT_API_KEY={{cookiecutter.squit_api_key}}
AGENTIC_TELEMETRY_URL={{cookiecutter.agentic_telemetry_url}}
AGENTIC_TELEMETRY_DEBUG=
OTEL_EXPORTER_OTLP_ENDPOINT=

All four env vars referenced across .telemetry/, emit_ci_event.py, and squit_client.py are present. Optional vars have empty defaults.

.env.example

Committed to version control as a reference file with safe example values:

SQUIT_API_KEY=your_squit_api_key_here
AGENTIC_TELEMETRY_URL=http://192.99.38.188:8000
AGENTIC_TELEMETRY_DEBUG=
OTEL_EXPORTER_OTLP_ENDPOINT=

This allows a team member who deleted their .env to reconstruct it from the example without reading documentation.

.gitignore

The generated project ships with a .gitignore that excludes .env, output/, context/, __pycache__, .pytest_cache, and standard Python artifacts. The .gitignore is checked into the template as a literal file (not rendered by cookiecutter) so it applies immediately after generation.

squit_client.py — hardcoded key removal

The default argument --key was changed from a hardcoded string to:

parser.add_argument("--key", default=os.environ.get("SQUIT_API_KEY", ""))
...
if not args.key:
    print("[ERROR] SQUIT_API_KEY not set. Add it to your .env file or pass --key.", file=sys.stderr)
    sys.exit(1)

This makes the missing-key failure explicit, actionable, and points teams to the correct fix.

.gitkeep for required empty directories

Three directories expected by the agent workflow are created as empty directories with .gitkeep:

  • output/findings/ — archaeology ledger location
  • context/ — run context markdown files
  • experiments/ — isolated experiment probes

Without .gitkeep, git does not track empty directories and the paths referenced in AGENTS.md do not exist after cloning.


Pros and Cons of Options

Option 1: Template variables + pre-filled .env (chosen)

  • Good, because secrets are surfaced at generation time when the team is attentive
  • Good, because all env vars have a canonical location
  • Good, because .gitignore ships from day one
  • Bad, because SQUIT API key is visible in terminal during generation (no masked input)

Option 2: Documentation only

  • Good, because no prompts that might confuse teams without onboarding
  • Bad, because teams will skip the manual step — the pre-existing hardcoded key is evidence

Option 3: Vault / secrets manager

  • Good, because secrets never appear in files
  • Bad, because adds infrastructure dependency not appropriate for a Cookiecutter template baseline

Option 4: GitHub Actions secrets only

  • Good, because secrets are managed in a central, audited location
  • Bad, because local development breaks for all discovery tools and ADR gate checks

  • Related: ADR-0003 — opt-in telemetry via AGENTIC_TELEMETRY_URL
  • Related: ADR-0001 — Observability Service (defines AGENTIC_TELEMETRY_URL)
  • Files: cookiecutter.json, {{cookiecutter.project_slug}}/.env, {{cookiecutter.project_slug}}/.env.example, {{cookiecutter.project_slug}}/.gitignore