# Playbook — Three agents, one attribution, a disagreement that has to survive

Reproduce a multi-agent disagreement over one attribution question, and verify that the engine refuses to collapse it into a comfortable single answer.

## What you need

- Docker 24 or later
- Python 3.9 or later
- About 3 minutes

## 1. Start a disposable Corrobore

```bash
mkdir -p .corrobore-tls .corrobore-secrets
printf '%s\n' 'change-me' > .corrobore-secrets/http-token
openssl req -x509 -newkey rsa:2048 -sha256 -nodes \
  -keyout .corrobore-tls/server.key -out .corrobore-tls/server.crt \
  -days 30 -subj '/CN=localhost' \
  -addext 'basicConstraints=critical,CA:FALSE' \
  -addext 'keyUsage=critical,digitalSignature,keyEncipherment' \
  -addext 'extendedKeyUsage=serverAuth' \
  -addext 'subjectAltName=DNS:localhost,IP:127.0.0.1'

docker run --rm -d --name corrobore-playbook \
  -p 127.0.0.1:8080:8080 \
  -e CORROBORE_STORAGE_MODE=ephemeral \
  -v "$PWD/.corrobore-secrets/http-token:/run/secrets/corrobore-http-token:ro" \
  -v "$PWD/.corrobore-tls/server.crt:/run/secrets/tls.crt:ro" \
  -v "$PWD/.corrobore-tls/server.key:/run/secrets/tls.key:ro" \
  ghcr.io/estance-labs/corrobore:latest

curl --insecure -H 'Authorization: Bearer change-me' https://127.0.0.1:8080/health/ready
```

## 2. Install the notebook dependencies

```bash
pip install requests
```

## 3. Run the notebook

```bash
jupyter lab notebook.ipynb
```

The notebook loads `dataset.json` from the same directory. Keep the three files together.

## 4. What to observe

1. All three competing claims and all six stances survive a single bounded recall; none overwrites another.
2. `consolidate` in propose mode returns a proposal and mutates nothing: every claim is still active at version 1 afterwards.
3. Requesting consolidation with `preserve_disagreements: false` is refused with `POLICY_APPROVAL_REQUIRED`.
4. An approved merge moves the non-canonical claims to `superseded`: they leave `recall` and stay in `trace` with their evidence.
5. A payload trying to declare its own actor is rejected; attribution comes from `trace`, derived from authenticated state.

## 5. Change one thing and re-run

- Choose `claim--atlas-owns` as the canonical id and observe which readings become superseded instead.
- Re-run the approved apply with the same idempotency key and confirm the receipt replays rather than merging twice.
- Add a fourth stance challenging the canonical claim after the merge, then recall again.
- Start the server with `CORROBORE_MEMORY_PERMISSIONS=read,write,trace` and confirm consolidation returns `PERMISSION_DENIED`.

## Boundaries

- The dataset is synthetic: the cluster, the reseller, the feeds and the analysts are invented.
- The `agent` property on each stance is application data. Trusted authorship is what `trace` reports.
- Claims, stances and resolution policies are first-class typed objects in `graph-core`; over HTTP they are modeled as typed memory records and relationship kinds.

## Tear down

```bash
docker rm -f corrobore-playbook
```

---

Documentation: https://docs.corrobore.org — Source: https://github.com/Estance-Labs/corrobore
