Skip to main content
← Back to list
01Issue
FeatureShippedUAT
Assigneesstack72

Relationships

#1124 UAT Phase 2: doctor install, secrets, and workflows

Opened by stack72 · 7/13/2026· Shipped 7/14/2026

Summary

Phase 2 (doctor family) adds dedicated tests for the three untested doctor subcommands: install, secrets, and workflows. The doctor family already has coverage for audit and extensions (aggregate state + repair) and vaults. This phase fills the remaining gaps.

3 new test files in existing tests/cli/doctor/ directory. Lands in the existing data-repo-doctor CI shard.

Dependency

Phase 8a landed. No other dependencies.

Existing State (do NOT duplicate)

These doctor test files already exist:

  • tests/cli/doctor/audit_test.ts
  • tests/cli/doctor/extensions/aggregate_state_test.ts
  • tests/cli/doctor/extensions/repair_test.ts
  • tests/cli/doctor/vaults_test.ts

Adversarial tests with related coverage (do NOT duplicate):

  • tests/cli/adversarial/sensitive_global_arg_at_rest_test.ts — covers the refusal path (preventing sensitive args from being persisted). Doctor secrets is different — it scans for existing cleartext already on disk.
  • tests/cli/adversarial/sensitive_vault_preflight_test.ts

Available fixtures for doctor secrets:

  • tests/cli/fixtures/extensions/models/test/sensitive-global-arg.ts — model type with sensitive: true global arg
  • tests/cli/fixtures/extensions/models/test/sensitive-args.ts
  • tests/cli/fixtures/extensions/models/test/sensitive-nested.ts

Verified Contracts

doctor install

  • Checks: binary path/version, owner (uid/root), writability (probe file <binary>.swamp-write-test), autoupdate prefs (~/.config/swamp/update.yaml) + scheduler status
  • Unhealthy (exit 1) iff: writable fail OR (autoupdate enabled AND scheduler missing) OR last log entry error
  • JSON: {binaryPath, owner:{uid, username, isRoot}, currentVersion, writable:"pass|fail|skip", writableMessage, autoupdate:{enabled, cadence, schedulerInstalled, schedulerType?, lastEntry}, overallStatus}
  • Log trailer: HEALTHY or UNHEALTHY
  • Does NOT require a repo — runs against the binary itself

doctor secrets

  • Scans models/ AND .swamp/auto-definitions/ for schema-sensitive: true global args holding cleartext literals (findLiteralSensitiveGlobalArgs)
  • Findings → exit 1; unresolved types alone do NOT fail
  • Never prints the actual secret value (leak safety)
  • Clean log: "✓ No cleartext sensitive global arguments found (N definition(s) scanned)"
  • JSON: {overallStatus, scanned, findings:[{definitionId, definitionName, type, leakedPaths, remediations}], unresolved:[…]}
  • Remediation message mentions swamp vault put
  • Requires an initialized repo with model definitions

doctor workflows

  • Loads every workflow YAML through the real parser (Workflow.fromData)
  • Per-file status: or + → <error>
  • Any failure → exit 1
  • JSON: {overallStatus, workflows:[{file, name, status, error?}], totalPassed, totalFailed}
  • Requires an initialized repo with workflow definitions

New Test Files

doctor/install_test.ts (NEW)

  • Healthy dev binary → HEALTHY, exit 0, JSON schema validated with overallStatus: "healthy"
  • writable field is "pass" or "skip" for a dev binary (verify in Task 0)
  • currentVersion matches the binary's version
  • owner.isRoot is false for a non-root test run
  • Read-only binary dir → writable: "fail", UNHEALTHY, exit 1 (create a temp copy of the binary in a read-only dir, or use SWAMP_BINARY_PATH pointing to a read-only location)
  • JSON schema: DoctorInstallSchema
  • Exit codes: 0 (healthy), 1 (unhealthy)

