Rectify: Config Schema Contamination & Misplaced Secrets Immunity — PART A ONLY#476
Rectify: Config Schema Contamination & Misplaced Secrets Immunity — PART A ONLY#476Trecek merged 13 commits intointegrationfrom
Conversation
…t A) - SS-INIT-5: update to assert .state.yaml (not config.yaml) persists bypass - SS-INIT-ROUNDTRIP: round-trip test — bypass-accepted init then load_config must not raise - SS-INIT-STATE-FILE: _log_secret_scan_bypass must write to .state.yaml only - SEC-3b: validate_layer_keys misplaced-secret error must include exact fix guidance - SEC-3c: user-level ~/.autoskillit/config.yaml also rejects misplaced secrets - _SECRETS_ONLY_KEYS completeness contract test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…error guidance Change 2A: _log_secret_scan_bypass now writes to .autoskillit/.state.yaml instead of config.yaml. The .state.yaml file is never passed through validate_layer_keys, so the safety.secret_scan_bypass_accepted timestamp no longer contaminates the schema-validated config layer. Previously, every subsequent load_config call after a bypass-accepted init raised ConfigSchemaError. Change 2B: validate_layer_keys error for misplaced _SECRETS_ONLY_KEYS now includes the dotted key path, the exact YAML block to add to .secrets.yaml, and a removal instruction — making the error copy-paste actionable rather than a bare detection message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rets check (Part B)
…odule import violation
Trecek
left a comment
There was a problem hiding this comment.
AutoSkillit PR Review — Verdict: changes_requested
| config_data, default_flow_style=False, allow_unicode=True | ||
| ), | ||
| ) | ||
| write_config_layer(config_path, config_data) |
There was a problem hiding this comment.
[critical] bugs: write_config_layer raises ConfigSchemaError if config_data contains schema-invalid keys, but the surrounding try/except only catches (OSError, YAMLError). A ConfigSchemaError will escape unhandled and propagate up the call stack, crashing the caller instead of printing the warning message. Fix: add ConfigSchemaError to the except clause.
There was a problem hiding this comment.
Investigated — this is intentional. The reviewer's diff_hunk describes a hypothetical write_config_layer(config_data, config_path, *, validate: bool = True) -> bool signature that does not exist. The actual implementation at config/settings.py intentionally raises ConfigSchemaError before touching the filesystem (documented contract: 'Raises ConfigSchemaError before touching the file'). The call at L391 writes known-valid schema data. No change warranted for this finding.
src/autoskillit/cli/_doctor.py
Outdated
| Scans the user-level and project-level config.yaml files for any keys | ||
| that belong only in .secrets.yaml. Reports ERROR with exact fix guidance. | ||
| """ | ||
| from autoskillit.config import _SECRETS_ONLY_KEYS |
There was a problem hiding this comment.
[warning] cohesion: _check_config_layers_for_secrets uses lazy (inside-function) imports for _SECRETS_ONLY_KEYS and load_yaml, while _init_helpers.py imports them at module top-level. Mixing lazy and top-level import styles across files in the same PR creates inconsistency.
There was a problem hiding this comment.
Investigated — this is intentional. Deferred function-level imports are the established pattern throughout _doctor.py (e.g., run_doctor() at line 236 uses deferred imports for _clear_plugin_cache). The imports at lines 189–190 follow this module-wide convention used to avoid circular import chains and reduce startup cost. Moving them to module level would break from the module's own consistent style.
|
|
||
|
|
||
| # SS-INIT-ROUNDTRIP | ||
| def test_bypass_accepted_init_load_config_round_trip( |
There was a problem hiding this comment.
[warning] tests: SS-INIT-ROUNDTRIP calls cli.init() which exercises many side effects. If any unrelated init step fails under tmp_path isolation, the test fails for the wrong reason. Consider a narrower unit test that calls _log_secret_scan_bypass directly then load_config.
There was a problem hiding this comment.
Investigated — this is intentional. SS-INIT-ROUNDTRIP's docstring explicitly states it is a regression guard for _log_secret_scan_bypass writing safety.secret_scan_bypass_accepted to config.yaml — a bug undetected in prior tests because they never called load_config. The full cli.init() call is deliberate: the regression only manifests through the complete init path, and isolating to just the config-write part would miss the original bug.
| config_data, default_flow_style=False, allow_unicode=True | ||
| ), | ||
| ) | ||
| write_config_layer(config_path, config_data) |
There was a problem hiding this comment.
[critical] bugs: write_config_layer raises ConfigSchemaError if config_data contains schema-invalid keys, but the surrounding try/except only catches (OSError, YAMLError). A ConfigSchemaError will escape unhandled, crashing the caller instead of printing the warning. Fix: add ConfigSchemaError to the except clause at L392.
src/autoskillit/cli/_init_helpers.py
Outdated
| autoskillit_dir = project_dir / ".autoskillit" | ||
| autoskillit_dir.mkdir(exist_ok=True) | ||
| atomic_write(config_path, f"github:\n default_repo: '{github_repo}'\n") | ||
| write_config_layer(config_path, {"github": {"default_repo": github_repo}}) |
There was a problem hiding this comment.
[critical] bugs: write_config_layer at L397 is outside the try/except block (which ends at L393). Any ConfigSchemaError, OSError, or YAMLError it raises will be completely unhandled. Fix: wrap this call in a try/except (OSError, YAMLError, ConfigSchemaError) block.
| for config_path in config_paths: | ||
| if not config_path.is_file(): | ||
| continue | ||
| try: |
There was a problem hiding this comment.
[warning] arch: _check_config_layers_for_secrets re-implements the secrets-placement scan (iterating top_key/sub_key, checking _SECRETS_ONLY_KEYS, building fix guidance) entirely in the CLI layer. The same logic already lives in validate_layer_keys in config/settings.py, creating a second source of truth.
src/autoskillit/cli/_doctor.py
Outdated
| continue | ||
| try: | ||
| data = load_yaml(config_path) or {} | ||
| except YAMLError: |
There was a problem hiding this comment.
[warning] defense: YAMLError is silently caught and the loop continues with no diagnostic output. A corrupt config.yaml will produce a false-negative OK result, masking a real problem. Surface this as DoctorResult(Severity.WARNING, ...) instead of silently continuing.
src/autoskillit/cli/_doctor.py
Outdated
| Scans the user-level and project-level config.yaml files for any keys | ||
| that belong only in .secrets.yaml. Reports ERROR with exact fix guidance. | ||
| """ | ||
| from autoskillit.config import _SECRETS_ONLY_KEYS |
There was a problem hiding this comment.
[warning] cohesion: _check_config_layers_for_secrets uses lazy (inside-function) imports for _SECRETS_ONLY_KEYS and load_yaml, while _init_helpers.py imports them at module top-level in the same PR. Prefer one import style consistently across files.
|
|
||
|
|
||
| # DR-SECRETS-1 | ||
| def test_doctor_detects_misplaced_token_in_project_config( |
There was a problem hiding this comment.
[warning] tests: DR-SECRETS-1 patches Path.home() to tmp_path AND passes project_dir=tmp_path, so both config_paths resolve to the same .autoskillit directory. The test cannot distinguish whether the ERROR is from the home-layer or project-layer check. Add a separate test with home pointing to a different empty directory.
| # MCP server should be registered to user home, not project dir | ||
| assert (tmp_path / ".claude.json").exists() | ||
|
|
||
| def test_register_all_config_write_is_validated( |
There was a problem hiding this comment.
[warning] tests: test_register_all_config_write_is_validated asserts ConfigSchemaError is raised but does not assert that no partial file was written. Add: assert not (config_dir / 'config.yaml').exists() after the raises block to verify the no-write-on-error guarantee.
|
|
||
|
|
||
| # SS-INIT-ROUNDTRIP | ||
| def test_bypass_accepted_init_load_config_round_trip( |
There was a problem hiding this comment.
[warning] tests: SS-INIT-ROUNDTRIP calls cli.init() which exercises many unrelated side effects. If any init step fails under tmp_path isolation, the test fails for the wrong reason. Consider a narrower unit test calling _log_secret_scan_bypass directly then load_config.
tests/config/test_config.py
Outdated
| # Must include the exact YAML to add (so user can copy-paste) | ||
| assert "token:" in msg | ||
| # Must include removal instruction | ||
| assert "remove" in msg.lower() or "delete" in msg.lower() or "config.yaml" in msg |
There was a problem hiding this comment.
[warning] tests: The actionability assertion uses a trivially-true third disjunct: 'config.yaml' in msg will always be true for this error. Tighten to: assert 'remove' in msg.lower() or 'delete' in msg.lower().
Trecek
left a comment
There was a problem hiding this comment.
AutoSkillit review found 10 blocking issues (2 critical, 8 warning). See inline comments.
Critical (must fix):
_init_helpers.py:391— ConfigSchemaError not caught in try/except; crashes caller silently_init_helpers.py:397— write_config_layer call outside try/except entirely
Warnings:
_doctor.py— secrets scan logic duplicated from config layer; YAMLError silently swallowed; lazy vs top-level import inconsistencytests/— DR-SECRETS tests ambiguous (home/project paths overlap); weak/missing assertions in test_init.py and test_config.py
verdict=changes_requested
…ability assertion
…revent uncaught OSError
…face YAML parse failures as WARNING
…endently exercise each config path
…sfy cross-submodule import guard
Summary
The config system had two crash-inducing architectural weaknesses:
_log_secret_scan_bypasswrotesafety.secret_scan_bypass_acceptedintoconfig.yaml— a key not inSafetyConfig— causing every subsequentload_config()call to raiseConfigSchemaErrorafter a bypass-acceptedautoskillit init. Additionally, whengithub.tokenappeared inconfig.yaml, the error message provided no actionable fix steps (no exact YAML to copy, no removal instruction). A third structural gap:_SECRETS_ONLY_KEYSwas manually maintained with no test enforcing completeness against the config dataclasses.The fixes are threefold: (1) route bypass timestamps to
.autoskillit/.state.yaml(never schema-validated), eliminating the self-inflicted crash; (2) makevalidate_layer_keysself-diagnosing for misplaced secrets — the error now includes the exact YAML block and a removal instruction; (3) addtest_secrets_only_keys_completenessas a structural guard that fails if any secret-typed field inGitHubConfigis absent from_SECRETS_ONLY_KEYS. Part B (write-time config validation gateway andautoskillit doctormisplaced-secrets check) is included in this branch.Architecture Impact
Error/Resilience Diagram
%%{init: {'flowchart': {'nodeSpacing': 45, 'rankSpacing': 55, 'curve': 'basis'}}}%% flowchart TB classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; classDef gap fill:#ff6f00,stroke:#ffa726,stroke-width:2px,color:#000; LOAD_CONFIG([load_config]) WRITE_CONFIG([● write_config_layer]) subgraph LayerPipeline ["LAYER LOAD PIPELINE (_make_dynaconf)"] direction TB DEFAULTS["defaults.yaml<br/>━━━━━━━━━━<br/>skip validation"] USER_CFG["~/.autoskillit/config.yaml<br/>━━━━━━━━━━<br/>validated, secrets disallowed"] PROJ_CFG[".autoskillit/config.yaml<br/>━━━━━━━━━━<br/>validated, secrets disallowed"] SECRETS_LAYER[".autoskillit/.secrets.yaml<br/>━━━━━━━━━━<br/>validated, secrets allowed"] end subgraph ValidationGate ["● VALIDATION GATE (validate_layer_keys)"] VLK{"validate<br/>layer keys"} ERR_TOP["unknown top key<br/>━━━━━━━━━━<br/>difflib suggestion"] ERR_SECRET["● secret in config.yaml<br/>━━━━━━━━━━<br/>exact YAML fix block<br/>removal instruction"] ERR_SUB["unknown sub-key<br/>━━━━━━━━━━<br/>difflib suggestion"] end subgraph WriteGate ["● WRITE-TIME GATE (write_config_layer)"] WCL["● write_config_layer<br/>━━━━━━━━━━<br/>validates BEFORE write"] WCL_FAIL["write blocked<br/>━━━━━━━━━━<br/>no file touched"] WCL_OK["atomic write<br/>━━━━━━━━━━<br/>schema-valid only"] end subgraph StateIsolation ["● STATE ISOLATION (_log_secret_scan_bypass)"] BYPASS["● _log_secret_scan_bypass<br/>━━━━━━━━━━<br/>bypass timestamp"] STATE_YAML[".autoskillit/.state.yaml<br/>━━━━━━━━━━<br/>never validated<br/>internal state"] end subgraph DoctorDiag ["● DOCTOR DIAGNOSTIC (_check_config_layers_for_secrets)"] DOCTOR["● _check_config_layers_for_secrets<br/>━━━━━━━━━━<br/>scans user + project config.yaml<br/>returns DoctorResult, never raises"] DOC_OK["DoctorResult.OK<br/>━━━━━━━━━━<br/>no secrets in config layers"] DOC_ERR["DoctorResult.ERROR<br/>━━━━━━━━━━<br/>actionable YAML fix<br/>+ removal step"] end T_SCHEMA_ERR([ConfigSchemaError]) T_LOADED([AutomationConfig loaded]) T_WRITTEN([file written]) LOAD_CONFIG --> DEFAULTS DEFAULTS --> USER_CFG USER_CFG --> VLK VLK -->|"pass"| PROJ_CFG PROJ_CFG --> VLK VLK -->|"pass"| SECRETS_LAYER SECRETS_LAYER --> VLK VLK -->|"pass — all layers valid"| T_LOADED VLK -->|"unknown top key"| ERR_TOP VLK -->|"secret in non-secrets layer"| ERR_SECRET VLK -->|"unknown sub-key"| ERR_SUB ERR_TOP --> T_SCHEMA_ERR ERR_SECRET --> T_SCHEMA_ERR ERR_SUB --> T_SCHEMA_ERR WRITE_CONFIG --> WCL WCL -->|"validation fails"| WCL_FAIL WCL -->|"schema valid"| WCL_OK WCL_FAIL --> T_SCHEMA_ERR WCL_OK --> T_WRITTEN BYPASS --> STATE_YAML DOCTOR -->|"clean"| DOC_OK DOCTOR -->|"violation found"| DOC_ERR class LOAD_CONFIG,WRITE_CONFIG,T_SCHEMA_ERR,T_LOADED,T_WRITTEN terminal; class DEFAULTS stateNode; class USER_CFG,PROJ_CFG,SECRETS_LAYER handler; class VLK,WCL detector; class ERR_TOP,ERR_SUB,WCL_FAIL,DOC_ERR gap; class ERR_SECRET,BYPASS,DOCTOR,WCL newComponent; class STATE_YAML,WCL_OK,DOC_OK output;Color Legend:
ERR_SECRET(actionable message),_log_secret_scan_bypass,_check_config_layers_for_secrets,write_config_layerState Lifecycle Diagram
%%{init: {'flowchart': {'nodeSpacing': 45, 'rankSpacing': 55, 'curve': 'basis'}}}%% flowchart TB classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; classDef gap fill:#ff6f00,stroke:#ffa726,stroke-width:2px,color:#000; subgraph StorageLocations ["STORAGE LOCATIONS — MUTATION RULES"] direction LR DEFAULTS_YAML["config/defaults.yaml<br/>━━━━━━━━━━<br/>DEFAULTS<br/>read-only baseline<br/>never validated"] CONFIG_YAML["● .autoskillit/config.yaml<br/>━━━━━━━━━━<br/>SCHEMA_LOCKED<br/>secrets forbidden<br/>validated on every load + write"] SECRETS_YAML[".autoskillit/.secrets.yaml<br/>━━━━━━━━━━<br/>SECRETS_ONLY<br/>secrets allowed here only<br/>validated on every load"] STATE_YAML["● .autoskillit/.state.yaml<br/>━━━━━━━━━━<br/>INTERNAL_STATE<br/>no schema enforcement<br/>never passed to validate_layer_keys"] end subgraph SecretContract ["● SECRET KEY CONTRACT (_SECRETS_ONLY_KEYS)"] SOK["● _SECRETS_ONLY_KEYS<br/>━━━━━━━━━━<br/>frozenset{'github.token'}<br/>re-exported from config/__init__.py<br/>used by validate_layer_keys + doctor"] SOK_TEST["● test_secrets_only_keys_completeness<br/>━━━━━━━━━━<br/>structural guard: asserts every<br/>token/key/secret field in GitHubConfig<br/>is in _SECRETS_ONLY_KEYS"] end subgraph LoadGate ["LOAD-TIME SCHEMA GATE (validate_layer_keys)"] VLK{"validate<br/>layer keys"} VLK_SCHEMA["● schema check<br/>━━━━━━━━━━<br/>unknown top/sub keys<br/>→ ConfigSchemaError"] VLK_SECRET["● secrets-placement check<br/>━━━━━━━━━━<br/>SECRETS_ONLY key in non-secrets layer<br/>→ actionable ConfigSchemaError"] end subgraph WriteGate2 ["● WRITE-TIME SCHEMA GATE (write_config_layer)"] WCL2["● write_config_layer<br/>━━━━━━━━━━<br/>validates before write<br/>schema-valid only"] WCL_BLOCK["write blocked<br/>━━━━━━━━━━<br/>no file modified"] end subgraph BypassRoute ["● BYPASS STATE ISOLATION (_log_secret_scan_bypass)"] BYPASS_FN["● _log_secret_scan_bypass<br/>━━━━━━━━━━<br/>writes bypass timestamp to .state.yaml<br/>never touches config.yaml"] end subgraph DoctorGate2 ["● DOCTOR GATE (_check_config_layers_for_secrets)"] DOCTOR2["● _check_config_layers_for_secrets<br/>━━━━━━━━━━<br/>reads _SECRETS_ONLY_KEYS<br/>scans user + project config.yaml"] DOC_CLEAN["DoctorResult.OK"] DOC_CORRUPT["DoctorResult.ERROR<br/>━━━━━━━━━━<br/>contract violation detected<br/>actionable fix guidance"] end T_SCHEMA_ERR2([ConfigSchemaError]) T_LOADED2([AutomationConfig loaded]) T_WRITTEN2([file written]) DEFAULTS_YAML -->|"merged first, no validation"| VLK CONFIG_YAML -->|"validated, secrets forbidden"| VLK SECRETS_YAML -->|"validated, is_secrets=True"| VLK VLK -->|"pass"| T_LOADED2 VLK -->|"unknown key"| VLK_SCHEMA VLK -->|"secret in config.yaml"| VLK_SECRET VLK_SCHEMA --> T_SCHEMA_ERR2 VLK_SECRET --> T_SCHEMA_ERR2 WCL2 -->|"schema valid"| T_WRITTEN2 WCL2 -->|"validation fails"| WCL_BLOCK WCL_BLOCK --> T_SCHEMA_ERR2 BYPASS_FN -->|"writes to"| STATE_YAML SOK -->|"drives"| VLK_SECRET SOK -->|"drives"| DOCTOR2 SOK_TEST -.->|"prevents drift"| SOK DOCTOR2 -->|"clean"| DOC_CLEAN DOCTOR2 -->|"violation"| DOC_CORRUPT class DEFAULTS_YAML stateNode; class CONFIG_YAML,SECRETS_YAML handler; class STATE_YAML,T_LOADED2,T_WRITTEN2 newComponent; class VLK detector; class VLK_SCHEMA gap; class VLK_SECRET,WCL2,BYPASS_FN,DOCTOR2,SOK,SOK_TEST newComponent; class WCL_BLOCK,DOC_CORRUPT gap; class DOC_CLEAN output; class T_SCHEMA_ERR2,T_LOADED2,T_WRITTEN2 terminal;Color Legend:
write_config_layer,_log_secret_scan_bypass,_SECRETS_ONLY_KEYS, doctor check,.state.yamlvalidate_layer_keysgateConfigSchemaErrorOperational Diagram
%%{init: {'flowchart': {'nodeSpacing': 45, 'rankSpacing': 55, 'curve': 'basis'}}}%% flowchart TB classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; classDef gap fill:#ff6f00,stroke:#ffa726,stroke-width:2px,color:#000; subgraph InitCmd ["autoskillit init (● modified)"] direction TB INIT_CMD["autoskillit init<br/>━━━━━━━━━━<br/>interactive project setup"] SCAN_CHECK["_check_secret_scanning<br/>━━━━━━━━━━<br/>detect pre-commit scanner<br/>or prompt for bypass consent"] BYPASS_OK["bypass accepted<br/>━━━━━━━━━━<br/>user typed exact phrase"] NO_BYPASS["scanner found / no consent<br/>━━━━━━━━━━<br/>normal path / abort"] LOG_BYPASS["● _log_secret_scan_bypass<br/>━━━━━━━━━━<br/>writes bypass timestamp<br/>to .state.yaml ONLY"] WRITE_CFG["write_config_layer<br/>━━━━━━━━━━<br/>writes user config to<br/>.autoskillit/config.yaml<br/>(schema-validated)"] REGISTER["_register_mcp_server<br/>━━━━━━━━━━<br/>writes ~/.claude.json"] end subgraph DoctorCmd ["autoskillit doctor (● modified)"] direction TB DOCTOR_CMD["autoskillit doctor<br/>━━━━━━━━━━<br/>runs 9 health checks<br/>always all, no early exit"] CHK1["Check 1–4: version, scanner,<br/>config existence, MCP server<br/>━━━━━━━━━━<br/>existing checks"] CHK4B["● Check 4b: _check_config_layers_for_secrets<br/>━━━━━━━━━━<br/>scans ~/.autoskillit/config.yaml<br/>+ .autoskillit/config.yaml<br/>for _SECRETS_ONLY_KEYS violations"] CHK5N["Check 5–9: hooks, git,<br/>worktree, recipe, branch<br/>━━━━━━━━━━<br/>existing checks"] DOCTOR_OK["All checks OK<br/>━━━━━━━━━━<br/>Severity.OK for each"] DOCTOR_ERR["● Severity.ERROR<br/>━━━━━━━━━━<br/>Shows: dotted key path,<br/>exact YAML fix block,<br/>removal instruction"] end subgraph ConfigFiles ["CONFIGURATION FILES"] direction TB USER_CFG2["~/.autoskillit/config.yaml<br/>━━━━━━━━━━<br/>user-level config<br/>SCHEMA_LOCKED<br/>secrets forbidden"] PROJ_CFG2[".autoskillit/config.yaml<br/>━━━━━━━━━━<br/>project-level config<br/>SCHEMA_LOCKED<br/>secrets forbidden"] SECRETS_FILE2[".autoskillit/.secrets.yaml<br/>━━━━━━━━━━<br/>secret keys here only<br/>e.g. github.token"] STATE_FILE2["● .autoskillit/.state.yaml<br/>━━━━━━━━━━<br/>internal operational state<br/>bypass timestamp<br/>never schema-validated"] end subgraph LoadConfig2 ["load_config (called by all commands)"] LC2["● load_config<br/>━━━━━━━━━━<br/>merges all layers<br/>validates each (except defaults)"] LC_ERR2["● ConfigSchemaError<br/>━━━━━━━━━━<br/>actionable: exact YAML fix<br/>+ removal instruction"] end INIT_CMD --> SCAN_CHECK SCAN_CHECK -->|"bypass phrase matched"| BYPASS_OK SCAN_CHECK -->|"scanner OK / no consent"| NO_BYPASS BYPASS_OK --> LOG_BYPASS LOG_BYPASS -->|"writes bypass timestamp"| STATE_FILE2 NO_BYPASS --> WRITE_CFG WRITE_CFG -->|"schema-validated write"| PROJ_CFG2 WRITE_CFG --> REGISTER DOCTOR_CMD --> CHK1 CHK1 --> CHK4B CHK4B -->|"reads"| USER_CFG2 CHK4B -->|"reads"| PROJ_CFG2 CHK4B -->|"clean"| DOCTOR_OK CHK4B -->|"violation found"| DOCTOR_ERR CHK4B --> CHK5N LC2 -->|"validated layers"| USER_CFG2 LC2 -->|"validated layers"| PROJ_CFG2 LC2 -->|"validated layers"| SECRETS_FILE2 LC2 -->|"secret in config.yaml"| LC_ERR2 class INIT_CMD,DOCTOR_CMD cli; class SCAN_CHECK,NO_BYPASS,WRITE_CFG,REGISTER,CHK1,CHK5N handler; class BYPASS_OK,LOG_BYPASS,CHK4B,DOCTOR_ERR,LC_ERR2,LC2 newComponent; class USER_CFG2,PROJ_CFG2,SECRETS_FILE2 phase; class STATE_FILE2 newComponent; class DOCTOR_OK,LC2 output; class LC2 detector;Color Legend:
● _log_secret_scan_bypass,● _check_config_layers_for_secrets,● .state.yaml,● ConfigSchemaError(actionable)load_configschema validation gateImplementation Plan
Plan file:
/home/talon/projects/autoskillit-runs/remediation-20260322-002721-721157/temp/rectify/rectify_config-schema-immunity_2026-03-22_000000_part_a.md🤖 Generated with Claude Code via AutoSkillit