my-sea bud-invite Phase A: SeaInvite model + @mailman log + OK/BYE accept/decline — TDD

Phase A of the my-sea invite → @mailman → spectator → voice blueprint
(magical-dancing-quasar.md). Pure Django; no new infra this phase (the coturn
droplet lands in Phase C5). Mirrors the @taxman ledger shape throughout.

- A1: SeaInvite model (gameboard) — single source of truth for a my-sea invite
  (owner / invitee / status / timestamps + OneToOne FK to its @mailman Line).
  is_expired / voice_active / is_present / expires_at properties; 12 UTs.
  created_at uses default=timezone.now (MySeaDraw precedent) for testable 24h
  expiry; a token deposit makes the invite non-expiring per spec.
- A2: reserved @mailman system user — get_or_create_mailman + "mailman" added
  to RESERVED_USERNAMES + seed migration lyric/0015. Email domain confirmed
  w. user as mailman@earthmanrpg.local (matches adman/taxman).
- A3: billboard KIND_MAIL_ACCEPTANCE on Post + Brief; extends the post_save
  unsolicited-line guard (_SYSTEM_AUTHOR_POST_KINDS) + migration billboard/0009.
- A4: apps/billboard/mail.py log_sea_invite — appends one interactive Line +
  invitee Brief on the invitee's "Acceptances & rejections" Post, links the
  Line back onto the SeaInvite; "Listen!—@owner invites you to {poss} drawing
  table" prose via at_handle + resolve_pronouns. Unregistered invitee no-ops.
- A5: post.html renders OK .btn-confirm / BYE .btn-abandon (PENDING) or a
  status badge (ACCEPTED / DECLINED / LEFT / EXPIRED) from line.sea_invite.status
  via new _partials/_invite_actions.html; 'mailman' added to the system-author
  |safe + read-only-input + bud-panel-suppression branches.
- A6: real my_sea_invite (replaces the coming-soon stub) — resolves recipient,
  dedups outstanding PENDING/ACCEPTED, creates SeaInvite + logs the @mailman
  line; new my_sea_invite_accept / my_sea_invite_decline endpoints (invitee-only,
  redirect back to the invite-log Post; accept links invitee FK + stamps
  accepted_at). 16 ITs.
- A7: updated MySeaBudBtnInviteTest (stub→real invite) + new
  MySeaInviteAcceptanceLogTest FT (invitee opens their log Post, sees the line
  + OK/BYE). Both green.

457 IT/UT green. Phase B (invitee spectator seat-2 + visitor token gate) +
Phase C (WebRTC mesh voice + coturn droplet) to follow.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-27 13:14:06 -04:00
parent 1c799d35ca
commit fb8563eed2
15 changed files with 1037 additions and 38 deletions

View File

@@ -0,0 +1,38 @@
# Generated 2026-05-27 — seed the @mailman system user (author of the
# "Acceptances & rejections" my-sea invite log Lines). Mirrors
# `0014`'s seed_taxman / `0003_seed_adman`. Pairs w. `billboard/0009_
# mail_acceptance_kind` for the new Post/Brief kind registration.
from django.contrib.auth.hashers import make_password
from django.db import migrations
def seed_mailman(apps, schema_editor):
"""Mirror `0003_seed_adman` for the @mailman system user — authors the
interactive OK/BYE invite log Lines per [[my-sea-invite-voice-blueprint]]."""
User = apps.get_model("lyric", "User")
User.objects.get_or_create(
username="mailman",
defaults={
"email": "mailman@earthmanrpg.local",
"password": make_password(None),
"is_staff": False,
"is_superuser": False,
"searchable": False,
},
)
def reverse_noop(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("lyric", "0014_brief_dismissal_fields"),
]
operations = [
migrations.RunPython(seed_mailman, reverse_noop),
]

View File

@@ -47,7 +47,7 @@ def resolve_pronouns(pronouns_key):
# username and existing tests assign it; revisit if/when other-entity
# impersonation becomes a concrete concern.
RESERVED_USERNAMES = frozenset({"adman", "taxman"})
RESERVED_USERNAMES = frozenset({"adman", "taxman", "mailman"})
def is_reserved_username(name, current_user=None):
@@ -99,6 +99,26 @@ def get_or_create_taxman():
return taxman
def get_or_create_mailman():
"""Idempotent fetch of the sitewide `mailman` User — system-author for the
"Acceptances & rejections" invite log Lines (my-sea bud-invite flow, see
[[my-sea-invite-voice-blueprint]]). Parallels `get_or_create_taxman`
exactly; production migration `lyric/0015_seed_mailman` seeds the row once,
this helper backstops TransactionTestCase flushes."""
from django.contrib.auth.hashers import make_password
mailman, _ = User.objects.get_or_create(
username="mailman",
defaults={
"email": "mailman@earthmanrpg.local",
"password": make_password(None),
"is_staff": False,
"is_superuser": False,
"searchable": False,
},
)
return mailman
class UserManager(BaseUserManager):
def create_user(self, email):
user = self.model(email=email)