PTWM
Guides

Writing your first extension

Step-by-step walkthrough from `ptwm ext init` to a signed, installed plane codec.

This guide walks through publishing a tiny plane codec end-to-end: scaffold the project, write the encode/decode functions, sign the bundle, install it, and verify PTWM picks it up at runtime.

Total time: ~15 minutes. You'll need a Rust toolchain with the wasm32-wasip1 target, plus the extension-author extras:

rustup target add wasm32-wasip1
pip install 'ptwm[ext-dev]'

1. Scaffold

ptwm ext init xor-codec --lang rust --kind plane_codec
cd xor-codec
ls

You get:

Cargo.toml          manifest.toml       src/lib.rs

The Rust crate is configured as a cdylib targeting wasm32-wasip1. manifest.toml declares one plane_codec contribution with a placeholder canonical id (rewritten at sign time).

src/lib.rs exports the byte-buffer ABI symbols (see the extension system doc for the full plane_codec signature):

#[unsafe(no_mangle)]
pub extern "C" fn ptwm_plane_codec_v1_init() -> u32 { 0 }

#[unsafe(no_mangle)]
pub extern "C" fn ptwm_plane_codec_v1_cleanup(_state: u32) {}

#[unsafe(no_mangle)]
pub extern "C" fn ptwm_plane_codec_v1_encode(
    _state: u32,
    in_ptr: u32, in_len: u32,
    out_ptr: u32, out_len: u32,
) -> i64 { /* copy input → output */ }

#[unsafe(no_mangle)]
pub extern "C" fn ptwm_plane_codec_v1_decode(
    _state: u32,
    in_ptr: u32, in_len: u32,
    out_ptr: u32, out_len: u32,
) -> i64 { /* copy input → output */ }

The generated stub is a passthrough — input bytes flow out unchanged. A real codec replaces the bodies with entropy coding; for the tutorial we'll XOR every byte with 0x5A.

2. Edit src/lib.rs

Replace the encode and decode bodies:

const KEY: u8 = 0x5A;

#[unsafe(no_mangle)]
pub extern "C" fn ptwm_plane_codec_v1_encode(
    _state: u32,
    in_ptr: u32, in_len: u32,
    out_ptr: u32, out_len: u32,
) -> i64 {
    if out_len < in_len { return -2; }
    let input = unsafe { std::slice::from_raw_parts(in_ptr as *const u8, in_len as usize) };
    let output = unsafe { std::slice::from_raw_parts_mut(out_ptr as *mut u8, out_len as usize) };
    for (i, b) in input.iter().enumerate() {
        output[i] = b ^ KEY;
    }
    in_len as i64
}

Decode is identical (XOR is self-inverse). Keep init returning 0 and cleanup empty — this codec has no per-call state.

3. Build

ptwm ext build

Behind the scenes this runs cargo build --target wasm32-wasip1 --release and copies the resulting .wasm to the bundle root. The output:

target/wasm32-wasip1/release/xor_codec.wasm    [intermediate]
xor-codec.wasm                                  [bundle artifact]

4. Sign

ptwm ext sign --key <path> takes a 32-byte raw Ed25519 seed file. The v1 CLI doesn't ship a gen-key helper, so generate one with openssl (or any source of 32 random bytes):

mkdir -p ~/.ptwm/keys
openssl rand 32 > ~/.ptwm/keys/dev.priv
chmod 600 ~/.ptwm/keys/dev.priv

Then sign the bundle:

ptwm ext sign --key ~/.ptwm/keys/dev.priv

ext sign rewrites manifest.toml so that:

  • [bundle].author_pubkey becomes the real ed25519:<hex> of the key.
  • Every [[contributions]].id becomes the real blake3:<hex> canonical id (derived from your pubkey + name + version).
  • signature.bin is written next to the manifest: a 64-byte Ed25519 signature over blake3(manifest_bytes ‖ xor-codec.wasm).

Tampering with either the manifest or the binary after this step invalidates the signature; the container reader will reject the bundle.

5. Pack & install

After signing, manifest.toml carries your ed25519:<hex> public key under [bundle].author_pubkey. Read it out and add it to your keyring before installing:

ptwm ext pack       # produces xor-codec-0.1.0.tar.zst

# Pull the public key from the signed manifest and trust it locally.
PUBKEY=$(python -c \
    'import tomllib, pathlib; \
     print(tomllib.loads(pathlib.Path("manifest.toml").read_text())["bundle"]["author_pubkey"])')
ptwm trust add --key "$PUBKEY" --label dev

ptwm ext install ./xor-codec-0.1.0.tar.zst
ptwm ext list

The trust add --key step tells the user keyring to accept any contribution signed by this author. Without it, ext install would refuse the bundle as untrusted.

6. Confirm it loads

ptwm ext show xor-codec

Look for pubkey: ed25519:<your-hex> — that's the keyring matching the bundle's author. The contribution count, canonical id, and installed flavors are also reported.

To exercise the new codec end-to-end, run the ablation harness so a policy variant can target it. Write a policy that allows just your new codec, then run ptwm bench compress so the dispatcher attempts it against the built-ins:

python -c 'print("Hello, " * 50, end="")' > sample.bin

cat > policy.toml <<EOF
[allow]
extra = ["blake3:<canonical-id-from-show>"]
EOF

ptwm bench compress sample.bin --variant policy.toml --out bench-out/

bench compress writes a CSV under bench-out/. The resolved_policy_toml column records exactly which codecs were eligible per row; the compressed_bytes and ratio columns reflect the dispatcher's choice. Since XOR is roughly identity for cost (ratio ≈ 1.0), Huffman typically wins on a real model — but the attempt proves the bundle loaded, verified, and ran.

Where to go from here

Common pitfalls

  • Forgot to add wasm32-wasip1ptwm ext build will report linker error: …unknown target. Fix: rustup target add wasm32-wasip1.
  • Tampered with binary after sign — install will fail with ContributionUntrusted. Re-run ptwm ext sign to refresh the signature.
  • Author key not in keyring — same error. Run ptwm trust add --key … first.
  • out_len < in_len — return -2 (BufferTooSmall). The dispatcher will then skip your codec and pick a different one.