This release includes 5 breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+8 more
Affected surfaces
ReleasePort's take
Light signal`content_save` now validates input before committing and rejects invalid content; `model_save` enforces stricter validation rules including unknown keys, constraint violations, and singleton uniqueness.
Why it matters: Breaking changes affect the content_save and model_save APIs with severity 70; update integration code to handle new rejection behaviors immediately.
Summary
AI summaryUpdates Major Changes, Patch Changes, and mcp across a mixed release.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Breaking | High |
`content_save` now validates before committing and rejects invalid content. `content_save` now validates before committing and rejects invalid content. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Breaking | High |
`model_save` rejects models with unknown keys, `min > max`, invalid constraints, and `unique` on singletons. `model_save` rejects models with unknown keys, `min > max`, invalid constraints, and `unique` on singletons. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Breaking | High |
`validateModelDefinition` now returns `{ errors, warnings }` instead of a string array. `validateModelDefinition` now returns `{ errors, warnings }` instead of a string array. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Feature | Medium |
Added `validateSemanticType`, `validateAccept`, and `isMediaType` utilities to `@contentrain/types`. Added `validateSemanticType`, `validateAccept`, and `isMediaType` utilities to `@contentrain/types`. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Dependency | Low |
Updated dependency `@contentrain/types` to version 0.9.0. Updated dependency `@contentrain/types` to version 0.9.0. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
Array item validation now recurses through the same validator as scalar fields. Array item validation now recurses through the same validator as scalar fields. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
`unique` constraint now correctly enforces uniqueness on documents. `unique` constraint now correctly enforces uniqueness on documents. Source: llm_adapter@2026-07-16 Confidence: high |
— |
| Bugfix | Medium |
`accept`, `maxSize`, and other non‑enforceable constraints are now rejected by `model_save`. `accept`, `maxSize`, and other non‑enforceable constraints are now rejected by `model_save`. Source: llm_adapter@2026-07-16 Confidence: high |
— |
Full changelog
Major Changes
-
173326c: feat(mcp)!: enforce the field constraints the schema already accepted
A project reported that
items,acceptandmaxSizeare accepted on a field but
never enforced —emails: ["not-an-email"]andaccept: "image/jpeg"against a
.webpboth produced zero errors. The report was right, and the surface was larger
than the three properties it named: 4 of 27 field types had any semantic
validation, three constraints were read by nothing, and none of it blocked a write.A constraint that isn't a constraint is worse than no constraint — the author stops
looking.content_savenow validates before committing and refuses to write. It ran
plan → commit → validate → report, so an invalid value landed in git, was
auto-merged, and the caller learned about it from a string innext_stepswhile
statusstill said"committed". Validation now runs on the pending changes and
blocks on errors, returningisErrorand no commit. Warnings still pass — they are
heuristics, and a legitimate value can sit outside an approximate pattern. Only the
entries being saved are fatal: a pre-existing bad entry elsewhere in the model does
not hold up an unrelated save.Array items share the scalar rule set. They ran through a parallel type switch
that knew 10 of the 27 types and checked onlytypeof, somin/max/pattern/
optionsnever reached an item, anditemsgiven as a FieldDef with a non-object
type ({type:'array', items:{type:'string', max:50}}) matched no branch at all —
silently unvalidated, while the type emitter rendered it as real. Items now recurse
through the same validator, which also closes theintegersplit where3.7was
rejected inside an array but accepted as a scalar.17 types were pure
typeofchecks.slugnow uses theSLUG_PATTERNthe
codebase already owned — every shipped template declaresslug: { type: 'slug' },
so"Hello World!!"used to validate clean.date/datetimeare parsed (the same
checkschedule.tsalready did for meta),percentis range-checked, andcolor/
phonewarn. Mechanical rules are errors; heuristics are warnings.email/url
keep their existing warning severity.ratingis deliberately untouched — its scale
is never declared, so any range would be invented.uniqueworks on documents. It was gated on a context only the collection
validator passed, so it was a no-op exactly where every shipped template declares it.
On singletons it is now rejected at model_save: the model holds one record per
locale, so there is nothing to compare against.The dead constraints, handled honestly.
acceptis enforced by extension-sniff
and says that is what it is.defaultis coherence-checked at model_save (right
type, within its ownoptions) but not written into content.maxSizecannot be
enforced by MCP — it holds a path, never the bytes — so model_save now says so and
points at the provider, which owns the policy at ingest. The docs claimed all three
worked; they no longer do.model_save rejects what it will not enforce.
optionson a non-select,items
on a non-array,accept/maxSizeon a non-media field,min > max, and an
uncompilablepatternare now errors instead of silent no-ops. Nestedfields/
itemsschemas are validated recursively — they were typedz.unknown()and never
checked. The field schema is.strict(): a typo'd constraint (requird: true) used
to be stripped without a word.BREAKING CHANGE:
content_saverejects content it previously committed. Runcontentrain_validate
before upgrading to see what would now be blocked.model_saverejects models it previously accepted (unknown keys,min > max,
optionson a non-select,uniqueon a singleton).validateModelDefinitionreturns{ errors, warnings }instead ofstring[].- Array-item type errors carry
validateFieldValue's message ("Type mismatch:
expected string, got number") instead of "must be a string". The field path is
unchanged. - Nested object errors are qualified by their parent (
seo.title, nottitle) —
a bare name was ambiguous with a top-level field.
@contentrain/typesgainsvalidateSemanticType,validateAcceptand
isMediaType;validateFieldValuenow applies semantic andacceptrules.Studio picks all of this up automatically — its
content-validation.tsdelegates to
this validator.
Patch Changes
- Updated dependencies [173326c]
- @contentrain/[email protected]
Breaking Changes
- `content_save` now validates before committing and rejects invalid data that was previously committed.
- `model_save` rejects models with unknown keys, `min > max`, `options` on non-select fields, and `unique` on singletons.
- `validateModelDefinition` returns `{ errors, warnings }` instead of a string array.
- Array-item type error messages now include field path qualification (e.g., `seo.title`).
- Nested object validation errors are qualified by their parent path.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About Contentrain/ai
Local-first MCP server for AI content governance — 13 tools for model/content CRUD, validation, normalization, and i18n across any framework.
Related context
Related tools
Earlier breaking changes
- v@contentrain/[email protected] `validateModelDefinition` now returns `{ errors, warnings }` instead of a string array.
- v@contentrain/[email protected] `model_save` rejects models with unknown keys, `min > max`, invalid constraints, and `unique` on singletons.
- v@contentrain/[email protected] `content_save` now validates before committing and rejects invalid content.
- v@contentrain/[email protected] Migration required: projects must run `contentrain_validate` and fix draft status drift before upcoming Studio enforcement.
- v@contentrain/[email protected] `CdnDocumentQuery.first()` now returns `DocumentIndexEntry<T> | undefined`, no longer including `body`.
Beta — feedback welcome: [email protected]