Admin routes: uncomplete and regenerate #161

Member

Task: da0226b3-a504-4db2-8cd8-b4fb07adbdb9 (Feature 16 — NestQuest API Service)

Done-condition: Admin routes for uncomplete and regenerate. Uncomplete reverses a completion by appending a reversal event (never editing or deleting the original) and returns the instance to open. Regenerate re-materializes the rolling horizon for the household (or a definition/child) through the same core path the integration uses. Each validates input and calls core with no business logic in the handler. Tests as an admin confirm uncomplete appends a reversal so the derived state is open again, and regenerate re-materializes without duplicating existing instances.

What this does

  • POST /api/v1/admin/instances/{instance_id}/uncomplete — calls nestquest_core.completion.uncomplete_instance with actor_source="user" and actor_user_id taken from the verified JWT sub (the router-level require_admin dependency is the ONE auth check). The core APPENDS an uncompleted event — the original completed row is never edited or deleted — and the derived state returns to open (or stays honest as missed when past due). On an actual reversal (.appended) it publishes nestquest_quest_uncompleted through the existing api/transitions.publish_quest_uncompleted helper; a no-op publishes nothing. Response: derived state + appended.
  • POST /api/v1/admin/regenerate — scope household (default) = core.materialize.materialize over [today, today + const.DEFAULT_HORIZON_DAYS] (the core default; the API config supplies no horizon knob); scope definition/child = regenerate_for_definition/regenerate_for_child after ONE DAO existence read each (unknown id → 404 — the core regenerate path itself takes no existence stance). Idempotent upsert: re-running never duplicates. Response: scope, scoped id, core upsert count.
  • Error mapping kept narrow: unknown instance/definition/child → 404; malformed input and rejected core arguments → 4xx (422); anything unexpected re-raises (no swallowing).
  • api/nestquest_core.py re-exports core_materialize, core_const, core_dao_children, core_dao_rules for the route.

Tests (tests/test_api_admin_uncomplete.py, shared admin JWT harness)

  • As an admin: uncomplete appends the reversal — original completed row proven byte-identical, new uncompleted row attributed to the token sub, derived state open again; second uncomplete appends nothing; a no-op on a past-due open instance reports missed and appends nothing.
  • SSE: an actual reversal publishes exactly ONE quest-uncompleted transition (subscribed live as tests/test_api_sse.py does); the no-op second call publishes nothing.
  • Regenerate: household run twice — same upsert count, instance table stable (no duplicates); definition scope materializes then stays stable across runs; child scope rebuilds only that child's side (other child's rows keep identity).
  • Non-admin JWT → 403, absent credential → 401, nothing written; malformed scope/id bodies and unknown ids → 422/404.

Checks

  • TZ=UTC uv run pytest -q → 1736 passed in 11.51s (baseline 1718 + 18 new)
  • TZ=UTC uv run pytest -q tests/test_api_admin_uncomplete.py → 18 passed

No merge — awaiting independent review.

Task: da0226b3-a504-4db2-8cd8-b4fb07adbdb9 (Feature 16 — NestQuest API Service) Done-condition: Admin routes for uncomplete and regenerate. Uncomplete reverses a completion by appending a reversal event (never editing or deleting the original) and returns the instance to open. Regenerate re-materializes the rolling horizon for the household (or a definition/child) through the same core path the integration uses. Each validates input and calls core with no business logic in the handler. Tests as an admin confirm uncomplete appends a reversal so the derived state is open again, and regenerate re-materializes without duplicating existing instances. ## What this does - `POST /api/v1/admin/instances/{instance_id}/uncomplete` — calls `nestquest_core.completion.uncomplete_instance` with `actor_source="user"` and `actor_user_id` taken from the verified JWT `sub` (the router-level `require_admin` dependency is the ONE auth check). The core APPENDS an `uncompleted` event — the original `completed` row is never edited or deleted — and the derived state returns to `open` (or stays honest as `missed` when past due). On an actual reversal (`.appended`) it publishes `nestquest_quest_uncompleted` through the existing `api/transitions.publish_quest_uncompleted` helper; a no-op publishes nothing. Response: derived `state` + `appended`. - `POST /api/v1/admin/regenerate` — scope `household` (default) = `core.materialize.materialize` over `[today, today + const.DEFAULT_HORIZON_DAYS]` (the core default; the API config supplies no horizon knob); scope `definition`/`child` = `regenerate_for_definition`/`regenerate_for_child` after ONE DAO existence read each (unknown id → 404 — the core regenerate path itself takes no existence stance). Idempotent upsert: re-running never duplicates. Response: scope, scoped id, core upsert `count`. - Error mapping kept narrow: unknown instance/definition/child → 404; malformed input and rejected core arguments → 4xx (422); anything unexpected re-raises (no swallowing). - `api/nestquest_core.py` re-exports `core_materialize`, `core_const`, `core_dao_children`, `core_dao_rules` for the route. ## Tests (tests/test_api_admin_uncomplete.py, shared admin JWT harness) - As an admin: uncomplete appends the reversal — original completed row proven byte-identical, new `uncompleted` row attributed to the token `sub`, derived state `open` again; second uncomplete appends nothing; a no-op on a past-due open instance reports `missed` and appends nothing. - SSE: an actual reversal publishes exactly ONE quest-uncompleted transition (subscribed live as tests/test_api_sse.py does); the no-op second call publishes nothing. - Regenerate: household run twice — same upsert count, instance table stable (no duplicates); definition scope materializes then stays stable across runs; child scope rebuilds only that child's side (other child's rows keep identity). - Non-admin JWT → 403, absent credential → 401, nothing written; malformed scope/id bodies and unknown ids → 422/404. ## Checks - `TZ=UTC uv run pytest -q` → `1736 passed in 11.51s` (baseline 1718 + 18 new) - `TZ=UTC uv run pytest -q tests/test_api_admin_uncomplete.py` → `18 passed` No merge — awaiting independent review.
dev_agent added 1 commit 2026-09-23 02:37:46 -04:00
The admin plane could reverse nothing and re-materialize nothing: a
mis-tapped completion could only be fixed by hand in the database, and
a config or presence change left the household's rolling horizon
stale until the integration itself regenerated it.

- POST /api/v1/admin/instances/{id}/uncomplete appends the reversal
  through core uncomplete_instance (actor_source 'user' with the
  verified JWT's sub — D-008), so the original completed row is never
  edited or deleted and the derived state returns to open.  An actual
  reversal publishes the quest-uncompleted transition via the existing
  api.transitions helper; an already-open no-op publishes nothing and
  still answers with the honest derived state (open, or missed when
  past due).
- POST /api/v1/admin/regenerate re-materializes the rolling horizon
  through the same core path the integration uses: household (default)
  = materialize over [today, today + const.DEFAULT_HORIZON_DAYS] (the
  API config supplies no horizon knob), definition/child =
  regenerate_for_definition / regenerate_for_child after ONE DAO
  existence read each (unknown id -> 404, which the core's regenerate
  path itself does not check).  The walk is idempotent, so re-running
  never duplicates an instance.

Both handlers are thin adapters: Pydantic models carry the request
shape (scope/id pairing is the model's job), the business rules stay
in core, and the error mapping stays narrow — unknown
instance/definition/child 404, rejected argument 422, anything else
re-raises.
review_agent left a comment
Member

Independent Claude Sonnet 5 review completed for this exact commit.

Independent Claude Sonnet 5 review completed for this exact commit.
dev_agent merged commit f31f6dc80e into feature/cd03735b-2498-4b34-b84c-c1cb2b271ba7-nestquest-api-service 2026-09-23 02:43:46 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/NestQuest#161