diff --git a/.changeset/field-constraints.md b/.changeset/field-constraints.md deleted file mode 100644 index 583134f..0000000 --- a/.changeset/field-constraints.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -"@contentrain/mcp": major -"@contentrain/types": minor ---- - -feat(mcp)!: enforce the field constraints the schema already accepted - -A project reported that `items`, `accept` and `maxSize` are accepted on a field but -never enforced — `emails: ["not-an-email"]` and `accept: "image/jpeg"` against a -`.webp` both 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_save` now 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 in `next_steps` while -`status` still said `"committed"`. Validation now runs on the pending changes and -blocks on errors, returning `isError` and 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 only `typeof`, so `min`/`max`/`pattern`/ -`options` never reached an item, and `items` given 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 the `integer` split where `3.7` was -rejected inside an array but accepted as a scalar. - -**17 types were pure `typeof` checks.** `slug` now uses the `SLUG_PATTERN` the -codebase already owned — every shipped template declares `slug: { type: 'slug' }`, -so `"Hello World!!"` used to validate clean. `date`/`datetime` are parsed (the same -check `schedule.ts` already did for meta), `percent` is range-checked, and `color`/ -`phone` warn. Mechanical rules are errors; heuristics are warnings. `email`/`url` -keep their existing warning severity. `rating` is deliberately untouched — its scale -is never declared, so any range would be invented. - -**`unique` works 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.** `accept` is enforced by extension-sniff -and says that is what it is. `default` is coherence-checked at model_save (right -type, within its own `options`) but not written into content. `maxSize` **cannot 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.** `options` on a non-select, `items` -on a non-array, `accept`/`maxSize` on a non-media field, `min > max`, and an -uncompilable `pattern` are now errors instead of silent no-ops. Nested `fields`/ -`items` schemas are validated recursively — they were typed `z.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_save` rejects content it previously committed. Run `contentrain_validate` - before upgrading to see what would now be blocked. -- `model_save` rejects models it previously accepted (unknown keys, `min > max`, - `options` on a non-select, `unique` on a singleton). -- `validateModelDefinition` returns `{ errors, warnings }` instead of `string[]`. -- 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`, not `title`) — - a bare name was ambiguous with a top-level field. - -`@contentrain/types` gains `validateSemanticType`, `validateAccept` and -`isMediaType`; `validateFieldValue` now applies semantic and `accept` rules. - -Studio picks all of this up automatically — its `content-validation.ts` delegates to -this validator. diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a7248a7..706598a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,16 @@ # contentrain +## 0.7.9 + +### Patch Changes + +- Updated dependencies [173326c] + - @contentrain/mcp@2.0.0 + - @contentrain/types@0.9.0 + - @contentrain/rules@0.6.0 + - @contentrain/skills@0.6.0 + - @contentrain/query@7.0.1 + ## 0.7.8 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 20ce016..b39eaa6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "contentrain", - "version": "0.7.8", + "version": "0.7.9", "license": "MIT", "description": "CLI for Contentrain — AI content governance infrastructure", "type": "module", diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index 88b3eea..4e1f18d 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,88 @@ # @contentrain/mcp +## 2.0.0 + +### Major Changes + +- 173326c: feat(mcp)!: enforce the field constraints the schema already accepted + + A project reported that `items`, `accept` and `maxSize` are accepted on a field but + never enforced — `emails: ["not-an-email"]` and `accept: "image/jpeg"` against a + `.webp` both 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_save` now 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 in `next_steps` while + `status` still said `"committed"`. Validation now runs on the pending changes and + blocks on errors, returning `isError` and 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 only `typeof`, so `min`/`max`/`pattern`/ + `options` never reached an item, and `items` given 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 the `integer` split where `3.7` was + rejected inside an array but accepted as a scalar. + + **17 types were pure `typeof` checks.** `slug` now uses the `SLUG_PATTERN` the + codebase already owned — every shipped template declares `slug: { type: 'slug' }`, + so `"Hello World!!"` used to validate clean. `date`/`datetime` are parsed (the same + check `schedule.ts` already did for meta), `percent` is range-checked, and `color`/ + `phone` warn. Mechanical rules are errors; heuristics are warnings. `email`/`url` + keep their existing warning severity. `rating` is deliberately untouched — its scale + is never declared, so any range would be invented. + + **`unique` works 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.** `accept` is enforced by extension-sniff + and says that is what it is. `default` is coherence-checked at model_save (right + type, within its own `options`) but not written into content. `maxSize` **cannot 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.** `options` on a non-select, `items` + on a non-array, `accept`/`maxSize` on a non-media field, `min > max`, and an + uncompilable `pattern` are now errors instead of silent no-ops. Nested `fields`/ + `items` schemas are validated recursively — they were typed `z.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_save` rejects content it previously committed. Run `contentrain_validate` + before upgrading to see what would now be blocked. + - `model_save` rejects models it previously accepted (unknown keys, `min > max`, + `options` on a non-select, `unique` on a singleton). + - `validateModelDefinition` returns `{ errors, warnings }` instead of `string[]`. + - 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`, not `title`) — + a bare name was ambiguous with a top-level field. + + `@contentrain/types` gains `validateSemanticType`, `validateAccept` and + `isMediaType`; `validateFieldValue` now applies semantic and `accept` rules. + + Studio picks all of this up automatically — its `content-validation.ts` delegates to + this validator. + +### Patch Changes + +- Updated dependencies [173326c] + - @contentrain/types@0.9.0 + ## 1.11.0 ### Minor Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index be63788..8f9ab80 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@contentrain/mcp", - "version": "1.11.0", + "version": "2.0.0", "mcpName": "io.github.Contentrain/contentrain", "license": "MIT", "description": "Local-first MCP server for AI-generated content governance — 24 deterministic tools (19 core + 5 media), stdio + HTTP transports, Local / GitHub / GitLab providers", diff --git a/packages/sdk/js/CHANGELOG.md b/packages/sdk/js/CHANGELOG.md index 04c9711..218e455 100644 --- a/packages/sdk/js/CHANGELOG.md +++ b/packages/sdk/js/CHANGELOG.md @@ -1,5 +1,12 @@ # @contentrain/query +## 7.0.1 + +### Patch Changes + +- Updated dependencies [173326c] + - @contentrain/types@0.9.0 + ## 7.0.0 ### Major Changes diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 4e508fe..51ad90b 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,6 +1,6 @@ { "name": "@contentrain/query", - "version": "7.0.0", + "version": "7.0.1", "license": "MIT", "description": "Optional type-safe query SDK for Contentrain — generated TypeScript client for platform-independent JSON content", "type": "module", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 36acf6e..134a969 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,83 @@ # @contentrain/types +## 0.9.0 + +### Minor Changes + +- 173326c: feat(mcp)!: enforce the field constraints the schema already accepted + + A project reported that `items`, `accept` and `maxSize` are accepted on a field but + never enforced — `emails: ["not-an-email"]` and `accept: "image/jpeg"` against a + `.webp` both 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_save` now 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 in `next_steps` while + `status` still said `"committed"`. Validation now runs on the pending changes and + blocks on errors, returning `isError` and 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 only `typeof`, so `min`/`max`/`pattern`/ + `options` never reached an item, and `items` given 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 the `integer` split where `3.7` was + rejected inside an array but accepted as a scalar. + + **17 types were pure `typeof` checks.** `slug` now uses the `SLUG_PATTERN` the + codebase already owned — every shipped template declares `slug: { type: 'slug' }`, + so `"Hello World!!"` used to validate clean. `date`/`datetime` are parsed (the same + check `schedule.ts` already did for meta), `percent` is range-checked, and `color`/ + `phone` warn. Mechanical rules are errors; heuristics are warnings. `email`/`url` + keep their existing warning severity. `rating` is deliberately untouched — its scale + is never declared, so any range would be invented. + + **`unique` works 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.** `accept` is enforced by extension-sniff + and says that is what it is. `default` is coherence-checked at model_save (right + type, within its own `options`) but not written into content. `maxSize` **cannot 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.** `options` on a non-select, `items` + on a non-array, `accept`/`maxSize` on a non-media field, `min > max`, and an + uncompilable `pattern` are now errors instead of silent no-ops. Nested `fields`/ + `items` schemas are validated recursively — they were typed `z.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_save` rejects content it previously committed. Run `contentrain_validate` + before upgrading to see what would now be blocked. + - `model_save` rejects models it previously accepted (unknown keys, `min > max`, + `options` on a non-select, `unique` on a singleton). + - `validateModelDefinition` returns `{ errors, warnings }` instead of `string[]`. + - 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`, not `title`) — + a bare name was ambiguous with a top-level field. + + `@contentrain/types` gains `validateSemanticType`, `validateAccept` and + `isMediaType`; `validateFieldValue` now applies semantic and `accept` rules. + + Studio picks all of this up automatically — its `content-validation.ts` delegates to + this validator. + ## 0.8.0 ### Minor Changes diff --git a/packages/types/package.json b/packages/types/package.json index 092d086..f5137c6 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@contentrain/types", - "version": "0.8.0", + "version": "0.9.0", "license": "MIT", "description": "Shared TypeScript types for Contentrain ecosystem", "type": "module",