The project detail page becomes a full project showcase built entirely
from existing platform mechanisms. One encompassing dark card wraps the
page; inner panels (tab bar, sidebar cards, devlog entries, comments)
sit one elevation lighter. The hero opens with a cover banner and an
optional logo tile, both plain attachment references
(cover_attachment_uid/logo_attachment_uid) uploaded through the
standard dp-upload attachment widget and linked via the existing
link_attachments choke point - the route validates each uid belongs to
the actor and is an image, and an empty value on edit keeps the current
one. The title block, type/platform chips and author row overlay the
banner behind a scrim with a dark text shadow, next to an owner-set
Visit Website CTA; website_url and repo_url are normalized in models
and render with rel noopener nofollow.
An anchor tab bar (Overview, Devlog, Screenshots when present,
Comments, Files) navigates the page. The main column keeps About, the
devlog timeline (with devlog_count and an owner Post update button
opening the shared composer preset to the devlog topic + project - the
form now lives once in _post_composer_form.html, included by feed.html
and project_detail.html), a Screenshots gallery built from image
attachments minus the cover/logo (thumbnails, lightbox, 12 rendered),
and the comment thread; the sidebar holds Links, Stats and the Author
card. Owners add gallery images from the More menu via
POST /projects/{slug}/screenshots (owner-only, audit
project.screenshots.add, Devii action project_add_screenshots, docs id
projects-screenshots). comment_count/devlog_count ride
ProjectDetailOut, the new fields ride ProjectOut, and the create/edit
faces (modals, Devii actions, API docs) carry them. The project
comment/files e2e tests scope their locators per the documented
dual-control idiom, and new unit/api/e2e tests cover URL normalization,
the counts, the hero attachment guard, the screenshots flow and the
preset composer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23 KiB
Audit event catalogue
retoor retoor@molodetz.nl
Every state-changing action in DevPlace records one append-only row through devplacepy/services/audit/record.py (record for a request-scoped actor, record_system for a background one). This file is the authoritative catalogue of the 331 event keys currently emitted, grouped by the category services/audit/categories.py category_for resolves them to.
Rules
- An event key is
<domain>.<noun>.<verb>(or<domain>.<verb>when the domain is the noun). The domain is the first segment and MUST have an entry inCATEGORY_BY_PREFIX; an unmapped domain resolves to categoryotherand is a bug. target_typenames the entity acted upon, not the event (gateway_quota, notgateway_quota_reset).- Every row carries a result:
success,failure,denied. A refused action is recorded withdenied, never dropped. - Actor kinds:
user,guest,system,cli,service. Origins:web,api,devii,cli,service,scheduler,devrant. - The recorder never raises into its caller; a failed audit write is logged and swallowed so it can never break the action it describes.
- Some families are built from a variable at the call site (
vote.{target_type}.{direction},comment.create.{target_type},quiz.question.{action},profile.customization.{scope}.toggle, and thecreate/edit/deleteverbs the sharedcontent.pyhelpers emit per content type). Every member of those families is listed below individually. - Adding an event means all three of: the key in this file, a
CATEGORY_BY_PREFIXentry for a new domain, and the recorder call at the mutation point.
Categories
| Category | Keys |
|---|---|
account |
16 |
admin |
21 |
ai |
11 |
attachment |
5 |
auth |
8 |
backup |
2 |
cli |
43 |
container |
36 |
content |
37 |
database |
4 |
devii |
18 |
email |
6 |
engagement |
23 |
game |
6 |
ingress |
1 |
message |
2 |
moderation |
12 |
news |
9 |
notification |
4 |
project |
11 |
project_files |
14 |
pubsub |
1 |
push |
2 |
reward |
4 |
security |
3 |
service |
5 |
social |
10 |
telegram |
5 |
tools |
12 |
Total: 331 keys.
Account and profile (account)
| Event key | Recorded in |
|---|---|
profile.ai_correction |
routers/profile/ai_correction.py |
profile.ai_modifier |
routers/profile/ai_modifier.py |
profile.api_key.regenerate |
routers/profile/index.py |
profile.avatar.regenerate |
routers/profile/avatar.py |
profile.customization.global.toggle |
routers/profile/customization.py |
profile.customization.pagetype.toggle |
routers/profile/customization.py |
profile.interactions |
routers/profile/interactions.py, services/devii/actions/dispatcher.py |
profile.notification.reset |
routers/profile/notifications.py |
profile.notification.toggle |
routers/profile/notifications.py |
profile.update |
routers/devrant/auth.py, routers/profile/index.py |
account.delete.purge |
services/moderation/deletion.py |
account.delete.request |
routers/devrant/auth.py, routers/profile/delete.py |
consent.grant |
routers/profile/consent.py, services/acceptance/grant.py |
profile.mature_content |
routers/profile/consent.py |
consent.withdraw |
routers/profile/consent.py |
terms.accept |
routers/auth/terms.py, services/acceptance/grant.py |
Administration (admin)
| Event key | Recorded in |
|---|---|
admin.ai_quota.reset_all |
routers/admin/aiquota.py |
admin.ai_quota.reset_guests |
routers/admin/aiquota.py |
admin.backup.delete |
routers/admin/backups.py |
admin.backup.run |
routers/admin/backups.py |
admin.backup_schedule.create |
routers/admin/backups.py |
admin.backup_schedule.delete |
routers/admin/backups.py |
admin.backup_schedule.toggle |
routers/admin/backups.py |
admin.backup_schedule.update |
routers/admin/backups.py |
admin.devii_task.delete |
routers/admin/devii_tasks.py |
admin.devii_task.disable |
routers/admin/devii_tasks.py |
admin.game.era_end |
routers/admin/game.py |
admin.game.era_start |
routers/admin/game.py |
admin.notification.default |
routers/admin/notifications.py |
admin.setting.update |
docs_api/groups/admin.py, routers/admin/settings.py |
admin.trash.purge |
routers/admin/trash.py |
admin.trash.restore |
routers/admin/trash.py |
admin.user.active.disable |
routers/admin/users.py |
admin.user.active.enable |
routers/admin/users.py |
admin.user.ai_quota.reset |
routers/admin/users.py |
admin.user.password.reset |
routers/admin/users.py |
admin.user.role.change |
routers/admin/users.py |
AI gateway (ai)
| Event key | Recorded in |
|---|---|
ai.consent.denied |
services/openai_gateway/service.py |
ai.clippy.chat |
routers/devii.py |
ai.gateway.call |
services/openai_gateway/gateway.py, services/openai_gateway/usage.py |
ai.quota.exceeded |
routers/devii.py, services/openai_gateway/service.py |
gateway.model.delete |
routers/admin/gateway_configs.py |
gateway.model.update |
routers/admin/gateway_configs.py |
gateway.provider.delete |
routers/admin/gateway_configs.py |
gateway.provider.update |
routers/admin/gateway_configs.py |
gateway.quota.reset |
cli/gateway.py, routers/admin/gateway_configs.py |
gateway.quota_rule.delete |
cli/gateway.py, routers/admin/gateway_configs.py |
gateway.quota_rule.update |
cli/gateway.py, routers/admin/gateway_configs.py |
Attachments (attachment)
| Event key | Recorded in |
|---|---|
attachment.delete |
routers/admin/media.py, routers/media.py |
attachment.rename |
routers/uploads.py |
attachment.restore |
routers/media.py |
attachment.upload |
routers/uploads.py |
attachment.upload_url |
routers/uploads.py |
Authentication (auth)
| Event key | Recorded in |
|---|---|
auth.login.failure |
routers/auth/login.py, routers/devrant/auth.py |
auth.login.success |
routers/auth/login.py, routers/devrant/auth.py |
auth.logout |
routers/auth/logout.py |
auth.password.forgot_request |
routers/auth/forgotpassword.py |
auth.password.reset_complete |
routers/auth/resetpassword.py |
auth.signup |
routers/auth/signup.py, routers/devrant/auth.py |
auth.token.failure |
routers/auth/token.py |
auth.token.issued |
routers/auth/token.py |
Backups (backup)
| Event key | Recorded in |
|---|---|
job.backup.complete |
services/backup/service.py |
job.backup.failed |
services/backup/service.py |
Command line (cli)
| Event key | Recorded in |
|---|---|
cli.accounts.prune |
cli/accounts.py |
cli.apikey.backfill |
cli/apikeys.py |
cli.apikey.reset |
cli/apikeys.py |
cli.attachments.prune |
cli/attachments.py |
cli.backups.clear |
cli/backups.py |
cli.backups.prune |
cli/backups.py |
cli.backups.run |
cli/backups.py |
cli.containers.gc_workspaces |
cli/containers.py |
cli.containers.prune |
cli/containers.py |
cli.containers.prune_builds |
cli/containers.py |
cli.containers.reconcile |
cli/containers.py |
cli.deepsearch.clear |
cli/jobs.py |
cli.deepsearch.prune |
cli/jobs.py |
cli.devii.lessons.clear |
cli/devii.py |
cli.devii.lessons.prune |
cli/devii.py |
cli.devii.quota.reset |
cli/devii.py |
cli.devii.task.disable |
cli/devii.py |
cli.devii.task.prune |
cli/devii.py |
cli.emoji.sync |
cli/migrate.py |
cli.forks.clear |
cli/jobs.py |
cli.forks.prune |
cli/jobs.py |
cli.game.era.end |
cli/game.py |
cli.game.era.start |
cli/game.py |
cli.game.market.prune |
cli/game.py |
cli.game.steals.prune |
cli/game.py |
cli.isslop.analyze |
cli/jobs.py |
cli.isslop.clear |
cli/jobs.py |
cli.isslop.prune |
cli/jobs.py |
cli.messaging.prune_tickets |
cli/messaging.py |
cli.news.clear |
cli/news.py |
cli.news.sanitize |
cli/news.py |
cli.quiz.prune |
cli/quiz.py |
cli.role.set |
cli/roles.py |
cli.seo.clear |
cli/jobs.py |
cli.seo.prune |
cli/jobs.py |
cli.seo_meta.clear |
cli/jobs.py |
cli.seo_meta.prune |
cli/jobs.py |
cli.token.issue |
cli/tokens.py |
cli.token.prune |
cli/tokens.py |
cli.token.revoke |
cli/tokens.py |
cli.token.revoke_all |
cli/tokens.py |
cli.zips.clear |
cli/jobs.py |
cli.zips.prune |
cli/jobs.py |
Containers (container)
| Event key | Recorded in |
|---|---|
container.instance.configure |
routers/admin/containers.py, services/devii/actions/dispatcher.py |
container.instance.create |
routers/admin/containers.py, routers/projects/containers/instances.py |
container.instance.delete |
routers/admin/containers.py, routers/projects/containers/instances.py |
container.instance.exec |
routers/projects/containers/instances.py, services/devii/actions/dispatcher.py |
container.instance.shell.close |
routers/projects/containers/instances.py |
container.instance.shell.open |
routers/projects/containers/instances.py |
container.instance.start |
docs_api/groups/admin.py |
container.instance.status |
services/containers/service.py |
container.instance.sync |
routers/admin/containers.py, routers/projects/containers/instances.py |
container.reconcile.action |
services/containers/service.py |
container.tunnel.cert.failure |
services/containers/workspace_service.py |
container.tunnel.cert.issue |
services/containers/workspace_service.py |
container.tunnel.create |
routers/projects/containers/workspace.py |
container.tunnel.delete |
routers/projects/containers/workspace.py |
container.tunnel.failure |
services/containers/workspace_service.py |
container.tunnel.suspend |
services/containers/workspace_service.py |
container.workspace.create |
routers/projects/containers/workspace.py |
container.workspace.delete |
routers/projects/containers/workspace.py |
container.workspace.editor.update |
routers/projects/containers/workspace.py, routers/admin/workspaces.py |
container.workspace.flag.dismiss |
routers/admin/workspaces.py |
container.workspace.flag.raise |
services/containers/workspace_service.py |
container.workspace.flag.resolve |
routers/admin/workspaces.py |
container.workspace.idle.stop |
services/containers/workspace_service.py |
container.workspace.idle.warn |
services/containers/workspace_service.py |
container.workspace.open |
routers/projects/containers/workspace.py |
container.workspace.purge |
services/containers/workspace_service.py |
container.workspace.quota.block |
routers/projects/containers/workspace.py |
container.workspace.quota.warn |
services/containers/workspace_service.py |
container.workspace.restore |
routers/admin/workspaces.py |
container.workspace.resume |
routers/projects/containers/workspace.py |
container.workspace.retention.warn |
services/containers/workspace_service.py |
container.workspace.settings.update |
routers/admin/workspaces.py |
container.workspace.stop |
routers/projects/containers/workspace.py |
container.workspace.suspend |
routers/admin/workspaces.py |
container.workspace.unsuspend |
routers/admin/workspaces.py |
container.schedule.create |
routers/projects/containers/schedules.py, services/devii/actions/dispatcher.py |
container.schedule.delete |
routers/projects/containers/schedules.py |
Content (content)
| Event key | Recorded in |
|---|---|
comment.create.gist |
content.py |
comment.create.issue |
content.py |
comment.create.news |
content.py |
comment.create.post |
content.py |
comment.create.project |
content.py |
comment.create.quiz |
content.py |
comment.delete |
content.py, routers/comments.py |
comment.edit |
content.py, routers/comments.py |
gist.create |
content.py |
gist.delete |
content.py |
gist.edit |
content.py |
issue.attachment.add |
routers/issues/attachments.py |
issue.attachment.delete |
routers/issues/attachments.py |
issue.comment |
routers/issues/comment.py |
issue.create |
services/jobs/issue_create_service.py |
issue.create.request |
routers/issues/create.py |
issue.planning.generate |
services/jobs/planning_service.py |
issue.planning.request |
routers/issues/planning.py |
issue.status |
routers/issues/status.py |
issue.sync.reply |
services/gitea/service.py |
issue.sync.status |
services/gitea/service.py |
post.create |
content.py |
post.delete |
content.py |
post.edit |
content.py |
quiz.attempt.answer |
routers/quizzes/attempts.py |
quiz.attempt.finish |
routers/quizzes/attempts.py |
quiz.attempt.start |
routers/quizzes/attempts.py |
quiz.create |
content.py |
quiz.delete |
content.py |
quiz.edit |
content.py |
quiz.grade.failed |
routers/quizzes/attempts.py |
quiz.import |
routers/quizzes/index.py |
quiz.publish |
routers/quizzes/index.py |
quiz.question.create |
routers/quizzes/questions.py |
quiz.question.delete |
routers/quizzes/questions.py |
quiz.question.edit |
routers/quizzes/questions.py |
quiz.question.reorder |
routers/quizzes/questions.py |
Database API (database)
| Event key | Recorded in |
|---|---|
database.access.denied |
routers/dbapi/_shared.py |
database.nl.design |
routers/dbapi/nl.py |
database.query |
routers/dbapi/query.py |
database.query.async |
routers/dbapi/query.py |
Devii assistant (devii)
| Event key | Recorded in |
|---|---|
devii.behavior.update |
services/devii/actions/dispatcher.py |
devii.customization.css.set |
services/devii/actions/dispatcher.py |
devii.customization.js.set |
services/devii/actions/dispatcher.py |
devii.customization.reset |
services/devii/actions/dispatcher.py |
devii.lesson.forget |
services/devii/actions/dispatcher.py |
devii.lesson.reflect |
services/devii/actions/dispatcher.py |
devii.notification.reset |
services/devii/actions/dispatcher.py |
devii.notification.set |
services/devii/actions/dispatcher.py |
devii.task.blocked |
services/devii/tasks/scheduler.py |
devii.task.create |
services/devii/actions/dispatcher.py |
devii.task.deferred |
services/devii/tasks/scheduler.py |
devii.task.delete |
services/devii/actions/dispatcher.py |
devii.task.execute |
services/devii/tasks/scheduler.py |
devii.task.update |
services/devii/actions/dispatcher.py |
devii.tool.create |
services/devii/actions/dispatcher.py |
devii.tool.delete |
services/devii/actions/dispatcher.py |
devii.tool.update |
services/devii/actions/dispatcher.py |
devii.turn |
services/devii/session/core.py |
Email (email)
| Event key | Recorded in |
|---|---|
email.account.delete |
services/devii/actions/dispatcher.py |
email.account.set |
services/devii/actions/dispatcher.py |
email.message.delete |
services/devii/actions/dispatcher.py |
email.message.flag |
services/devii/actions/dispatcher.py |
email.message.move |
services/devii/actions/dispatcher.py |
email.send |
services/devii/actions/dispatcher.py |
Engagement (engagement)
| Event key | Recorded in |
|---|---|
bookmark.add |
content.py, routers/bookmarks.py |
bookmark.remove |
content.py, routers/bookmarks.py |
poll.create |
routers/posts.py |
poll.vote.cast |
routers/polls.py |
poll.vote.change |
routers/polls.py |
poll.vote.clear |
routers/polls.py |
reaction.add |
routers/reactions.py |
reaction.remove |
routers/reactions.py |
vote.comment.clear |
content.py |
vote.comment.down |
content.py |
vote.comment.up |
content.py |
vote.gist.clear |
content.py |
vote.gist.down |
content.py |
vote.gist.up |
content.py |
vote.post.clear |
content.py |
vote.post.down |
content.py |
vote.post.up |
content.py |
vote.project.clear |
content.py |
vote.project.down |
content.py |
vote.project.up |
content.py |
vote.quiz.clear |
content.py |
vote.quiz.down |
content.py |
vote.quiz.up |
content.py |
Code Farm (game)
| Event key | Recorded in |
|---|---|
game.cosmetic.buy |
routers/game/index.py |
game.defense.downgrade |
routers/game/index.py |
game.defense.upgrade |
routers/game/index.py |
game.grant.claim |
routers/game/index.py |
game.infrastructure.buy |
routers/game/index.py |
game.prestige |
routers/game/index.py |
Container ingress (ingress)
| Event key | Recorded in |
|---|---|
proxy.access |
routers/proxy.py |
Direct messages (message)
| Event key | Recorded in |
|---|---|
message.read_on_view |
routers/messages.py |
message.send |
services/messaging/persist.py |
Moderation (moderation)
| Event key | Recorded in |
|---|---|
filter.block |
services/moderation/screening.py |
filter.maturity |
services/moderation/screening.py |
filter.review |
services/moderation/screening.py |
moderation.ban |
routers/admin/moderation.py, routers/admin/users.py |
moderation.lift |
routers/admin/moderation.py, routers/admin/users.py |
moderation.remove |
routers/admin/moderation.py |
moderation.restore |
routers/admin/moderation.py |
moderation.suspend |
routers/admin/moderation.py, routers/admin/users.py |
moderation.warn |
routers/admin/moderation.py |
report.create |
routers/reports.py |
report.decide |
routers/admin/moderation.py |
report.status |
routers/admin/moderation.py |
News (news)
| Event key | Recorded in |
|---|---|
news.delete |
routers/admin/news.py |
news.featured.toggle |
routers/admin/news.py |
news.landing.toggle |
routers/admin/news.py |
news.publish.toggle |
routers/admin/news.py |
news.service.draft |
services/news/service.py |
news.service.ingest |
services/news/service.py |
news.service.landing |
services/news/service.py |
news.service.publish |
services/news/service.py |
news.service.reject |
services/news/service.py |
Notifications (notification)
| Event key | Recorded in |
|---|---|
notification.create |
utils/notifications.py |
notification.open |
routers/notifications.py |
notification.read.all |
routers/devrant/notifs.py, routers/notifications.py |
notification.read.one |
routers/notifications.py |
Projects (project)
| Event key | Recorded in |
|---|---|
project.create |
content.py |
project.delete |
content.py |
project.edit |
content.py |
project.fork.complete |
services/jobs/fork_service.py |
project.fork.failed |
services/jobs/fork_service.py |
project.fork.request |
routers/projects/index.py |
project.readonly.disable |
routers/projects/index.py |
project.readonly.enable |
routers/projects/index.py |
project.screenshots.add |
routers/projects/index.py |
project.visibility.private |
routers/projects/index.py |
project.visibility.public |
routers/projects/index.py |
project.zip.request |
routers/projects/index.py |
Project files (project_files)
| Event key | Recorded in |
|---|---|
dir.create |
routers/projects/files.py |
file.append |
routers/projects/files.py |
file.delete |
routers/projects/files.py |
file.delete_lines |
routers/projects/files.py |
file.insert_lines |
routers/projects/files.py |
file.move |
routers/projects/files.py |
file.replace_lines |
routers/projects/files.py |
file.upload |
routers/projects/files.py |
file.write |
routers/projects/files.py |
file.write.create |
routers/projects/files.py |
file.write.overwrite |
routers/projects/files.py |
files.zip.request |
routers/projects/files.py |
job.zip.complete |
services/jobs/zip_service.py |
job.zip.failed |
services/jobs/zip_service.py |
Pub/sub (pubsub)
| Event key | Recorded in |
|---|---|
pubsub.publish |
routers/pubsub.py |
Web push (push)
| Event key | Recorded in |
|---|---|
push.subscribe |
routers/push.py |
push.update |
routers/push.py |
Both carry metadata.provider (webpush, apns, ...) plus created; metadata.endpoint_host is set for endpoint-based providers and null for token-based ones.
Gamification (reward)
| Event key | Recorded in |
|---|---|
reward.badge.award |
utils/badges.py |
reward.level.up |
utils/rewards.py |
reward.streak.milestone |
utils/rewards.py |
reward.xp.grant |
utils/rewards.py |
Security (security)
| Event key | Recorded in |
|---|---|
security.authz.denied |
routers/admin/backups.py, routers/profile/delete.py, services/devii/actions/dispatcher.py, utils/guards.py |
security.maintenance.block |
main.py |
security.rate_limit.block |
main.py |
Background services (service)
| Event key | Recorded in |
|---|---|
service.config.update |
routers/admin/services.py |
service.logs.clear |
routers/admin/services.py |
service.run_now |
routers/admin/services.py |
service.start |
routers/admin/services.py |
service.stop |
routers/admin/services.py |
Social graph (social)
| Event key | Recorded in |
|---|---|
award.complete |
services/jobs/award_service.py |
award.failed |
services/jobs/award_service.py |
award.give |
routers/profile/award.py |
award.revoke |
routers/admin/awards.py |
follow.follow |
routers/follow.py |
follow.unfollow |
routers/follow.py |
relation.block |
routers/relations.py |
relation.mute |
routers/relations.py |
relation.unblock |
routers/relations.py |
relation.unmute |
routers/relations.py |
Telegram (telegram)
| Event key | Recorded in |
|---|---|
telegram.pair.failure |
services/telegram/bridge.py |
telegram.pair.request |
routers/profile/telegram.py |
telegram.pair.success |
services/telegram/bridge.py |
telegram.send |
services/devii/actions/dispatcher.py |
telegram.unpair |
routers/profile/telegram.py |
Developer tools (tools)
| Event key | Recorded in |
|---|---|
deepsearch.chat |
routers/tools/deepsearch.py |
deepsearch.run.complete |
services/jobs/deepsearch/service.py |
deepsearch.run.failed |
services/jobs/deepsearch/service.py |
deepsearch.run.request |
routers/tools/deepsearch.py |
isslop.run.complete |
services/jobs/isslop/service.py |
isslop.run.failed |
services/jobs/isslop/service.py |
isslop.run.request |
routers/tools/isslop.py |
seo.meta.failed |
services/jobs/seo_meta_service.py |
seo.meta.generate |
services/jobs/seo_meta_service.py |
seo.run.complete |
services/jobs/seo/service.py |
seo.run.failed |
services/jobs/seo/service.py |
seo.run.request |
routers/tools/seo.py |
Mapped domains with no recorder
The backup prefix has a CATEGORY_BY_PREFIX entry but no call site emits a bare backup.* key - backup activity is recorded as admin.backup* (operator actions) and job.backup.* (worker outcomes), both of which resolve to the backup category through their own prefixes. Keep the mapping: it is what routes job.backup.* correctly.