Skip to content

Fresh-mark gate for paper settlement, CI on PRs, README refresh - #1

Merged
devsolux merged 9 commits into
mainfrom
claude/nifty-shamir-966338
Oct 16, 2026
Merged

devsolux merged 9 commits into
mainfrom
claude/nifty-shamir-966338

Conversation

@devsolux

Copy link
Copy Markdown
Owner

Summary

Paper settlement refuses stale marks (src/coinfly/worker/paper_engine.py)

  • DATED_FUTURE expiry and PAPER_FUNDING are booked only at a fresh mark: a two-sided book observed no more than the matcher's book_max_age (5 s, the bound a resting paper order needs to fill) before the settlement check.
  • Without a fresh mark the event stays due and is retried on every tick. It is reported once per reason in automation.status.settlements as PAPER_EXPIRY_DEFERRED / PAPER_FUNDING_DEFERRED (..._MARK_STALE or ..._MARK_MISSING). No substitute mark is ever used.
  • A crossed funding boundary is charged for the quantity held at the crossing. If that position closed before any fresh mark priced it, the margin book cannot hold the payment, so it is reported as PAPER_FUNDING_NOT_CHARGED (PAPER_FUNDING_POSITION_CLOSED).
  • Commit rollback also restores the due and reported state. The subscription keep list already carries every held instrument and was not the cause, so it is unchanged.
  • The rule is documented in docs/runbooks/paper-startup.md section 4.

CI runs on pushes and pull requests (.github/workflows/ci.yml)

  • Previously manual dispatch only, so nothing was ever checked remotely. It now runs on pushes to main, pull requests and on demand, and superseded PR runs are cancelled.
  • actions/checkout v5 → v7 (no persisted credentials) and astral-sh/setup-uv v7 → v10 (default cache policy).
  • uv 0.12.15 → 0.13.1 and Python 3.14.7 → 3.14.8, pinned once.
  • The checks themselves are unchanged. Docs and the Makefile no longer call the workflow manual-only.

README: restructured into a concise landing page (badges, warning, highlights, pipeline diagram, status, getting started, development, docs). Claims are carried over; detailed caveats stay in docs/.

Tests

  • New regression tests in tests/worker/test_paper_settlement.py. Each failed before its fix because the stale mark was booked:
    • expiry stale → missing → fresh at exactly the age limit
    • the expiry deferral shows up in automation status
    • funding stale → missing → charged at the next fresh mark
    • funding due on a closed position is reported, not charged
  • Existing expiry and funding tests now feed a fresh book at the settlement time.
  • Locally: 1288 passed, 14 skipped; ruff and mypy src/coinfly clean. The one local failure is tests/test_macos_build_profile.py, which requires the interpreter to be <checkout>/.venv. That doesn't hold in a worktree using the main checkout's venv; the test passes in the main checkout.
  • This is the first remote CI run of the 3-OS matrix: Windows and Linux have never been run before.

Paper-only: no live credentials, orders or transfers involved.

🤖 Generated with Claude Code

devsolux and others added 9 commits October 16, 2026 19:04
A DATED_FUTURE expiry close books fee-free realized P&L into lots, journal,
drawdown and strategy capital at the last mid. That mid had no age check, so
a book left behind by a contract that stopped being quoted (or a feed/worker
outage across expiry) became permanent realized P&L.

settle() now books the expiry only at a two-sided book observed no more than
the matcher's book_max_age (the bound a resting paper order needs to fill)
before now. Otherwise nothing is booked, the event stays unsettled and is
retried every call, and a PAPER_EXPIRY_DEFERRED record with reason
PAPER_EXPIRY_MARK_STALE or PAPER_EXPIRY_MARK_MISSING is returned once per
reason, so it shows in automation.status.settlements. No mark is substituted;
commit rollback also restores the reported-deferral state.

