BARBICAN — synthetic influence-operation detection
Public defensive detector for coordinated synthetic social-post content. The offensive twin (SPECTRE) is private and federal-only and is not part of this package.
This is a self-contained, zero runtime dependency Python package (standard
library only). The content-scanner signals it relies on are vendored into
barbican._scanners, so importing barbican never loads any other Oubliette
package.
Quickstart
Point it at a file of posts and ask whether anything in it is coordinated:
pip install oubliette-barbican
barbican demo
A ready-made corpus ships inside the package, so there is nothing to source
first and nothing to clone —
21 posts, two constructed coordinated pushes and twelve organic controls. It is
CC0 text written for the fixture, so it carries no licence encumbrance. See
examples/README.md for provenance and for what it does
and does not demonstrate. Point detect at your own corpus the same way:
barbican detect -i your_posts.jsonl.
Examined 21 posts (embeddings: none).
Thresholds: edge=0.6 coord=0.6 window=3600s
2 coordinated cluster(s):
[1] 5 posts, score 0.807
ferry-push-1 eastbank_1 2026-03-02T09:00:00Z
The Rivermouth ferry closure is a disaster for working families on th...
...
Score is coordination evidence, not proof of inauthenticity: quotation,
syndication and genuine consensus also cluster.
No network by default. The correlator blends an embedding signal with
n-gram, temporal and persona signals; with embeddings off the embedder is never
consulted, so detect runs on a disconnected host using the standard library
alone. --embed ollama opts in to a local model server and buys paraphrase
robustness.
Input is JSONL, one post per line. post_id, text, author_id and
timestamp are required; campaign_id, label and backend_model are
evaluation metadata and optional.
{"post_id": "c1", "author_id": "a1", "timestamp": "2026-01-01T10:00:00Z", "text": "..."}
Exit codes suit pipelines: 0 nothing flagged, 1 coordination found, 2 bad
input.
With ground truth available, score the discovered clusters against the known campaigns rather than eyeballing them:
barbican evaluate -i labelled.jsonl -f json
evaluate refuses a corpus with no campaign_id, rather than reporting a
number computed against nothing.
What it does
- Operator CLI (
cli.py).detectandevaluateover a JSONL corpus — the analyst-facing surface over the library below. - Per-artifact baseline (
artifact.py). A deterministic single-surface detector: short-text lexical/statistical features plus two vendored scanner signals (invisible-text hits, AI-generation hits), combined by a linear baseline. Demonstrates — and bounds — what per-post scoring alone can achieve. - Coordination correlator (
correlator.py). The novelty: pairwise coordination signals (narrative n-gram/embedding similarity, temporal proximity, persona similarity) → thresholded graph → union-find clustering → per-cluster coordination score. Recovers coordinated campaigns that look individually authentic and only become suspicious in aggregate. - Embeddings (
embed.py). An injectableEmbeddingFnseam plus a local Ollama client that uses only stdliburllib(no third-party HTTP dependency). - Evaluation harness (
experiment.py). Baseline, layered (baseline + correlator), and real-world-split conditions with precision/recall/F1 and campaign-level metrics; writesresults.json+RESULTS.md. - Real-world loader (
realworld.py). Normalizes a documented intermediate JSONL into thePost/Datasetschema for a held-out generalization check.
Boundary
The offensive SPECTRE framework and the eval-only corpus generator that imports
it (corpus.py in the private monorepo) are deliberately excluded from this
package. A regression test (tests/test_barbican_smoke.py) enforces the
invariant that import barbican succeeds standalone and pulls in no
oubliette_shield module.
Real-world validation
The loader reads a documented intermediate JSONL — one object per line:
{"text": "...", "author_id": "...", "timestamp": "ISO-8601", "label": "io|control", "campaign_id": "... | null"}
label maps io→synthetic, control→authentic; campaign_id is kept for
io posts (the coordinated group) and forced to null for controls. Producing
that JSONL from a specific licensed release is a one-time local preprocessing
step you run — no source dataset is bundled or committed. When the JSONL is
absent, run_realworld_split returns status="SKIPPED"; the real-world number
is never fabricated.
Suggested held-out corpus: ISI/USC "Labeled Datasets for Research on Information Operations" (Zenodo 10.5281/zenodo.14141549) — labeled IO posts plus matched organic control, per-post schema. Its license is CC BY-NC-ND 4.0 (NonCommercial): usable for research/validation, not for bundling into a shipped product.
Development
pip install -e ".[dev]"
pytest
ruff check src/
ruff format --check src/
mypy --strict src/
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oubliette_barbican-0.1.0.tar.gz.
File metadata
- Download URL: oubliette_barbican-0.1.0.tar.gz
- Upload date:
- Size: 33.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65d1f264566fd567d2e52dfe11c32fc5e85772816fa3e68b55288ae78be69419
|
|
| MD5 |
b85e6139ea2035aec3d0303e43578df1
|
|
| BLAKE2b-256 |
57107ec85161ff9b52708c07033dea6d56d14961baa6fe5b6f5c256603cb5a83
|
File details
Details for the file oubliette_barbican-0.1.0-py3-none-any.whl.
File metadata
- Download URL: oubliette_barbican-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
77c7b01ee60044ab0ef792500167bc57fc99f3c4754ac7e26b2feabb296e44c1
|
|
| MD5 |
9e1f1702813bab5fd242aa55a296fc30
|
|
| BLAKE2b-256 |
7739df1cc234c3bb3e31a7bfd1ac3d40e4d9bc1b5bb25bf07cea0c915280bcb6
|