Replace the single file-structure.md with a stratified layout designed
for AI/agent skill consumption: tables and concrete identifiers over
prose, files loaded on demand, content separated by churn rate.
Layers:
- architecture/ stable structural reference (overview, source-map,
test-map, data-flow)
- patterns/ memory, commands, autocomplete, events, xmpp,
encryption, ui, plugins
- testing/ unit-tests, stubs, functional-tests, bench
- build/ local, docker, ci
- playbooks/ add-command, add-test, add-autocomplete,
add-event-handler, add-encryption
- gotchas.md append-only dated entries (seven seed entries)
- wip/ branch-specific notes; deleted on merge to master
Stable layers describe cproof on master only. In-flight feature
branches (currently feat/ai) get a single file under wip/.
INDEX.md is the entry map with churn labels; SKILL.md is the
always-loaded skill hint pointing to it.
3.1 KiB
Playbook: touch encryption modules safely
Encryption stacks (OMEMO, OTR, PGP/OX) are independent and each is gated
behind a configure option. Edits must respect three things: the gating
macro, the bridge file split, and the matching unit-test stubs.
See patterns/encryption.md for the architectural split.
1. Identify the bridge
| Stack | Module dir | Bridge file (XMPP side) |
|---|---|---|
| OMEMO | src/omemo/ |
src/xmpp/omemo.c |
| OTR | src/otr/ |
(none — in-stream on src/xmpp/message.c) |
| PGP / OX | src/pgp/ |
src/xmpp/ox.c |
Outbound calls (cproof → encryption) usually live in the bridge file.
Inbound (decrypted message → cproof) usually surfaces in the bridge or
directly in src/xmpp/message.c.
2. Identify the gating macro
grep -n "HAVE_OMEMO\|HAVE_LIBOTR\|HAVE_LIBGPGME" src/xmpp/<bridge>.c
Every call into the encryption module is wrapped:
#ifdef HAVE_OMEMO
omemo_encrypt_message(jid, plaintext, &ciphertext);
#else
// graceful fallback or skip
#endif
When you add a new call, copy the existing guard. Do not drop the fallback — the build must succeed with the feature disabled.
3. Update the public surface (header)
If you add or change an exported function in src/omemo/omemo.h /
src/otr/otr.h / src/pgp/gpg.h / src/pgp/ox.h, update two places:
- The header itself.
- The corresponding stub:
tests/unittests/{omemo,otr,pgp}/stub_*.c.
Keep the stub signature byte-for-byte identical to the header.
4. Stubs
Stubs exist regardless of whether a stack is enabled at runtime — they
satisfy the unit-test linker. Three flavours, see testing/stubs.md.
If a unit test needs to drive the new function, switch the stub to mock()
and queue with will_return. If a unit test needs to assert arguments,
switch to check_expected and queue with expect_string / expect_value.
5. test_forced_encryption
tests/unittests/test_forced_encryption.{c,h} exercises the policy that
refuses plaintext sends when a session is encryption-locked. Run it after
any encryption-policy change.
6. Encryption-aware command tests
| Test file | Covers |
|---|---|
test_cmd_otr.c / .h |
/otr command surface. |
test_cmd_pgp.c / .h |
/pgp command surface. |
(No test_cmd_omemo.c exists at the time of writing — confirm with ls tests/unittests/test_cmd_*.c before assuming.)
7. Build matrix
After changes, build with each encryption flag in turn:
./configure --disable-omemo
make check
./configure --disable-otr
make check
./configure --disable-pgp
make check
(Inside Docker — see build/docker.md.)
CI does not necessarily run every disable combination; verify locally if your change affects gating logic.
Conventions
- Never strip the
#ifdef HAVE_*guard "for clarity". The disabled-build must still compile. - Encryption keys / fingerprints persist via the module's own store (
src/ omemo/store.c, libotr's keystore, gpgme's keyring). Do not invent parallel storage. - Outbound encryption sits between message build and stanza send — see
patterns/xmpp.mdfor the flow.