Implements Workstream B of the character-depth-ghost-log spec:
ritual_start/ritual_step/judgment WS handlers, the pure judgment.py
logic module, and the User.favor / Entity.at_peace columns + migration.
- app/judgment.py: pure ritual success roll (base 65%, floored at 30%,
driven by an entity's power+deceptiveness difficulty), the "stuck
spirit" cross_over rule (alignment >= 0.5 and volatility > 0.6, ~20%
of entities), judgment correctness/favor-delta/essence-delta/
consequence resolution for all four verdicts, favor clamping, the
favor-to-trait-roll bias applied at mint time, and tell-line
generation (opaque behavioral flavor text, never a raw stat).
- app/ws.py: wires ritual_start/ritual_step/judgment frames, emits
ritual_complete/tell/judgment_result/item_drop per the spec's
Contract; traits are added to serialize_entity for internal
server-side use but stripped from the outbound `entity` frame via a
new _public_entity helper so hidden ground truth never reaches the
client outside ritual_complete; _summon excludes at-peace entities
from signature re-contact and mints a fresh (salted-signature) entity
instead; new entities' traits are nudged by the discovering user's
favor before being persisted.
- models/user.py, models/entity.py, main.py: User.favor and
Entity.at_peace columns plus their idempotent ADD COLUMN IF NOT
EXISTS migration lines in lifespan, alongside the existing ones.
- tests/test_judgment.py, tests/test_ws_ritual_judgment.py: 56 new
tests covering the ritual/judgment correctness matrix, favor
clamping/bias, essence crediting, at_peace persistence + re-contact,
and the entity-frame trait leak guard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolved conflict in main.py: combined both workstreams' router imports
and registrations (device_router from G, inventory_router from C).
143/143 backend tests pass after cleaning stray pollution from an
earlier parallel workstream run against the shared test DB.
Implements the backend REST surface and WS wiring for
docs/superpowers/specs/2026-07-23-character-depth-ghost-log-design.md's
Workstream C:
- New models: UnlockRecord (unlocks), InventoryItem (inventory_items),
Sigil (sigils) — brand-new tables, picked up by main.py's existing
create_all.
- New app/inventory.py: unlock price table, item drop table/odds,
essence economy constants, sigil design validation, and an atomic
(row-locked) purchase_unlock() that guards against double-spend races.
- New app/routes/inventory.py: GET unlocks/items/sigils, POST sigils
(validates the placeholder {points, rune} shape, points capped at 12),
POST unlocks/{unlock_key} (402 on insufficient essence, 404 on unknown
key, idempotent re-buy).
- GET /auth/me now includes unlocks: list[str] and essence: int.
- ws.py: wires essence trickle + item_drop rolls into the one trigger
point that exists in this worktree today (_handle_summon, covering
every successful summon plus high-rarity summons); the other two
contract trigger points (correct judgment, successful ritual) belong
to Workstream B's not-yet-landed ritual/judgment WS handlers, which
should call app.inventory's same helpers once they land.
- User.essence: int added (Workstream B owns this column per the spec;
added here per orchestrator instruction so this workstream is
independently testable — merge controller reconciles the duplicate
edit).
Also fast-forwarded this worktree's branch onto master (it had fallen
behind several commits) so the files this workstream depends on
(shop.py, ws.py, entities.py, etc.) were actually present to build
against.
Tests: 109 passed (drop-roll statistical sanity with seeded RNG,
inventory/sigil CRUD, purchase success/insufficient-funds/idempotency/
unknown-key paths, /auth/me shape, ws summon-trickle and item-drop
wiring).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements the backend half of the ESP32-P4 sensor node spec's pairing,
ingestion, and live-broadcast contract:
- New Device model (backend/app/models/device.py): id, user_id FK, name,
token_hash (unique+indexed), created_at, last_seen_at. Reuses
generate_session_token()/hash_token() from auth_session.py verbatim for
the one-time raw pairing token / stored hash.
- POST /api/device, GET /api/device (session-cookie authenticated REST
pairing endpoints) and POST /api/device/telemetry (device bearer-token
authenticated ingestion, per-device rate limited, 16KB body cap, 64
reading cap, strict shape validation — never a 500 on garbage input) in
backend/app/routes/device.py.
- /ws/device-feed live dashboard WS (qm_session cookie authenticated),
fanning out ingested readings to the owning user's connected dashboard
sockets via an in-process dict[user_id, connections] registry, each with
its own send-queue + single sender task (mirrors app.ws's
SeanceState/_sender convention).
- last_seen_at updates on every successful ingestion.
- _process_reading(device, reading) left as an explicit no-op handoff point
for Workstream K's summon-pipeline integration.
Backend suite: 102 passed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Entity.traits (alignment/power/volatility/deceptiveness, 0.0-1.0 each) is
rolled once at mint time in entities.py, seeded from the entity's
signature via random.Random(f"traits:{signature}") — a separate rng
namespace from normalize_profile's existing "norm:" rng, and never fed
into mint_prompt, so persona text stays fully decoupled from ground
truth. normalize_profile now includes "traits" in its returned dict;
fallback_profile inherits it for free since it already delegates to
normalize_profile.
Adds the new JSONB column to the Entity model (default {}) and the
idempotent `ALTER TABLE entities ADD COLUMN IF NOT EXISTS traits ...`
migration line to main.py's lifespan, per the live-Postgres migration
convention this spec introduces (no Alembic in this repo).
Tests cover trait value ranges, signature-determinism, and
persona/trait independence (same persona template pairs with a wide
spread of alignment rolls across signatures), plus a regression check
that mint_prompt's signature never grows a traits parameter.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>