FactoryWager Wiki

Ops loop throughput (Identity → Gate → Settle → Channels)

Claim: ops-loop-throughput — closed-loop automation with measurable baseline/post lift (target ≥60% on loopCompletionRate).

Piece Path
Metrics lib/operations/ops-loop-metrics.ts · buildOpsSummary().loop
Settlement lib/operations/play-settlement.ts · ops-settle-batch.ts · tools/ops-settle.ts · runOpsSettleCycle in snapshot-cron.ts
Sync consumer cron runOpsSyncCycle · tools/ops-sync-consumer.ts
Durable projector lib/channels/outbox.ts · outbox-prod-opts.ts (projectorBackend)
Outbox requeue tools/ops-outbox-requeue.ts · bun run ops:outbox:requeue
Portal panel Ops loop on /portal/ops/ · <notification-center> topics identity + plays
Reports reports/ops-loop-baseline.json · reports/ops-loop-post.json · reports/ops-loop-post-live.json
Live proof caller tools/ops-loop-live-proof.ts · bun run ops:loop:live
Legacy gate backfill lib/operations/ops-loop-gate-backfill.ts · tools/ops-loop-gate-backfill.ts

Metric definitions

Field Definition
dispatched Rows in play_distribution (recipient fan-out).
gatedAllow / gatedAdjust / gatedDeny / gatedDefer Gate counts from play_gate_decisions (defer split from deny).
reserved Same as dispatched (successful reserve + enqueue).
settled plays.result closed (not pending).
settledViaFullLoop Distribution rows with gate allow/adjust on same node + play.settled outbox row sent.
manualStepsPerCycle Unsettled distributed plays + pending outbox + failed outbox rows.
loopCompletionRate settledViaFullLoop / dispatched — row-aligned (same unit as dispatched). Attribution only — not R2 durability.
loopCompletionRateByPlay settledPlaysViaFullLoop / distinctPlaysDispatched — play-level (not fan-out inflated).
capitalEfficiencyProxy / limitEfficiencyProxy / processReturnProxy CE / LE / RP capital-return proxies (see module header).

60% claim: (post.loopCompletionRate - baseline.loopCompletionRate) / baseline.loopCompletionRate ≥ 0.6 when baseline rate > 0; when baseline ≈ 0, post rate must be ≥ 0.6 absolute.

Row-aligned numerator (2026-07-24): settledViaFullLoop counts distribution rows with full gate + settle attribution (not COUNT(DISTINCT play_id)). settlePlay fans out play.settled and releases per-node liquidity (stake-proportional PnL/cuts). Legacy DBs still need one ops:loop:backfill pass for pre-fan-out history.

Attribution ≠ durability: loopCompletionRate is SQLite outbox sent status. Drain requires an explicit plane: bun run ops:outbox:requeue -- --drain --r2 (Pages registry bucket via resolveChannelR2BridgeConfig) or --drain --memory (attribution-only). Ops-summary exposes projectorBackend, projectorBucket, and projectorDurable (false when outboxFailed > 0). Do not point channel/outbox at the bench cascade (R2_BUCKET_NAME=bun-secrets).

Developer velocity is tracked separately via bun run harness:status → reports/harness-gate-timing.json gate sum.

Commands

# Capture baseline (before hardening) / post (after)
bun tools/ops-loop-report.ts --out reports/ops-loop-baseline.json
bun tools/ops-loop-report.ts --out reports/ops-loop-post.json --fixture --compare reports/ops-loop-baseline.json

# Live DB proof (one gated play → settle → outbox) — or: bun run ops:loop:live
bun tools/ops-loop-live-proof.ts
bun tools/ops-loop-report.ts --out reports/ops-loop-post-live.json --compare reports/ops-loop-baseline.json

# Production callers
bun run ops:settle -- --help
bun run ops:sync
bun run ops:snapshot:once   # also runs sync + settle ticks

# Cron daemon (snapshot + sync + settle UTC schedules)
bun run ops:snapshot:cron

# Legacy gate + settle outbox backfill (live DB repair — not re-dispatch)
bun run ops:loop:backfill -- --dry-run
bun run ops:loop:backfill
bun tools/ops-loop-report.ts --out reports/ops-loop-post-live.json --compare reports/ops-loop-baseline.json

# Requeue failed outbox → pending, optionally drain (local memory projectors; add --r2 for durable)
bun run ops:outbox:requeue -- --dry-run
bun run ops:outbox:requeue -- --drain
bun run ops:outbox:requeue -- --drain --r2

# Tests
bun test tests/ops-loop-hardening.test.ts tests/ops-summary.test.ts

Legacy gate backfill semantics

Dispatches that predate play_gate_decisions (and settled plays missing play.settled outbox rows) under-count settledViaFullLoop. The backfill attributes history; it does not re-dispatch, re-settle, or mutate plays.result / liquidity.

Step Action
Gate For each play_distribution row with no matching play_gate_decisions, insert allowed=1, action='allow', adjusted_stake from stake_actual or plays.stake_recommended, reason Legacy gate attribution (backfill — not re-dispatch).
Settle outbox For each settled play (result != pending) and distribution node lacking a sent play.settled row (idempotency_key settle:{playId}:{nodeId}), enqueue via enqueueSettlementChannelEvent then drain with processChannelOutbox (deliver: false by default).

Live DB after backfill: With row-aligned metrics, fully attributed history reports loopCompletionRate ≈ fullLoopRows / dispatched. Run bun tools/ops-loop-report.ts --out reports/ops-loop-post-live.json after backfill to capture the achieved rate.

Closed loop (target)

Pages onboard → R2 ops-sync → runOpsSyncCycle → bindPartnerProfile
  → publishAndDispatch → gate → reservePlayWithRetry → ops_channel_outbox
  → runOpsSettleCycle / ops:settle → play.settled → R2 projector
  → /portal/ops/ notification-center

Auth note

/api/channels/events on serve-public requires REGISTRY_SECRET bearer outside SERVE_PUBLIC_DEV=1 / NODE_ENV=development.

Post-audit closure (e26651c9e → e2f85a6ec)

Finding Severity Status Evidence
settlePlay no production caller Critical Closed tools/ops-settle.ts · runOpsSettleCycle
Stake from stake_recommended not stake_actual Critical Closed play-settlement.ts · tests/ops-loop-hardening.test.ts
Outbox R2 projector = memory only Critical Closed resolveProductionOutboxOpts · flushOutbox · cron
ops:sync manual only Critical Closed registerOpsSyncCron · snapshot --once tick
OpSec TOML not enforced Critical Closed evaluateForNode + metadata_json opsec fields
No <notification-center> on /portal/ops/ Critical Closed public/portal/ops/index.html
/api/channels/events unauthenticated Critical Closed serve-public.ts SERVE_DEVELOPMENT gate
bindPartnerProfile race Medium Closed ON CONFLICT DO UPDATE upsert
reservePlay no version retry Medium Closed reservePlayWithRetry
Book coverage unused at dispatch Medium Closed play.bookSlug → checkCoverage
Demo snapshot vs live DB Medium Closed Row-aligned LCR + settle fan-out + backfill: live loopCompletionRate can reach ≥65% when all distribution rows have gate + play.settled sent; drain pending via ops:settle / ops:outbox:requeue --drain
Topic taxonomy drift Medium Closed Tenant doc lists identity · plays · ops-sync

Production R2 projection is best-effort: when config/r2-env credentials are absent, projectors fall back to in-process memory (dev parity only).