The subscription keep list already carries every held instrument first and
is not re-planned mid-run, so it is not the cause and is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synthetic PAPER_FUNDING was charged against the last mid with no age check,
so a book left behind by a dropped feed set a permanent collateral debit or
credit. Each funding boundary the clock crosses is now recorded as due, with
the quantity held at the crossing, and charged only at a fresh mark (a
two-sided book no older than the matcher's book_max_age), the same rule as
expiry settlement. Until then it stays due across calls and is reported once
per reason as PAPER_FUNDING_DEFERRED (PAPER_FUNDING_MARK_STALE / _MISSING).
A due boundary whose position closed before a fresh mark priced it cannot be
charged by the margin book; it is dropped and reported as
PAPER_FUNDING_NOT_CHARGED (PAPER_FUNDING_POSITION_CLOSED).

Expiry and funding now share one mark helper and one deferral report.
Funding events also carry the quantity and mark they were charged at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The validation workflow only ran on manual dispatch, so no change was ever
checked remotely. It now runs on pushes to main, on pull requests and on
demand, and superseded pull-request runs are cancelled.

- actions/checkout v5 -> v7 without persisted credentials.
- astral-sh/setup-uv v7 -> v10; caching follows the action's safer default
  instead of being forced on.
- uv 0.12.15 -> 0.13.1 and Python 3.14.7 -> 3.14.8 (the local toolchain),
  pinned once at workflow level.
- Named jobs and steps; the checks themselves are unchanged.

Docs and the Makefile no longer describe the workflow as manual-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Header with icon and CI/license/Python/platform badges, a warning callout,
highlights, a pipeline diagram with a stage table, a status table, getting
started, development and CI, and documentation links. Claims are carried
over from the previous README; detailed caveats stay in docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup-uv stopped publishing floating major tags in v8, so `setup-uv@v10`
could not be resolved and every job failed at set-up. Pin it and checkout
to the commit SHAs of their exact releases (v10.3.0, v7.0.1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows job resolved Python 3.14.8 to the runner's preinstalled
python.org build, which links SQLite 3.50.4, below the 3.51.3 minimum, so
the SQLite gate failed. uv prefers an existing system interpreter over a
download; UV_MANAGED_PYTHON makes every runner use uv's managed CPython,
which bundles a current SQLite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mypy checks Windows runners as sys.platform == "win32". With
warn_unreachable, the POSIX code after an `if sys.platform == "win32": return`
block was reported unreachable, and resource.getrusage does not exist in the
Windows stubs. Put the POSIX paths in explicit platform else-branches (which
mypy treats as platform checks) in native_user_home, development_socket and
_max_rss_bytes. Behavior is unchanged; mypy --platform win32/linux/darwin
all pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the first Windows CI run:
- Check text out with LF everywhere (.gitattributes). A CRLF checkout on
  Windows changed the bytes behind pinned digests, so the schema and
  manifest identity checks failed. No committed content changes.
- Dataset preparation fsynced graph files through read-only handles;
  Windows flushes only writable ones (EBADF). Open them r+b.
- The source archive took launcher execute bits from the filesystem, which
  Windows does not record; shell launchers are now always 0o755.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The network guard now allows loopback: Windows asyncio wakes its event
  loop through a loopback socket pair. Internet addresses and DNS stay
  blocked.
- Worker RPC fixtures identify the caller with tests.support.local_peer()
  (coinfly.ipc.auth.current_uid) instead of POSIX-only os.getuid/getgid.
- Read sources as UTF-8, use a platform socketpair, and expect
  coinfly-worker.exe for the packaged worker on Windows.
- Skip on Windows only what is inherently POSIX, each with its reason:
  Unix-domain socket IPC tests (Windows uses named pipes) and the macOS
  release tooling's rename-over-empty-directory test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@devsolux
devsolux merged commit 7f4bdd1 into main Oct 16, 2026
4 checks passed
@devsolux
devsolux deleted the claude/nifty-shamir-966338 branch October 16, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant