obsmage atom layer -- SPEC (checked by tests/e2e.sh, independently of tests/test_atoms.py)

AT1  split then join of a whole OBS configuration is lossless: every .ini byte-identical
     (BOM, CRLF, comments, blank lines kept), every .json parse-equal.
AT2  a secret-class atom's value appears in no file under the atom store and not in STAGES.md;
     the atom shows @secret(len=N) with N the value's length.
AT3  the vault holding secret values is created mode 600.
AT4  join without the vault refuses (exit 1), names each missing secret, and writes no
     configuration file that needs one.
AT5  rollup writes STAGES.md with exactly one pin line `<!-- omg:pin <64 hex> -->` and one
     ```kv set=...``` block per set file.
AT6  an edit made only in STAGES.md unrolls into the atom store (exit 0).
AT7  when both the atom store and STAGES.md changed since the pin, unroll exits 1, names the
     conflicting set, and writes nothing.
AT8  suggest never changes the atom store.
AT9  `find` exits 0 and prints FOUND or absent for each usual location.
AT10 rollup and unroll accept a bare file name (no directory part) for STAGES.md.
     (Found 2026-11-01 when a rollup to `STAGES.md` in the current directory crashed.)
AT11 obs-websocket's password (`ServerPassword` in [OBSWebSocket]) is a secret: withheld as
     @secret(len=N), never published. (Found 2026-11-02: the rule knew only `server_password`.)

Copyright 2026 INPUTLOOP; CC-BY-SA-4.0.
Mentat (for Corwin; claude-opus-5-5)
