Repository navigation
fix(cli): resolve control level from tags and sort unleveled controls last (fixes #568, fixes #589) - #597
Conversation
mlieberman85
left a comment
There was a problem hiding this comment.
Thanks, this fixes both crashes, and having the validator feed get_controls_by_level and composition include_levels is a nice side effect. A few things before it's one source of truth:
- Conflicts pass silently:
level = 1withtags = { level = 2 }loads with both values, andControlSpeclater keeps the top-level one. Could the validator reject a conflict (and the same fordomain/security_severity)? - A non-integer
tags.level("L1",1.5) becomesNonewith no error, andtruebecomes levelTrueand passes the 1–3 check. Could those fail loading instead, so a typo can't drop a control out of level filters? - The spec text says "synchronize" but not which field is canonical. Could it name one (the issue suggested either) and say a conflict is an error?
- With the validator in place, the fallbacks in
merger.pyand the two incontrol_loader.pyare redundant. Could they go, so the rule lives in one place? - Please add a CHANGELOG entry (it also changes what composition
include_levelsselects for openssf-baseline).
Note for the spec line: plan now shows "No level", but audit/harness compliance count a level-less result as L1 (r.get("level") or 1); worth one sentence saying which is intended.
Drafted with Claude Code; reviewed and posted by me.
…onflict validation Address review feedback from @mlieberman85 on PR darnitdevorg#597: 1. Reject conflicting declarations between top-level attributes and tags (level, domain, security_severity) in ControlConfig schema validation. 2. Strictly enforce integer type for tags.level (reject strings, floats, and booleans). 3. Document canonical attribute rules, conflict error handling, and unleveled control display vs compliance evaluation semantics in framework-design.md. 4. Eliminate redundant tags fallback logic in merger.py and control_loader.py. 5. Add unit tests for conflict rejection, invalid tag types, and update CHANGELOG.md. Refs darnitdevorg#568, Refs darnitdevorg#589 Assisted-by: Antigravity:eni-gemini-2.5-pro Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
|
Thanks for the review. I have addressed all points in commit
|
mlieberman85
left a comment
There was a problem hiding this comment.
Thanks, all five points from the last round are in. One gap: composite overrides go through model_copy in _apply_override (core/composition.py:500), so the new validator never runs on them. With a base control at level 1 and [overrides.X] tags = { level = 3 }, the control loads at level 1: merge_control writes the top-level value back over the tag. On main the override took effect. tags = { level = "L3" } also loads without an error. Could _apply_override revalidate the result (model_validate)? Also, please mention in the CHANGELOG that composition include_levels and get_controls_by_level now select tag-leveled controls, and rebase (the CHANGELOG conflict with #609 only).
Drafted with Claude Code; reviewed and posted by me.
…onflict validation Address review feedback from @mlieberman85 on PR darnitdevorg#597: 1. Reject conflicting declarations between top-level attributes and tags (level, domain, security_severity) in ControlConfig schema validation. 2. Strictly enforce integer type for tags.level (reject strings, floats, and booleans). 3. Document canonical attribute rules, conflict error handling, and unleveled control display vs compliance evaluation semantics in framework-design.md. 4. Eliminate redundant tags fallback logic in merger.py and control_loader.py. 5. Add unit tests for conflict rejection, invalid tag types, and update CHANGELOG.md. Refs darnitdevorg#568, Refs darnitdevorg#589 Assisted-by: Antigravity:eni-gemini-2.5-pro Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
… attributes Address review feedback from @mlieberman85 on PR darnitdevorg#597: - Revalidate controls in _apply_override via ControlConfig.model_validate. - Synchronize tag-level attributes (level, domain, security_severity) with top-level attributes in overrides so overriding tags.level updates control.level and prevents conflicts. - Reject invalid tags.level types (e.g. 'L3') during override application. - Add unit tests covering override level synchronization and validation rejection. Refs darnitdevorg#568, Refs darnitdevorg#589 Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
4d4734b to
ff8f124
Compare
|
Addressed review feedback in ff8f124:
|
mlieberman85
left a comment
There was a problem hiding this comment.
Thanks, the points from both rounds are in: conflicting and non-integer levels are rejected, the spec names the canonical field, the fallbacks are gone, and overrides revalidate (an override of tags = { level = 3 } now loads at level 3; main loaded it at 1). On jonschlinkert/is-number, audits with openssf-baseline, gittuf, reproducibility and testchecks give the same level and status for every control on main and this branch, and darnit validate and darnit plan now exit 0 on the baseline instead of raising the TypeError.
It conflicts with main again, in CHANGELOG.md only. Once that's rebased I'll approve.
Two small things:
include_tags = { level = N }now also selects controls that set a top-levellevel, because the validator writestags.level(framework_schema.py:718-719). On the mock-source-a fixture it selects 2 controls where main selects 0. That looks right, but it's worth a line in the CHANGELOG entry.- The formatting changes in
merger.py,control_loader.py:182,264,framework_schema.py:417andtest_merger.pyare unrelated to the fix. The project isn't formatted withruff format, so please drop them.
Separate from this PR: ControlConfig.level has no range check (0, 7 and -1 all load, and a level above 3 is in no level verdict). That was already true on main, so it's for its own issue.
Drafted with AI assistance (Claude), reviewed and filed by me. Measurements quoted were executed and verified.
ff8f124 to
566a668
Compare
…onflict validation Address review feedback from @mlieberman85 on PR darnitdevorg#597: 1. Reject conflicting declarations between top-level attributes and tags (level, domain, security_severity) in ControlConfig schema validation. 2. Strictly enforce integer type for tags.level (reject strings, floats, and booleans). 3. Document canonical attribute rules, conflict error handling, and unleveled control display vs compliance evaluation semantics in framework-design.md. 4. Eliminate redundant tags fallback logic in merger.py and control_loader.py. 5. Add unit tests for conflict rejection, invalid tag types, and update CHANGELOG.md. Refs darnitdevorg#568, Refs darnitdevorg#589 Assisted-by: Antigravity:eni-gemini-2.5-pro Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
… attributes Address review feedback from @mlieberman85 on PR darnitdevorg#597: - Revalidate controls in _apply_override via ControlConfig.model_validate. - Synchronize tag-level attributes (level, domain, security_severity) with top-level attributes in overrides so overriding tags.level updates control.level and prevents conflicts. - Reject invalid tags.level types (e.g. 'L3') during override application. - Add unit tests covering override level synchronization and validation rejection. Refs darnitdevorg#568, Refs darnitdevorg#589 Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
|
Rebased cleanly onto latest
All 93 tests in |
566a668 to
0ccef6c
Compare
…onflict validation Address review feedback from @mlieberman85 on PR darnitdevorg#597: 1. Reject conflicting declarations between top-level attributes and tags (level, domain, security_severity) in ControlConfig schema validation. 2. Strictly enforce integer type for tags.level (reject strings, floats, and booleans). 3. Document canonical attribute rules, conflict error handling, and unleveled control display vs compliance evaluation semantics in framework-design.md. 4. Eliminate redundant tags fallback logic in merger.py and control_loader.py. 5. Add unit tests for conflict rejection, invalid tag types, and update CHANGELOG.md. Refs darnitdevorg#568, Refs darnitdevorg#589 Assisted-by: Antigravity:eni-gemini-2.5-pro Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
… attributes Address review feedback from @mlieberman85 on PR darnitdevorg#597: - Revalidate controls in _apply_override via ControlConfig.model_validate. - Synchronize tag-level attributes (level, domain, security_severity) with top-level attributes in overrides so overriding tags.level updates control.level and prevents conflicts. - Reject invalid tags.level types (e.g. 'L3') during override application. - Add unit tests covering override level synchronization and validation rejection. Refs darnitdevorg#568, Refs darnitdevorg#589 Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
mlieberman85
left a comment
There was a problem hiding this comment.
Approving 0ccef6c. The points from the earlier rounds are in: the formatting churn is gone (merger.py is untouched, control_loader.py only loses the fallbacks), the CHANGELOG has the include_tags = { level = N } sentence, and a bad or conflicting level, domain or severity in an override is now rejected.
| command | main (075556e) | this PR (0ccef6c) |
|---|---|---|
darnit validate openssf-baseline.toml |
exit 1, TypeError: '<' not supported between instances of 'int' and 'NoneType' |
exit 0, L1=26, L2=19, L3=21 |
darnit plan -f openssf-baseline <repo> |
exit 1, same TypeError | exit 0 |
one control, level = 1, tags.level = 2 |
exit 0, loads at L1 | exit 1, Conflicting level declarations |
one control, tags.level = "L1" / true |
exit 0, loads with no level | exit 1, tags.level must be an integer |
On main, the last two rows also hit the TypeError when the file has a sibling control with a different kind of level. For example, the conflict plus a control with no level, or the string plus a control with a top-level level.
On 075556e merged with this PR, keeping both sides of the CHANGELOG conflict: every shipped framework TOML validates, the full suite gives 5141 passed, ruff and validate_sync pass, and a real-repo audit (wtf-frontend) gives the same per-control statuses as 075556e.
One thing before this can merge: it now conflicts with main in CHANGELOG.md, since #623 landed. Please rebase. The push will dismiss this approval, so I'll re-approve on the new head.
Not for this PR: the missing range check from my last review now has one more route. [overrides.X] tags = { level = 4 } on a control with a top-level level loads at that control's level on main, so its FAIL is reported. Here the control is dropped from the default audit with exit 0, though darnit validate flags it. A plain tags.level = 4 already behaves that way on main, and no shipped framework does this, so it belongs in the same separate issue.
Nits, non-blocking:
- The validator also writes
domainandsecurity_severityintoself.tags, soinclude_tagson those now selects controls that only set the top-level field. No shipped framework changes, but the CHANGELOG only nameslevel. - An invalid override fails with
1 validation error for ControlConfig, without the control id the non-composite path prints. - The PR description still lists a fallback in
merger.py(item 2), which is no longer in the diff.
Drafted with AI assistance (Claude), reviewed and filed by me. Measurements quoted were executed and verified.
… last (fixes darnitdevorg#568, fixes darnitdevorg#589) Controls in framework TOML configurations frequently declare maturity levels, domains, and severity within their tags mapping (e.g. tags = { level = 1 }), leaving top-level attributes as None. In openssf-baseline.toml, 65 controls declared tags.level while STAGE1-REF-SECURITY-01 declared a top-level level, causing sorted(by_level.items()) in darnit validate and sorted(by_level.keys()) in darnit plan to crash with TypeError: '<' not supported between instances of 'int' and 'NoneType'. 1. Synchronize level, domain, and security_severity between control attributes and tags in ControlConfig (framework_schema.py) and EffectiveControl (merger.py), establishing a single canonical source of truth. 2. In cmd_plan and cmd_validate (cli.py), sort level breakdowns safely using key=lambda k: (k is None, k) so controls without a maturity level sort last instead of failing with a TypeError. 3. Added framework design requirement to docs/architecture/framework-design.md. 4. Added tests running validate and plan across all shipped framework TOMLs and verifying sorting behavior on frameworks with mixed and unleveled controls. Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com> Assisted-by: Antigravity:eni-gemini-2.5-pro
…onflict validation Address review feedback from @mlieberman85 on PR darnitdevorg#597: 1. Reject conflicting declarations between top-level attributes and tags (level, domain, security_severity) in ControlConfig schema validation. 2. Strictly enforce integer type for tags.level (reject strings, floats, and booleans). 3. Document canonical attribute rules, conflict error handling, and unleveled control display vs compliance evaluation semantics in framework-design.md. 4. Eliminate redundant tags fallback logic in merger.py and control_loader.py. 5. Add unit tests for conflict rejection, invalid tag types, and update CHANGELOG.md. Refs darnitdevorg#568, Refs darnitdevorg#589 Assisted-by: Antigravity:eni-gemini-2.5-pro Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
… attributes Address review feedback from @mlieberman85 on PR darnitdevorg#597: - Revalidate controls in _apply_override via ControlConfig.model_validate. - Synchronize tag-level attributes (level, domain, security_severity) with top-level attributes in overrides so overriding tags.level updates control.level and prevents conflicts. - Reject invalid tags.level types (e.g. 'L3') during override application. - Add unit tests covering override level synchronization and validation rejection. Refs darnitdevorg#568, Refs darnitdevorg#589 Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
…l selection
- Remove unrelated formatting in control_loader.py, framework_schema.py, merger.py, and test_merger.py
- Note in CHANGELOG.md that include_tags = { level = N } now selects controls declaring top-level level
Signed-off-by: Paradox <159635503+i-am-paradox@users.noreply.github.com>
0ccef6c to
89f4b18
Compare
|
Rebased cleanly onto latest |
mlieberman85
left a comment
There was a problem hiding this comment.
Re-approving on 89f4b18. The rebase changed only the CHANGELOG entry; the rest of the diff is identical to 0ccef6c, which I approved.
On 075556e merged with this branch and #576: full suite 5144 passed, 18 skipped; ruff and validate_sync pass. darnit plan and darnit validate on openssf-baseline.toml, which crash on main with the TypeError from #568, now work, and a full audit of jonschlinkert/is-odd reports the same status as main for all 66 controls.
Drafted with AI assistance (Claude), reviewed and filed by me. Measurements quoted were executed and verified.
darnitdevorg#597 typed one of the two by_level dicts in cli.py, so mypy reports it once and the gate fails the second baseline entry as stale. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
Summary
Fixes #568 and fixes #589.
Framework controls frequently declare maturity levels, domains, and security severity within their
tagsdictionary (for exampletags = { level = 1, domain = "AC" }), leaving the optional top-level attributes asNone. Inopenssf-baseline.toml, 65 of 66 controls declaredtags.level, whileSTAGE1-REF-SECURITY-01set top-levellevel = 1. This mixedintandNonekeys in the level grouping dictionaries, causingsorted(by_level.items())indarnit validateandsorted(by_level.keys())indarnit planto crash withTypeError: '<' not supported between instances of 'int' and 'NoneType'.Changes:
ControlConfig(packages/darnit/src/darnit/config/framework_schema.py), added an after-model validator synchronizinglevel,domain, andsecurity_severitybetween the top-level attributes andtags. If top-level fields are omitted but declared intags, the attributes are populated, giving maturity levels and domains a single source of truth across filtering, planning, validation, and compliance calculations.EffectiveControl(packages/darnit/src/darnit/config/merger.py), added fallback totagsforlevel,domain, andsecurity_severitywhen building effective controls.cmd_planandcmd_validate(packages/darnit/src/darnit/cli.py), sorted level breakdowns usingkey=lambda k: (k is None, k if k is not None else 0)so that controls without a maturity level sort last (No level/none=...) rather than raising aTypeError.docs/architecture/framework-design.mdsection 2.3.tests/darnit/test_cli.pyensuringvalidateandplanrun successfully on all shipped framework TOML files, and verifying sorting behavior on frameworks with mixed and unleveled controls. Added tests intests/darnit/config/test_merger.pyverifying tag synchronization andget_controls_by_level.Type of Change
Framework Changes Checklist
If this PR modifies the darnit framework (
packages/darnit/):docs/architecture/framework-design.md) if behavior changeduv run python scripts/validate_sync.py --verboseand it passesControl/TOML Changes Checklist
If this PR modifies controls or TOML configuration:
Testing
uv run pytest tests/ -v)uv run ruff check .)Ran
uv run pytest tests/darnit/test_cli.py tests/darnit/config/test_merger.py -v(all passed). Randarnit validateon all 8 shipped framework TOMLs in the repo (all passed). Ranscripts/validate_sync.py --verboseandruff check .(all clean).AI assistance
Assisted by Antigravity using model
eni-gemini-2.5-pro. Developed the validator logic, CLI sorting keys, and test cases; verified all execution and validations locally.Additional Notes