mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 18:07:29 +01:00
feat: no photo reaches the album from the chat until somebody approves it
!dodaj is unwired: register_add_command is no longer called, its aliases
are gone from aliases.json, and the word is an unknown one like any
other -- answered with "Nie znam !dodaj", storing nothing. The ingest
path itself (pack_add, pack_ingest, pack_store, the stripper) stays,
tested at 100%, because the approval queue that replaces the direct
write will reuse every piece of it: !dodaj lands in pending/, and the
tagger view gets approve / reject.
The eight e2e scenarios that proved the direct write, the media
generation step and prove_the_add_photo_invariants leave with it;
64852ae has them for the queue to bring back in their new shape. One
scenario takes their place and pins the disabled state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6b69deb37d
commit
3107a9e55f
@ -1,5 +1,9 @@
|
||||
# Adding a photo from the chat: `!add_photo`
|
||||
|
||||
> **Unwired since 2026-09-04.** `!dodaj` is not a command until photos land
|
||||
> in an approval queue instead of straight in the album. Everything below
|
||||
> describes the ingest path as it was wired, and as the queue will reuse it.
|
||||
|
||||
How a member of an album's group puts a photo into it by sending one, and
|
||||
what keeps that path from leaking anything. The album itself — where it
|
||||
lives, what its manifests look like, how several albums coexist — is
|
||||
|
||||
@ -30,7 +30,6 @@ This file is the trigger reference — every spelling that resolves, which
|
||||
| stats_me | `!ja` `!moje` `!mnie` |
|
||||
| command_stats | `!analityka` `!command_stats` `!commandstats` `!analytics` `!uzycie` `!użycie` `!ranking` |
|
||||
| help | `!pomoc` `!pomocy` `!help` `!komendy` `!commands` |
|
||||
| add_photo | `!dodaj` `!dodaj_zdjecie` `!dodajzdjecie` `!add_photo` `!addphoto` |
|
||||
| random | `!losowe` `!losuj` `!random` `!rand` |
|
||||
| suggest | `!zaproponuj` `!propozycja` `!sugestia` `!suggest` `!pomysl` |
|
||||
|
||||
@ -66,11 +65,16 @@ group — the album's, whose triggers are real people's names — is neither
|
||||
answered nor recorded, because a refusal would announce that the album
|
||||
exists.
|
||||
|
||||
`add_photo` and `random` exist **only in the photo album's own groups**, and
|
||||
only on an installation that has an album at all — everywhere else they are
|
||||
not commands, not refused and never logged. Both take arguments, and those
|
||||
`random` exists **only in the photo album's own groups**, and only on an
|
||||
installation that has an album at all — everywhere else it is not a command,
|
||||
not refused and never logged. It takes arguments, and those
|
||||
arguments are resolved to pseudonym keys by the registry before any handler,
|
||||
log line or `command_uses` row sees them. See `DOCS-photo-pack.md` and `DOCS-add-photo.md`.
|
||||
log line or `command_uses` row sees them. See `DOCS-photo-pack.md`.
|
||||
|
||||
`add_photo` (`!dodaj`) is **unwired since 2026-09-04**: a photo posted to a
|
||||
group must not reach the album without somebody approving it, and no approval
|
||||
queue exists yet. It comes back when one does; `DOCS-add-photo.md` describes
|
||||
the path as it was.
|
||||
|
||||
## Why `!pomoc` shows the first alias and not a trigger
|
||||
|
||||
|
||||
13
fake_signal/scenarios/pack_add_disabled.yaml
Normal file
13
fake_signal/scenarios/pack_add_disabled.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
# Adding a photo from the chat is switched off until an approval queue
|
||||
# exists (2026-09-04): a photo somebody posts must not reach the album
|
||||
# unreviewed. Unwired rather than gated, so !dodaj is an unknown word like
|
||||
# any other -- answered, and never storing anything. The eight scenarios
|
||||
# that proved the ingest path are at 64852ae, for the queue to bring back.
|
||||
name: "!dodaj is not a command while photos cannot be approved"
|
||||
inject:
|
||||
- {
|
||||
text: "!dodaj alfa", uuid: uuid-add-off, name: AddOff,
|
||||
attachment: att-off,
|
||||
}
|
||||
expect:
|
||||
- { kind: text, to: reply, contains: "Nie znam !dodaj" }
|
||||
@ -158,9 +158,6 @@ main() {
|
||||
wait_for_url "http://127.0.0.1:${BOT_PORT}/healthz" "bot"
|
||||
wait_for_subscription
|
||||
|
||||
echo "== generating media for !add_photo (real exif, never committed)"
|
||||
"$PYTHON" "$REPO_ROOT/tool/e2e_media.py" --upload "$FAKE_URL"
|
||||
|
||||
echo "== replaying scenarios"
|
||||
"$PYTHON" -m fake_signal.cli \
|
||||
"$REPO_ROOT"/fake_signal/scenarios/*.yaml \
|
||||
@ -176,7 +173,9 @@ main() {
|
||||
--group "fourth_reply=${FOURTH_GROUP}" \
|
||||
--group "other=${OTHER_GROUP}"
|
||||
|
||||
prove_the_add_photo_invariants
|
||||
# !dodaj is unwired until photos land in an approval queue instead of
|
||||
# the album (2026-09-04). Its scenarios, the media generation step and
|
||||
# prove_the_add_photo_invariants left with it; git has them at 64852ae.
|
||||
prove_the_deafness_gate
|
||||
enforce_the_time_budget "$STARTED_AT"
|
||||
}
|
||||
|
||||
@ -8,45 +8,6 @@
|
||||
# and the same bot process the scenarios used.
|
||||
# ============================================================================
|
||||
|
||||
prove_the_add_photo_invariants() {
|
||||
local pack="${TEMP_DIR}/pack"
|
||||
|
||||
echo "== checking every stored file was actually stripped"
|
||||
"$PYTHON" "$REPO_ROOT/tool/e2e_media.py" --verify-pack "$pack"
|
||||
|
||||
echo "== checking a typed label never reached custom.json as a key"
|
||||
if grep -qE '"(wakacje|piesek)"[[:space:]]*:' "${pack}/custom.json"; then
|
||||
echo "Error: a typed label became a key in custom.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "== checking !dodaj with no attachment minted nothing"
|
||||
if grep -q "piesek" "${pack}/custom.json"; then
|
||||
echo "Error: a mint survived a message that carried no attachment" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "== checking the refused uploads were not stored"
|
||||
if grep -q "att-junk" "${pack}/tags.json"; then
|
||||
echo "Error: a refused file reached tags.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "== checking signal-cli's own copies were deleted"
|
||||
local left
|
||||
left="$(curl -sf "${FAKE_URL}/_control/attachments")"
|
||||
if [[ "$left" == *"att-person"* || "$left" == *"att-custom"* ]]; then
|
||||
echo "Error: an ingested attachment is still in the container store: ${left}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "== checking the adder was recorded for a future !undo_photo"
|
||||
if [[ ! -s "${pack}/adds.json" ]]; then
|
||||
echo "Error: adds.json was never written" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# The gate that did not exist on 2026-08-29, proven against the real process:
|
||||
# with signal-cli unable to reach Signal, /healthz must go red rather than
|
||||
# keep answering 200 while nothing arrives.
|
||||
|
||||
@ -145,13 +145,6 @@
|
||||
"!komendy",
|
||||
"!commands"
|
||||
],
|
||||
"add_photo": [
|
||||
"!dodaj",
|
||||
"!dodaj_zdjecie",
|
||||
"!dodajzdjecie",
|
||||
"!add_photo",
|
||||
"!addphoto"
|
||||
],
|
||||
"random": [
|
||||
"!losowe",
|
||||
"!losuj",
|
||||
|
||||
@ -23,8 +23,6 @@ from signal_bot.decorators import cooldown
|
||||
from signal_bot.dispatcher import Dispatcher
|
||||
from signal_bot.help_command import register_help_command
|
||||
from signal_bot.http_client import ImageFetcher
|
||||
from signal_bot.media_strip import ExiftoolStripper
|
||||
from signal_bot.pack_add import register_add_command
|
||||
from signal_bot.pack_command import register_pack_commands
|
||||
from signal_bot.pack_holder import PackHolder
|
||||
from signal_bot.pack_load import load as load_pack
|
||||
@ -178,7 +176,6 @@ def _register_album(registry: CommandRegistry, directory: Path, rate_limit: Deco
|
||||
holder = PackHolder(load_pack(directory))
|
||||
register_pack_commands(registry, holder, decorate=rate_limit)
|
||||
register_random_command(registry, holder, tuple(aliases["random"]), decorate=rate_limit)
|
||||
register_add_command(registry, holder, ExiftoolStripper(), tuple(aliases["add_photo"]))
|
||||
|
||||
|
||||
def build_bot(
|
||||
|
||||
@ -109,21 +109,26 @@ def test_two_albums_may_share_a_trigger_when_their_groups_do_not_overlap(
|
||||
assert first is not second
|
||||
|
||||
|
||||
def test_add_photo_and_random_exist_separately_in_each_album(
|
||||
def test_random_exists_separately_in_each_album(
|
||||
tmp_path: Path,
|
||||
config: Config,
|
||||
store: Store,
|
||||
transport: FakeTransport,
|
||||
directory: GroupDirectory,
|
||||
) -> None:
|
||||
"""!dodaj and !losowe carry the same triggers in both, by construction."""
|
||||
"""!losowe carries the same triggers in both, by construction.
|
||||
|
||||
!dodaj used to be asserted here too. It is unwired until an approval
|
||||
queue exists (2026-09-04), and a word nothing registers is the same in
|
||||
every group: the scoping this test guards is the album's, not its.
|
||||
"""
|
||||
real = _album(tmp_path / "real", WATCHED_GROUP)
|
||||
test = _album(tmp_path / "test", SECOND_GROUP, key="p9", trigger="!zeta")
|
||||
registry = _registry(config, store, transport, directory, (real, test))
|
||||
|
||||
for group in (WATCHED_GROUP, SECOND_GROUP):
|
||||
assert _claim(registry, "!dodaj alfa", group) is not None
|
||||
assert _claim(registry, "!losowe", group) is not None
|
||||
assert _claim(registry, "!dodaj alfa", group) is None
|
||||
|
||||
|
||||
def test_two_albums_sharing_a_group_are_refused_at_startup(
|
||||
|
||||
@ -88,7 +88,7 @@ def test_a_label_that_is_a_command_word_is_not_forbidden(
|
||||
assert "ciocia_hania" in words
|
||||
|
||||
|
||||
@pytest.mark.parametrize("label", ["losowe", "papiez", "add_photo", "staty"])
|
||||
@pytest.mark.parametrize("label", ["losowe", "papiez", "komendy", "staty"])
|
||||
def test_no_command_word_can_be_outlawed_by_a_label(
|
||||
sources: ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, label: str
|
||||
) -> None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user