doctor/secrets_test.ts (NEW)

  • Cleartext sensitive literal → finding detected, exit 1, remediation message mentions swamp vault put
    • Setup: install the sensitive-global-arg fixture extension type, create a model with a literal value for the sensitive arg (this should be refused by the save guard — so instead, hand-write a model YAML file directly to disk with a cleartext sensitive value, bypassing the guard)
  • Vault-expression model passes → no findings, exit 0
    • Setup: create a model where the sensitive arg uses a vault expression like ${{ vault.get('v', 'k') }}
  • Secret value never echoed in stdout or stderr (scan output for the literal value)
  • unresolved types alone do NOT cause failure (exit 0 with unresolved populated)
  • Clean scan message: "✓ No cleartext sensitive global arguments found (N definition(s) scanned)"
  • JSON schema: DoctorSecretsSchema
  • Exit codes: 0 (clean), 1 (findings)

doctor/workflows_test.ts (NEW)

  • All valid workflows → overallStatus: "pass", exit 0, totalFailed: 0
    • Setup: install fixture workflows (single-step, multi-step-pipeline)
  • Corrupt workflow YAML → per-file with error message, overallStatus: "fail", exit 1, totalFailed > 0
    • Setup: write a malformed YAML file directly to the workflows/ directory
  • JSON schema: DoctorWorkflowsSchema
  • Exit codes: 0 (pass), 1 (fail)

Schemas

Add to src/cli/helpers/schemas.ts:

  • DoctorInstallSchema — full install check shape
  • DoctorSecretsSchema{overallStatus, scanned, findings, unresolved}
  • DoctorWorkflowsSchema{overallStatus, workflows, totalPassed, totalFailed}

Use .passthrough() (matching existing codebase convention).

Ground Rules

  • Read CLAUDE.md in the repo root first
  • Tests are more accurate than the code under test. Assert the documented contract. If swamp disagrees, file upstream.
  • Use the github-pr skill for commits and PR creation
  • Task 0 — ground truth: Build tip-of-main swamp, run doctor install --json, doctor secrets --json, doctor workflows --json by hand. Verify exact JSON shapes, exit codes, and the writable probe behavior.
  • Check open swamp-uat PRs before starting

Test Pattern

import { assertEquals } from "@std/assert";
import {
  createRunnerFromEnv,
  installFixtureModel,
  installFixtureWorkflow,
  runCommand,
  runJsonCommand,
  withInitializedRepoContext,
} from "@swamp-uat/helpers";

const runner = createRunnerFromEnv();

Deno.test("swamp doctor secrets detects cleartext sensitive global argument", () =>
  withInitializedRepoContext(runner, async (repo) => {
    // Hand-write a model YAML with a cleartext sensitive value
    // (bypassing the save guard that would refuse it)
    // Run doctor secrets --json
    // Assert finding, exit 1, remediation mentions vault put
    // Assert the literal value does NOT appear in stdout or stderr
  }));

Every test MUST:

  • Spawn the real swamp binary
  • Assert an explicit exit code
  • Validate --json output through a Zod schema

How to Run & Verify

deno task fmt
deno task lint
deno task check
deno task test

SWAMP_NO_TELEMETRY=1 SWAMP_SOURCE_DIR=~/code/swamp-club/swamp deno test --allow-read --allow-write --allow-env --allow-run --allow-net tests/cli/doctor/

SWAMP_NO_TELEMETRY=1 SWAMP_SOURCE_DIR=~/code/swamp-club/swamp deno task uat:cli

CI Shard Wiring

All new files go under tests/cli/doctor/ which is already covered by the data-repo-doctor CI shard. No CI changes needed.

Definition of Done

  • 3 test files created: doctor/install_test.ts, doctor/secrets_test.ts, doctor/workflows_test.ts
  • All Zod schemas added to schemas.ts
  • Doctor secrets: cleartext literal detected via hand-written YAML (bypasses save guard)
  • Doctor secrets: secret value never appears in test output (leak scan)
  • Every test spawns the real swamp binary — no mocking
  • Every test asserts an explicit exit code
  • Every --json test validates through a Zod schema
  • deno task fmt && deno task lint && deno task check && deno task test all green
  • SWAMP_NO_TELEMETRY=1 SWAMP_SOURCE_DIR=~/code/swamp-club/swamp deno task uat:cli passes
  • PR opened via github-pr skill, branching from main
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 3 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/14/2026, 12:03:49 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack727/13/2026, 10:33:37 PM

Sign in to post a ripple.