535 lines
23 KiB
Markdown
Raw Normal View History

Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
# Audit event catalogue
retoor <retoor@molodetz.nl>
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
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.
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
## 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 in `CATEGORY_BY_PREFIX`; an unmapped domain resolves to category `other` and is a bug.
- `target_type` names the entity acted upon, not the event (`gateway_quota`, not `gateway_quota_reset`).
- Every row carries a result: `success`, `failure`, `denied`. A refused action is recorded with `denied`, 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 the `create`/`edit`/`delete` verbs the shared `content.py` helpers 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_PREFIX` entry for a new domain, and the recorder call at the mutation point.
## Categories
| Category | Keys |
|---|---|
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `account` | 16 |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `admin` | 21 |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `ai` | 11 |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `attachment` | 5 |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `auth` | 8 |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `backup` | 2 |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `cli` | 43 |
| `container` | 36 |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `content` | 37 |
| `database` | 4 |
| `devii` | 18 |
| `email` | 6 |
| `engagement` | 23 |
| `game` | 6 |
| `ingress` | 1 |
| `message` | 2 |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `moderation` | 12 |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `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 |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
**Total: 331 keys.**
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
## 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` |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `account.delete.purge` | `services/moderation/deletion.py` |
| `account.delete.request` | `routers/devrant/auth.py`, `routers/profile/delete.py` |
Converge every account onto every policy agreement it has not declined An instance kept production-identical for extended manual testing is otherwise taxed forever by its own safety controls: five consents, a versioned terms gate on every mutating request, and every account predating the trust and safety commit reading terms_version NULL because init_db deliberately never backfills it. AcceptanceService grants each agreement to each account that has not declined it, so the instance stays production byte for byte while nobody clicks the same dialog again. It is opt-in, dry run by default, and one switch per agreement type. The application is not allowed to know it exists. One registration line in main.py is the only import anywhere, there is no route, schema, template, Devii tool or environment flag, and a unit test greps the tree and fails the suite if a second importer appears. The decline register needs no storage: the ledger is append-only in effect, the service only ever grants, so any withdrawn row was written by a human and that pair is never touched again. No provenance column, nothing to observe. Satisfaction is the gate's own expression, never a proxy, which is why the ordering is created_at then id exactly as consent_state selects, and why the live-account clauses are built with has_column: init_db ensures terms_version and deletion_requested_at but not is_active, so a hardcoded reference raises no such column on an instance where nobody was ever suspended. Every write is one conditional statement decided on the real rowcount, proven with sixteen processes racing one account to exactly one ledger row and one audit row. The two existing audit keys carry it, with actor kind service, because a service that silently mutated consent state would be the worst possible exception to the append-only rule. lensfl.md is the source brief accept.md records the design against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 00:17:47 +02:00
| `consent.grant` | `routers/profile/consent.py`, `services/acceptance/grant.py` |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `profile.mature_content` | `routers/profile/consent.py` |
| `consent.withdraw` | `routers/profile/consent.py` |
Converge every account onto every policy agreement it has not declined An instance kept production-identical for extended manual testing is otherwise taxed forever by its own safety controls: five consents, a versioned terms gate on every mutating request, and every account predating the trust and safety commit reading terms_version NULL because init_db deliberately never backfills it. AcceptanceService grants each agreement to each account that has not declined it, so the instance stays production byte for byte while nobody clicks the same dialog again. It is opt-in, dry run by default, and one switch per agreement type. The application is not allowed to know it exists. One registration line in main.py is the only import anywhere, there is no route, schema, template, Devii tool or environment flag, and a unit test greps the tree and fails the suite if a second importer appears. The decline register needs no storage: the ledger is append-only in effect, the service only ever grants, so any withdrawn row was written by a human and that pair is never touched again. No provenance column, nothing to observe. Satisfaction is the gate's own expression, never a proxy, which is why the ordering is created_at then id exactly as consent_state selects, and why the live-account clauses are built with has_column: init_db ensures terms_version and deletion_requested_at but not is_active, so a hardcoded reference raises no such column on an instance where nobody was ever suspended. Every write is one conditional statement decided on the real rowcount, proven with sixteen processes racing one account to exactly one ledger row and one audit row. The two existing audit keys carry it, with actor kind service, because a service that silently mutated consent state would be the worst possible exception to the append-only rule. lensfl.md is the source brief accept.md records the design against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 00:17:47 +02:00
| `terms.accept` | `routers/auth/terms.py`, `services/acceptance/grant.py` |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
## 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 |
|---|---|
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `ai.consent.denied` | `services/openai_gateway/service.py` |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `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 |
|---|---|
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `cli.accounts.prune` | `cli/accounts.py` |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `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` |
2026-08-07 10:53:08 +02:00
| `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` |
2026-08-10 00:23:20 +02:00
| `container.workspace.editor.update` | `routers/projects/containers/workspace.py`, `routers/admin/workspaces.py` |
2026-08-07 10:53:08 +02:00
| `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` |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `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` |
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
## 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` |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
## 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.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.
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
## 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 |
|---|---|
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
| `security.authz.denied` | `routers/admin/backups.py`, `routers/profile/delete.py`, `services/devii/actions/dispatcher.py`, `utils/guards.py` |
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
| `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.