Skip to content

Commit cdc3cc4

Browse files
fix: enforce the §2 unknown-property rule below the envelope (#10)
* fix(component): reject unknown properties below the envelope spec.md §2 requires unknown properties to be rejected at every level, but additionalProperties: false was present on only SeedComponentMetadata. The 21 Component*Request defs were open, so a misspelled optional key anywhere below the envelope validated clean and fell back to its default. The gap is an artifact of the bootstrap import (ADR 0001): platform Seed* models set Pydantic extra="forbid"; reused *Request wire models inherit the default extra="ignore". Map-shaped schemas (contract inputs/outputs, workload endpoints/volumes, build arguments) are untouched — their keys are chosen by the document author. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com> * fix(blueprint): reject unknown properties below the envelope The six Blueprint*Request defs carried no additionalProperties, so a misspelled optional key inside a parameter or a connection ref validated clean. The Seed* defs were already closed, which is why the first level below the envelope appeared strict while everything under it was not. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com> * test(conformance): pin nested unknown-field rejection structural/003-unknown-field only injects a key at the root, which is why the gap below the envelope survived the bootstrap import. structural-005 injects the typo deeper: a probe field on component, a parameter's ui block on blueprint, and an array element on listing. Against the pre-change schemas the component case validated cleanly and the blueprint case produced no ERR_UNKNOWN_FIELD. The listing case passes either way — listing was already fully strict, so it is a regression pin rather than proof of a fix. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com> * feat(tools): require object schemas to close additionalProperties Nothing checked this before, which is how 27 open defs survived the import. Any subschema declaring type: object and properties must now also declare additionalProperties: false. Map-shaped schemas declare no properties and are unaffected, so the rule needs no exemption list. walkObjects now carries a JSON Pointer so the diagnostic can name the offending subschema. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com> * docs(component): name ERR_UNKNOWN_FIELD in the envelope clause All four unknown-field fixtures cite #envelope, but §2 never named the code they assert — the binding came only from the §8 registry. State it inline, as the specVersion sentence two lines down already does, and say explicitly that the rule covers optional fields, where ignoring a typo substitutes the default. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com> --------- Signed-off-by: Justin Merrell <merrelljustin@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 262a006 commit cdc3cc4

20 files changed

Lines changed: 212 additions & 9 deletions

File tree

conformance/blueprint/v1/cases.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"id": "structural-004-unsupported-spec-version",
2727
"phase": "structural",
2828
"path": "structural/004-unsupported-spec-version"
29+
},
30+
{
31+
"id": "structural-005-nested-unknown-field",
32+
"phase": "structural",
33+
"path": "structural/005-nested-unknown-field"
2934
}
3035
]
3136
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# A misspelled optional field below the envelope. `lable` is a typo for
2+
# `label`, three levels down inside a parameter's presentation block.
3+
# Rejection must not depend on how deeply the typo is nested.
4+
specVersion: v1
5+
kind: BLUEPRINT
6+
metadata:
7+
slug: web-and-database
8+
version: 1
9+
spec:
10+
components:
11+
db:
12+
component: postgres
13+
size: general.standard.small
14+
connections: {}
15+
parameters:
16+
siteTitle:
17+
schema:
18+
type: STRING
19+
default: My Application
20+
isRequired: false
21+
ui:
22+
lable: Site title
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{
3+
"code": "ERR_UNKNOWN_FIELD",
4+
"path": "/spec/parameters/siteTitle/ui"
5+
}
6+
]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "structural-005-nested-unknown-field",
3+
"phase": "structural",
4+
"expected": "fail",
5+
"clause": "specifications/blueprint/v1/spec.md#envelope",
6+
"summary": "An undefined property nested below the envelope is rejected, not silently dropped."
7+
}

conformance/component/v1/cases.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"id": "structural-004-unsupported-spec-version",
2727
"phase": "structural",
2828
"path": "structural/004-unsupported-spec-version"
29+
},
30+
{
31+
"id": "structural-005-nested-unknown-field",
32+
"phase": "structural",
33+
"path": "structural/005-nested-unknown-field"
2934
}
3035
]
3136
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# A misspelled optional field below the envelope. `initialDelaySecond` is
2+
# singular; the defined property is `initialDelaySeconds`. Rejection must not
3+
# depend on how deeply the typo is nested.
4+
specVersion: v1
5+
kind: COMPONENT
6+
metadata:
7+
version: 1
8+
spec:
9+
workload:
10+
kind: SERVICE
11+
source:
12+
type: IMAGE
13+
ref: nginx:1.29.4-alpine
14+
health:
15+
readiness:
16+
path: /healthz
17+
initialDelaySecond: 30
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{
3+
"code": "ERR_UNKNOWN_FIELD",
4+
"path": "/spec/workload/health/readiness"
5+
}
6+
]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "structural-005-nested-unknown-field",
3+
"phase": "structural",
4+
"expected": "fail",
5+
"clause": "specifications/component/v1/spec.md#envelope",
6+
"summary": "An undefined property nested below the envelope is rejected, not silently dropped."
7+
}

conformance/listing/v1/cases.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"id": "structural-004-unsupported-spec-version",
2727
"phase": "structural",
2828
"path": "structural/004-unsupported-spec-version"
29+
},
30+
{
31+
"id": "structural-005-nested-unknown-field",
32+
"phase": "structural",
33+
"path": "structural/005-nested-unknown-field"
2934
}
3035
]
3136
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# A misspelled optional field below the envelope. `captions` is a typo for
2+
# `caption`, inside an array element. Rejection must not depend on how deeply
3+
# the typo is nested, nor on whether it sits inside an array.
4+
specVersion: v1
5+
kind: LISTING
6+
metadata:
7+
slug: postgres
8+
version: 1
9+
spec:
10+
listingKind: COMPONENT
11+
displayName: PostgreSQL
12+
summary: The open-source relational database
13+
category: INFRASTRUCTURE
14+
lifecycleStage: STABLE
15+
screenshots:
16+
- file: media/overview.png
17+
captions: Connection details after deployment

0 commit comments

Comments
 (0)