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.
- haconf-0.1.0/.gitignore +7 -0
- haconf-0.1.0/LICENSE +21 -0
- haconf-0.1.0/PKG-INFO +105 -0
- haconf-0.1.0/README.md +70 -0
- haconf-0.1.0/docs/AUDIT-CORRECTIONS.md +29 -0
- haconf-0.1.0/docs/FINDINGS.md +139 -0
- haconf-0.1.0/docs/PLAN.md +277 -0
- haconf-0.1.0/docs/REAL-CONFIG-VALIDATION.md +121 -0
- haconf-0.1.0/docs/RELEASE.md +147 -0
- haconf-0.1.0/docs/releases/0.1.0.md +49 -0
- haconf-0.1.0/pyproject.toml +30 -0
- haconf-0.1.0/scripts/security_sweep.py +134 -0
- haconf-0.1.0/src/haconf/__init__.py +16 -0
- haconf-0.1.0/src/haconf/__main__.py +9 -0
- haconf-0.1.0/src/haconf/catalog/v0.1/integrations.json +165 -0
- haconf-0.1.0/src/haconf/catalog/v0.1/meta.json +12 -0
- haconf-0.1.0/src/haconf/catalog/v0.1/rules.json +19 -0
- haconf-0.1.0/src/haconf/catalog.py +76 -0
- haconf-0.1.0/src/haconf/cli.py +212 -0
- haconf-0.1.0/src/haconf/entities.py +235 -0
- haconf-0.1.0/src/haconf/errors.py +105 -0
- haconf-0.1.0/src/haconf/includes.py +186 -0
- haconf-0.1.0/src/haconf/loader.py +183 -0
- haconf-0.1.0/src/haconf/rules.py +210 -0
- haconf-0.1.0/src/haconf/structure.py +229 -0
- haconf-0.1.0/src/haconf/suppress.py +101 -0
- haconf-0.1.0/src/haconf/templates.py +388 -0
- haconf-0.1.0/src/haconf/walker.py +109 -0
- haconf-0.1.0/tests/test_audit_sweep_nocompile.py +50 -0
- haconf-0.1.0/tests/test_i1_cli.py +114 -0
- haconf-0.1.0/tests/test_i1_loader.py +105 -0
- haconf-0.1.0/tests/test_i1_walker.py +92 -0
- haconf-0.1.0/tests/test_i2_catalog.py +57 -0
- haconf-0.1.0/tests/test_i2_cli.py +49 -0
- haconf-0.1.0/tests/test_i2_rules.py +90 -0
- haconf-0.1.0/tests/test_i3_cli.py +36 -0
- haconf-0.1.0/tests/test_i3_templates.py +92 -0
- haconf-0.1.0/tests/test_i4_cli.py +55 -0
- haconf-0.1.0/tests/test_i4_entities.py +76 -0
- haconf-0.1.0/tests/test_i4_includes.py +105 -0
- haconf-0.1.0/tests/test_i4_structure.py +67 -0
- haconf-0.1.0/tests/test_i5_json.py +57 -0
- haconf-0.1.0/tests/test_i5_severity.py +73 -0
- haconf-0.1.0/tests/test_i5_suppression.py +70 -0
- haconf-0.1.0/tests/test_i5c_packaging.py +59 -0
- haconf-0.1.0/tests/test_i6_negatives.py +100 -0
- haconf-0.1.0/tests/test_i7_fixes.py +143 -0
- haconf-0.1.0/tests/test_i8_scope_closures.py +299 -0
- haconf-0.1.0/tests/test_i8_script_forms.py +178 -0
haconf-0.1.0/.gitignore
ADDED
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.*
|