Skip to content
This repository was archived by the owner on Jul 16, 2026. It is now read-only.

feat(discord-config): shared owner-resolution helper (Phase 5.10) - #78

Merged
jgluck96 merged 1 commit into
dev-clientfrom
josh/phase-5.10-discord-config
Jun 2, 2026
Merged

jgluck96 merged 1 commit into
dev-clientfrom
josh/phase-5.10-discord-config

Conversation

@jgluck96

@jgluck96 jgluck96 commented Jun 2, 2026

Copy link
Copy Markdown

Summary

Phase 5.10 of the OSS→private sync. Closes the deferred half of #77 by funneling both owner-resolution sites (dm-result.py REST fallback and discord-bridge.py live proactive loop) through one shared helper. Pre-Phase-5.10 both files inlined the same env → tierMap → bot-filter walk; that's the drift class @liususan091219 flagged on OSS PR sonichi#1029 and that bit production in sonichi#846.

Change Shape Impact
src/discord_config.py (new) Verbatim port from OSS Single source of truth for owner-id resolution: env → workspace owner → workspace tierMap → legacy owner → legacy tierMap
src/dm-result.py:_resolve_owner_id -57 +30 lines Delegates to helper; keeps the sync REST bot-filter walk locally
src/discord-bridge.py:_poll_proactive + on_ready -42 +30 in block, +13 for auto-seed Delegates to helper; auto-seeds <workspace>/state/discord-config.json on first boot

