feat: standardize HTTP response shapes on snake_case#32
Merged
Conversation
Establishes snake_case as the canonical wire format for all responses, bringing them in line with the request-side convention already in place and aligning with the AI API ecosystem (Anthropic, OpenAI, Stripe). Ingest response now splits memory IDs by outcome (stored_memory_ids + updated_memory_ids) so consumers can honestly distinguish creates from updates — the accumulator previously lumped all touched IDs into a single memoryIds array, losing the per-outcome split. Also flips the search response's nested observability traces (retrieval / packaging / assembly) to snake_case, fixes the scope echo, and routes every leaky route (stats, consolidate, decay, cap, reset-source, reconcile, lessons/stats, audit/summary, audit trail, lessons/report, /:id/audit, PUT /config applied) through explicit formatters in a new src/routes/memory-response-formatters.ts. Internal TS types stay camelCase; formatters are the only seam that touches the wire. IngestResult gains storedMemoryIds + updatedMemoryIds and keeps memoryIds (union) for post-write processors. Tests: route-validation, research-consumption-seams, and composed-boot-parity updated to assert snake_case bodies. All 28 HTTP-seam tests pass end-to-end. OpenAPI regenerated with zero diff (spec declares only request bodies today — response schemas are a separate follow-up worth doing so CI's check:openapi catches wire drift automatically). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2.45.0 (installed by npx on 2026-04-22) flags 17 pre-existing cyclomatic complexity violations on untouched files (llm.ts, quick-extraction.ts, repository-claims.ts, embedding.ts, supplemental-extraction.ts, atomicmem-uri.ts). Main's last green CI ran 2.43.0 with the same code and reported 0 above threshold. Pin until someone deliberately bumps and addresses the flagged functions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
memory-route-config-seam.test.ts asserted that PUT /memories/config returned 'maxSearchResults' in the applied array; after the wire flip this is emitted as 'max_search_results'. The earlier audit missed it because it was in a file that did not match the camelCase field-name search pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stored_memory_ids/updated_memory_ids). The accumulator previously lumped all touched IDs into one array, so callers couldn't distinguish creates from updates — now each list length matches its corresponding count.src/routes/memory-response-formatters.tscentralizes the translation layer; internal TS types stay camelCase, formatters are the only seam that touches the wire. All leaky routes that were passing internal types directly tores.json()now go through explicit formatters.What changed
episode_id,facts_extracted,memories_stored,memories_updated,memories_deleted,memories_skipped,stored_memory_ids,updated_memory_ids,links_created,composites_created.{ kind, user_id, workspace_id, agent_id, agent_scope }. Observability nested fields snake_cased (package_type,included_ids,dropped_ids,evidence_roles,has_current_marker,final_token_cost,primary_evidence_position, etc.).lesson_id,memory_id/version_counton audit trail, snake_caseappliedarray on PUT /config, and explicit formatters for/stats,/consolidate,/decay,/cap,/reset-source,/reconcile,/lessons/stats,/audit/summary,/audit/recent.IngestResultgainsstoredMemoryIds+updatedMemoryIds; keepsmemoryIds(union) for post-write processors that iterate over every touched memory regardless of outcome.Follow-ups (out of scope)
npm run check:openapicatch future wire drift automatically.Test plan
npx tsc --noEmitcleannpm run generate:openapiproduces zero diff against checked-in specsnake-case-wire-convention) merged in lockstep — maps the new snake_case shapes back to camelCase for SDK consumers