From b1ded26a0a5cba5f5c1f1a8cccaedb950c73fc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kris=20Kr=C3=BCg?= Date: Sat, 18 Jul 2026 15:48:59 -0700 Subject: [PATCH 1/2] chore: adopt shared Varlock schema imports Selectively import confirmed reusable credentials, replace stale agent guidance, and add isolated contract coverage for schema tracking, runtime names, audit exclusions, and redacted Varlock execution. --- .env.schema | 16 +- .gitignore | 1 + AGENTS.md | 3 +- README.md | 6 +- .../fixtures/varlock/shared/.env.schema | 5 + .../varlock/values/.env.fixture-sanitized | 1 + tests/agentic/test_varlock_contract.py | 271 ++++++++++++++++++ 7 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 tests/agentic/fixtures/varlock/shared/.env.schema create mode 100644 tests/agentic/fixtures/varlock/values/.env.fixture-sanitized create mode 100644 tests/agentic/test_varlock_contract.py diff --git a/.env.schema b/.env.schema index f2667b5..12225df 100644 --- a/.env.schema +++ b/.env.schema @@ -1,7 +1,11 @@ # This env file uses @env-spec - see https://varlock.dev/env-spec for more info # +# @import(~/.agents/env/values/.env.shared.local, pick=[OPENAI_API_KEY,ANTHROPIC_API_KEY,NOTION_API_KEY,NOTION_DATABASE_ID,GOOGLE_API_KEY,LINEAR_API_KEY,WHISPERX_HF_TOKEN,HF_TOKEN], allowMissing=true) +# @import(~/.agents/env/values/.env.spektorai.local, pick=[OPENAI_API_KEY,ANTHROPIC_API_KEY,NOTION_API_KEY,NOTION_DATABASE_ID,GOOGLE_API_KEY,LINEAR_API_KEY,WHISPERX_HF_TOKEN,HF_TOKEN], allowMissing=true) # @defaultRequired=infer @defaultSensitive=false -# ---------- +# --- + +# Varlock 1.10 audit misses os.environ.get, helper indirection, and workflow-fed references. # Provider credentials. # @sensitive @optional @@ -12,7 +16,7 @@ ANTHROPIC_API_KEY= NOTION_API_KEY= # @sensitive @optional NOTION_DATABASE_ID= -# @sensitive @optional +# @sensitive @optional @auditIgnore GOOGLE_API_KEY= # @sensitive @optional SERVICE_TOKEN= @@ -96,15 +100,15 @@ BROWSER_E2E_FRESH_PORT= WHISPERFORGE_E2E_FIXTURE_PATH= # Agentic development controls. -# @optional +# @optional @auditIgnore AGENTIC_PROVIDER=noop # @sensitive @optional @auditIgnore AGENTIC_PROVIDER_COMMAND= -# @optional +# @optional @auditIgnore LOOP_PAUSED= -# @sensitive @optional +# @sensitive @optional @auditIgnore ISSUE_BODY= -# @optional +# @optional @auditIgnore ISSUE_LABELS= # Candidate providers stay outside this contract until a runtime integration exists. diff --git a/.gitignore b/.gitignore index 2187ecd..13c05b6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .env* !.env.example !.env.schema +!tests/agentic/fixtures/varlock/values/.env.fixture-sanitized # Python __pycache__/ diff --git a/AGENTS.md b/AGENTS.md index 15a5101..a971be2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,9 +10,10 @@ spektorAI is the home of WhisperForge, a local-first Streamlit voice-to-knowledg - public copy follows the house voice: see kk-brand/VOICE.md. full punk, no em dashes. - business logic lives in `whisperforge_core/`. UI and CSS live in `ui/` and `styles.py`. `app.py` is just the composition root. - the Makefile is the command surface. `make app` runs it, `make test` runs the suite, `make smoke` boots the shell and hits health, `make docs-check` checks doc truth. -- read `.env.schema` for the Varlock env contract. do not read `.env` or `.env.local`, and do not print, copy, rotate, or commit real secret values. +- read `.env.schema` for the authoritative Varlock env contract. reusable values may come from the selective, optional imports under `~/.agents/env/values/`; never create or inspect that directory, any `.env*` value file, the process environment, Keychain, or another credential store. - use `make env-check` for Varlock's agent-safe env summary. if Varlock is not on `PATH`, run `VARLOCK=/path/to/varlock make env-check`. - run real provider/service commands through `varlock run --inject vars -- `. keep offline `make app` behavior dummy-env friendly. +- keep application runtimes unchanged. Varlock 1.10 validation uses Node 22.3+ or the standalone CLI. - routing stays dry-run by default. handoffs to GitHub/Linear/Notion, paid-provider swaps, deploys, and external sends need explicit human approval. do not flip those on your own. - no open-source license is granted. treat the code as source-available for collaboration, not freely redistributable. diff --git a/README.md b/README.md index cf4ae4c..8eec6ba 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ make app # → http://localhost:8501 ## environment contract -`.env.example` is the human setup template. `.env.schema` is the Varlock contract agents can read for env names, defaults, and sensitivity. Do not read, print, copy, or commit real `.env` or `.env.local` values. +`.env.example` is the human setup template. `.env.schema` is the authoritative Varlock contract agents can read for env names, defaults, and sensitivity. It selectively imports reusable values from `~/.agents/env/values/.env.shared.local` and optional SpektorAI overrides from `~/.agents/env/values/.env.spektorai.local`; both imports allow missing files so CI and other machines stay portable. The user owns those local files. Agents must not create or inspect that directory, any `.env*` value file, the process environment, Keychain, or another credential store. Use `make env-check` to run Varlock's agent-safe schema check. The command uses agent mode so runtime values stay redacted. If Varlock is not on `PATH`, point the Makefile at the CLI: @@ -46,6 +46,8 @@ Use `make env-check` to run Varlock's agent-safe schema check. The command uses VARLOCK=/path/to/varlock make env-check ``` +Keep the application runtime unchanged. Varlock 1.10 validation requires Node 22.3+ or the standalone CLI. + Run a credential-free fixture check through the same runtime injection boundary: ```bash @@ -56,6 +58,8 @@ varlock run --inject vars -- make eval-fixture The schema covers the providers and transcription backends implemented in this repository. Candidate providers stay outside the runtime contract until their integrations exist. +GitHub-owned `GH_TOKEN`, `GITHUB_*`, and `RUNNER_*` names plus container-only `PYTHONPATH` and `PYTHONUNBUFFERED` settings are intentionally external to the application schema. Make-only `PYTHON`, `PORT`, `SMOKE_PORT`, `COMPOSE`, and `VARLOCK` overrides are command-surface controls and stay external too. + full setup, the provider matrix, and the run-recovery flow live in the engine handbook, [`WHISPERFORGE.md`](WHISPERFORGE.md). roadmap: `ROADMAP.md`. current handoff state: `STATUS.md`. ## these repos run on agents diff --git a/tests/agentic/fixtures/varlock/shared/.env.schema b/tests/agentic/fixtures/varlock/shared/.env.schema new file mode 100644 index 0000000..06bdc33 --- /dev/null +++ b/tests/agentic/fixtures/varlock/shared/.env.schema @@ -0,0 +1,5 @@ +# @import(../values/.env.fixture-sanitized, pick=[VARLOCK_REDACTION_PROOF], allowMissing=true) +# --- + +# @sensitive @required +VARLOCK_REDACTION_PROOF= diff --git a/tests/agentic/fixtures/varlock/values/.env.fixture-sanitized b/tests/agentic/fixtures/varlock/values/.env.fixture-sanitized new file mode 100644 index 0000000..5b903d8 --- /dev/null +++ b/tests/agentic/fixtures/varlock/values/.env.fixture-sanitized @@ -0,0 +1 @@ +VARLOCK_REDACTION_PROOF=fixture-varlock-redaction-proof diff --git a/tests/agentic/test_varlock_contract.py b/tests/agentic/test_varlock_contract.py new file mode 100644 index 0000000..49e9e80 --- /dev/null +++ b/tests/agentic/test_varlock_contract.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import os +import re +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +SCHEMA = ROOT / ".env.schema" +FIXTURE = ROOT / "tests" / "agentic" / "fixtures" / "varlock" / "shared" +VARLOCK = shutil.which("varlock") +SANITIZED_VALUE = "fixture-varlock-redaction-proof" +SHARED_NAMES = ( + "OPENAI_API_KEY", + "ANTHROPIC_API_KEY", + "NOTION_API_KEY", + "NOTION_DATABASE_ID", + "GOOGLE_API_KEY", + "LINEAR_API_KEY", + "WHISPERX_HF_TOKEN", + "HF_TOKEN", +) +RUNTIME_ROOTS = { + "app.py", + "services", + "shared", + "scripts", + "styles.py", + "ui", + "whisperforge.py", + "whisperforge_core", +} +EXCLUDED_PARTS = { + ".venv", + "ENV", + "archive", + "archives", + "backups", + "build", + "dist", + "fixtures", + "generated", + "tests", + "venv", + "whisperforge-env", +} +INDIRECT_RUNTIME_NAMES = { + "AGENTIC_PROVIDER_COMMAND", + "WF_RAG", + "WHISPERFORGE_E2E_FIXTURE_PATH", +} +VARLOCK_AUDIT_IGNORE_NAMES = { + "AGENTIC_PROVIDER", + "AGENTIC_PROVIDER_COMMAND", + "GOOGLE_API_KEY", + "ISSUE_BODY", + "ISSUE_LABELS", + "LOOP_PAUSED", + "WF_RAG", + "WHISPERFORGE_E2E_FIXTURE_PATH", +} +ENV_REFERENCE = re.compile( + r"(?:os\.getenv|os\.environ\.get)\(\s*['\"]([A-Z][A-Z0-9_]*)['\"]" +) +SCHEMA_DECLARATION = re.compile(r"^([A-Z][A-Z0-9_]*)=", re.MULTILINE) + + +def is_runtime_path(path: Path) -> bool: + return path.parts[0] in RUNTIME_ROOTS and not EXCLUDED_PARTS.intersection( + path.parts + ) + + +class VarlockContractTests(unittest.TestCase): + def test_schema_is_tracked_while_value_files_stay_ignored(self) -> None: + tracked = subprocess.run( + ["git", "ls-files", "--error-unmatch", ".env.schema"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(tracked.returncode, 0) + + schema_ignore = subprocess.run( + ["git", "check-ignore", "--no-index", ".env.schema"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(schema_ignore.returncode, 1) + + values_ignore = subprocess.run( + [ + "git", + "check-ignore", + "--no-index", + ".env.local", + ".env.shared.local", + ".env.spektorai.local", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(values_ignore.returncode, 0) + + def test_shared_imports_are_selective_optional_and_non_authoritative(self) -> None: + schema = SCHEMA.read_text(encoding="utf-8") + picks = ",".join(SHARED_NAMES) + + for source in ( + "~/.agents/env/values/.env.shared.local", + "~/.agents/env/values/.env.spektorai.local", + ): + self.assertIn( + f"# @import({source}, pick=[{picks}], allowMissing=true)", schema + ) + + import_lines = "\n".join( + line for line in schema.splitlines() if line.startswith("# @import(") + ) + self.assertNotIn("SERVICE_TOKEN", import_lines) + self.assertTrue( + set(SHARED_NAMES).issubset(set(SCHEMA_DECLARATION.findall(schema))) + ) + + def test_active_tracked_runtime_names_are_declared(self) -> None: + tracked = subprocess.run( + ["git", "ls-files", "-z", "--", "*.py"], + cwd=ROOT, + capture_output=True, + check=True, + ).stdout.decode().split("\0") + runtime_paths = [ + Path(name) for name in tracked if name and is_runtime_path(Path(name)) + ] + referenced = set(INDIRECT_RUNTIME_NAMES) + + self.assertIn(Path("whisperforge_core/config.py"), runtime_paths) + + for path in runtime_paths: + referenced.update( + ENV_REFERENCE.findall((ROOT / path).read_text(encoding="utf-8")) + ) + + declared = set( + SCHEMA_DECLARATION.findall(SCHEMA.read_text(encoding="utf-8")) + ) + self.assertIn("OPENAI_API_KEY", referenced) + self.assertIn("TRANSCRIPTION_BACKEND", referenced) + self.assertEqual(referenced - declared, set()) + + def test_names_only_audit_excludes_non_runtime_noise(self) -> None: + self.assertTrue(is_runtime_path(Path("whisperforge_core/config.py"))) + for path in ( + "venv/lib/python/site-packages/provider.py", + "generated/runtime.py", + "archive/legacy.py", + "tests/fixtures/runtime.py", + ): + with self.subTest(path=path): + self.assertFalse(is_runtime_path(Path(path))) + + schema = SCHEMA.read_text(encoding="utf-8") + for name in VARLOCK_AUDIT_IGNORE_NAMES: + with self.subTest(name=name): + self.assertRegex( + schema, + rf"# [^\n]*@auditIgnore[^\n]*\n{re.escape(name)}=", + ) + + def test_external_runtime_names_are_documented(self) -> None: + contract = (ROOT / "README.md").read_text(encoding="utf-8") + + for name in ( + "GH_TOKEN", + "GITHUB_*", + "RUNNER_*", + "PYTHONPATH", + "PYTHONUNBUFFERED", + "PYTHON", + "PORT", + "SMOKE_PORT", + "COMPOSE", + "VARLOCK", + ): + with self.subTest(name=name): + self.assertIn(f"`{name}`", contract) + + @unittest.skipUnless(VARLOCK, "standalone Varlock CLI is not installed") + def test_redacted_load_and_noop_run_use_an_isolated_home(self) -> None: + with tempfile.TemporaryDirectory() as home: + isolated_env = { + "HOME": home, + "PATH": f"{Path(str(VARLOCK)).parent}{os.pathsep}{os.defpath}", + } + version = subprocess.run( + [str(VARLOCK), "--version"], + cwd=ROOT, + env=isolated_env, + capture_output=True, + text=True, + check=True, + ).stdout.strip() + self.assertGreaterEqual(tuple(map(int, version.split("."))), (1, 10, 0)) + + self._run_varlock( + ["load", "--agent", "--show-all", "--path", ROOT], isolated_env + ) + self._run_varlock( + [ + "run", + "--inject", + "vars", + "--path", + ROOT, + "--", + sys.executable, + "-c", + "pass", + ], + isolated_env, + ) + output = self._run_varlock( + ["load", "--agent", "--show-all", "--path", FIXTURE], isolated_env + ) + self.assertNotIn(SANITIZED_VALUE, output) + self._run_varlock( + [ + "run", + "--inject", + "vars", + "--path", + FIXTURE, + "--", + sys.executable, + "-c", + ( + "import os,sys;" + "sys.exit(os.getenv('VARLOCK_REDACTION_PROOF') != " + f"'{SANITIZED_VALUE}')" + ), + ], + isolated_env, + ) + + def _run_varlock(self, arguments: list[str | Path], env: dict[str, str]) -> str: + result = subprocess.run( + [str(VARLOCK), *(str(argument) for argument in arguments)], + cwd=ROOT, + env=env, + capture_output=True, + text=True, + check=False, + ) + output = result.stdout + result.stderr + self.assertEqual(result.returncode, 0, output) + self.assertNotIn(SANITIZED_VALUE, output) + return output + + +if __name__ == "__main__": + unittest.main() From 01948a0ac68d0b5ef5a895b23443c8ef3c849083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kris=20Kr=C3=BCg?= Date: Sat, 18 Jul 2026 17:01:07 -0700 Subject: [PATCH 2/2] fix: keep database id out of shared credentials --- .env.schema | 4 ++-- tests/agentic/test_varlock_contract.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.env.schema b/.env.schema index 12225df..5b40841 100644 --- a/.env.schema +++ b/.env.schema @@ -1,7 +1,7 @@ # This env file uses @env-spec - see https://varlock.dev/env-spec for more info # -# @import(~/.agents/env/values/.env.shared.local, pick=[OPENAI_API_KEY,ANTHROPIC_API_KEY,NOTION_API_KEY,NOTION_DATABASE_ID,GOOGLE_API_KEY,LINEAR_API_KEY,WHISPERX_HF_TOKEN,HF_TOKEN], allowMissing=true) -# @import(~/.agents/env/values/.env.spektorai.local, pick=[OPENAI_API_KEY,ANTHROPIC_API_KEY,NOTION_API_KEY,NOTION_DATABASE_ID,GOOGLE_API_KEY,LINEAR_API_KEY,WHISPERX_HF_TOKEN,HF_TOKEN], allowMissing=true) +# @import(~/.agents/env/values/.env.shared.local, pick=[OPENAI_API_KEY,ANTHROPIC_API_KEY,NOTION_API_KEY,GOOGLE_API_KEY,LINEAR_API_KEY,WHISPERX_HF_TOKEN,HF_TOKEN], allowMissing=true) +# @import(~/.agents/env/values/.env.spektorai.local, pick=[OPENAI_API_KEY,ANTHROPIC_API_KEY,NOTION_API_KEY,GOOGLE_API_KEY,LINEAR_API_KEY,WHISPERX_HF_TOKEN,HF_TOKEN], allowMissing=true) # @defaultRequired=infer @defaultSensitive=false # --- diff --git a/tests/agentic/test_varlock_contract.py b/tests/agentic/test_varlock_contract.py index 49e9e80..5e770ba 100644 --- a/tests/agentic/test_varlock_contract.py +++ b/tests/agentic/test_varlock_contract.py @@ -19,7 +19,6 @@ "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "NOTION_API_KEY", - "NOTION_DATABASE_ID", "GOOGLE_API_KEY", "LINEAR_API_KEY", "WHISPERX_HF_TOKEN",