pmquant 0.4.2__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. pmquant-0.4.4/.githooks/pre-push +10 -0
  2. pmquant-0.4.4/.github/dependabot.yml +13 -0
  3. {pmquant-0.4.2 → pmquant-0.4.4}/.github/workflows/canary.yml +8 -4
  4. pmquant-0.4.4/.github/workflows/codeql.yml +21 -0
  5. pmquant-0.4.4/.github/workflows/mcp-publish.yml +35 -0
  6. {pmquant-0.4.2 → pmquant-0.4.4}/.github/workflows/publish.yml +6 -3
  7. pmquant-0.4.4/.github/workflows/scorecard.yml +28 -0
  8. {pmquant-0.4.2 → pmquant-0.4.4}/.github/workflows/test.yml +5 -2
  9. {pmquant-0.4.2 → pmquant-0.4.4}/.gitignore +1 -0
  10. pmquant-0.4.4/.hypothesis/.gitignore +9 -0
  11. pmquant-0.4.4/.hypothesis/constants/07a2a0eac57d1dd0 +4 -0
  12. pmquant-0.4.4/.hypothesis/constants/3687cdf4cf8f7af3 +4 -0
  13. pmquant-0.4.4/.hypothesis/constants/6c9ffb0a1efc27b6 +4 -0
  14. pmquant-0.4.4/.hypothesis/constants/855d9c2e5b4693f1 +4 -0
  15. pmquant-0.4.4/.hypothesis/constants/ef909bf87e6ac33f +4 -0
  16. pmquant-0.4.4/.hypothesis/unicode_data/15.0.0/charmap.json.gz +0 -0
  17. pmquant-0.4.4/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz +0 -0
  18. {pmquant-0.4.2 → pmquant-0.4.4}/CHANGELOG.md +50 -0
  19. pmquant-0.4.4/CLAUDE.md +80 -0
  20. pmquant-0.4.4/CONTRIBUTING.md +80 -0
  21. {pmquant-0.4.2 → pmquant-0.4.4}/PKG-INFO +70 -12
  22. {pmquant-0.4.2 → pmquant-0.4.4}/README.md +68 -11
  23. {pmquant-0.4.2 → pmquant-0.4.4}/SECURITY.md +18 -6
  24. pmquant-0.4.4/bot-template/bot.py +354 -0
  25. {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/dash/bot_dash.py +19 -7
  26. {pmquant-0.4.2 → pmquant-0.4.4}/llms.txt +6 -0
  27. {pmquant-0.4.2 → pmquant-0.4.4}/pyproject.toml +2 -2
  28. {pmquant-0.4.2 → pmquant-0.4.4}/server.json +3 -3
  29. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/__init__.py +1 -1
  30. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/doctor.py +88 -58
  31. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/executor.py +41 -12
  32. {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_canary_live.py +34 -0
  33. {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_executor.py +53 -0
  34. pmquant-0.4.4/tests/test_fill_fuzz.py +118 -0
  35. pmquant-0.4.4/tests/test_template_engine.py +398 -0
  36. pmquant-0.4.2/CLAUDE.md +0 -52
  37. pmquant-0.4.2/CONTRIBUTING.md +0 -52
  38. pmquant-0.4.2/bot-template/bot.py +0 -305
  39. pmquant-0.4.2/tests/test_template_engine.py +0 -82
  40. {pmquant-0.4.2 → pmquant-0.4.4}/AGENTS.md +0 -0
  41. {pmquant-0.4.2 → pmquant-0.4.4}/LICENSE +0 -0
  42. {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/README.md +0 -0
  43. {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/dash/dash.html +0 -0
  44. {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/pmq-bot.service +0 -0
  45. {pmquant-0.4.2 → pmquant-0.4.4}/bot-template/strategy.py +0 -0
  46. {pmquant-0.4.2 → pmquant-0.4.4}/docs/assets/pmq-doctor.svg +0 -0
  47. {pmquant-0.4.2 → pmquant-0.4.4}/docs/recipes.md +0 -0
  48. {pmquant-0.4.2 → pmquant-0.4.4}/docs/rounding-study.md +0 -0
  49. {pmquant-0.4.2 → pmquant-0.4.4}/docs/war-story.md +0 -0
  50. {pmquant-0.4.2 → pmquant-0.4.4}/examples/fak_buy_guarded.py +0 -0
  51. {pmquant-0.4.2 → pmquant-0.4.4}/examples/read_market.py +0 -0
  52. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/data.py +0 -0
  53. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/exceptions.py +0 -0
  54. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/mcp.py +0 -0
  55. {pmquant-0.4.2 → pmquant-0.4.4}/src/pmq/py.typed +0 -0
  56. {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_data.py +0 -0
  57. {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_doctor.py +0 -0
  58. {pmquant-0.4.2 → pmquant-0.4.4}/tests/test_mcp.py +0 -0
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+ # pmq pre-push guard: refuse to push red. CI is the backstop; this catches
3
+ # it before the remote does. Bypass knowingly with --no-verify.
4
+ set -e
5
+ cd "$(git rev-parse --show-toplevel)"
6
+ PY=./.venv/bin/python
7
+ [ -x "$PY" ] || PY=python3
8
+ $PY -m ruff check . || { echo "pre-push: ruff rouge"; exit 1; }
9
+ $PY -m mypy || { echo "pre-push: mypy rouge"; exit 1; }
10
+ $PY -m pytest -q || { echo "pre-push: tests rouges"; exit 1; }
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 5
9
+ - package-ecosystem: github-actions
10
+ directory: /
11
+ schedule:
12
+ interval: weekly
13
+ day: monday
@@ -12,8 +12,8 @@ jobs:
12
12
  canary:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v5
16
- - uses: actions/setup-python@v6
15
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
17
17
  with:
18
18
  python-version: "3.12"
19
19
  - run: pip install -e ".[dev]"
@@ -21,6 +21,10 @@ jobs:
21
21
  env:
22
22
  PMQ_CANARY: "1"
23
23
  run: pytest tests/test_canary_live.py -q
24
+ - name: Dependency vulnerability audit
25
+ run: |
26
+ pip install pip-audit
27
+ pip-audit --skip-editable
24
28
  - name: Open an issue if Polymarket drifted
25
29
  if: failure()
26
30
  env:
@@ -29,6 +33,6 @@ jobs:
29
33
  existing=$(gh issue list --label canary --state open --json number --jq length)
30
34
  if [ "$existing" = "0" ]; then
31
35
  gh issue create --label canary \
32
- --title "Canary: Polymarket surface drifted ($(date -u +%F))" \
33
- --body "The weekly live canary failed: an endpoint shape or the installed py-clob-client-v2 surface no longer matches what pmq was verified against. See the failed run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
36
+ --title "Canary failed: endpoint drift or vulnerable dependency ($(date -u +%F))" \
37
+ --body "The weekly canary failed: an endpoint shape or the installed py-clob-client-v2 surface no longer matches what pmq was verified against, the egress allowlist saw a foreign host, or pip-audit found a vulnerable dependency. See the failed run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
34
38
  fi
@@ -0,0 +1,21 @@
1
+ name: codeql
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ schedule:
7
+ - cron: "41 7 * * 1"
8
+
9
+ permissions: read-all
10
+
11
+ jobs:
12
+ analyze:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ security-events: write
16
+ steps:
17
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
18
+ - uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
19
+ with:
20
+ languages: python
21
+ - uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
@@ -0,0 +1,35 @@
1
+ name: mcp-publish
2
+ on:
3
+ release:
4
+ types: [published]
5
+ workflow_dispatch:
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write # OIDC login to the MCP registry
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+ - name: Fetch mcp-publisher
17
+ env:
18
+ GH_TOKEN: ${{ github.token }}
19
+ run: |
20
+ gh release download --repo modelcontextprotocol/registry \
21
+ --pattern "mcp-publisher_*linux_amd64.tar.gz" --output mp.tgz
22
+ tar -xzf mp.tgz
23
+ ./mcp-publisher --version
24
+ - name: Wait for the version to exist on PyPI
25
+ run: |
26
+ V=$(python3 -c "import json; print(json.load(open('server.json'))['version'])")
27
+ for i in $(seq 1 20); do
28
+ curl -s "https://pypi.org/pypi/pmquant/$V/json" | grep -q '"version"' && exit 0
29
+ echo "PyPI does not serve $V yet ($i/20)"; sleep 30
30
+ done
31
+ echo "giving up: registry would reject an unpublished version"; exit 1
32
+ - name: Publish server.json to the MCP registry
33
+ run: |
34
+ ./mcp-publisher login github-oidc
35
+ ./mcp-publisher publish
@@ -5,16 +5,19 @@ on:
5
5
  types: [published]
6
6
  workflow_dispatch:
7
7
 
8
+ permissions:
9
+ contents: read
10
+
8
11
  jobs:
9
12
  publish:
10
13
  runs-on: ubuntu-latest
11
14
  permissions:
12
15
  id-token: write
13
16
  steps:
14
- - uses: actions/checkout@v5
17
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
15
18
 
16
19
  - name: Set up Python
17
- uses: actions/setup-python@v6
20
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
18
21
  with:
19
22
  python-version: "3.12"
20
23
 
@@ -41,4 +44,4 @@ jobs:
41
44
  python -m build
42
45
 
43
46
  - name: Publish to PyPI
44
- uses: pypa/gh-action-pypi-publish@release/v1
47
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
@@ -0,0 +1,28 @@
1
+ name: scorecard
2
+ on:
3
+ schedule:
4
+ - cron: "23 7 * * 1" # weekly, monday 07:23 UTC
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ permissions: read-all
10
+
11
+ jobs:
12
+ analysis:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ security-events: write # SARIF upload
16
+ id-token: write # signed publication to scorecard.dev
17
+ steps:
18
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ with:
20
+ persist-credentials: false
21
+ - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
22
+ with:
23
+ results_file: results.sarif
24
+ results_format: sarif
25
+ publish_results: true
26
+ - uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
27
+ with:
28
+ sarif_file: results.sarif
@@ -2,6 +2,9 @@ name: tests
2
2
  on:
3
3
  push:
4
4
  pull_request:
5
+
6
+ permissions:
7
+ contents: read
5
8
  jobs:
6
9
  test:
7
10
  runs-on: ubuntu-latest
@@ -9,8 +12,8 @@ jobs:
9
12
  matrix:
10
13
  python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
11
14
  steps:
12
- - uses: actions/checkout@v5
13
- - uses: actions/setup-python@v6
15
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
14
17
  with:
15
18
  python-version: ${{ matrix.python-version }}
16
19
  - run: pip install -e ".[dev]"
@@ -7,3 +7,4 @@ build/
7
7
  .pytest_cache/
8
8
  .env
9
9
  .coverage
10
+ .pyscn/
@@ -0,0 +1,9 @@
1
+ # This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
2
+ # .hypothesis by default, because we generally recommend that .hypothesis not be checked
3
+ # into version control.
4
+ #
5
+ # If you *would* like to check .hypothesis into version control, you should delete this
6
+ # file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
7
+ # if it does, that's a bug - please report it!)
8
+
9
+ *
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/executor.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ [0.0, 1000000.0, 137, 300, 400, 500, '0', '0.01', '0x[0-9a-fA-F]{64}', '; ', 'AssetType', 'BUY', 'FAILED', 'FAK', 'MAKER', 'MarketOrderArgs', 'MarketOrderArgsV2', 'OpenOrderParams', 'OrderArgs', 'OrderArgsV2', 'OrderType', 'POLY_BUILDER_CODE', 'POLY_FUNDER', 'POLY_PRIVATE_KEY', 'POLY_SIG_TYPE', 'SELL', 'TradeParams', '_pmq_taker4', 'amount', 'balance', 'builder_code', 'cancel_market_orders', 'crypto', 'error_msg', 'fd', 'get_open_orders', 'get_trades', 'makingAmount', 'off', 'on', 'orderID', 'order_args', 'order_type', 'params', 'payload', 'pmq', 'price', 'r', 'side', 'size', 'status', 'status_code', 'success', 'takingAmount', 'token_id', 'trader_side']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/__init__.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ ['0.4.4', 'DEFAULT_BUILDER_CODE', 'FEE_RATES', 'Fill', 'OrderUncertain', 'PmqError', 'PolymarketExecutor', '__version__', 'band_ask_depth_usd', 'best_bid_ask', 'book_inferred_winner', 'book_meta', 'event_markets', 'fee', 'get_book', 'get_market', 'get_tape', 'http_get_json', 'parse_market', 'positions', 'resolved_winner']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/exceptions.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/data.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ [0.0, 0.03, 0.04, 0.05, 0.07, 0.9, 0.99, 1.0, 1.5, 200, '%Y-%m-%dT%H:%M:%SZ', 'Mozilla/5.0', 'User-Agent', 'a', 'asks', 'b', 'bids', 'clobTokenIds', 'conditionId', 'crypto', 'culture', 'economics', 'endDate', 'endDateIso', 'finance', 'geopolitics', 'idx_a', 'last_trade_price', 'markets', 'mentions', 'min_order_size', 'neg_risk', 'outcomePrices', 'outcome_a', 'outcome_b', 'outcome_prices_raw', 'outcomes', 'politics', 'price', 'size', 'slug', 'sports', 'tech', 'tick_size', 'timestamp', 'weather']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/doctor.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ [1000000.0, 120, 160, 400, '\nverdict:', ', ', '--market', '0', '0x', '0x70a08231', '0x8da5cb5b', '2.0', 'CLOB auth/collateral', 'Content-Type', 'FAK', 'Mozilla/5.0', 'OrderType.FAK', 'POLY_FUNDER', 'POLY_PRIVATE_KEY', 'POLY_SIG_TYPE', 'User-Agent', '[!!]', '[??]', '[ok]', '__main__', 'application/json', 'data', 'drifted: ', 'error', 'eth_call', 'eth_getCode', 'funder', 'id', 'jsonrpc', 'latest', 'method', 'min_order_size', 'params', 'result', 'to', 'token_a']
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.4 (2026-07-04)
4
+
5
+ * Harden: json.loads accepts NaN and Infinity, so a drifted or hostile
6
+ exchange response could book non-finite or negative matched amounts.
7
+ `_parse_fill` now zeroes anything non-finite or negative (fail closed),
8
+ and a hypothesis fuzz suite (four property groups, hundreds of generated
9
+ adversarial responses per run) pins the whole fill contract: market and
10
+ limit paths book only confirmed finite amounts, the 4xx/uncertain
11
+ exception partition is total, every transport exception surfaces as
12
+ OrderUncertain.
13
+ * Security surface: CodeQL workflow (its first scan caught and we fixed a
14
+ host-boundary bypass in the egress allowlist), Scorecard alert triage
15
+ with written dismissal reasons, top-level permissions on the publish
16
+ workflow, direct private-advisory link in SECURITY.md, Dependabot
17
+ vulnerability alerts enabled. Listed in the official MCP registry as
18
+ io.github.crp4222/pmq (publish rides releases via OIDC).
19
+
20
+ ## 0.4.3 (2026-07-04)
21
+
22
+ * Fix: py-clob-client-v2 1.0.2 reuses its limit-order rounding table for
23
+ MARKET orders, so on markets whose tick size is finer than 0.01 it signs
24
+ taker amounts with 5-6 decimals; the V2 exchange rejects those with
25
+ "invalid amounts ... taker amount a max of 4 decimals" and every FAK on
26
+ such a market fails. The executor now clamps the market-order path to 4
27
+ decimals at client level (round-down: the never-exceed-budget contract is
28
+ intact, the dust given up is under 0.0001 share per order). Found in
29
+ production: a fine-tick market rejected 10 consecutive buys and the
30
+ fail-closed halt fired exactly as designed; nothing was booked, nothing
31
+ was lost. Regression test pins maker 2dp / taker 4dp on both sides for
32
+ ticks 0.01, 0.001 and 0.0001.
33
+
34
+ * Trust batch: executable egress proof in the canary suite (records every
35
+ DNS resolution during a full session incl. a signed zero-fund order;
36
+ fails on any host outside polymarket.com; weekly CI prints the list in
37
+ public logs), a test pinning that the private key never reaches logs,
38
+ OpenSSF Scorecard workflow + badge, GitHub Actions pinned by commit SHA,
39
+ explicit workflow permissions, Dependabot (pip + actions), weekly
40
+ pip-audit wired into the canary alarm, README sections "Verify the
41
+ claims yourself" (egress, PEP 740 provenance, dependency watch) and
42
+ "Stability and maintenance" (pre-1.0 SemVer contract, the stated bar for
43
+ 1.0, bus-factor honesty, precisely scoped help-wanted).
44
+ * Quality pass driven by pyscn (CFG complexity, dead code, clones): the
45
+ template engine loop and pmq-doctor were split into single-purpose phase
46
+ functions (worst cyclomatic complexity 36 -> 10 across the repo, zero dead
47
+ code, behavior identical), and the previously untested template main loop
48
+ is now pinned by 17 end-to-end tests (fake clock, stubbed exchange):
49
+ paper fills at the real ask, budget headroom, halts, poisoning,
50
+ consecutive-failure exit, exchange-truth scoring. 113 tests total.
51
+ `pyscn check src/pmq bot-template` passes at default thresholds.
52
+
3
53
  ## 0.4.2 (2026-07-03)
4
54
 
5
55
  * Fix: `buy_fak`/`sell_fak` cent-rounding used `int(x * 100) / 100`, which
@@ -0,0 +1,80 @@
1
+ # pmq: engineering invariants for agents CONTRIBUTING to this repo
2
+
3
+ (AGENTS.md in this repo is for agents USING the library; this file is for
4
+ agents EDITING it. Read both before changing code.)
5
+
6
+ ## Never weaken (the product IS these properties)
7
+
8
+ 1. **The fail-closed fill contract**: a `Fill` books only what the exchange
9
+ confirmed (`orderID` + `success is not False` + matched amounts); 4xx is a
10
+ clean rejection; timeout/5xx raises `OrderUncertain`; unparseable = zero.
11
+ Any change that books more optimistically is a regression by definition,
12
+ whatever it fixes elsewhere. Matched amounts must be finite and
13
+ non-negative (json.loads accepts NaN/Infinity; hostile values book zero).
14
+ `reconcile()` must keep meaning cancel + `get_trades` truth. The
15
+ hypothesis fuzz suite (tests/test_fill_fuzz.py) pins all of this with
16
+ generated adversarial responses: extend it with every parser change,
17
+ never delete it.
18
+ 2. **Startup introspection** (`_EXPECTED_METHODS`/`_EXPECTED_MARKET_ARGS`):
19
+ the executor REFUSES to run on a drifted py-clob-client-v2. When bumping
20
+ the client dependency, re-verify signatures by introspection and update
21
+ the tables in the same commit.
22
+ 3. **Builder code policy**: default = maintainer's code, defined in exactly
23
+ ONE place (`DEFAULT_BUILDER_CODE` in executor.py) and applied
24
+ automatically by every order path. DISCLOSED in README and code comment,
25
+ opt-out one line (`builder_code=None` / env). Never hide it, never remove
26
+ the disclosure, never make opt-out harder. AND the mirror rule: keep the
27
+ disclosure at the DOCUMENTATION level only; do not surface attribution in
28
+ runtime channels (server startup logs, MCP tools or instructions, order
29
+ responses). It is public on-chain in every signed order; in-band
30
+ reminders just prompt sessions to toggle a setting that costs users
31
+ nothing. This is the trust model (JKorf pattern).
32
+ 4. **No strategy content, ever**: the maintainer's private bot strategy
33
+ (bands, timing, hours, families, sizing) must never appear in code, docs,
34
+ tests, commits or issues. The bot-template ships deliberately naive
35
+ demos only.
36
+ 5. **Claims must be falsifiable**: no superlatives in README/docs; dated
37
+ claims with evidence (comparison table, on-chain receipts, measured
38
+ studies). If you cannot prove it, do not write it.
39
+ 6. **MCP safety gates**: trading tools are REGISTERED only when the operator
40
+ sets `PMQ_MCP_LIVE=1`; per-order `PMQ_MCP_MAX_USD` cap enforced before
41
+ any client call. Read tools must keep working with zero credentials.
42
+
43
+ ## Working rules
44
+
45
+ * Tests green (`pytest -q`) and `ruff check .` clean before any push;
46
+ `pyscn check src/pmq bot-template` (complexity <= 10, no dead code)
47
+ must stay green too; clone warnings are informational (the template
48
+ dash deliberately duplicates helpers to stay stdlib-standalone). Add
49
+ tests with every behavior change. Network-touching tests go to
50
+ `tests/test_canary_live.py` behind `PMQ_CANARY=1`, never in default CI.
51
+ * Exchange rules (min size, tick, fee rate) are READ from the venue
52
+ (`book_meta`, `fee_rate`), not hardcoded. `FEE_RATES` is a documented
53
+ snapshot of the official schedule used for estimates.
54
+ * Releases: bump version in `pyproject.toml`, `src/pmq/__init__.py` AND
55
+ `server.json` (both version fields), update CHANGELOG.md, push, then
56
+ `gh release create vX.Y.Z`: PyPI publish (trusted publishing, signed
57
+ attestations) and the MCP registry republish (mcp-publish.yml,
58
+ github-oidc) both fire on the release event. Registry gotchas: the
59
+ server.json description caps at 100 characters, and the version must
60
+ exist on PyPI. PyPI name is `pmquant`, import name `pmq`: keep the
61
+ README line explaining it.
62
+ * CLAUDE.md and CONTRIBUTING.md are THE SAME FILE by contract: after
63
+ editing one, copy it over the other in the same commit (`cp CLAUDE.md
64
+ CONTRIBUTING.md`). Drift between them means an agent read stale rules.
65
+ * Local guard: `git config core.hooksPath .githooks` once per clone
66
+ enables the pre-push hook (ruff + mypy + pytest). CI is the backstop,
67
+ but the hook catches a broken push before it lands.
68
+ * GitHub Actions stay pinned by commit SHA (dependabot bumps them); new
69
+ workflows get an explicit least-privilege permissions block. The egress
70
+ test and pip-audit ride the weekly canary: never move them to default CI
71
+ (they need network) and never widen the egress allowlist beyond
72
+ polymarket.com without updating SECURITY.md and the README section.
73
+ * The weekly canary workflow is the drift alarm: if it opens an issue, the
74
+ fix starts by re-running the introspection against the new surface, not
75
+ by loosening the checks.
76
+ * Keep the library small and auditable (five modules): resist adding
77
+ dependencies; stdlib first. Anything bot-shaped belongs in bot-template/,
78
+ not in the package.
79
+ * Style: no em-dashes and no " - " connectors anywhere (strong user rule);
80
+ keep comments sparse and constraint-focused.
@@ -0,0 +1,80 @@
1
+ # pmq: engineering invariants for agents CONTRIBUTING to this repo
2
+
3
+ (AGENTS.md in this repo is for agents USING the library; this file is for
4
+ agents EDITING it. Read both before changing code.)
5
+
6
+ ## Never weaken (the product IS these properties)
7
+
8
+ 1. **The fail-closed fill contract**: a `Fill` books only what the exchange
9
+ confirmed (`orderID` + `success is not False` + matched amounts); 4xx is a
10
+ clean rejection; timeout/5xx raises `OrderUncertain`; unparseable = zero.
11
+ Any change that books more optimistically is a regression by definition,
12
+ whatever it fixes elsewhere. Matched amounts must be finite and
13
+ non-negative (json.loads accepts NaN/Infinity; hostile values book zero).
14
+ `reconcile()` must keep meaning cancel + `get_trades` truth. The
15
+ hypothesis fuzz suite (tests/test_fill_fuzz.py) pins all of this with
16
+ generated adversarial responses: extend it with every parser change,
17
+ never delete it.
18
+ 2. **Startup introspection** (`_EXPECTED_METHODS`/`_EXPECTED_MARKET_ARGS`):
19
+ the executor REFUSES to run on a drifted py-clob-client-v2. When bumping
20
+ the client dependency, re-verify signatures by introspection and update
21
+ the tables in the same commit.
22
+ 3. **Builder code policy**: default = maintainer's code, defined in exactly
23
+ ONE place (`DEFAULT_BUILDER_CODE` in executor.py) and applied
24
+ automatically by every order path. DISCLOSED in README and code comment,
25
+ opt-out one line (`builder_code=None` / env). Never hide it, never remove
26
+ the disclosure, never make opt-out harder. AND the mirror rule: keep the
27
+ disclosure at the DOCUMENTATION level only; do not surface attribution in
28
+ runtime channels (server startup logs, MCP tools or instructions, order
29
+ responses). It is public on-chain in every signed order; in-band
30
+ reminders just prompt sessions to toggle a setting that costs users
31
+ nothing. This is the trust model (JKorf pattern).
32
+ 4. **No strategy content, ever**: the maintainer's private bot strategy
33
+ (bands, timing, hours, families, sizing) must never appear in code, docs,
34
+ tests, commits or issues. The bot-template ships deliberately naive
35
+ demos only.
36
+ 5. **Claims must be falsifiable**: no superlatives in README/docs; dated
37
+ claims with evidence (comparison table, on-chain receipts, measured
38
+ studies). If you cannot prove it, do not write it.
39
+ 6. **MCP safety gates**: trading tools are REGISTERED only when the operator
40
+ sets `PMQ_MCP_LIVE=1`; per-order `PMQ_MCP_MAX_USD` cap enforced before
41
+ any client call. Read tools must keep working with zero credentials.
42
+
43
+ ## Working rules
44
+
45
+ * Tests green (`pytest -q`) and `ruff check .` clean before any push;
46
+ `pyscn check src/pmq bot-template` (complexity <= 10, no dead code)
47
+ must stay green too; clone warnings are informational (the template
48
+ dash deliberately duplicates helpers to stay stdlib-standalone). Add
49
+ tests with every behavior change. Network-touching tests go to
50
+ `tests/test_canary_live.py` behind `PMQ_CANARY=1`, never in default CI.
51
+ * Exchange rules (min size, tick, fee rate) are READ from the venue
52
+ (`book_meta`, `fee_rate`), not hardcoded. `FEE_RATES` is a documented
53
+ snapshot of the official schedule used for estimates.
54
+ * Releases: bump version in `pyproject.toml`, `src/pmq/__init__.py` AND
55
+ `server.json` (both version fields), update CHANGELOG.md, push, then
56
+ `gh release create vX.Y.Z`: PyPI publish (trusted publishing, signed
57
+ attestations) and the MCP registry republish (mcp-publish.yml,
58
+ github-oidc) both fire on the release event. Registry gotchas: the
59
+ server.json description caps at 100 characters, and the version must
60
+ exist on PyPI. PyPI name is `pmquant`, import name `pmq`: keep the
61
+ README line explaining it.
62
+ * CLAUDE.md and CONTRIBUTING.md are THE SAME FILE by contract: after
63
+ editing one, copy it over the other in the same commit (`cp CLAUDE.md
64
+ CONTRIBUTING.md`). Drift between them means an agent read stale rules.
65
+ * Local guard: `git config core.hooksPath .githooks` once per clone
66
+ enables the pre-push hook (ruff + mypy + pytest). CI is the backstop,
67
+ but the hook catches a broken push before it lands.
68
+ * GitHub Actions stay pinned by commit SHA (dependabot bumps them); new
69
+ workflows get an explicit least-privilege permissions block. The egress
70
+ test and pip-audit ride the weekly canary: never move them to default CI
71
+ (they need network) and never widen the egress allowlist beyond
72
+ polymarket.com without updating SECURITY.md and the README section.
73
+ * The weekly canary workflow is the drift alarm: if it opens an issue, the
74
+ fix starts by re-running the introspection against the new surface, not
75
+ by loosening the checks.
76
+ * Keep the library small and auditable (five modules): resist adding
77
+ dependencies; stdlib first. Anything bot-shaped belongs in bot-template/,
78
+ not in the package.
79
+ * Style: no em-dashes and no " - " connectors anywhere (strong user rule);
80
+ keep comments sparse and constraint-focused.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pmquant
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Fail-closed execution and market-data layer for Polymarket CLOB V2: local signing, confirmed fills only, fee-correct math, working deposit-wallet (POLY_1271) support.
5
5
  Project-URL: Homepage, https://github.com/crp4222/pmq
6
6
  Project-URL: Issues, https://github.com/crp4222/pmq/issues
@@ -23,6 +23,7 @@ Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.10
24
24
  Requires-Dist: py-clob-client-v2<2,>=1.0.2
25
25
  Provides-Extra: dev
26
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
26
27
  Requires-Dist: mcp>=1.2; extra == 'dev'
27
28
  Requires-Dist: mypy>=1.10; extra == 'dev'
28
29
  Requires-Dist: pytest-cov>=5; extra == 'dev'
@@ -41,6 +42,7 @@ Description-Content-Type: text/markdown
41
42
  [![canary](https://github.com/crp4222/pmq/actions/workflows/canary.yml/badge.svg)](https://github.com/crp4222/pmq/actions/workflows/canary.yml)
42
43
  [![coverage gate](https://img.shields.io/badge/coverage-%E2%89%A585%25%20enforced%20in%20CI-blue)](.github/workflows/test.yml)
43
44
  [![typed](https://img.shields.io/badge/types-mypy%20strict-blue)](pyproject.toml)
45
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/crp4222/pmq/badge)](https://scorecard.dev/viewer/?uri=github.com/crp4222/pmq)
44
46
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
45
47
 
46
48
  Fail-closed execution and market data for **Polymarket CLOB V2**, in Python.
@@ -81,13 +83,15 @@ a real error in live trading:
81
83
 
82
84
  The full write-up with reproduction details: [docs/war-story.md](docs/war-story.md).
83
85
 
84
- ## Runs in production
86
+ ## Runs in production: my own money, daily
85
87
 
86
- The maintainer's own bot trades through this exact executor 24/7 with real
87
- money. Example receipt (2026-07-03): settlement transaction
88
+ I built pmq for my own trading. It executes real volume with my funds every
89
+ day, and it has never booked a fill the exchange did not confirm. If you
90
+ want to see it on-chain, here is a settlement from one of my wallets
91
+ (2026-07-03):
88
92
  [`0x387f5f09...100d88a8`](https://polygonscan.com/tx/0x387f5f09c031bb36a71c54adc978b1ed4d50c67f6dd3f0c2c8068391100d88a8)
89
93
  on the CTF Exchange V2: a FAK market buy built by this library, matched and
90
- settled, with the builder code visible in the calldata. Additionally, a weekly
94
+ settled, with the builder code visible in the calldata. A weekly
91
95
  [canary workflow](.github/workflows/canary.yml) exercises the real endpoints
92
96
  and the installed client surface, and opens an issue by itself if Polymarket
93
97
  drifts.
@@ -126,7 +130,10 @@ not your hopes.
126
130
 
127
131
  At startup pmq **introspects the installed py-clob-client-v2** against the API
128
132
  surface it was verified on, and refuses to trade on drift instead of sending
129
- orders through changed semantics.
133
+ orders through changed semantics. The whole table is pinned by an executable
134
+ test per row plus a hypothesis fuzz suite (hundreds of generated adversarial
135
+ responses per run, including NaN/Infinity and negative amounts, which book
136
+ zero).
130
137
 
131
138
  ## Quickstart
132
139
 
@@ -160,9 +167,10 @@ else:
160
167
  print(fill.matched_shares, "shares at", fill.price, "order", fill.order_id)
161
168
  ```
162
169
 
163
- `sell_fak` and `limit_gtc` follow the same contract. The buy path has carried
164
- live volume; treat the sell path as following the same documented semantics
165
- with less battle time.
170
+ `sell_fak` and `limit_gtc` follow the same contract. Both FAK paths have
171
+ carried real volume: a production round trip (buy 5.149 @ 0.94, sell back
172
+ 5.14 @ 0.94, cross-checked via `get_trades`) confirmed the mirrored
173
+ `makingAmount`/`takingAmount` semantics on 2026-07-03.
166
174
 
167
175
  ## The signature_type table nobody gives you
168
176
 
@@ -212,7 +220,9 @@ JKorf/Polymarket.Net; the official client defaults to zero attribution.)
212
220
 
213
221
  ## MCP server (agents)
214
222
 
215
- `pip install "pmquant[mcp]"` then run `pmq-mcp` (stdio). Read tools (market,
223
+ `pip install "pmquant[mcp]"` then run `pmq-mcp` (stdio). Listed in the
224
+ [official MCP registry](https://registry.modelcontextprotocol.io) as
225
+ `io.github.crp4222/pmq`. Read tools (market,
216
226
  book, taker_fee, account_collateral, account_trades) always exist. Trading
217
227
  tools (`fak_buy`, `fak_sell`, `cancel_and_reconcile`) are **only registered
218
228
  when the operator sets `PMQ_MCP_LIVE=1`** in the server environment: an
@@ -247,12 +257,60 @@ shipped demo strategy is an API illustration meant to be replaced.
247
257
  * Keys are read from the environment, used to instantiate the signer, and
248
258
  never logged. No custody, no backend, no telemetry, zero network calls
249
259
  besides Polymarket endpoints.
250
- * Beware of the documented wave of fake "polymarket bot" repositories that
251
- steal private keys. Read the source: pmq is small on purpose.
260
+ * A documented wave of fake "polymarket bot" repositories steals private
261
+ keys; pmq is deliberately small so the entire execution path stays
262
+ readable in minutes by anyone who wants to look.
252
263
  * Fund the trading wallet with what you can afford to lose. Nothing here is
253
264
  financial advice; prediction-market access is restricted in some
254
265
  jurisdictions and compliance is on you.
255
266
 
267
+ ## If you feel like checking any of it
268
+
269
+ None of the claims above require taking my word; each one comes with a
270
+ handle you can pull, whenever you care to:
271
+
272
+ * **Egress.** `PMQ_CANARY=1 pytest tests/test_canary_live.py -k egress -s`
273
+ records every DNS resolution during a full session (market data, auth
274
+ derivation, one signed order) and fails on any host outside
275
+ `polymarket.com`. Last observed list: `clob.polymarket.com`,
276
+ `gamma-api.polymarket.com`, nothing else. The weekly
277
+ [canary](../../actions/workflows/canary.yml) prints that list in public
278
+ CI logs. One designed exception: `pmq-doctor`'s optional on-chain checks
279
+ use the public Polygon RPCs named in its source.
280
+ * **Provenance.** Releases carry a signed PEP 740 attestation (Sigstore,
281
+ via PyPI trusted publishing): click "provenance" next to any file on the
282
+ [PyPI files page](https://pypi.org/project/pmquant/#files), or fetch it
283
+ raw from PyPI's integrity API. The signing identity is this repository's
284
+ `publish.yml` workflow.
285
+ * **Dependencies.** Dependabot files weekly bump PRs (Python and
286
+ SHA-pinned GitHub Actions), and the weekly canary runs `pip-audit`; a
287
+ hit opens an issue by itself.
288
+ * **The source.** Five small modules; the whole execution path reads in
289
+ minutes. The grep targets that answer the important questions fastest
290
+ are listed in [SECURITY.md](SECURITY.md).
291
+
292
+ ## Stability and maintenance
293
+
294
+ * Pre-1.0 SemVer: PATCH releases only fix, MINOR releases may change the
295
+ public API with the migration named in [CHANGELOG.md](CHANGELOG.md).
296
+ Nothing changes silently.
297
+ * Deprecated APIs keep working and warn for at least one MINOR release
298
+ before removal.
299
+ * The bar for 1.0, stated in advance: months of green weekly canaries, the
300
+ maker path (`limit_gtc`) production-proven with real volume the way the
301
+ FAK paths already are, and external production users.
302
+ * Bus-factor honesty: one maintainer, who trades real money through this
303
+ exact code daily (strongest available incentive to keep it correct). The
304
+ mitigations are structural, not promises: five small modules, the
305
+ executable fill-contract test table, a weekly canary that opens issues by
306
+ itself, SHA-pinned CI. Operational rule: if the canary badge goes red and
307
+ stays red, treat the project as unmaintained and pin your last known-good
308
+ version.
309
+ * Help wanted, precisely scoped: production receipts for `signature_type`
310
+ 1 and 2 accounts (legacy Magic/email and browser-wallet proxies). Both
311
+ paths are introspection-tested but have never carried real money through
312
+ this library; the maintainer's own accounts are all types 0 and 3.
313
+
256
314
  ## License
257
315
 
258
316
  MIT