Skip to content

fix(content): hold new field names to snake_case, and title models the way MCP backfills them - #230

Merged
ABB65 merged 3 commits into
mainfrom
feat/legacy-field-names-title-parity
Sep 2, 2026
Merged

fix(content): hold new field names to snake_case, and title models the way MCP backfills them#230
ABB65 merged 3 commits into
mainfrom
feat/legacy-field-names-title-parity

Conversation

@ABB65

@ABB65 ABB65 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #228#229 (merge in that order; this PR's own diff is the last commit).

Two agreements with MCP 3.1.8 on what a valid model is and how a model without title_field is titled.

  • Field names (content-engine/save-model.ts). MCP's contentrain_model_save keeps a legacy non-snake_case name the model already has and refuses a new one (ai perf: ~6x fewer GitHub API calls in the agent write path (W1-W4) #116); its validator lists kept names as notices. Studio enforced nothing, so the chat agent could mint heroImage and the ecosystem would flag it. saveModel now applies the same rule with the same tolerance via MCP's exported collectFieldPaths / legacyFieldNames: a new non-snake_case field (nested paths included, e.g. seo.ogTitle) is refused naming the path; a legacy one is kept and reported in ModelWriteResult.warnings, which the conversation engine passes to the agent. Tool description states the rule.
  • Title inference (shared/utils/entry-title.ts). validate --fix backfills with requiredness as the tiebreak on every rung, a name-like token anywhere in the key, and never a slug/url/code field (ai Chore/bump contentrain 1.8.0 #120). Studio's fallback ranked name-likeness first, so an authors model with an optional title (job title) and a required name was titled by the job title. The chain is now MCP's — name-like with required first, required prose by type priority, any prose, never slug/url/code — plus Studio's first-field last rung, kept because a read path must render something where the writer can decline and ask.
  • CLAUDE.md invariants for both.

Test plan

  • content-engine.test.ts: new camelCase field (top-level and nested) refused before git; legacy field kept on a title_field-only update with a warning
  • entry-title.test.ts: the Chore/bump contentrain 1.8.0 #120 authors case, token match (post_title), type priority among required prose, url never inferred; all prior expectations unchanged
  • lint, nuxt typecheck, unit + integration (129 files), nuxt (31 files)
  • Staging: on a model that carries a legacy camelCase field, change only title_field through the picker — the save succeeds and the field is untouched

🤖 Generated with Claude Code

https://claude.ai/code/session_01Chid3TnLdPHjsKip4n63Ky

Contentrain added 3 commits September 2, 2026 16:25
Studio pinned types 1.2.0 / mcp 3.1.0 / query 7.0.6, seven releases
behind. The gap is narrower than it looks: between mcp 3.1.0 and 3.1.7
the package source did not change (types bumps only), and types 1.3
through 1.9 added the WordPress migration contracts without touching
FieldDef, ModelDefinition, PATH_PATTERNS or any symbol Studio imports.

The substantive change is 3.1.8 / 1.9.1: the generic `api_key = …`
secret rule now checks what it captured before it fires. Studio's
validateContent comes from the MCP validator and calls detectSecrets,
so until now a setup guide that said `api_key = your_project_api_key_here`
was refused by every Studio write path — UI, agent, Conversation API.
The new test pins that the fix reaches Studio, and that an issued key
is still refused.

Also in 3.1.8, for the next PRs: publish_at/expire_at ride on
ContentEntry (meta only, null clears), legacy field names are
grandfathered by the MCP validator, and title_field inference matches
Studio's resolver.
… in meta

MCP 3.1.8 moved publish_at/expire_at onto the entry: meta only, never
the content file, never a status change. Studio had no way to set them
— the save_content tool, the content route and shapeEntriesForSave
carried data alone — and could not have kept them if it did:
applyStudioMetaOverrides rebuilt the meta record from the file on
contentrain and threw away every key the plan had just set.

The override now starts from the meta MCP planned, which already holds
the prior record plus this write's schedule, and re-owns only status,
updated_by and updated_at (autoPublish is the one rule Studio adds on
top; MCP already keeps the current status). One fewer read per save.

save_content takes publish_at/expire_at beside data (null clears,
absent leaves alone); the content route accepts the same body keys;
the engine validates the dates and lifts any the caller still put
inside data, so nothing leaks into the content file — the habit the
older MCP taught the agent, and the leak its validator now warns about.
A prompt rule says scheduling is a delivery gate, not a publish.
…e way MCP backfills them

Two places where Studio and MCP 3.1.8 disagreed about a model.

Field names. MCP's contentrain_model_save now keeps a legacy
non-snake_case name the model already has and refuses a new one;
its validator lists the kept ones as notices. Studio enforced nothing,
so a field minted through the chat agent could carry a name the rest
of the ecosystem then flags. saveModel applies the same rule with the
same tolerance, through MCP's own collectFieldPaths/legacyFieldNames:
a new camelCase field is refused with the field path, a legacy one is
kept and reported in `warnings` so the agent can say what it tolerated.

Titles. validate --fix backfills title_field with requiredness as the
tiebreak on every rung and never picks a slug, url or code field.
Studio's fallback resolver ranked name-likeness first, so `authors`
with an optional `title` (the job title) and a required `name` was
titled by the job title — the exact ai #120 case. The chain is now the
same one, with Studio's first-field last rung kept because a read path
has to render something where the writer can decline and ask.
@ABB65
ABB65 merged commit c67f18c into main Sep 2, 2026
2 checks passed
@ABB65
ABB65 deleted the feat/legacy-field-names-title-parity branch September 2, 2026 16:31
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.

1 participant