Skip to content

fix(config): th config diff/push compares schemas across serializations#102

Merged
brentrager merged 1 commit into
mainfrom
fix-config-push-schema-diff
Jun 22, 2026
Merged

fix(config): th config diff/push compares schemas across serializations#102
brentrager merged 1 commit into
mainfrom
fix-config-push-schema-diff

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

The bug

th config diff / th config push flatten only the local manifest shape — { public: [NAMES], secret: [...], featureFlag: [...] } with SCREAMING_SNAKE names. But the config server stores + returns its schema as a JSON-Schemaproperties.<tier>ConfigSchema.properties.<camelCaseKey>. flatten_schema returned an empty map for that shape, so every local key looked "added".

For the smooai master-org schema, a push --dry-run reported 150 phantom additions / 0 removals — a real push would have duplicated the entire schema in two casings. The monorepo's .smooai-config/schema.json could never be safely pushed. (Found while renaming smooaiOrgBackendKey → smooaiLlmKey.)

The fix

flatten_schema now parses both serializations and compares keys canonically (lowercased, non-alphanumerics stripped), so ANTHROPIC_API_KEY (manifest) ≡ anthropicApiKey (JSON-Schema). Original display name + tier are preserved for output.

Before: 150 added / 0 removed (all phantom).
After (same real schema): an honest 22 added / 3 removed — genuine local↔remote drift (config.ts keys never synced, e.g. SMOOAI_LLM_KEY/MICROSOFT_AUTH/CRAWLER_SERVICE_URL, vs. stale payload* keys still on the remote).

Tests

  • flatten_schema handles the remote JSON-Schema shape.
  • cross-serialization identical schemas diff empty (the core regression).
  • a real rename across serializations reports exactly one add + one remove.
  • existing manifest-shape tests still pass (display names preserved). 3 flatten + 5 diff tests green, clean build.

Out of scope / follow-up

This makes the diff honest (and push correctly no-ops when in sync). Separate concern: the push POST sends the local manifest doc as jsonSchema while the server stores JSON-Schema form — the server ingest contract should be confirmed before relying on real (non-dry-run) pushes that carry changes.

🤖 Generated with Claude Code

…zations

`th config diff`/`push` flattened only the local manifest shape
(`{public:[NAMES], secret:[...], featureFlag:[...]}`, SCREAMING_SNAKE). The
config server stores + returns its schema as a JSON-Schema
(`properties.<tier>ConfigSchema.properties.<camelCaseKey>`), which flattened to
an EMPTY map — so every local key looked "added". For the smooai master-org
schema that meant a dry-run reporting 150 phantom additions / 0 removals, i.e.
`push` would have duplicated the entire schema in two casings. The local
`.smooai-config/schema.json` could never be safely pushed.

Fix: `flatten_schema` now parses BOTH serializations and compares keys
canonically (lowercased, non-alphanumerics stripped) so `ANTHROPIC_API_KEY`
(manifest) ≡ `anthropicApiKey` (JSON-Schema). The original display name + tier
are preserved for output. Against the real smooai schema the dry-run now reports
an honest 22 added / 3 removed (genuine local↔remote drift) instead of 150
phantom adds.

Tests: flatten handles the remote JSON-Schema shape; cross-serialization
identical schemas diff empty; a real rename (smooaiOrgBackendKey → smooaiLlmKey)
across serializations reports exactly one add + one remove. 5 diff + 3 flatten
tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6b50d05

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentrager
brentrager merged commit eb0b473 into main Jun 22, 2026
2 checks passed
brentrager added a commit that referenced this pull request Jun 22, 2026
`pushConfigSchemaVersion` persists the POSTed `jsonSchema` verbatim. push was
POSTing the local *manifest* doc (`{public:[SCREAMING_SNAKE], …, types:{…}}`),
which would overwrite the remote's *JSON-Schema* form
(`properties.<tier>ConfigSchema.properties.<camelKey>`) — changing the shape every
consumer of the stored schema reads. So push could never safely carry a real
change, even after the diff was fixed (#102).

Add `manifest_to_json_schema`: converts the manifest to the server's JSON-Schema
shape before POST (camelCase property names from `types`, tier from matching each
key's canonical form against the tier arrays; bare type strings become
`{type: ...}`). Already-JSON-Schema input passes through. Both push paths (update +
create) now POST the converted form.

Tests: produces the server shape; passthrough for JSON-Schema input; and the key
invariant — the converted form flattens to the SAME key→tier map as the manifest
(`compute_diff(manifest, converted)` is empty), so a push is a key-set update, not
a format rewrite. 3 new tests pass (+ the #102 diff tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brentrager added a commit that referenced this pull request Jul 8, 2026
`pushConfigSchemaVersion` persists the POSTed `jsonSchema` verbatim. push was
POSTing the local *manifest* doc (`{public:[SCREAMING_SNAKE], …, types:{…}}`),
which would overwrite the remote's *JSON-Schema* form
(`properties.<tier>ConfigSchema.properties.<camelKey>`) — changing the shape every
consumer of the stored schema reads. So push could never safely carry a real
change, even after the diff was fixed (#102).

Add `manifest_to_json_schema`: converts the manifest to the server's JSON-Schema
shape before POST (camelCase property names from `types`, tier from matching each
key's canonical form against the tier arrays; bare type strings become
`{type: ...}`). Already-JSON-Schema input passes through. Both push paths (update +
create) now POST the converted form.

Tests: produces the server shape; passthrough for JSON-Schema input; and the key
invariant — the converted form flattens to the SAME key→tier map as the manifest
(`compute_diff(manifest, converted)` is empty), so a push is a key-set update, not
a format rewrite. 3 new tests pass (+ the #102 diff tests).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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