Skip to content

fix(E3051): skip Automation schema for AppConfig SSM document types - #4844

Merged
kddejong merged 4 commits into
aws-cloudformation:mainfrom
Varshith-Kali:fix-4834-e3051-ssm-appconfig-documenttype
Oct 26, 2026
Merged

kddejong merged 4 commits into
aws-cloudformation:mainfrom
Varshith-Kali:fix-4834-e3051-ssm-appconfig-documenttype

Conversation

@Varshith-Kali

Copy link
Copy Markdown
Contributor

What

E3051 validates the Content of AWS::SSM::Document against the Automation document schema, which requires a schemaVersion key. Resources with DocumentType ApplicationConfiguration or ApplicationConfigurationSchema hold AppConfig configuration data or a JSON Schema (draft-04); neither format carries an SSM schemaVersion key, so every such document was a false positive. E3051 now skips the Automation-document schema when the resource's DocumentType is one of the two AppConfig types. A missing DocumentType or one given as an intrinsic keeps the current validation (fail closed).

Why

Resolves the report in #4834. The schema in src/cfnlint/data/schemas/other/ssm/document.json is the Automation document schema (its title says so), and applying it to AppConfig document types is meaningless.

How

  • src/cfnlint/rules/resources/ssm/Document.py: new _is_appconfig_document helper reads the containing resource's DocumentType from the validator's context path and template; validate returns early for the two AppConfig types. Only a plain-string DocumentType is conclusive.

Testing

  • Reproduced the issue template locally before the fix (E3051 fired on both AppConfig documents); after the fix the template is clean.
  • Negative controls still flagged: an Automation document without schemaVersion, an Automation document with an out-of-enum schemaVersion (9.9), and a document whose DocumentType is an intrinsic (!Ref).
  • New regression tests in test/unit/rules/resources/ssm/test_document.py::test_validate_document_type_scoping: both AppConfig types produce 0 errors; Automation and intrinsic-typed documents still produce 1 error each.
  • Suite: test_document.py 12 passed (8 existing plus 4 new). ruff check and ruff format clean on all touched files.

Fixes #4834

When a resource's Condition provably always evaluates to true, there is
no scenario in which the resource does not exist, so E3005 no longer
reports that the DependsOn target will not exist when the condition is
False. Adds Conditions.is_always_true (SAT-backed, cached) and consults
it in Template.is_resource_available before scenario analysis. A
genuinely conditional target still reports the False scenario.

Fixes aws-cloudformation#4820

Signed-off-by: Varshith Puli <pulivarshit@gmail.com>
AWS::SSM::Document resources with DocumentType ApplicationConfiguration
or ApplicationConfigurationSchema hold configuration data or a JSON
Schema. Neither content format carries an SSM schemaVersion key, so
validating Content against the Automation document schema only produces
false positives. E3051 now skips the Automation schema when DocumentType
is one of the AppConfig types. A missing or intrinsic DocumentType keeps
the current validation (fail closed).

Fixes aws-cloudformation#4834

Signed-off-by: Varshith Puli <pulivarshit@gmail.com>
@Varshith-Kali

Copy link
Copy Markdown
Contributor Author

Hi team, on this fix(E3051) PR: the [CI] Test PR workflow run on the current head is still parked waiting for a first-time-contributor approval (action_required). Could a maintainer approve the run so CI can proceed? Thanks.

@Varshith-Kali

Copy link
Copy Markdown
Contributor Author

Hi @kddejong, friendly follow up: the CI runs on this PR are still parked at the first-time-contributor approval gate (action_required), so the checks cannot start until a maintainer approves the run. Could you approve it when you have a moment? Thanks!

@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.55556% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.66%. Comparing base (f8baa08) to head (3bee179).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/cfnlint/rules/resources/ssm/Document.py 55.55% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4844      +/-   ##
==========================================
- Coverage   94.68%   94.66%   -0.03%     
==========================================
  Files         439      439              
  Lines       16016    16025       +9     
  Branches     3111     3114       +3     
==========================================
+ Hits        15165    15170       +5     
- Misses        468      470       +2     
- Partials      383      385       +2     
Flag Coverage Δ
unittests 94.65% <55.55%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kddejong

Copy link
Copy Markdown
Contributor

The substance here is right, and I confirmed it against the service rather than the docs: an ApplicationConfiguration and an ApplicationConfigurationSchema document both reach Active with no schemaVersion, while a Command document without one is rejected with InvalidDocumentContent: Missing "schemaVersion" in the document. So the narrow exemption is correct, and a blanket removal of the requirement would have lost a real check.

codecov/patch is failing on _is_appconfig_document. Three of its five guards are unreachable from any test — I deleted each in turn and the suite stayed green:

guard removed result
if cfn is None 12 passed — uncovered
path-shape check 8 failed — covered
isinstance(resource, dict) 12 passed — uncovered
isinstance(properties, dict) 12 passed — uncovered
(sanity: always return True) 2 failed — covered

