feat(v2): Phase 4 — annotation (questions, suggestions, responses)#229
Merged
Conversation
…sion, uniqueness-test factories (roborev job 101)
roborev job 103 (Low): downgrade() re-added kind with server_default="table" but never cleared it, leaving the schema slightly divergent from the original (Python-side model default only) after a downgrade+upgrade cycle.
Wire the questions vertical slice end-to-end: QuestionCreate/Update/Read
schemas, contexts.v2.annotation (create/list/get/update/delete backed by
QuestionBindingValidator + SchemaVersion.columns_cache), V2QuestionPolicy,
and the /schemas/{id}/questions + /questions/{id} router. Eager-loads
question.schema via selectinload for the write policies since AsyncSession
cannot lazy-load relationships outside an active greenlet.
Adds Task 7 of the v2 Phase 4 annotation vertical: SuggestionUpsert/
SuggestionRead schema, upsert_suggestion/list_suggestions context
functions (idempotent per record/question via the unique constraint
from Task 3), V2SuggestionPolicy (owner or member for read; owner or
admin+member for write), and the PUT|GET /records/{id}/suggestions
router. The PUT handler 422s when the payload's question does not
belong to the record's schema.
Adds coverage for the update.columns branch flagged by roborev job 109: a valid rebind, an unknown-column rejection, and an arity mismatch for a non-table question type re-running QuestionBindingValidator on update.
… job 110)
Adds a negative test asserting a workspace-member-but-non-admin annotator
gets 403 from PUT /records/{id}/suggestions, mirroring the existing
read-path negative test and guarding V2SuggestionPolicy.write's
owner-or-admin+member requirement against regressing to mere membership.
Adds annotation_ctx.upsert_response/get_response (keyed by question name,
idempotent per record+user, never mutates record.status or touches the
LanceDB index engine per spec §17.3/§17.5), V2ResponsePolicy for own-response
authz, and PUT/GET /records/{id}/responses routes.
…ex_sync' idiom The AST guard only inspected ImportFrom.module, so `from extralit_server.contexts.v2 import index_sync` (the exact idiom used in api/v2/records.py and api/v2/schemas.py) slipped through undetected since neither assertion looked at the imported alias names. Refactor detection into _imports_index_engine(), checking bare and qualified alias names plus relative-import forms, and add a self-test that exercises every realistic violating and innocent import form against synthetic source snippets.
…ranches; polish validator messages
…store fail-closed value dispatch (roborev job 120) - Add QuestionSettingsValidator (validators/v2/questions.py), reusing v1's QuestionSettings TypeAdapter, and call it in create_question/update_question after binding validation so settings-driven questions (rating/label_selection/ multi_label_selection/ranking) with empty or type-mismatched settings fail loudly at create/update time instead of persisting unusable. - Restore the terminal fail-closed `else: raise` in V2ResponseValueValidator.validate so an unmapped future QuestionType rejects rather than silently accepting an unvalidated value.
…ontradictory type (roborev job 121) QuestionSettingsValidator injected the "type" discriminator only for validation, while create_question/update_question persisted raw settings and annotation-time _parsed() never injected it. A settings-driven question created with valid settings but no "type" key thus passed create-time validation yet was permanently unusable at annotation time (opaque 422 on every suggestion/response). - create_question/update_question now normalize the *stored* settings with the question's own type, so create.type/question.type is always the source of truth for the persisted discriminator. - QuestionSettingsValidator.validate rejects an explicit "type" in the incoming settings that contradicts the question's real type, instead of silently overwriting it.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
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.
Phase 4 — Annotation (v2)
Builds the schema-centric annotation layer on top of the v2 records (Phases 1–3): questions (reviewable column bindings), suggestions (LLM pre-populated values), responses (human submissions), and a query-time projection view that resolves each reviewable cell. Postgres-only — no LanceDB sync. Implements spec §17; see the new §18 "Resolved during Phase 4 implementation" for as-built decisions.
What's included
schemas.kind(§14): the kind is emergent from bindings. Migration6393b1a01aa0.v2_-prefixed tables:V2Question/V2Suggestion/V2Response(+ enumsv2_*_enum), migrationc1510e93882a, factories. Reuses v1QuestionType/SuggestionType/ResponseStatusand v1 per-type value validators — not forked.tablestructure-only)./schemas/{id}/questions,/questions/{id}), suggestions (PUT|GET /records/{id}/suggestions, idempotent per(record, question)), responses (PUT|GET /records/{id}/responses, idempotent per(record, user), keyed by question name, norecord.statusside-effect, own-response authz).GET /schemas/{id}/versions/{version}(§17.5).GET /projection/references/{reference:path}?workspace_id=…— resolves each cell as submitted-response(requesting user) → suggestion → none; batched queries, distinct prefix so it can't be shadowed by the Phase-3/references/{reference:path}route.Key design decisions (§18)
sa.Enum(name=…).drop(…, checkfirst=True), Postgres-only DDL guarded.V2*classes/enums/policies, one-directional relationships) → no collision with v1's relationship registry.settingsare validated againsttypeat create/update and normalized to carry the"type"discriminator on store, so a settings-driven question can never be persisted-but-unusable.QuestionTyperaises, never silently accepts).Testing & review
errorin the multi-directory run is the pre-existing session-scoped Elasticsearch-teardown-at-localhost:9200infra noise (the flagged test passes in isolation); not a Phase-4 regression.🤖 Generated with Claude Code