Files
cproof-context/playbooks/add-encryption.md
jabber.developer2 22977846a3 docs: split context into layered, agent-oriented files
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.
2026-04-30 20:52:06 +03:00

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:

  1. The header itself.
  2. 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.md for the flow.