Rather than add tests for shapes that mostly cannot occur, the helper can go away entirely. The rule is keyworded at .../Properties/Content, which is why it has to climb back out to the template to read a sibling. Keyword it one level up and DocumentType and Content are both keys of the instance:

     def __init__(self):
         super().__init__(
-            ["Resources/AWS::SSM::Document/Properties/Content"],
+            ["Resources/AWS::SSM::Document/Properties"],
-        if self._is_appconfig_document(validator):
+        if not validator.is_type(instance, "object"):
+            return
+        if instance.get("DocumentType") in self._APPCONFIG_DOCUMENT_TYPES:
             return
+        if "Content" not in instance:
+            return
+        instance = instance.get("Content")

and the whole _is_appconfig_document method is deleted. The error paths need one segment restored so findings keep pointing at Content rather than Properties:

                 yield ValidationError(
                     f"Document is not valid ({errs[0].message!r})",
                     validator="type",
                     rule=self,
+                    path=["Content"],
                 )
             if not err.validator.startswith("fn_") and err.validator not in ["cfnLint"]:
                 err.rule = self
+            err.path.appendleft("Content")
             yield err

I built this and compared it against your branch. Output is identical, error locations included:

both trees, same template:
  E3051  Resources/AutoDoc/Properties/Content   'schemaVersion' is a required property
  E3051  Resources/CmdDoc/Properties/Content    'schemaVersion' is a required property
  (ApplicationConfiguration document: silent)

Also identical on a string Content, on a Content that is missing entirely (E3003 from the schema, not E3051), and across the good/ fixture sweep — 162 findings on both.

What this buys: no validator.cfn dependency, no path arithmetic, no isinstance guards, so the three uncovered lines stop existing and codecov/patch goes green without writing tests for unreachable branches. It is also the shape the repo prefers for a constraint that depends on a sibling property.

The cost is that the existing unit tests pass Content's value as instance, so they would need to pass {"DocumentType": ..., "Content": ...}. That is mechanical, and it makes them exercise the sibling relationship the rule actually depends on instead of mocking a template around it.

Two test gaps worth closing while you are in there, neither of which mutation testing can see because there is no assertion either way:

  • Content as a string on an AppConfig type. The new early return sits ahead of the decode_str branch, so that path changed behaviour with nothing pinning it.
  • Command without schemaVersion. That is the type AWS actually rejects, so it is the case that proves the exemption is narrow rather than broad.

Unrelated to this PR but adjacent, and worth knowing before the string path is tested: E3051 cannot validate a string Content at all on current main. decode_str is handed cfn-lint's str_node, which it rejects as not being a string, so a well-formed document and a broken one both produce Document is not valid ('Template could not be parsed: a string or stream input is required'). Filed separately.

…limb

Keyword the rule at Resources/AWS::SSM::Document/Properties so
DocumentType and Content are both keys of the instance. This deletes
the _is_appconfig_document helper and its template climb, path
arithmetic, and isinstance guards, and restores the Content path
segment on both error paths so findings still point at Content.

Update the unit tests to pass Properties objects instead of Content
values, and add coverage for string Content on AppConfig types and
for a Command document without schemaVersion.

Signed-off-by: Varshith Puli <pulivarshit@gmail.com>
@Varshith-Kali

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review and for verifying the exemption against the service, that confirms the narrow scope is the right call.

Applied the refactor as suggested:

  • Rule re-keyworded from Resources/AWS::SSM::Document/Properties/Content to Resources/AWS::SSM::Document/Properties, and the whole _is_appconfig_document helper is deleted. The match logic is now the object guard, the DocumentType exemption return, the missing Content return, then descending into Content.
  • Both error paths restore the Content segment (path=["Content"] on the decode ValidationError, err.path.appendleft("Content") on the schema errors), so findings still point at Content, not Properties.
  • Unit tests updated mechanically to pass {"DocumentType": ..., "Content": ...} instead of the bare Content value, and the expected error paths gained the Content prefix.
  • Two new tests: string Content on both ApplicationConfiguration and ApplicationConfigurationSchema (silent, since the exemption return now sits ahead of the decode branch), and a Command document without schemaVersion (flagged with 'schemaVersion' is a required property at Content, proving the exemption is narrow).

Verification: test/unit/rules/resources/ssm/ green, 26 passed, including all 15 in test_document.py. Output comparison on a template with Automation, Command, and AppConfig documents matches your stated output exactly (E3051 at Resources/AutoDoc/Properties/Content and Resources/CmdDoc/Properties/Content, AppConfig silent), and the good/ fixture sweep is identical to the old head (162 findings on the yaml scope, same set).

Noted on the string-Content caveat: E3051 cannot validate a string Content at all on current main since decode_str rejects cfn-lint's str_node. Left as you filed it separately, no attempt to fix that here.

@kddejong
kddejong merged commit fecf750 into aws-cloudformation:main Oct 26, 2026
22 of 24 checks passed
kddejong added a commit that referenced this pull request Oct 29, 2026
#4844 moved E3051's keyword from
Resources/AWS::SSM::Document/Properties/Content up to
Resources/AWS::SSM::Document/Properties so validate() could read the
sibling DocumentType off the instance. Properties sits in front of
cfn-lint's intrinsic-function machinery, so the rule began receiving a
raw dict for Content instead of a resolved value and reported
'schemaVersion' is a required property on every document whose Content
comes from Ref, Fn::GetAtt or Fn::Sub.

Put the keyword back at Content and read DocumentType through
Template.get_cfn_path, the same way E1041 reads a sibling VpcId. The
logical ID comes from validator.context.path.path[1], as in E3035,
E3036, E3055 and W2530. get_cfn_path yields each Fn::If branch with its
condition bound, so a conditional DocumentType is now handled too;
#4844 and its predecessor both validated it as an unresolved dict.

The AppConfig exemption from #4844 is unchanged: ApplicationConfiguration
and ApplicationConfigurationSchema still skip the Automation schema, an
absent or unresolvable DocumentType still validates, and a Command
document without a schemaVersion still errors.

Errors are no longer prefixed with Content: at this keyword the path
already ends in Content, so appending it produced a doubled
Resources/<id>/Properties/Content/Content in json and sarif output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E3051: 'schemaVersion' is a required property on AWS::SSM::Document with DocumentType ApplicationConfiguration or ApplicationConfigurationSchema

2 participants