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

fix(slack-bridge): prime access cache at startup so #899 fix survives restarts (Phase 5.14) - #82

Merged
jgluck96 merged 1 commit into
dev-clientfrom
josh/phase-5.14-slack-send-allowlist
Jun 2, 2026
Merged

jgluck96 merged 1 commit into
dev-clientfrom
josh/phase-5.14-slack-send-allowlist

Conversation

@jgluck96

@jgluck96 jgluck96 commented Jun 2, 2026

Copy link
Copy Markdown

Summary

Completes Phase 5.13's sonichi#899 cache port. The cache machinery only protects against external deletion of access.json if the cache is populated. Phase 5.13 added the cache + restore plumbing but missed the 1-line OSS pairing that primes the cache on bridge startup.

Why the priming matters

Without it, _access_cache stays None until the first successful load_allowed() call. The hostile race:

  1. Bridge crashes / restarted.
  2. access.json deleted externally while the bridge was down (Sutando.app Settings revoke flow, accidental rm, sync conflict).
  3. New Slack DM arrives.
  4. Bridge calls load_allowed() to validate sender → file missing → returns None, cache stays None.
  5. Falls into tofu_onboard().
  6. _restore_access_from_cache() returns False (cache is None).
  7. Genuine TOFU runs → new sender becomes owner → data loss.

OSS sutando has had this priming since the original sonichi#899 fix; Phase 5.13 ported the cache surface but not the startup wiring. This PR closes that gap.

Changes

src/slack-bridge.py — in main(), call load_allowed() immediately after _recover_orphan_sending_files() and before any threads spin up. Reads access.json into the in-memory cache while the file is still on disk, so the very first inbound DM after a restart can detect and recover from external deletions.

tests/slack-bridge-access-cache.test.py — new test_main_primes_access_cache_at_startup structural guard. Verifies main() contains load_allowed() AND that it appears AFTER _recover_orphan_sending_files() in source order. A future PR that drops the priming or reorders it would silently regress sonichi#899.

Audit note on original Phase 5.14 scope

The original Phase 5.14 plan was a send_allowlist helper extract for slack-bridge (parallel to Phase 5.9 for discord). Audit found OSS sutando still has the SAME inline allowlist as stando — the extract was done only for the discord family (discord-bridge + dm-result). Extracting slack would have added divergence, violating the minimal-divergence rule.

Scope was redirected to the actual remaining gap: the missing sonichi#899 priming call. After this PR, slack-bridge has converged with OSS structurally — same function set, same top-level constants, same startup ordering. No remaining net-portable wins for slack-bridge.

Test plan

  • python3 tests/slack-bridge-access-cache.test.py — 11/11 pass (+1 net-new)
  • Full Python regression sweep — 56/56 pass
  • pnpm -s typecheck clean
  • python3 -m py_compile src/slack-bridge.py clean

Roadmap update

This closes the slack-bridge backlog from Phase 5.11/5.12/5.13/5.14. Future Phase 5 work will pick from the remaining inventory items (none currently identified as high-confidence net-portable; this may be a natural stopping point for the bridge-family sync).

Made with Cursor

…urvives restarts (Phase 5.14)

Completes the Phase 5.13 sonichi#899 cache port. The cache machinery only
protects against external deletion of `access.json` if the cache is
actually populated. Phase 5.13 added the cache + restore plumbing but
missed the 1-line OSS pairing that primes the cache on bridge startup
— without it, `_access_cache` stays `None` until the first successful
`load_allowed()` call. If that first call happens to be the access
check for an inbound DM that arrives AFTER an external deletion (file
deleted while the bridge was down, then a message lands), the cache
is still `None`, `_restore_access_from_cache()` returns False, and
`tofu_onboard()` silently overwrites the prior owner — exactly the
regression sonichi#899 was meant to prevent.

OSS sutando has had this priming since the original sonichi#899 fix; the
Phase 5.13 PR ported the cache surface but not the startup wiring.

Changes:
- `src/slack-bridge.py`: in `main()`, call `load_allowed()` immediately
  after `_recover_orphan_sending_files()` and before any threads spin
  up. Reads access.json into the in-memory cache while the file is
  still on disk, so the very first inbound DM after a restart can
  detect and recover from external deletions.
- `tests/slack-bridge-access-cache.test.py`: new
  `test_main_primes_access_cache_at_startup` structural guard. Verifies
  `main()` contains `load_allowed()` AND that it appears AFTER
  `_recover_orphan_sending_files()` in source order. A future PR that
  drops the priming or reorders it would silently regress sonichi#899.

Audit summary:
- Original Phase 5.14 scope was a `send_allowlist` helper extract for
  slack-bridge. Audit found OSS sutando still has the SAME inline
  allowlist as stando — the extract was done only for the discord
  family (discord-bridge + dm-result). Extracting slack would have
  ADDED divergence, violating the minimal-divergence rule. Scope
  was redirected to the actual remaining gap: the missing sonichi#899
  priming call. After this PR, slack-bridge has converged with OSS
  structurally — no extant net-portable wins.

Verification:
- 56/56 Python tests pass (no test count change; cache-test file
  grew by 1 net-new test).
- TypeScript typecheck clean.
- `python3 -m py_compile src/slack-bridge.py` clean.

Phase 5.14 of the OSS → private sync. Closes the slack-bridge backlog.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jgluck96
jgluck96 merged commit 71aed04 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