Repository navigation
feat(core): wire parsers for the remaining spec'd endpoints, and strict element keys - #356
Merged
Merged
Conversation
Every endpoint core specifies now has a ./wire parser, so a server no longer copies core's param and field names into its own lists, where they drift: parseAuthChallengeBody, parseAuthTokenBody, parseEntityPatchBody, parseTypeBody, parseMigrationBody, parseDeleteParams, parseDownloadParams and parseAssociationParams. Each refuses an unknown name or malformed boolean with 400 and a wrong-typed known body field with 422, following the split POST /records already makes. The body helpers are shared with wire-record.ts. The spec's § Unrecognized input now tables the parser for each endpoint, § Types names the keys POST /types accepts and ignores, and two error fixtures pin a non-boolean purge and an unknown /auth/token key. Unknown keys inside grant and association elements are left for a separate decision, as the issue notes. Refs #354 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_014ah52eTkayJ1JxPuEmgVGe
…target elements
validateAssociation() and validateGrantTarget() checked that required
fields were present but not that others were absent, so
{ kind: 'entity', entityId, scope } on a grantee stored and answered 200.
The SQLite adapters then dropped the extra key because they have no
column for it, while MemoryAdapter kept it, so the two disagreed about
the same write.
Every element is now held to its own kind's keys, at every depth, with
400. The key tables are typed against each union arm, so a new field
fails to compile until it is listed. _grant content applies the same
keys to its grantee as a 422. wire-request.ts reuses the shared
target-key table.
Refusing on write strands nothing: SQLite never stored such keys, and
there is no install base.
Refs #354
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014ah52eTkayJ1JxPuEmgVGe
🦋 Changeset detectedLatest commit: 73e98de The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
parseEntityPatchBody() reads { content }, but § Entity named no body,
so a client copying PATCH /records/:id's contentPatch got a 400 the
spec did not explain.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01STGzVnzxKKwGh3bs6RU5Fe
content on the wire means whole content (POST /records, migrate), so a merging key spelled content was the trap data-model.md § Mutations names. The parser is new in this PR, so its existing changeset covers it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01STGzVnzxKKwGh3bs6RU5Fe
Merged
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
Closes #354.
Wire parsers. Every endpoint core specifies now has a parser in
@haverstack/core/wire. A server no longer needs its own copies of core's param and field names (as in haverstack/server#130'sknownParams()/readJson()lists).POST /auth/challengeparseAuthChallengeBody()POST /auth/tokenparseAuthTokenBody()PATCH /entityparseEntityPatchBody()POST /typesparseTypeBody()POST /records/:id/migrateparseMigrationBody()DELETE /records/:idparseDeleteParams()GET /attachments/:fileIdparseDownloadParams()GET /records/:id/associationsparseAssociationParams()Each parser answers bad input the way
POST /recordsalready does:The body parsers live in a new
wire-body.ts, which also holds the helperswire-record.tsnow imports from it.Element keys.
Stacknow refuses a key that an element's kind does not define, with a 400. This covers association, permission, relationship-target and grant-target elements, at every depth. It applies to:create(),mutate(),associate(),dissociate(),grantAccess(),revokeAccess()relatedTofilter targetsgrantType(),revokeType(),listTypeGrants()A
_grantrecord'sgranteeis held to the same keys and refused as content with a 422. The key tables are typed against each union arm, so adding a field to an arm fails to compile until the field is listed.Spec
docs/spec/wire-format.md§ Unrecognized input: a table matching each endpoint to its parser, the 400/422 split, and element keys added to "at every depth".docs/spec/wire-format.md§ Types:baseId,version,schemaHashandcreatedAtare accepted and ignored.docs/spec/data-model.md§ Associations: an element carries only the keys its kind defines.error-bad-request-non-boolean-purge,error-bad-request-unknown-auth-token-keyanderror-bad-request-unknown-grantee-key.Verification
All six checks pass locally:
pnpm run format:check,check:refs,lint,test,buildandtypecheck.packages/core/tests/wire-body.test.tsandelement-keys.test.ts, plus additions towire-request.test.ts.StackTypeinto JSON the wayAPIAdapter.saveType()sends it and runs it throughparseTypeBody().defineType()and comes back as a 422.Notes for reviewers
schemaHash:POST /typesaccepts and ignoresschemaHash, which the issue didn't list.APIAdapter.saveType()sends the whole Type, so refusing it would break every client.MemoryAdapterkept them, so the two adapters disagreed about the same write, and this removes that disagreement. There's also no install base. The one cost: a foreign export whose elements carry extension fields is now refused on import, where it used to lose those fields silently.bad_requestorvalidation.invalid_didstill covers a string that isn't a verifiable DID.🤖 Generated with Claude Code
https://claude.ai/code/session_014ah52eTkayJ1JxPuEmgVGe
Generated by Claude Code