haconf 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. haconf-0.1.0/.gitignore +7 -0
  2. haconf-0.1.0/LICENSE +21 -0
  3. haconf-0.1.0/PKG-INFO +105 -0
  4. haconf-0.1.0/README.md +70 -0
  5. haconf-0.1.0/docs/AUDIT-CORRECTIONS.md +29 -0
  6. haconf-0.1.0/docs/FINDINGS.md +139 -0
  7. haconf-0.1.0/docs/PLAN.md +277 -0
  8. haconf-0.1.0/docs/REAL-CONFIG-VALIDATION.md +121 -0
  9. haconf-0.1.0/docs/RELEASE.md +147 -0
  10. haconf-0.1.0/docs/releases/0.1.0.md +49 -0
  11. haconf-0.1.0/pyproject.toml +30 -0
  12. haconf-0.1.0/scripts/security_sweep.py +134 -0
  13. haconf-0.1.0/src/haconf/__init__.py +16 -0
  14. haconf-0.1.0/src/haconf/__main__.py +9 -0
  15. haconf-0.1.0/src/haconf/catalog/v0.1/integrations.json +165 -0
  16. haconf-0.1.0/src/haconf/catalog/v0.1/meta.json +12 -0
  17. haconf-0.1.0/src/haconf/catalog/v0.1/rules.json +19 -0
  18. haconf-0.1.0/src/haconf/catalog.py +76 -0
  19. haconf-0.1.0/src/haconf/cli.py +212 -0
  20. haconf-0.1.0/src/haconf/entities.py +235 -0
  21. haconf-0.1.0/src/haconf/errors.py +105 -0
  22. haconf-0.1.0/src/haconf/includes.py +186 -0
  23. haconf-0.1.0/src/haconf/loader.py +183 -0
  24. haconf-0.1.0/src/haconf/rules.py +210 -0
  25. haconf-0.1.0/src/haconf/structure.py +229 -0
  26. haconf-0.1.0/src/haconf/suppress.py +101 -0
  27. haconf-0.1.0/src/haconf/templates.py +388 -0
  28. haconf-0.1.0/src/haconf/walker.py +109 -0
  29. haconf-0.1.0/tests/test_audit_sweep_nocompile.py +50 -0
  30. haconf-0.1.0/tests/test_i1_cli.py +114 -0
  31. haconf-0.1.0/tests/test_i1_loader.py +105 -0
  32. haconf-0.1.0/tests/test_i1_walker.py +92 -0
  33. haconf-0.1.0/tests/test_i2_catalog.py +57 -0
  34. haconf-0.1.0/tests/test_i2_cli.py +49 -0
  35. haconf-0.1.0/tests/test_i2_rules.py +90 -0
  36. haconf-0.1.0/tests/test_i3_cli.py +36 -0
  37. haconf-0.1.0/tests/test_i3_templates.py +92 -0
  38. haconf-0.1.0/tests/test_i4_cli.py +55 -0
  39. haconf-0.1.0/tests/test_i4_entities.py +76 -0
  40. haconf-0.1.0/tests/test_i4_includes.py +105 -0
  41. haconf-0.1.0/tests/test_i4_structure.py +67 -0
  42. haconf-0.1.0/tests/test_i5_json.py +57 -0
  43. haconf-0.1.0/tests/test_i5_severity.py +73 -0
  44. haconf-0.1.0/tests/test_i5_suppression.py +70 -0
  45. haconf-0.1.0/tests/test_i5c_packaging.py +59 -0
  46. haconf-0.1.0/tests/test_i6_negatives.py +100 -0
  47. haconf-0.1.0/tests/test_i7_fixes.py +143 -0
  48. haconf-0.1.0/tests/test_i8_scope_closures.py +299 -0
  49. haconf-0.1.0/tests/test_i8_script_forms.py +178 -0
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ dist/
6
+ build/
7
+ *.egg-info/
haconf-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WraithWall <contact@wraithwall.online>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
haconf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.5
2
+ Name: haconf
3
+ Version: 0.1.0
4
+ Summary: Check Home Assistant YAML and Jinja before reload — no instance needed.
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 WraithWall <contact@wraithwall.online>
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+ License-File: LICENSE
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: jinja2==3.1.6
29
+ Requires-Dist: pyyaml==6.0.3
30
+ Provides-Extra: dev
31
+ Requires-Dist: build==1.6.1; extra == 'dev'
32
+ Requires-Dist: pytest==8.3.4; extra == 'dev'
33
+ Requires-Dist: twine==7.0.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # haconf
37
+
38
+ Check Home Assistant YAML and Jinja before you reload — no instance needed.
39
+
40
+ Status: **I1–I8 — the v1 CLI is feature-complete.** It walks a configuration
41
+ directory, runs a robust YAML loader (duplicate keys, multi-document files,
42
+ quoting pitfalls such as `on`/`off`/`yes`/`no` and number-looking strings,
43
+ tab/indentation errors), applies a bundled, versioned integration catalog
44
+ (unknown keys under a known integration are errors, integration-name case
45
+ mistakes are errors naming the correct form, unknown integrations are
46
+ **informational only**), checks Jinja templates (compiled with Home Assistant's
47
+ strict environment shape, rendered against a stub of HA's documented
48
+ functions/filters — the **real** error text surfaces, e.g. `No filter named
49
+ 'strftime'`, instead of HA core's masked shape error), and runs the I4 integrity
50
+ checks (`!include`/`!include_dir_*` targets must exist and stay inside the config
51
+ dir; `!secret` keys must be defined in `secrets.yaml` — values are never read or
52
+ printed; `packages:` entries must not be empty; automation/script
53
+ trigger/condition/action shapes; duplicate `entity_id`s reported once each;
54
+ template references to entities neither declared in the config dir nor present in
55
+ `.storage/core.entity_registry` are flagged).
56
+
57
+ **I8 closes the three scopes the audit left open:** a `homeassistant: packages:`
58
+ body is checked like the top level (a package name is user-chosen and is never a
59
+ key); the nested branches are walked (`choose`, `repeat` with its `while`/`until`,
60
+ `parallel`, `if`/`then`/`else`, `wait_for_trigger`, and `condition: and`/`or`/`not`
61
+ recursively), so a bare string three levels down is the same error as one at the
62
+ top; and `custom_templates/*.jinja` files are checked as Jinja through one shared
63
+ environment, so a macro imported from a sibling file is not an "undefined name".
64
+ A `script:` block is a map of id → script config, in `configuration.yaml`,
65
+ `packages:` and `scripts.yaml` alike — the id is a name, the body holds the
66
+ options.
67
+
68
+ I5 completes the contract: every finding carries a **severity** (error / warning
69
+ / info) and a **plain-language hint**; warnings and infos pass the run unless
70
+ `--strict` escalates warnings; findings can be **suppressed visibly** via
71
+ `.haconfignore` or an inline `# haconf: ignore=<rule-id>` (suppressed findings
72
+ are always counted and printed, never hidden); and `--json` emits one complete
73
+ machine-readable object including the suppressed counts. The catalog ships as
74
+ package data and is never fetched at runtime.
75
+
76
+ Release work is complete: packaging **I5c** (`docs/RELEASE.md`), the security
77
+ pass **I6** (`scripts/security_sweep.py`; the N8–N12 absent-behaviour tests),
78
+ and real-config validation **I7** (`docs/REAL-CONFIG-VALIDATION.md` — two pinned
79
+ public config repositories; the validation exposed real gaps, all fixed and
80
+ pinned by `tests/test_i7_fixes.py`).
81
+
82
+ Exit codes: `0` clean (or informational findings only) · `1` findings with at
83
+ least one error · `64` usage. The `64` is a deliberate extension of the
84
+ contract's 0/1 (deviation **D-HC-1**, same rationale as RestVerify's D-I1: a
85
+ usage failure is not a lint outcome and must not collide with 1).
86
+
87
+ Read-only by construction: nothing under `src/` opens a file for writing, and the
88
+ test suite proves it (static AST check + a dynamic `open()` guard).
89
+
90
+ ## Development
91
+
92
+ ```bash
93
+ pip install -e '.[dev]'
94
+ python -m pytest -q # 174 tests, ~2 s
95
+ python scripts/security_sweep.py # read-only / no-network / no-eval sweep
96
+ python -m build # wheel + sdist in dist/
97
+ ```
98
+
99
+ The same two commands run in CI (`.github/workflows/ci.yml`) on Python 3.10,
100
+ 3.11 and 3.12, plus a packaging job that installs the built wheel into a clean
101
+ venv and asserts the catalog is inside it while `tests/` is not.
102
+
103
+ ## License
104
+
105
+ MIT — see [`LICENSE`](LICENSE).
haconf-0.1.0/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # haconf
2
+
3
+ Check Home Assistant YAML and Jinja before you reload — no instance needed.
4
+
5
+ Status: **I1–I8 — the v1 CLI is feature-complete.** It walks a configuration
6
+ directory, runs a robust YAML loader (duplicate keys, multi-document files,
7
+ quoting pitfalls such as `on`/`off`/`yes`/`no` and number-looking strings,
8
+ tab/indentation errors), applies a bundled, versioned integration catalog
9
+ (unknown keys under a known integration are errors, integration-name case
10
+ mistakes are errors naming the correct form, unknown integrations are
11
+ **informational only**), checks Jinja templates (compiled with Home Assistant's
12
+ strict environment shape, rendered against a stub of HA's documented
13
+ functions/filters — the **real** error text surfaces, e.g. `No filter named
14
+ 'strftime'`, instead of HA core's masked shape error), and runs the I4 integrity
15
+ checks (`!include`/`!include_dir_*` targets must exist and stay inside the config
16
+ dir; `!secret` keys must be defined in `secrets.yaml` — values are never read or
17
+ printed; `packages:` entries must not be empty; automation/script
18
+ trigger/condition/action shapes; duplicate `entity_id`s reported once each;
19
+ template references to entities neither declared in the config dir nor present in
20
+ `.storage/core.entity_registry` are flagged).
21
+
22
+ **I8 closes the three scopes the audit left open:** a `homeassistant: packages:`
23
+ body is checked like the top level (a package name is user-chosen and is never a
24
+ key); the nested branches are walked (`choose`, `repeat` with its `while`/`until`,
25
+ `parallel`, `if`/`then`/`else`, `wait_for_trigger`, and `condition: and`/`or`/`not`
26
+ recursively), so a bare string three levels down is the same error as one at the
27
+ top; and `custom_templates/*.jinja` files are checked as Jinja through one shared
28
+ environment, so a macro imported from a sibling file is not an "undefined name".
29
+ A `script:` block is a map of id → script config, in `configuration.yaml`,
30
+ `packages:` and `scripts.yaml` alike — the id is a name, the body holds the
31
+ options.
32
+
33
+ I5 completes the contract: every finding carries a **severity** (error / warning
34
+ / info) and a **plain-language hint**; warnings and infos pass the run unless
35
+ `--strict` escalates warnings; findings can be **suppressed visibly** via
36
+ `.haconfignore` or an inline `# haconf: ignore=<rule-id>` (suppressed findings
37
+ are always counted and printed, never hidden); and `--json` emits one complete
38
+ machine-readable object including the suppressed counts. The catalog ships as
39
+ package data and is never fetched at runtime.
40
+
41
+ Release work is complete: packaging **I5c** (`docs/RELEASE.md`), the security
42
+ pass **I6** (`scripts/security_sweep.py`; the N8–N12 absent-behaviour tests),
43
+ and real-config validation **I7** (`docs/REAL-CONFIG-VALIDATION.md` — two pinned
44
+ public config repositories; the validation exposed real gaps, all fixed and
45
+ pinned by `tests/test_i7_fixes.py`).
46
+
47
+ Exit codes: `0` clean (or informational findings only) · `1` findings with at
48
+ least one error · `64` usage. The `64` is a deliberate extension of the
49
+ contract's 0/1 (deviation **D-HC-1**, same rationale as RestVerify's D-I1: a
50
+ usage failure is not a lint outcome and must not collide with 1).
51
+
52
+ Read-only by construction: nothing under `src/` opens a file for writing, and the
53
+ test suite proves it (static AST check + a dynamic `open()` guard).
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ pip install -e '.[dev]'
59
+ python -m pytest -q # 174 tests, ~2 s
60
+ python scripts/security_sweep.py # read-only / no-network / no-eval sweep
61
+ python -m build # wheel + sdist in dist/
62
+ ```
63
+
64
+ The same two commands run in CI (`.github/workflows/ci.yml`) on Python 3.10,
65
+ 3.11 and 3.12, plus a packaging job that installs the built wheel into a clean
66
+ venv and asserts the catalog is inside it while `tests/` is not.
67
+
68
+ ## License
69
+
70
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,29 @@
1
+ # AUDIT corrections (I1–I7 independent audit, 2026-09-26)
2
+
3
+ Produced by an independent second agent. The full report is
4
+ `haconf-AUDIT-briefing.md` in the audit clone `/home/deploy/oss/haconf-audit`
5
+ (it was never part of this product repo). Audit HEAD was `046ab2f`; the audit
6
+ added no product code beyond the regression test named below.
7
+
8
+ - **AUDIT-HC-1 — regression guard added (`tests/test_audit_sweep_nocompile.py`).**
9
+ The I6 commit `476d0ce` was committed red: the security sweep flagged
10
+ `re.compile` as eval/exec, so `test_sweep_passes_on_the_shipped_tree` failed
11
+ (`1 failed, 122 passed`) and the sweep printed `RESULT: FAIL`, while
12
+ `haconf-I5-I7-briefing.md` §I6 and `haconf-BUILD-BRIEFING.md` §4 paste
13
+ `RESULT: PASS`. The code fix (`scripts/security_sweep.py`: `calls.items()` →
14
+ `bare_calls.items()`) landed in I7 `af08236` and was undocumented anywhere.
15
+ No user-facing impact: `src/haconf` was never affected.
16
+ - **AUDIT-HC-2 — briefing error (no code change).** `haconf-I4-briefing.md`
17
+ claims the suite is `88` with "+23 new tests"; its own new-test list sums to
18
+ 27 (10 + 6 + 7 + 4) and commit `2913307` actually collects and runs **92**.
19
+ - **AUDIT-HC-3 — premise mismatch (no code change).** There is no
20
+ `tests/conftest.py` and `docs/PLAN.md` has no "Test infrastructure" section.
21
+ - **AUDIT-HC-4 — plan instruction not followed (no code change; R43 NOT minted).**
22
+ Plan §3 I5c says packaging/release ids are "allocated when it starts"; commit
23
+ `6b6f33b` and `docs/RELEASE.md` mint no R-ids.
24
+ - **AUDIT-HC-5 — briefing set incomplete (no code change).** No
25
+ `haconf-I1/I2/I6/I7` briefing files exist; that content is folded into
26
+ `haconf-BUILD-BRIEFING.md` and `haconf-I5-I7-briefing.md`.
27
+
28
+ Unverifiable (recorded, not fixed): unreadable-file handling — the audit ran as
29
+ `root`, so `chmod 000` is bypassed. A non-root run is required to settle it.
@@ -0,0 +1,139 @@
1
+ # haconf — Phase 2 findings (evidence verification + landscape)
2
+
3
+ Read-only exploration, 2026-09-25. Every claim below was re-checked live against the
4
+ sources; pasted output is trimmed only by ellipses, never reworded. No accounts were
5
+ created and no API keys beyond those already present in this environment were used
6
+ (the existing `GITHUB_TOKEN` was used for read-only GitHub API calls; rate limit at
7
+ start: 5000/h, 0 used). No writes to any live system. Nothing in the RestVerify repo
8
+ was touched.
9
+
10
+ Contract: `GAP-RESEARCH-2026-09.pdf` (extracted with `pdftotext -layout`; Product 2
11
+ section lines 93–196 of the text dump). Where the PDF and the live world disagree,
12
+ the PDF wins for the plan and the difference is logged here.
13
+
14
+ *Amendment note (2026-09-25): the operator ruled the product name **haconf**; this
15
+ document's product-name uses now read `haconf`. Recorded evidence is untouched —
16
+ including the PyPI naming check below, where `haconf` is registered to an unrelated
17
+ HAProxy tool and `haccheck` is unregistered. See `haconf-PLAN.md` §4 Q1.*
18
+
19
+ ---
20
+
21
+ ## 1. Verification table — PDF Product 2 claims
22
+
23
+ | # | Claim (from the PDF) | Verdict | Pasted evidence |
24
+ |---|---|---|---|
25
+ | 1 | `home-assistant/core#169958` exists | **MATCH** | GitHub API 200. Title: *"Config validator masks TemplateAssertionError with a generic "should be a string" error in service-call data: dicts"* |
26
+ | 2 | …is **open** | **MISMATCH** | `state: closed`, `state_reason: not_planned`, `closed_at: 2026-08-25T10:10:34Z`, label `stale`; closed by `issue-triage-workflows[bot]` ("marked as stale"). It was already closed when the PDF's data was retrieved (2026-09-14). |
27
+ | 3 | Quoted text matches the issue | **MATCH** | Body: *"The discrepancy between the two error reports turns a one-line fix ("`strftime` isn't a filter, use `.strftime()` method") into an extended bisection."* Comment 2 (by `t0mr0zier`): *"the config validator catches the TemplateAssertionError: No filter named 'strftime' and re-reports it as a generic "template value should be a string for dictionary value" error pointing at the surrounding data: dict."* |
28
+ | 4 | "reported on 2026.5.0" | **MATCH** | Body field: *"### What version of Home Assistant Core has the issue? 2026.5.0"* |
29
+ | 5 | "maintainer-side discussion confirms the masking is the defect" | **MISMATCH** | Both human commenters have `author_association: NONE` (`tdejneka`, `t0mr0zier`); the third comment is the stale bot. No maintainer participated; the issue was auto-closed stale. The masking story is the reporter's, unchallenged — but not maintainer-confirmed. |
30
+ | 6 | `home-assistant/core#110705` | **MATCH** | 200: *"Cannot quick reload all YAML configurations"*, closed `not_planned` (2024-05-26); body: *"…because the configuration is not valid: Unexpected error calling config validator: 'NodeStrClass' object has no attribute 'items' Traceback…"* (the `[mqtt]` half of the PDF's combined quote comes from the next row) |
31
+ | 7 | `home-assistant/home-assistant.io#29440` | **MATCH** | 200: *"I cannot make examples in MQTT Light to work"*, closed `completed`; body contains the PDF's quote verbatim: *"Cannot quick reload all YAML configurations because the configuration is not valid: Invalid config for [mqtt]: expected dictionary for dictionary value @ data['mqtt']. Got [{'light': …"* |
32
+ | 8 | `home-assistant/frontend#26063` "closed not planned" | **MATCH** | 200: *"[automation] Misleading validation: choose.conditions does not support inline templates"*, `state: closed`, `state_reason: not_planned` (closed 2025-07-05) |
33
+ | 9 | Community thread *Quick reload not actually reloading YAML* + quote | **MATCH** | Topic 549186, 10 posts, 3,251 views. Post 1 (`teegee`): *"I press "Quick reload" and go back to debugging my shell_command only to find out that "Quick reload" did not reload my configuration.yaml file and is still using the old path configured in my shell_command."* Date nuance: created **2023-03-18**, last post **2024-03-16** — the PDF's "(Mar 2024)" is the thread's last activity, not the quoted post. |
34
+ | 10 | Community thread *How to troubleshoot template issue?* + quote | **MATCH** | Topic 716717, 16 posts, accepted answer. Post 4 (`mandolin`): *"…my outstanding generic question is how do you find the template issues that "Developer Tools" → YAML and "Check Configuration" finds? Whenever I get those messages, there is nothing new in the home-assistant.log or any of the System log areas."* (created 2024-04-13) |
35
+ | 11 | HA docs quote ("does not restart … persistent notification … logs") | **MATCH** | `source/_actions/homeassistant.restart.markdown` in the docs repo: *"If the configuration check fails, Home Assistant does not restart. Instead, it creates a persistent notification with the ID `persistent_notification.homeassistant_check_config`, and the logs show what failed the check."* (The PDF omits the notification ID.) |
36
+ | 12 | r/homeassistant "templates.yaml error" ("I didn't get any error notifications about it until the…") | **UNVERIFIABLE** | The thread exists — `reddit.com/r/homeassistant/comments/1lxslar/templatesyaml_error_name_value/` ("templates.yaml error - name & value", ~1y old) — but the quoted sentence could not be located: Reddit blocks scraping ("we do not support this site") and search does not index the phrase. Thread: MATCH; quoted line: unverified. |
37
+ | 13 | r/homeassistant "YAML syntax is the absolute worst!" / "YAML+Jinja2 is really crappy…" | **MATCH** | Live search hit: `reddit.com/r/homeassistant/comments/1mco55h/yaml_syntax_is_the_absolute_worst/` — snippet: *"YAML+Jinja2 is really crappy due to the weird indenting and paragraph and quoting rules."* |
38
+ | 14 | r/homeassistant "Cannot quick reload all YAML" / "yaml is fine and validates…" | **MATCH** | Live search hit: `reddit.com/r/homeassistant/comments/19dkla4/anyone_know_how_to_fix_this_cannot_quick_reload/` — snippet: *"yaml is fine and validates - i've no idea what this is - but it means i can't reload - any ideas where i look/how i fix?"* |
39
+ | 15 | Tool 1: HA built-in Check configuration — "server-side, reload-gated, masks template errors; **no laptop/CI/pre-commit path**" | **PARTIAL MISMATCH** | Server-side + reload-gating + masking: supported (docs + the issue's own text). "Runs **only inside a running instance**" / "no laptop path": **contradicted by core's own documented, maintained CLI**: `hass --script check_config` (docs `source/_docs/tools/check_config.markdown`: *"Test any changes … without the need to restart Home Assistant"*), container form `docker exec homeassistant python -m homeassistant --script check_config --config /config`, HAOS form `ha core check` (docs include `configuration_check.md`). The script is maintained: last commits 2026-08-25, 2026-07-16, 2026-04-30, 2026-03-27, 2025-11-06. What is true: it needs the (heavy) HA install or a running container; HAOS users cannot run it on a laptop; and it does **not** fix the masking. |
40
+ | 16 | Tool 2: "homeassistant-cli / API clients (remote check_config; same server dependency)" | **PARTIAL** | The package lives on PyPI (`homeassistant-cli` v1.0.0, 2026-04-12): *"The Home Assistant Command-line interface (``hass-cli``) allows one to work with a local or a remote Home Assistant instance…"*. Its declared GitHub home (`homeassistant-ecosystem/homeassistant-cli`, per PyPI project URLs) now **404s** — org gone — so current capabilities cannot be checked against source. "Same server dependency" is fair for any API client; a `check_config` capability is *not* evidenced in the description. |
41
+ | 17 | Tool 3: "Online validators / yamllint (syntax-only, not HA-aware)" | **MATCH** | yamllint repo live: ★3,467, pushed 2026-09-19, `"A linter for YAML files."` — generic YAML only; the "yaml is fine and validates" failure mode is the verified reddit quote above. |
42
+ | 18 | MVP: "~40-integration catalog" is a defensible number | **MATCH (with source)** | HA Analytics `data.json` (live): 688,333 active installations, 1,369 integrations tracked. Top-60 captured (sun, person, automation, script, scene, template, mqtt, mobile_app, cast, bluetooth, matter, input_boolean/number/select/text/datetime/button, sensor, group, zone, light-adjacent `switch_as_x`, tts, http, hue, utility_meter, recorder, …). A curated 40 covering the top usage + the demo cases is defensible; the catalog is explicit about being partial (N8/N13). |
43
+ | 19 | MVP: "Jinja compile with strict-undefined + stubbed context" is realistic | **MATCH (with scope note)** | HA still uses Jinja2 — `homeassistant` PyPI deps: `Jinja2==3.1.6`, `PyYAML==6.0.3`. Core source: `helpers/template/__init__.py:709: return jinja2.StrictUndefined` and `L822: class TemplateEnvironment(ImmutableSandboxedEnvironment):`. The documented function/filter surface (e.g. `states()`, `is_state()` in the docs page) can be hand-stubbed. Scope note: entity *existence* cannot be inferred from config files alone → see Q4; compile-time filter errors need no stub at all. |
44
+ | 20 | MVP: "surface the underlying error instead of masking it" is achievable offline | **MATCH (design note)** | The masking lives in HA's config-validation layer (re-reporting the `TemplateAssertionError` as a shape error — issue text above). A standalone tool that compiles templates itself gets `TemplateAssertionError: No filter named 'strftime'` directly at compile time and never passes through that layer. Caveat: HA's own wording stays HA's; and the issue is closed *not planned*, so the masking can persist in core. |
45
+ | 21 | Gap: "no maintained local validator exists" | **MATCH (as of 2026-09-25, with two newcomers logged)** | Searches found: no maintained standalone HA-aware linter. Closest: `ludeeus/action-ha-config-check` (★22, **archived 2021**), `custom-components/config_check` (★7, archived 2021), `MacDada/HomeAssistant_ConfigCheckCli` (★0, 2024, no README), `MacSiem/ha-yaml-checker` (★1, Aug 2026 — a **Lovelace card** that runs HA's own check against your **running instance**, not standalone), `KanadeK/hasstangle` (★0, created 2026-07-30, JS/MIT — offline *automation-semantics* analyzer: "feedback loops, conflicting writes, concurrency hazards, stale entities, SARIF"; not a YAML/Jinja config linter). No pre-commit hook for user configs (searches: `legovaer/pre-commit-hooks` ★0/2023; nothing maintained). |
46
+ | 22 | Star-traction & adoption-test claims | **MATCH (qualitative)** | No numeric star targets are given for haconf; the three community plays and the 7-point adoption test were extracted verbatim into `haconf-PLAN.md` §1. Nothing to re-measure. |
47
+
48
+ Summary: **12 MATCH · 3 MISMATCH/partial-that-matter (rows 2, 5, 15) · 1 partial
49
+ (16) · 1 UNVERIFIABLE line (12, quoted sentence only) · 5 MATCH-with-source/scope
50
+ notes.** No load-bearing claim that would invalidate the product idea failed: the
51
+ masking class is real and reproduced by the issue text; the demand quotes are real.
52
+
53
+ ---
54
+
55
+ ## 2. Landscape (current, with sources)
56
+
57
+ **Standalone linters.** There is still no maintained, standalone, HA-aware config
58
+ linter. The nearest things are: `ludeeus/action-ha-config-check` (★22, archived 2021)
59
+ and `custom-components/config_check` (★7, archived 2021) — both call HA's own check,
60
+ one from Actions against a live instance, one as a component. Two 2026 newcomers don't
61
+ change the shape: `MacSiem/ha-yaml-checker` (★1, Aug 2026) is a Lovelace card inside a
62
+ running HA; `KanadeK/hasstangle` (★0, created 2026-07-30, JS/MIT) is an offline
63
+ static analyzer for *automation semantics* (loops/conflicts/stale entities, SARIF
64
+ output), not a YAML/Jinja config checker — it is the closest "offline" neighbour and
65
+ worth watching, not a competitor to the PDF's scope.
66
+
67
+ **Offline check path in core.** HA core ships `hass --script check_config` with
68
+ `-c/--config`, `-f/--files`, `-i/--info`, `-s`, `--json`, `--fail-on-warnings`
69
+ (docs `source/_docs/tools/check_config.markdown`), and the docs propose
70
+ `docker exec homeassistant python -m homeassistant --script check_config --config /config`
71
+ for containers and `ha core check` for HAOS. The script is maintained (commits
72
+ 2025-11-06 → 2026-08-25; file exists on `dev`). So "runs only inside a running
73
+ instance" is **not** accurate as stated — but the offline CLI needs the full HA
74
+ install (today: Python ≥3.14.2, see below), it is unusable on HAOS from a laptop, and
75
+ it does not un-mask (#169958). This is the nuance the plan carries in D3/§1.
76
+
77
+ **Jinja2.** Still the engine, confirmed twice: `homeassistant` PyPI deps pin
78
+ `Jinja2==3.1.6` (and `PyYAML==6.0.3`), and core's template environment uses
79
+ `jinja2.StrictUndefined` with `TemplateEnvironment(ImmutableSandboxedEnvironment)`.
80
+ `haconf` can pin the same Jinja2 version and mirror the strictness.
81
+
82
+ **HA today.** Current core release: **2026.9.3** (published 2026-09-18; repo ★91,144,
83
+ active daily). PyPI requires Python **>= 3.14.2** — HA has moved well past the PDF's
84
+ "3.10+" floor for *its own* runtime. That does not bind haconf (we ship our own
85
+ deps), but it strengthens the standalone case and means catalog work must track a
86
+ fast-moving target; the analytics snapshot above is from the same live data.
87
+
88
+ **Pre-commit / CI today.** No accepted community pre-commit hook exists for HA user
89
+ YAML configs. The official `home-assistant/actions` repo (★48, active) ships
90
+ `hassfest` — which validates **integration repositories** for custom-component
91
+ developers, not user configuration — plus internal helpers. Core's own pre-commit
92
+ setup is developer tooling. The PDF's "config-repo ecosystem" play (pre-commit +
93
+ Action) remains open space.
94
+
95
+ **Naming.** PyPI: `haccheck` → 404 (unregistered); `haconf` → exists (HAProxy
96
+ health-check proxying service, v0.7.0), and GitHub has multiple `haconf` HAProxy
97
+ repos (e.g. `uber-archive/haconf` ★86, archived). GitHub search for `haccheck`
98
+ returns no repository. The PDF's spelling is the collision-free one.
99
+ *(Ruling 2026-09-25: the product name is nevertheless **haconf**; the collision and
100
+ its publish-name consequence are recorded in `haconf-PLAN.md` §4 Q1 / name note.
101
+ This evidence paragraph is left as recorded.)*
102
+
103
+ ---
104
+
105
+ ## 3. Appendix — artifacts and commands
106
+
107
+ Raw captures on this box (read-only fetches):
108
+
109
+ - `/tmp/gap_research.txt` — `pdftotext -layout` of the contract PDF (232 lines)
110
+ - `/tmp/haccheck_contract.txt` — the extracted Product 2 section (verbatim source of
111
+ `haconf-PLAN.md` §1)
112
+ - `/tmp/hc_169958.json`, `/tmp/hc_169958_comments.json` — issue + 3 comments
113
+ - `/tmp/hc_110705.json`, `/tmp/hc_io29440.json`, `/tmp/hc_frontend26063.json`
114
+ - `/tmp/hc_analytics.json` — analytics.home-assistant.io `data.json` (7.45 MB)
115
+ - `/tmp/hc_check_config.py` — core `homeassistant/scripts/check_config.py` (12,632 bytes)
116
+ - `/tmp/hc_template.py` — core template package listing + `__init__.py` evidence
117
+ - `/tmp/hc_docs_configuration.html`, `/tmp/hc_docs_templating.html` — rendered docs
118
+ - GitHub search captures: `/tmp/hc_search_*.json`, `/tmp/hc_yamllint.json`, etc.
119
+ - Firecrawl working dir: `/tmp/hc_firecrawl/` (reddit/linter searches)
120
+
121
+ Key commands (all read-only GETs; GitHub used the pre-existing token):
122
+ `pdftotext -layout …`; `curl`/`requests` to `api.github.com` (issues, search,
123
+ contents, commits), `pypi.org/pypi/{homeassistant,homeassistant-cli,haccheck,haconf,
124
+ yamllint,Jinja2,PyYAML}/json`, `analytics.home-assistant.io/data.json`,
125
+ `raw.githubusercontent.com` (core + docs), `community.home-assistant.io/*.json`
126
+ (via the webfetch path — direct requests were blocked with 403 by Cloudflare), and
127
+ `firecrawl search` for the reddit quotes.
128
+
129
+ Rated honesty: the one thing this phase could not do is run a real HA instance to pin
130
+ the exact current voluptuous messages for the two demo cases (unknown key under
131
+ `switch`; a template entity referencing an undefined entity). Those will be pinned as
132
+ fixtures against the catalog at I1/I2; the error *format* was verified live from a
133
+ real user error (`Invalid config for [mqtt]: expected dictionary for dictionary value
134
+ @ data['mqtt']` — io#29440).
135
+
136
+ ---
137
+
138
+ *Phase deliverable: this file + `haconf-PLAN.md`, committed together. No product
139
+ code, no product repo, no installs. Nothing in the RestVerify repo was touched.*