Resolution chain (encoded in discord_config.resolve_owner_id)

  1. $SUTANDO_DM_OWNER_ID env var (operator escape hatch)
  2. discord-config.json[\"owner\"] (workspace, Sutando-owned)
  3. discord-config.json[\"tierMap\"][uid] == \"owner\" (workspace)
  4. access.json[\"owner\"] (legacy plugin-territory)
  5. access.json[\"tierMap\"][uid] == \"owner\" (legacy fix(discord-bridge): proactive DM honors tierMap/list order, not set iteration (privacy) sonichi/sutando#846 path)
  6. (in caller, not helper) bot-filter walk via client.fetch_user (async, bridge) or _discord_api(\"GET\", /users/...) (sync, dm-result)

Helper deliberately does NOT fall through to allowFrom[0] — that's the sonichi#1147 bug path. Callers do that step paired with their own I/O-bound bot-filter walk.

Minimal-divergence accounting

  • New file discord_config.py: byte-identical to OSS. Zero conflict surface against future OSS pulls.
  • dm-result.py: 57 lines of stando-specific inline chain replaced with ~30 lines of delegate-to-helper code. Diff against upstream shrinks.
  • discord-bridge.py: 42 lines of stando-specific inline chain replaced with ~30 lines of delegate code + 13 lines for the auto-seed boot call. Diff against upstream shrinks.
  • Total: ~70 lines of stando-specific code removed; ~70 lines of byte-identical-to-OSS code added. The next OSS↔stando sync conflict surface on these three files shrinks by ~140 lines on net.

Behavioral notes

Pattern continuity

Same shape as Phase 5.9's send_allowlist.py extract — new module, byte-identical to OSS, two consumers refactored to delegate. This is the canonical "shrink-the-diff" minimal-divergence move.

Test plan

New tests

  • tests/discord-config.test.py — 15 cases covering:
    1. Resolution chain semantics (env / workspace owner / workspace tierMap / legacy owner / legacy tierMap / exhaustion → None)
    2. Stale-tier safety (tierMap entry for a user not in allowFrom must NOT resolve)
    3. File IO (load on missing, load on corrupt JSON, save_config atomic replace, auto_seed idempotency, auto_seed legacy-owner happy path, auto_seed allowFrom[0]-fallback WARN)
    4. Drift guards — both consumers import the helper AND call the right entry points

Updated test

  • tests/dm-result-send-dm.test.py — _with_access_json now also isolates discord_config.config_path() so the resolver doesn't consult the dev's live workspace discord-config.json. The 5 existing dm-result cases that pin tierMap behavior all pass against the new chain.

Regression sweep (all green)

  • 23 Python tests: discord-bridge-{allowlist, file-markers, file-send-allowlist, pending-replies, prose-marker, reply-chunks, state-prefetch, access-tier, discord-state-detection, welcome-on-first-post, reply-directive, mod-judge, mod-judge-actions}, dm-result-{send-dm, upload}, discord-config, send-allowlist, bridge-marker-no-leak, result-markers, discord-chunker, single-instance, agent-api-result-archive, health-check-phase-57.
  • pnpm typecheck clean.

Made with Cursor

Three coordinated changes that close the OSS->stando port of the
owner-resolution unification started in Phase 5.9. Pre-Phase-5.10
both `dm-result.py:_resolve_owner_id` and
`discord-bridge.py:_poll_proactive` carried inline copies of the
same env -> tierMap -> bot-filter walk chain. The drift class
@liususan091219 flagged on OSS PR sonichi#1029 — and that bit production
in sonichi#846 — was waiting to recur. This PR funnels both sites through
one helper.

1. src/discord_config.py (new, verbatim port from OSS sutando)
   - load_config / save_config / resolve_owner_id / auto_seed_if_missing.
   - resolve_owner_id chain: env override -> discord-config.json[owner]
     -> discord-config.json[tierMap] -> access.json[owner] (legacy)
     -> access.json[tierMap] (legacy sonichi#846 path). Returns None when
     exhausted; helper deliberately does NOT fall through to
     allowFrom[0] (the sonichi#1147 bug path) — callers do that step paired
     with their own bot-filter walk.
   - auto_seed_if_missing: idempotent. On first boot writes
     <workspace>/state/discord-config.json seeded from access.json.
     Emits WARN when seed has to fall back to allowFrom[0] so the
     operator catches a mis-seed.

2. src/dm-result.py: rewire _resolve_owner_id
   - Replaces the ~57 line inline chain with a delegate-to-helper
     pattern. Helper handles steps 1-5; bot-filter REST walk stays
     here (helper is sync-only, so async/sync split between the
     two callers stays clean).

3. src/discord-bridge.py: rewire _poll_proactive owner block + boot
   - on_ready now calls discord_config.auto_seed_if_missing on the
     parsed access.json. Idempotent; non-fatal on error.
   - Owner-resolution block in poll_proactive replaces ~42 inline
     lines with a discord_config.resolve_owner_id call followed by
     the existing async bot-filter walk. Preserves the list-order
     fix (`load_allowed()` set->list change from the 2026-05-18
     msze_/qingyunwu mis-route).

Net effect on diff with OSS upstream
------------------------------------
- discord-bridge.py: -42 +30 in the proactive block (closer to OSS).
- dm-result.py: -57 +30 in the resolver (closer to OSS).
- discord_config.py: byte-identical to OSS (zero conflict surface
  going forward).
- Total: ~70 lines of stando-specific inline chain replaced with
  delegate-to-shared-helper calls. The diff against upstream
  SHRINKS on the two existing files. Pattern matches Phase 5.9's
  send_allowlist extract.

Tests
-----
- tests/discord-config.test.py (new, 15 cases): resolution chain
  semantics (env / workspace owner / workspace tierMap / legacy
  owner / legacy tierMap / exhaustion -> None / stale-tier-for-
  delisted-user safety), file IO safety (load on missing, load on
  corrupt JSON, save_config atomic replace, auto_seed idempotency,
  auto_seed legacy-owner path, auto_seed allowFrom[0]-fallback
  WARN), and drift guards that both consumers import the helper
  AND call the right entry points.
- tests/dm-result-send-dm.test.py (updated): `_with_access_json`
  now also isolates `discord_config.config_path()` so the resolver
  doesn't consult the dev's live workspace discord-config.json
  (which would short-circuit the legacy chain the tests pin).

Verification
------------
- python3 -m py_compile on all three modified source files: OK.
- New tests: 15 cases, all green.
- Full Phase 5 regression sweep: 23 Python tests green
  (discord-bridge-{allowlist, file-markers, file-send-allowlist,
  pending-replies, prose-marker, reply-chunks, state-prefetch,
  access-tier, discord-state-detection, welcome-on-first-post,
  reply-directive, mod-judge, mod-judge-actions}, dm-result-{send-dm,
  upload}, discord-config, send-allowlist, bridge-marker-no-leak,
  result-markers, discord-chunker, single-instance,
  agent-api-result-archive, health-check-phase-57).
- `pnpm typecheck` clean.

Phase 5.10 of the OSS->private sync. Behavioral note: stando's
workspace already contains a `discord-config.json` (OSS code seeded
it during an earlier run); the helper picks it up immediately. For
fresh installs, auto_seed_if_missing runs on the first bridge
on_ready and seeds the file idempotently.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jgluck96
jgluck96 merged commit 1ed136c into dev-client Jun 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant