Skip to main content

max / makenotwork

Give the mt-db submodules and the flagging suite headers Thirty query and mutation submodules carried no //! at all, including the five largest, so the only way to learn what a file covered was to read its function list. Each gets a one-liner, and where a module carries an invariant the line names it rather than the subject matter: post and thread reads say their by-id loaders return Unscoped so the C1 community check cannot be skipped, membership upserts say idempotent, read-position advance says never backwards, auto_hide_if_threshold_met says exactly once under concurrent flaggers. Each of those was read against the SQL before being written down. flagging.rs is 1009 lines mixing four separate security properties with no header. Name them, and say why restore is tested alongside removal: they share the cascade, and a restore that does not resolve the flags rehides the post immediately. The C1 scope rule was written out three times, in queries/mod.rs, in routes/scope.rs and in clippy.toml, and had already drifted. Two of the three said "the few sanctioned call sites" where there is exactly one, in routes/internal.rs. Keep routes/scope.rs as the canonical account, since it is the fullest and clippy.toml already pointed at it, and cut the other two back to what is local to them. Also fixes three broken rustdoc links found while checking the new headers render: two sibling-module references in mutations/post.rs that needed super::, and a public doc linking a private item in mutations/user.rs. Pre-existing, unrelated to the headers, cheap here.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-05 17:02 UTC
Signed with PGP, not checked
Commit: 558f2ae577de3c0ae88e83feb25c3718999e3ff6
Parent: 17ceaa5
34 files changed, +104 insertions, -39 deletions
@@ -5009,6 +5009,14 @@
5009 5009 source = "registry+https://github.com/rust-lang/crates.io-index"
5010 5010 checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
5011 5011
5012 + [[patch.unused]]
5013 + name = "synckit-client"
5014 + version = "0.6.0"
5015 +
5016 + [[patch.unused]]
5017 + name = "synckit-config"
5018 + version = "0.1.2"
5019 +
5012 5020 [[patch.unused]]
5013 5021 name = "kberg"
5014 5022 version = "0.1.0"
@@ -5020,11 +5028,3 @@
5020 5028 [[patch.unused]]
5021 5029 name = "supernote-push"
5022 5030 version = "0.1.0"
5023 -
5024 - [[patch.unused]]
5025 - name = "synckit-client"
5026 - version = "0.6.0"
5027 -
5028 - [[patch.unused]]
5029 - name = "synckit-config"
5030 - version = "0.1.2"
@@ -1,24 +1,14 @@
1 - # The C1 resource-in-community invariant (ultra-fuzz CHRONIC) is sealed by the
2 - # type system now, with one lint-guarded escape hatch.
3 - #
4 - # The raw by-id loaders (`get_thread_with_breadcrumb`, `get_post_for_edit`)
5 - # return their resource wrapped in `mt_db::queries::Unscoped<T>`. The inner value
6 - # is private; the safe way out is `Unscoped::in_community(expected)`, which yields
7 - # it only when the resource's community matches the one the caller names.
8 - # `routes::scope::CommunityScope::resolve` is the sole such site and it passes the
9 - # community the URL slug resolved to, so a handler cannot serve a by-id community
10 - # resource without proving it belongs to that community. That check needs no lint:
11 - # it is enforced by the type.
1 + # The C1 resource-in-community invariant is sealed by the type system, with one
2 + # lint-guarded escape hatch. `src/routes/scope.rs` is the canonical account of
3 + # why the seal exists and what it replaced; this file only says what the lint
4 + # entry below is for.
12 5 #
13 6 # `Unscoped::into_inner_unchecked` unwraps with no community check, for callers
14 7 # that have no slug to scope against (the trusted internal server-to-server API).
15 8 # It is disallowed below so a `/p/{slug}/…` handler cannot reach for it by
16 - # accident; the few sanctioned sites carry a local `#[allow]`.
17 - #
18 - # This replaced a deny-list that enumerated every raw loader by hand and failed
19 - # open for any loader nobody remembered to add. The seal now fails closed: a new
20 - # by-id loader that returns `Unscoped<T>` is bound automatically, and the only
21 - # lint entry is on the single unchecked-unwrap primitive. See `src/routes/scope.rs`.
9 + # accident; the one sanctioned site, in `src/routes/internal.rs`, carries a local
10 + # `#[allow]`. It is the only lint entry because it is the only unchecked-unwrap
11 + # primitive: everything else is enforced by the type.
22 12 disallowed-methods = [
23 13 { path = "mt_db::queries::Unscoped::into_inner_unchecked", reason = "unwraps a community resource without the C1 scope check; a slug-scoped handler must use Unscoped::in_community via routes::scope::CommunityScope instead" },
24 14 ]
@@ -1,7 +1,23 @@
1 + //! Flagging, auto-hide and the moderation actions that resolve them.
2 + //!
3 + //! Four properties, each security-relevant:
4 + //!
5 + //! - auto-hide threshold arithmetic: a post hides at the threshold, never below
6 + //! it, and never when the threshold is null
7 + //! - concurrency: `auto_hide_if_threshold_met` hides exactly once when two
8 + //! flaggers race, and endorsement toggles never duplicate under the same race
9 + //! - mod-log attribution: an auto-hide logs the System actor, not whichever
10 + //! flagger happened to cross the threshold
11 + //! - the write-level access gate on flagging: logged out, own post, platform
12 + //! suspended and muted are each rejected
13 + //!
14 + //! Restore is covered alongside removal because the two share the cascade: a
15 + //! restore must also resolve the flags, or the post rehides immediately.
16 +
1 17 use crate::harness::TestHarness;
2 18 use mt_core::types::BanType;
3 19
4 - // Auto-hide threshold tests
20 + // --- auto-hide threshold
5 21
6 22 #[tokio::test]
7 23 async fn auto_hide_threshold_removes_post_at_threshold() {
@@ -1,3 +1,5 @@
1 + //! category writes, including the swap that maintains display order
2 +
1 3 use super::{PgPool, Uuid};
2 4
3 5 /// Create a new category in a community.
@@ -1,3 +1,5 @@
1 + //! community lifecycle: create, update, suspend, state transitions
2 +
1 3 use super::{CommunityState, PgPool, Utc, Uuid};
2 4
3 5 /// Create a new community and return its ID.
@@ -1,3 +1,5 @@
1 + //! endorsement toggle, idempotent per (user, post) under concurrent submits
2 +
1 3 use super::{PgPool, Uuid};
2 4
3 5 /// Toggle endorsement: insert if missing, delete if exists. Returns true if now endorsed.
@@ -1,3 +1,5 @@
1 + //! footnote inserts
2 +
1 3 use super::{PgPool, Uuid};
2 4
3 5 /// Insert a footnote on a post. Returns the footnote ID.
@@ -1,3 +1,5 @@
1 + //! image lifecycle from insert through to S3 purge marking
2 +
1 3 use super::{PgPool, Uuid};
2 4
3 5 /// Insert an uploaded image record.
@@ -1,3 +1,5 @@
1 + //! link preview inserts
2 +
1 3 use super::{PgPool, Uuid};
2 4
3 5 /// Insert a link preview for a post. Ignores duplicates.
@@ -1,3 +1,5 @@
1 + //! membership upserts, idempotent so a re-join never duplicates a row
2 +
1 3 use super::{CommunityRole, PgPool, Uuid};
2 4
3 5 /// Ensure a user has a membership in a community with the given role.
@@ -1,3 +1,5 @@
1 + //! mention inserts, written as one batch per post
2 +
1 3 use std::fmt::Write as _;
2 4
3 5 use super::Uuid;
@@ -1,3 +1,5 @@
1 + //! bans, mod-log entries and flag resolution
2 +
1 3 use super::{BanType, DateTime, ModAction, ModActor, PgPool, Utc, Uuid};
2 4
3 5 /// Create or update a ban/mute. Returns the ban ID.