Attribute Devii AI spend to its invoking action, fix quiz question-at-a-time review, DB API/isslop result routes, workspace docs, and drop redundant docstrings

- Route Devii-driven AI gateway cost to the action/tool that triggered
  it instead of a blanket "internal" bucket, so per-feature AI spend
  is attributable.
- Fix the quiz attempt review to show one previously-answered question
  at a time instead of all of them at once, and stop a quiz endpoint
  linked from the quiz flow from responding with raw JSON.
- Add DB API async query result route and AI Usage Analyzer annotated
  source/media routes, with traversal-safe uid/path handling and
  matching tests.
- Add Code Farm action audit logging (plant/harvest/buy-plot/upgrade/
  fertilize) and related admin workspace/services/trash/gateway route
  and doc touch-ups.
- Drop redundant docstrings from access_tokens.py per the no-comments
  convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VL9Xn57W5UR3HZbbuuzxdK
This commit is contained in:
2026-09-03 08:47:57 +02:00
co-authored by Claude Sonnet 5
parent 9d7b3db314
commit 57087536e5
76 changed files with 805 additions and 338 deletions
+1
View File
@@ -89,6 +89,7 @@ async def admin_backups(request: Request):
{"name": "Backups", "url": "/admin/backups"},
],
schemas=[website_schema(base)],
robots="noindex,nofollow",
)
return respond(
request,
+1
View File
@@ -139,6 +139,7 @@ async def admin_devii_tasks(request: Request, state: str = "active"):
{"name": "Devii tasks", "url": "/admin/devii-tasks"},
],
schemas=[website_schema(base)],
robots="noindex,nofollow",
)
return respond(
request,
+1 -1
View File
@@ -62,7 +62,7 @@ async def service_detail(request: Request, name: str):
request,
title=f"{info['title']} - Services",
description=info["description"] or f"Configure the {info['title']} service.",
robots="noindex",
robots="noindex,nofollow",
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Admin", "url": "/admin"},
+1
View File
@@ -89,6 +89,7 @@ async def admin_trash(request: Request, table: str = "posts", page: int = 1):
{"name": "Trash", "url": "/admin/trash"},
],
schemas=[website_schema(base)],
robots="noindex,nofollow",
)
return respond(
request,
+3 -8
View File
@@ -5,7 +5,7 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse
from devplacepy.database import db, get_table, get_users_by_uids
from devplacepy.database import get_projects_by_uids, get_table, get_users_by_uids
from devplacepy.dependencies import json_or_form
from devplacepy.models import (
EditorPrefsForm,
@@ -34,13 +34,8 @@ router = APIRouter()
def _decorate(rows: list[dict]) -> list[dict]:
owner_uids = {row.get("workspace_owner_uid") for row in rows if row.get("workspace_owner_uid")}
owners = get_users_by_uids(list(owner_uids)) if owner_uids else {}
projects = {}
if "projects" in db.tables:
project_uids = {row.get("project_uid") for row in rows if row.get("project_uid")}
for uid in project_uids:
found = get_table("projects").find_one(uid=uid)
if found:
projects[uid] = found
project_uids = {row.get("project_uid") for row in rows if row.get("project_uid")}
projects = get_projects_by_uids(list(project_uids)) if project_uids else {}
decorated = []
for row in rows:
view = provision.view(row)
-6
View File
@@ -22,12 +22,6 @@ async def token(
request: Request,
data: Annotated[LoginForm, Depends(json_or_form(LoginForm))],
):
"""Issue a DevPlace access token.
Accepts ``email`` + ``password`` (JSON or form-encoded). Returns a JSON
object with ``access_token``, ``token_type``, and ``expires_in`` on success,
or a ``401`` error on bad credentials.
"""
identifier = data.email.strip().lower()
password = data.password
+3 -1
View File
@@ -11,7 +11,7 @@ from devplacepy.dependencies import json_or_form
from devplacepy.models import WarJoinForm
from devplacepy.responses import action_result, json_error, respond, wants_json
from devplacepy.schemas import BattlesOut, WarEventsOut, WarOut
from devplacepy.seo import base_seo_context
from devplacepy.seo import base_seo_context, site_url, website_schema
from devplacepy.services.opinionwar import WarError, rules, store
from devplacepy.utils import get_current_user, not_found, require_user
@@ -43,6 +43,7 @@ async def battles_page(
search=search,
page=max(1, page),
)
base = site_url(request)
seo_ctx = base_seo_context(
request,
title="Opinion Wars",
@@ -54,6 +55,7 @@ async def battles_page(
{"name": "Home", "url": "/feed"},
{"name": "Battles", "url": "/battles"},
],
schemas=[website_schema(base)],
)
return respond(
request,
-2
View File
@@ -137,8 +137,6 @@ def _validate_target(value: str) -> str:
@router.get("/adopt")
async def devii_adopt(request: Request):
# The terminal's agent logged in; adopt the real session it minted into the browser so both
# share one session, then return to where the user was.
target = _validate_target(request.query_params.get("next", "/"))
response = RedirectResponse(target, status_code=303)
svc = _service()
+1 -1
View File
@@ -66,7 +66,7 @@ The `endpoint()` factory derives `min_role` from `auth` (`public` -> Public, `us
## Admin-only pages
A group with `"admin": True` (currently `services`, `admin`) is hidden from the sidebar (`docs.py` filters `visible_pages`) and returns 404 for non-admins (`page.get("admin") and not is_admin -> not_found`, same message as unknown slugs). `index.html` wraps the operator links in `{% if user and user.get('role') == 'Admin' %}`.
A group with `"admin": True` (currently `services`, `admin`, `containers`) is hidden from the sidebar (`docs.py` filters `visible_pages`) and returns 404 for non-admins (`page.get("admin") and not is_admin -> not_found`, same message as unknown slugs). `index.html` wraps the operator links in `{% if user and user.get('role') == 'Admin' %}`.
## Dynamic Background Services page
-15
View File
@@ -44,7 +44,6 @@ AUDIENCES = [
]
DOCS_PAGES = [
# General - how to use the site and Devii (everyone)
{"slug": "index", "title": "Overview", "kind": "prose", "section": SECTION_GENERAL},
{
"slug": "getting-started",
@@ -160,7 +159,6 @@ DOCS_PAGES = [
"kind": "prose",
"section": SECTION_GENERAL,
},
# Legal - the policies the platform is operated under (everyone)
{
"slug": "terms",
"title": "Terms of Service",
@@ -204,7 +202,6 @@ DOCS_PAGES = [
"section": SECTION_LEGAL,
"admin": True,
},
# Tools - public developer tools (everyone)
{
"slug": "tools-seo",
"title": "SEO Diagnostics",
@@ -229,7 +226,6 @@ DOCS_PAGES = [
"kind": "prose",
"section": SECTION_TOOLS,
},
# Claude Code - the native subagent, command, and workflow setup under .claude/
{
"slug": "claude",
"title": "Claude Code setup",
@@ -260,7 +256,6 @@ DOCS_PAGES = [
"kind": "prose",
"section": SECTION_CLAUDE,
},
# Components - custom HTML web components with live examples (everyone)
{
"slug": "components",
"title": "Components overview",
@@ -339,7 +334,6 @@ DOCS_PAGES = [
"kind": "prose",
"section": SECTION_COMPONENTS,
},
# Styles - the design system: colors, layout, responsiveness, and hard structural rules (everyone)
{
"slug": "styles",
"title": "Design system overview",
@@ -370,7 +364,6 @@ DOCS_PAGES = [
"kind": "prose",
"section": SECTION_STYLES,
},
# API - developer reference (everyone); admin-only groups are routed to Administration below
{
"slug": "authentication",
"title": "Authentication",
@@ -383,7 +376,6 @@ DOCS_PAGES = [
"kind": "prose",
"section": SECTION_API,
},
# devRant API - legacy-compatible protocol, spread over focused pages
{
"slug": "devrant",
"title": "Overview",
@@ -430,7 +422,6 @@ DOCS_PAGES = [
{**page, "section": (SECTION_ADMIN if page.get("admin") else SECTION_API)}
for page in api_doc_pages()
],
# Administration - operational guides (admins only)
{
"slug": "devii-admin",
"title": "Devii for admins",
@@ -473,7 +464,6 @@ DOCS_PAGES = [
"admin": True,
"section": SECTION_ADMIN,
},
# Devii internals - technical reference for the Devii assistant (admins only)
{
"slug": "audit-log",
"title": "Audit Log",
@@ -523,7 +513,6 @@ DOCS_PAGES = [
"admin": True,
"section": SECTION_DEVII,
},
# Bots internals - deep technical reference for the autonomous bot fleet (admins only)
{
"slug": "bots-internals",
"title": "Bots internals",
@@ -573,7 +562,6 @@ DOCS_PAGES = [
"admin": True,
"section": SECTION_BOTS,
},
# Services - the background service framework and every service in detail (admins only)
{
"slug": "services-overview",
"title": "Services overview",
@@ -651,7 +639,6 @@ DOCS_PAGES = [
"admin": True,
"section": SECTION_SERVICES,
},
# Architecture - platform design, structure, and development process (admins only)
{
"slug": "architecture",
"title": "Architecture overview",
@@ -701,7 +688,6 @@ DOCS_PAGES = [
"admin": True,
"section": SECTION_ARCH,
},
# Testing - test framework, load testing, and make targets (admins only)
{
"slug": "testing",
"title": "Testing overview",
@@ -737,7 +723,6 @@ DOCS_PAGES = [
"admin": True,
"section": SECTION_TESTING,
},
# Production - deployment and operations reference (admins only)
{
"slug": "production",
"title": "Production overview",
+41 -1
View File
@@ -9,6 +9,7 @@ from devplacepy.models import GameSlotForm
from devplacepy.responses import respond, wants_json
from devplacepy.schemas import GameFarmViewOut
from devplacepy.services.game import GameError, store
from devplacepy.services.audit import record as audit
from devplacepy.utils import (
create_notification,
get_current_user,
@@ -58,10 +59,24 @@ async def water_farm(
if not owner:
raise HTTPException(status_code=404, detail="Farm not found")
try:
store.water(viewer, owner, data.slot)
result = store.water(viewer, owner, data.slot)
except GameError as exc:
return action_error(request, str(exc), f"/game/farm/{username}")
track_action(viewer["uid"], "water")
audit.record(
request,
"game.water",
user=viewer,
target_type="user",
target_uid=owner["uid"],
target_label=owner["username"],
metadata=result,
summary=(
f"{viewer['username']} watered {owner['username']}'s build on plot "
f"{result['slot']} and earned {result['reward_coins']} coins"
),
links=[audit.target("user", owner["uid"], owner["username"])],
)
await notify_farm(owner["username"])
if wants_json(request):
payload = store.serialize_farm(
@@ -87,6 +102,31 @@ async def steal_farm(
track_action(owner["uid"], "got_stolen_from")
if result.get("underdog_triggered"):
track_action(viewer["uid"], "underdog_raid")
audit.record(
request,
"game.steal",
user=viewer,
target_type="user",
target_uid=owner["uid"],
target_label=owner["username"],
metadata={
"thief_uid": viewer["uid"],
"thief_username": viewer["username"],
"owner_uid": owner["uid"],
"owner_username": owner["username"],
"slot": result["slot"],
"crop": result["crop"],
"coins": result["coins"],
"share": result["share"],
"underdog_triggered": result.get("underdog_triggered", False),
},
summary=(
f"{viewer['username']} raided {owner['username']}'s Code Farm and took "
f"{result['coins']} coins ({round(result['share'] * 100)}%) from their "
f"{result['crop_name']} build"
),
links=[audit.target("user", owner["uid"], owner["username"])],
)
create_notification(
owner["uid"],
"harvest_stolen",
+145 -9
View File
@@ -80,8 +80,21 @@ async def _respond_action(request: Request, user: dict, fn, on_success=None):
@router.post("/plant")
async def game_plant(request: Request, data: Annotated[GamePlantForm, Form()]):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.plant",
user=user,
metadata=result,
summary=(
f"{user['username']} planted {result['crop']} on plot "
f"{result['slot']} for {result['spent']} coins"
),
)
return await _respond_action(
request, user, lambda: store.plant(user, data.slot, data.crop)
request, user, lambda: store.plant(user, data.slot, data.crop), recorded
)
@@ -92,6 +105,16 @@ async def game_harvest(request: Request, data: Annotated[GameSlotForm, Form()]):
def reward(result):
track_action(user["uid"], "harvest")
award_rewards(user["uid"], economy.site_xp_for(result.get("xp", 0)))
audit.record(
request,
"game.harvest",
user=user,
metadata=result,
summary=(
f"{user['username']} harvested {result['crop']} on plot "
f"{result['slot']} for {result['coins']} coins"
),
)
return await _respond_action(
request, user, lambda: store.harvest(user, data.slot), reward
@@ -101,25 +124,79 @@ async def game_harvest(request: Request, data: Annotated[GameSlotForm, Form()]):
@router.post("/buy-plot")
async def game_buy_plot(request: Request):
user = require_user(request)
return await _respond_action(request, user, lambda: store.buy_plot(user))
def recorded(result):
audit.record(
request,
"game.plot.buy",
user=user,
metadata=result,
summary=(
f"{user['username']} bought Code Farm plot "
f"{result['plot_count']} for {result['spent']} coins"
),
)
return await _respond_action(request, user, lambda: store.buy_plot(user), recorded)
@router.post("/upgrade")
async def game_upgrade(request: Request):
user = require_user(request)
return await _respond_action(request, user, lambda: store.upgrade_ci(user))
def recorded(result):
audit.record(
request,
"game.ci.upgrade",
user=user,
metadata=result,
summary=(
f"{user['username']} upgraded Code Farm CI to tier "
f"{result['ci_tier']} for {result['spent']} coins"
),
)
return await _respond_action(request, user, lambda: store.upgrade_ci(user), recorded)
@router.post("/fertilize")
async def game_fertilize(request: Request, data: Annotated[GameSlotForm, Form()]):
user = require_user(request)
return await _respond_action(request, user, lambda: store.fertilize(user, data.slot))
def recorded(result):
audit.record(
request,
"game.fertilize",
user=user,
metadata=result,
summary=(
f"{user['username']} fertilized plot {result['slot']} "
f"for {result['spent']} coins"
),
)
return await _respond_action(
request, user, lambda: store.fertilize(user, data.slot), recorded
)
@router.post("/daily")
async def game_daily(request: Request):
user = require_user(request)
return await _respond_action(request, user, lambda: store.claim_daily(user))
def recorded(result):
audit.record(
request,
"game.daily.claim",
user=user,
metadata=result,
summary=(
f"{user['username']} claimed the Code Farm daily bonus of "
f"{result['reward']} coins (streak {result['streak']})"
),
)
return await _respond_action(request, user, lambda: store.claim_daily(user), recorded)
@router.post("/grant")
@@ -141,8 +218,21 @@ async def game_claim_grant(request: Request):
@router.post("/perk")
async def game_perk(request: Request, data: Annotated[GamePerkForm, Form()]):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.perk.upgrade",
user=user,
metadata=result,
summary=(
f"{user['username']} upgraded perk {result['perk']} to level "
f"{result['level']} for {result['spent']} coins"
),
)
return await _respond_action(
request, user, lambda: store.upgrade_perk(user, data.perk)
request, user, lambda: store.upgrade_perk(user, data.perk), recorded
)
@@ -168,8 +258,21 @@ async def game_prestige(request: Request):
@router.post("/legacy")
async def game_legacy(request: Request, data: Annotated[GameLegacyForm, Form()]):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.legacy.upgrade",
user=user,
metadata=result,
summary=(
f"{user['username']} upgraded Legacy {result['key']} to level "
f"{result['level']} for {result['spent']} stars"
),
)
return await _respond_action(
request, user, lambda: store.upgrade_legacy(user, data.key)
request, user, lambda: store.upgrade_legacy(user, data.key), recorded
)
@@ -179,6 +282,16 @@ async def game_claim_quest(request: Request, data: Annotated[GameQuestForm, Form
def reward(result):
award_rewards(user["uid"], economy.site_xp_for(result.get("reward_xp", 0)))
audit.record(
request,
"game.quest.claim",
user=user,
metadata=result,
summary=(
f"{user['username']} claimed quest {result['kind']} for "
f"{result['reward_coins']} coins"
),
)
return await _respond_action(
request, user, lambda: store.claim_quest(user, data.quest, data.scope), reward
@@ -251,8 +364,21 @@ async def game_buy_infrastructure(request: Request, data: Annotated[GameInfraFor
@router.post("/mastery")
async def game_upgrade_mastery(request: Request, data: Annotated[GameMasteryForm, Form()]):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.mastery.upgrade",
user=user,
metadata=result,
summary=(
f"{user['username']} upgraded Mastery {result['key']} to level "
f"{result['level']} for {result['spent']} mastery points"
),
)
return await _respond_action(
request, user, lambda: store.upgrade_mastery(user, data.key)
request, user, lambda: store.upgrade_mastery(user, data.key), recorded
)
@@ -281,6 +407,16 @@ async def game_buy_cosmetic(request: Request, data: Annotated[GameCosmeticForm,
@router.post("/cosmetics/equip")
async def game_equip_cosmetic(request: Request, data: Annotated[GameCosmeticForm, Form()]):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.cosmetic.equip",
user=user,
metadata=result,
summary=f"{user['username']} equipped Code Farm title {result['active_title']}",
)
return await _respond_action(
request, user, lambda: store.equip_title(user, data.key)
request, user, lambda: store.equip_title(user, data.key), recorded
)
+8 -21
View File
@@ -8,10 +8,12 @@ from fastapi.responses import JSONResponse
from devplacepy.attachments import (
get_attachments,
get_orphan_attachments_batch,
link_attachments,
mirror_attachment_to_gitea,
remove_gitea_asset,
soft_delete_attachment,
split_attachment_uids,
)
from devplacepy.database import get_table
from devplacepy.dependencies import json_or_form
@@ -28,10 +30,6 @@ logger = logging.getLogger(__name__)
router = APIRouter()
def _split_uids(raw) -> list[str]:
return [u.strip() for item in raw or [] for u in str(item).split(",") if u.strip()]
def _can_modify(att: dict, user: dict | None, is_open: bool) -> bool:
if not user or not is_open:
return False
@@ -47,21 +45,6 @@ def _payload(rows: list[dict], user: dict | None, is_open: bool) -> list[dict]:
return items
def _claim_orphans(uids: list[str], user: dict) -> list[str]:
admin = is_admin(user)
owned = []
for uid in uids:
row = get_table("attachments").find_one(uid=uid, deleted_at=None)
if not row:
continue
if row.get("user_uid") and row["user_uid"] != user["uid"] and not admin:
continue
if row.get("target_uid"):
continue
owned.append(uid)
return owned
async def _load_issue(number: int) -> dict:
try:
return await runtime.get_client().get_issue(number)
@@ -106,7 +89,9 @@ async def add_issue_attachment(
summary=f"{user['username']} tried to attach to closed issue #{number}",
)
return json_error(409, "Attachments cannot be changed on a closed issue")
owned = _claim_orphans(_split_uids(data.attachment_uids), user)
owned = get_orphan_attachments_batch(
split_attachment_uids(data.attachment_uids), user, admin=is_admin(user)
)
if not owned:
return json_error(400, "No valid attachments to add")
link_attachments(owned, "issue", str(number))
@@ -202,7 +187,9 @@ async def add_comment_attachment(
issue = await _load_issue(number)
if issue.get("state") != STATE_OPEN:
return json_error(409, "Attachments cannot be changed on a closed issue")
owned = _claim_orphans(_split_uids(data.attachment_uids), user)
owned = get_orphan_attachments_batch(
split_attachment_uids(data.attachment_uids), user, admin=is_admin(user)
)
if not owned:
return json_error(400, "No valid attachments to add")
link_attachments(owned, "issue_comment", str(cid))
+9 -14
View File
@@ -5,7 +5,12 @@ from typing import Annotated
from fastapi import Depends, APIRouter, Request
from devplacepy.attachments import link_attachments, mirror_attachment_to_gitea
from devplacepy.attachments import (
get_orphan_attachments_batch,
link_attachments,
mirror_attachment_to_gitea,
split_attachment_uids,
)
from devplacepy.database import get_table
from devplacepy.models import IssueCommentForm
from devplacepy.responses import action_result, json_error
@@ -21,18 +26,6 @@ logger = logging.getLogger(__name__)
router = APIRouter()
def _owned_orphan_uids(raw: list[str], user: dict) -> list[str]:
flat = [u.strip() for item in raw or [] for u in str(item).split(",") if u.strip()]
owned = []
for uid in flat:
row = get_table("attachments").find_one(uid=uid, deleted_at=None)
if not row or row.get("target_uid"):
continue
if row.get("user_uid") and row["user_uid"] != user["uid"]:
continue
owned.append(uid)
return owned
def _notify_admins(actor: dict, number: int) -> None:
for admin in get_table("users").find(role="Admin"):
if admin["uid"] == actor["uid"]:
@@ -74,7 +67,9 @@ async def comment_issue(
comment_id = int(comment.get("id", 0))
store.record_comment_author(comment_id, number, user["uid"])
owned = _owned_orphan_uids(data.attachment_uids, user)
owned = get_orphan_attachments_batch(
split_attachment_uids(data.attachment_uids), user
)
if owned:
link_attachments(owned, "issue_comment", str(comment_id))
for uid in owned:
+4 -15
View File
@@ -6,7 +6,7 @@ from typing import Annotated
from fastapi import Depends, APIRouter, Request
from fastapi.responses import JSONResponse
from devplacepy.database import get_table
from devplacepy.attachments import get_orphan_attachments_batch, split_attachment_uids
from devplacepy.models import IssueForm
from devplacepy.responses import json_error
from devplacepy.schemas import IssueJobOut
@@ -20,19 +20,6 @@ logger = logging.getLogger(__name__)
router = APIRouter()
def _owned_orphan_uids(raw: list[str], user: dict) -> list[str]:
flat = [u.strip() for item in raw or [] for u in str(item).split(",") if u.strip()]
owned = []
for uid in flat:
row = get_table("attachments").find_one(uid=uid, deleted_at=None)
if not row or row.get("target_uid"):
continue
if row.get("user_uid") and row["user_uid"] != user["uid"]:
continue
owned.append(uid)
return owned
@router.post("/create")
async def create_issue(request: Request, data: Annotated[IssueForm, Depends(json_or_form(IssueForm))]):
user = require_user(request)
@@ -45,7 +32,9 @@ async def create_issue(request: Request, data: Annotated[IssueForm, Depends(json
"author_uid": user["uid"],
"title": title,
"description": data.description.strip(),
"attachment_uids": _owned_orphan_uids(data.attachment_uids, user),
"attachment_uids": get_orphan_attachments_batch(
split_attachment_uids(data.attachment_uids), user
),
},
"user",
user["uid"],
@@ -83,8 +83,6 @@ async def containers_json(request: Request, project_slug: str):
}
)
# ---------------- instances ----------------
@router.post("/{project_slug}/containers/instances")
async def create_instance(
request: Request, project_slug: str, data: Annotated[ContainerInstanceForm, Depends(json_or_form(ContainerInstanceForm))]