subproto 0.1.0__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 (122) hide show
  1. subproto-0.1.0/.github/ISSUE_TEMPLATE/bug-report.yml +62 -0
  2. subproto-0.1.0/.github/ISSUE_TEMPLATE/config.yml +11 -0
  3. subproto-0.1.0/.github/ISSUE_TEMPLATE/model-or-agent.yml +59 -0
  4. subproto-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +27 -0
  5. subproto-0.1.0/.github/workflows/ci.yml +168 -0
  6. subproto-0.1.0/.github/workflows/pages.yml +43 -0
  7. subproto-0.1.0/.github/workflows/publish.yml +81 -0
  8. subproto-0.1.0/.subproto.example.json +9 -0
  9. subproto-0.1.0/CHANGELOG.md +492 -0
  10. subproto-0.1.0/CONTRIBUTING.md +103 -0
  11. subproto-0.1.0/LICENSE +204 -0
  12. subproto-0.1.0/MANIFEST.in +25 -0
  13. subproto-0.1.0/PKG-INFO +708 -0
  14. subproto-0.1.0/README.md +679 -0
  15. subproto-0.1.0/ROADMAP.md +323 -0
  16. subproto-0.1.0/SECURITY.md +49 -0
  17. subproto-0.1.0/SPEC.md +672 -0
  18. subproto-0.1.0/TODO.md +489 -0
  19. subproto-0.1.0/WIRING.md +125 -0
  20. subproto-0.1.0/bench/ab.py +146 -0
  21. subproto-0.1.0/bench/ablation.py +421 -0
  22. subproto-0.1.0/bench/ablation_results.md +36 -0
  23. subproto-0.1.0/bench/launch_stats.json +374 -0
  24. subproto-0.1.0/bench/launch_stats.py +785 -0
  25. subproto-0.1.0/bench/laya_latency.json +423 -0
  26. subproto-0.1.0/bench/laya_latency.md +37 -0
  27. subproto-0.1.0/bench/laya_latency.py +342 -0
  28. subproto-0.1.0/bench/live.py +554 -0
  29. subproto-0.1.0/bench/live_results.md +29 -0
  30. subproto-0.1.0/bench/live_results_hard.md +29 -0
  31. subproto-0.1.0/bench/mutation_gate.py +763 -0
  32. subproto-0.1.0/bench/results.md +14 -0
  33. subproto-0.1.0/bench/s32_probe.py +91 -0
  34. subproto-0.1.0/bench/tasks.hard.jsonl +8 -0
  35. subproto-0.1.0/bench/tasks.sample.jsonl +20 -0
  36. subproto-0.1.0/docs/assets/first-30-seconds.cast +19 -0
  37. subproto-0.1.0/docs/assets/first-30-seconds.gif +0 -0
  38. subproto-0.1.0/docs/assets/first-30-seconds.sh +63 -0
  39. subproto-0.1.0/docs/assets/terminal-stills.sh +118 -0
  40. subproto-0.1.0/docs/commands.md +473 -0
  41. subproto-0.1.0/docs/configuration.md +183 -0
  42. subproto-0.1.0/docs/index.md +49 -0
  43. subproto-0.1.0/docs/troubleshooting.md +198 -0
  44. subproto-0.1.0/fakeup/__init__.py +1 -0
  45. subproto-0.1.0/fakeup/server.py +295 -0
  46. subproto-0.1.0/install.sh +46 -0
  47. subproto-0.1.0/pyproject.toml +78 -0
  48. subproto-0.1.0/requirements-dev.txt +1 -0
  49. subproto-0.1.0/run_tests.sh +257 -0
  50. subproto-0.1.0/setup.cfg +4 -0
  51. subproto-0.1.0/subproto/__init__.py +7 -0
  52. subproto-0.1.0/subproto/__main__.py +5 -0
  53. subproto-0.1.0/subproto/cli.py +1493 -0
  54. subproto-0.1.0/subproto/compiler.py +431 -0
  55. subproto-0.1.0/subproto/config.py +216 -0
  56. subproto-0.1.0/subproto/dataset.py +330 -0
  57. subproto-0.1.0/subproto/demo.py +260 -0
  58. subproto-0.1.0/subproto/doctor.py +282 -0
  59. subproto-0.1.0/subproto/engine.py +352 -0
  60. subproto-0.1.0/subproto/graph.py +502 -0
  61. subproto-0.1.0/subproto/heuristics.py +177 -0
  62. subproto-0.1.0/subproto/implicit.py +534 -0
  63. subproto-0.1.0/subproto/laya.py +19 -0
  64. subproto-0.1.0/subproto/laya_server.py +389 -0
  65. subproto-0.1.0/subproto/live.py +149 -0
  66. subproto-0.1.0/subproto/pricing.py +43 -0
  67. subproto-0.1.0/subproto/protocol.py +342 -0
  68. subproto-0.1.0/subproto/proxy.py +349 -0
  69. subproto-0.1.0/subproto/report.py +509 -0
  70. subproto-0.1.0/subproto/retrain.py +541 -0
  71. subproto-0.1.0/subproto/style.py +292 -0
  72. subproto-0.1.0/subproto/systemone/__init__.py +28 -0
  73. subproto-0.1.0/subproto/systemone/base.py +123 -0
  74. subproto-0.1.0/subproto/systemone/evidence/ablation.json +404 -0
  75. subproto-0.1.0/subproto/systemone/registry.py +212 -0
  76. subproto-0.1.0/subproto/systemone/router.py +192 -0
  77. subproto-0.1.0/subproto/systemone/versions.py +322 -0
  78. subproto-0.1.0/subproto/telemetry.py +147 -0
  79. subproto-0.1.0/subproto/tests/conftest.py +74 -0
  80. subproto-0.1.0/subproto/tests/fixtures/sample_repo/app/payments/retry.py +12 -0
  81. subproto-0.1.0/subproto/tests/fixtures/sample_repo/app/services/gateway_client.py +11 -0
  82. subproto-0.1.0/subproto/tests/fixtures/sample_repo/web/src/refund.ts +9 -0
  83. subproto-0.1.0/subproto/tests/test_ablation.py +128 -0
  84. subproto-0.1.0/subproto/tests/test_ambient_graph.py +145 -0
  85. subproto-0.1.0/subproto/tests/test_cli_demo_home.py +158 -0
  86. subproto-0.1.0/subproto/tests/test_cli_says_why.py +155 -0
  87. subproto-0.1.0/subproto/tests/test_compiler.py +687 -0
  88. subproto-0.1.0/subproto/tests/test_dataset_split.py +132 -0
  89. subproto-0.1.0/subproto/tests/test_dialects_e2e.py +224 -0
  90. subproto-0.1.0/subproto/tests/test_doctor.py +398 -0
  91. subproto-0.1.0/subproto/tests/test_engine.py +106 -0
  92. subproto-0.1.0/subproto/tests/test_finetune.py +68 -0
  93. subproto-0.1.0/subproto/tests/test_graph.py +335 -0
  94. subproto-0.1.0/subproto/tests/test_heuristics.py +42 -0
  95. subproto-0.1.0/subproto/tests/test_implicit.py +525 -0
  96. subproto-0.1.0/subproto/tests/test_invariants.py +326 -0
  97. subproto-0.1.0/subproto/tests/test_laya.py +92 -0
  98. subproto-0.1.0/subproto/tests/test_laya_latency.py +127 -0
  99. subproto-0.1.0/subproto/tests/test_laya_real.py +427 -0
  100. subproto-0.1.0/subproto/tests/test_learn.py +345 -0
  101. subproto-0.1.0/subproto/tests/test_live.py +141 -0
  102. subproto-0.1.0/subproto/tests/test_live_bench.py +119 -0
  103. subproto-0.1.0/subproto/tests/test_proxy_e2e.py +90 -0
  104. subproto-0.1.0/subproto/tests/test_report_sections.py +91 -0
  105. subproto-0.1.0/subproto/tests/test_retrain.py +665 -0
  106. subproto-0.1.0/subproto/tests/test_router.py +282 -0
  107. subproto-0.1.0/subproto/tests/test_show.py +373 -0
  108. subproto-0.1.0/subproto/tests/test_style.py +382 -0
  109. subproto-0.1.0/subproto/tests/test_systemone.py +251 -0
  110. subproto-0.1.0/subproto/tests/test_telemetry.py +75 -0
  111. subproto-0.1.0/subproto/tests/test_usage.py +70 -0
  112. subproto-0.1.0/subproto/tests/test_versions.py +491 -0
  113. subproto-0.1.0/subproto/tests/test_wiring.py +110 -0
  114. subproto-0.1.0/subproto/usage.py +268 -0
  115. subproto-0.1.0/subproto.egg-info/PKG-INFO +708 -0
  116. subproto-0.1.0/subproto.egg-info/SOURCES.txt +120 -0
  117. subproto-0.1.0/subproto.egg-info/dependency_links.txt +1 -0
  118. subproto-0.1.0/subproto.egg-info/entry_points.txt +2 -0
  119. subproto-0.1.0/subproto.egg-info/requires.txt +8 -0
  120. subproto-0.1.0/subproto.egg-info/top_level.txt +2 -0
  121. subproto-0.1.0/tools/check_install.sh +44 -0
  122. subproto-0.1.0/train/finetune_mlx.py +132 -0
@@ -0,0 +1,62 @@
1
+ name: Bug report
2
+ description: A command that does not do what its page says it does
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ The single most useful thing you can paste here is the command and its real
9
+ output. Most "it does not work" reports are a Python version, a port in use,
10
+ or a slot that is observing rather than enforcing — `doctor` tells you which.
11
+ - type: textarea
12
+ id: command
13
+ attributes:
14
+ label: The command, exactly as you typed it
15
+ render: shell
16
+ placeholder: |
17
+ subproto demo --port 8000 --slots
18
+ subproto report
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: expected
23
+ attributes:
24
+ label: What you expected, and what printed instead
25
+ description: Paste the output. Do not paraphrase a number.
26
+ validations:
27
+ required: true
28
+ - type: textarea
29
+ id: doctor
30
+ attributes:
31
+ label: Output of `subproto doctor`
32
+ description: |
33
+ If that command does not exist in your version, paste instead:
34
+ `python3 --version`, `uname -sr` (or your Windows build), and
35
+ `echo "$SUBPROTO_HOME"`.
36
+ render: shell
37
+ validations:
38
+ required: true
39
+ - type: dropdown
40
+ id: slotstate
41
+ attributes:
42
+ label: Are any slots enforcing?
43
+ description: Observation-only is the default; a drop you did not turn on is a different bug.
44
+ options: [none, SUBPROTO_APPLY set, a config file, I do not know]
45
+ validations:
46
+ required: true
47
+ - type: textarea
48
+ id: noise
49
+ attributes:
50
+ label: Anything else running on this machine
51
+ description: >
52
+ A second proxy, an Ollama server, a training run or a laptop on battery changes
53
+ both the latency numbers and whether a model backend answers in time.
54
+ - type: checkboxes
55
+ id: terms
56
+ attributes:
57
+ label: Confirmations
58
+ options:
59
+ - label: I can reproduce this on a fresh `SUBPROTO_HOME` (or I say what is in mine)
60
+ required: true
61
+ - label: I redacted any API key or source code from the pasted output
62
+ required: true
@@ -0,0 +1,11 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: SECURITY.md — what subproto does and does not send anywhere
4
+ url: https://github.com/aashish254/limen/blob/main/SECURITY.md
5
+ about: It is a local proxy with no telemetry and no update check. Read this before wiring it into an agent that handles private code.
6
+ - name: WIRING.md — base_url per coding agent
7
+ url: https://github.com/aashish254/limen/blob/main/WIRING.md
8
+ about: Claude Code, Codex, Gemini CLI, Cline, aider, OpenCode. Open an issue if your agent or version is missing.
9
+ - name: CONTRIBUTING.md — the invariants a PR has to keep
10
+ url: https://github.com/aashish254/limen/blob/main/CONTRIBUTING.md
11
+ about: Zero dependencies, colour only on a state word, and a test that would have failed without your change.
@@ -0,0 +1,59 @@
1
+ name: Wiring a model or an agent
2
+ description: Add a model backend to the registry, or document an agent's base_url setup
3
+ labels: ["good first issue"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Two kinds of contribution live here, and both are small:
9
+
10
+ **A model backend.** `subproto/systemone/registry.py` is the only file that
11
+ knows model families. If your server already answers `GET /health` and
12
+ `POST /score` (`{"state", "question", "options"}` in, `{option: probability}`
13
+ out), you can point at it today with `SUBPROTO_MODEL=http://host:port` and no
14
+ code change — a registry entry is only needed to give it a name in the report
15
+ and the ablation.
16
+
17
+ **An agent's wiring.** `WIRING.md` holds the exact `base_url` / env setup per
18
+ coding agent. A missing agent, or a setup that changed upstream, is an issue
19
+ worth opening.
20
+ - type: dropdown
21
+ id: kind
22
+ attributes:
23
+ label: Which one is this?
24
+ options: [a model backend / adapter, an agent's wiring page, something else]
25
+ validations:
26
+ required: true
27
+ - type: textarea
28
+ id: contract
29
+ attributes:
30
+ label: For a backend — what does it answer?
31
+ description: >
32
+ Paste a real `curl` of `/health` and of one `/score` request and response. If
33
+ it answers a different shape, say so: the adapter seam is in
34
+ `subproto/systemone/base.py`, and a translation layer there is a reasonable
35
+ change if the question it answers is genuinely the same question.
36
+ render: shell
37
+ - type: textarea
38
+ id: evidence
39
+ attributes:
40
+ label: For a wiring page — how did you verify it?
41
+ description: The command you ran, and the line in `subproto report` that proves traffic reached the proxy.
42
+ - type: textarea
43
+ id: numbers
44
+ attributes:
45
+ label: What it scores on the labelled set (optional, and never required to open this)
46
+ description: >
47
+ `LAYA_URL=http://127.0.0.1:8000 python3 -m bench.ablation --adapters laya` prints
48
+ precision against the shipped heuristic and an `answered N/M` column. A backend
49
+ that answered nothing must print `—`, not a number: if your run shows a number
50
+ for an unreachable endpoint, that is a bug, please open it separately.
51
+ - type: checkboxes
52
+ id: terms
53
+ attributes:
54
+ label: Confirmations
55
+ options:
56
+ - label: I read CONTRIBUTING.md, including the invariants
57
+ required: true
58
+ - label: No API key or proprietary prompt data is pasted above
59
+ required: true
@@ -0,0 +1,27 @@
1
+ ## What rule does this change?
2
+
3
+ One or two sentences. Name the decision the code makes, not the file you edited.
4
+
5
+ ## How do I see it fail without this change?
6
+
7
+ ```
8
+ the command you ran
9
+ ```
10
+
11
+ Paste the **real** output, including the line that shows the fix.
12
+
13
+ ## Gate
14
+
15
+ - [ ] `bash run_tests.sh` prints `ALL GATES PASS` (paste the last 3 lines)
16
+ - [ ] New rule ⇒ new mutant in `bench/mutation_gate.py`, and it is reported `KILLED`
17
+ - [ ] Any printed page touched ⇒ `subproto/tests/test_style.py` still passes
18
+ - [ ] Any claim in README/SPEC/ROADMAP changed ⇒ the number came from a command above
19
+
20
+ ## Invariants
21
+
22
+ Which of I2 (cached prefix), I3 (tail is sacred), I4 (local only), I5 (byte
23
+ transparent), I6 (measured not claimed) does this touch, and what keeps it whole?
24
+
25
+ ## Labels
26
+
27
+ Fixes #… — or say `no issue` for a drive-by.
@@ -0,0 +1,168 @@
1
+ name: verify
2
+
3
+ # Three jobs, answering three different questions:
4
+ # suite — do the tests pass on someone else's OS, on both supported Pythons?
5
+ # gates — does the whole one-command gate (benches, demo, mutation gate) hold?
6
+ # install — can a stranger `pip install` this and run the command, from a
7
+ # directory that is *not* the clone? Nothing in the runtime may
8
+ # depend on repo files, so this job unpacks the wheel elsewhere.
9
+ #
10
+ # No API key, no spend, no model weights: the Laya legs skip themselves unless
11
+ # SUBPROTO_LAYA_PYTHON names an interpreter that can import `laya`, and `fakeup`
12
+ # stands in for every provider.
13
+
14
+ on:
15
+ push:
16
+ branches: [main]
17
+ pull_request:
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ concurrency:
24
+ group: ${{ github.workflow }}-${{ github.ref }}
25
+ cancel-in-progress: true
26
+
27
+ jobs:
28
+ suite:
29
+ name: pytest ${{ matrix.os }} py${{ matrix.python }}
30
+ runs-on: ${{ matrix.os }}
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ python: ["3.9", "3.11"]
35
+ os: [ubuntu-latest, macos-latest]
36
+ include:
37
+ # Windows runs, but is not a release gate: `os.getloadavg` is POSIX and
38
+ # ANSI escape handling differs. It is reported rather than hidden, so a
39
+ # Windows contributor sees the real state of their platform here.
40
+ - os: windows-latest
41
+ python: "3.11"
42
+ continue-on-error: ${{ matrix.os == 'windows-latest' }}
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: ${{ matrix.python }}
48
+ allow-prereleases: false
49
+ - name: Install pytest (the package itself has zero dependencies)
50
+ run: python -m pip install --quiet "pytest>=7"
51
+ - name: Byte-compile every package under -W error
52
+ run: >-
53
+ python -W error -m compileall -q subproto fakeup bench train
54
+ - name: pytest
55
+ run: python -m pytest -q
56
+
57
+ gates:
58
+ name: one-command gate (benches + demo + mutation gate)
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+ - uses: actions/setup-python@v5
63
+ with:
64
+ python-version: "3.11"
65
+ - name: Install dev requirements
66
+ run: python -m pip install --quiet -r requirements-dev.txt
67
+ - name: bash run_tests.sh
68
+ run: bash run_tests.sh
69
+ env:
70
+ PYTHON: python3.11
71
+
72
+ install:
73
+ name: pip install and run (${{ matrix.os }} py${{ matrix.python }})
74
+ runs-on: ${{ matrix.os }}
75
+ strategy:
76
+ fail-fast: false
77
+ matrix:
78
+ os: [ubuntu-latest, macos-latest]
79
+ python: ["3.9", "3.11"]
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+ - uses: actions/setup-python@v5
83
+ with:
84
+ python-version: ${{ matrix.python }}
85
+ - name: Build the sdist and the wheel
86
+ run: |
87
+ python -m pip install --quiet build
88
+ python -m build
89
+ - name: Install the wheel into a clean venv, offline
90
+ run: |
91
+ python -m venv .verify-venv
92
+ .verify-venv/bin/python -m pip install --quiet --no-index --find-links dist subproto
93
+ .verify-venv/bin/python -m pip check
94
+ - name: The installed package agrees about which build it is
95
+ run: |
96
+ .verify-venv/bin/python - <<'PYV'
97
+ import importlib.metadata as md
98
+ import subproto
99
+ assert md.version("subproto") == subproto.__version__, (
100
+ md.version("subproto"), subproto.__version__)
101
+ print("metadata and subproto.__version__ agree:", subproto.__version__)
102
+ PYV
103
+ .verify-venv/bin/subproto --version
104
+ - name: Run the installed command from outside the clone
105
+ shell: bash
106
+ run: |
107
+ set -euo pipefail
108
+ ROOT="$PWD"
109
+ WORK="$(mktemp -d)"
110
+ BIN="$ROOT/.verify-venv/bin/subproto"
111
+ cd "$WORK"
112
+ SUBPROTO_HOME="$WORK/home" "$BIN" --help > /dev/null
113
+ SUBPROTO_HOME="$WORK/home" "$BIN" doctor --port 0 --json > doctor.json
114
+ SUBPROTO_HOME="$WORK/home" "$BIN" demo --port 8743 --slots > demo.log
115
+ SUBPROTO_HOME="$WORK/home" "$BIN" report
116
+ echo "--- doctor ---"; cat doctor.json
117
+ echo "--- demo tail ---"; tail -5 demo.log
118
+ - name: The installed Router ranks on evidence that shipped with it
119
+ shell: bash
120
+ run: |
121
+ set -euo pipefail
122
+ PY="$GITHUB_WORKSPACE/.verify-venv/bin/python"
123
+ # From a directory that is not the clone, twice over: inside the clone a
124
+ # `import subproto` answers from the source tree, so every assertion below
125
+ # would pass with nothing installed at all. A pip user has no source tree,
126
+ # and a Router that reads its ranking evidence out of one worked in CI and
127
+ # nowhere in their day — degrading to manual selection so quietly that no
128
+ # test fails (S46/M4).
129
+ cd "$(mktemp -d)"
130
+ "$PY" -c "import subproto; assert 'site-packages' in subproto.__file__, subproto.__file__"
131
+ "$PY" - <<'PYV'
132
+ import json, os, subproto
133
+ from subproto.systemone import router
134
+ path = router.DEFAULT_ABLATION_JSON
135
+ pkg = os.path.dirname(os.path.abspath(subproto.__file__))
136
+ assert path.startswith(pkg + os.sep), "%s is outside the package" % path
137
+ data = json.load(open(path))
138
+ assert {"heuristic", "adapters"} <= set(data), sorted(data)
139
+ # `adapters` is a list of records keyed by "name" — the schema load_evidence()
140
+ # reads. This used to test `"laya" in data["adapters"]`, a shape guess that was
141
+ # never true of the file and reddened all four install legs of the first public
142
+ # run: the suite proves this through load_evidence(), so nothing local saw it.
143
+ assert "laya" in [a.get("name") for a in data["adapters"]], data["adapters"]
144
+ ev = router.load_evidence()
145
+ assert {"heuristic", "laya"} <= set(ev), sorted(ev)
146
+ print("installed Router ranks on %s" % os.path.relpath(path, pkg))
147
+ PYV
148
+ - name: Confirm the sdist carries what the docs tell people to run
149
+ run: |
150
+ python - <<'PYSMOKE'
151
+ import glob, os, tarfile
152
+ name = glob.glob("dist/*.tar.gz")[0]
153
+ names = set(tarfile.open(name).getnames())
154
+ top = os.path.basename(name)[: -len(".tar.gz")]
155
+ need = ["pyproject.toml", "run_tests.sh", "bench/ablation.py",
156
+ "bench/mutation_gate.py", "fakeup/server.py", "WIRING.md"]
157
+ missing = [n for n in need if "%s/%s" % (top, n) not in names]
158
+ assert not missing, "sdist is missing: %s" % missing
159
+ print("sdist carries the bench and the fakeup mock")
160
+ PYSMOKE
161
+ - name: Run install.sh, the no-package-index route the README leads with
162
+ run: |
163
+ set -euo pipefail
164
+ SUBPROTO_BIN_DIR="$PWD/.verify-bin" bash install.sh
165
+ export SUBPROTO_HOME="$(mktemp -d)"
166
+ ./.verify-bin/subproto doctor --port 0 --json
167
+ ./.verify-bin/subproto demo --port 8744 --slots > /dev/null
168
+ ./.verify-bin/subproto report | head -3
@@ -0,0 +1,43 @@
1
+ name: pages
2
+
3
+ # The landing page is a build artifact of the measurement run, so it is committed and
4
+ # published as-is: `bench/launch_stats.py` measures, `tools/make_site.py` renders, and
5
+ # this workflow only carries `_site/` to Pages. Nothing recomputes a number here, so the
6
+ # page a reviewer reads on github.io is byte-identical to the one in the repo.
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ paths: ["_site/**", "docs/assets/fonts/**", ".github/workflows/pages.yml"]
12
+ workflow_dispatch:
13
+
14
+ permissions:
15
+ contents: read
16
+ pages: write
17
+ id-token: write
18
+
19
+ concurrency:
20
+ group: pages
21
+ cancel-in-progress: false
22
+
23
+ jobs:
24
+ deploy:
25
+ runs-on: ubuntu-latest
26
+ environment:
27
+ name: github-pages
28
+ url: ${{ steps.deployment.outputs.page_url }}
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - name: Refuse to publish a page with an unrendered token
32
+ run: |
33
+ if grep -rl '{{[A-Z_]*}}' _site >/dev/null 2>&1; then
34
+ grep -rn '{{[A-Z_]*}}' _site
35
+ exit 1
36
+ fi
37
+ test -s _site/index.html
38
+ - uses: actions/configure-pages@v5
39
+ - uses: actions/upload-pages-artifact@v3
40
+ with:
41
+ path: _site
42
+ - id: deployment
43
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,81 @@
1
+ name: publish
2
+
3
+ # The release is built and uploaded from the tag, by CI, with no credential stored on
4
+ # anybody's laptop: PyPI trusts this workflow's OIDC identity, registered once as a
5
+ # "pending publisher" on pypi.org. Nothing here recomputes a number — `verify` is the
6
+ # gate on the code, and this job only packages the tagged tree, proves the artifact
7
+ # installs and runs, and hands it to the index.
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+ workflow_dispatch:
13
+ inputs:
14
+ tag:
15
+ description: Which tag to publish
16
+ default: v0.1.0
17
+ type: string
18
+
19
+ permissions:
20
+ contents: read
21
+ id-token: write
22
+
23
+ concurrency:
24
+ group: publish
25
+ cancel-in-progress: false
26
+
27
+ jobs:
28
+ publish:
29
+ runs-on: ubuntu-latest
30
+ environment: pypi
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ with:
34
+ ref: ${{ github.event.inputs.tag || github.ref_name }}
35
+ - uses: actions/setup-python@v5
36
+ with:
37
+ python-version: "3.11"
38
+ - name: Every classifier must be one PyPI recognises
39
+ run: |
40
+ set -euo pipefail
41
+ python - <<'PYP'
42
+ import re, html, tomllib, urllib.request
43
+ declared = tomllib.load(open("pyproject.toml", "rb"))["project"]["classifiers"]
44
+ page = urllib.request.urlopen("https://pypi.org/classifiers/").read().decode()
45
+ valid = {html.unescape(re.sub(r"<[^>]+>", "", m.group(1))).strip()
46
+ for m in re.finditer(r'data-clipboard-target="source">(.*?)</a>', page, re.S)}
47
+ assert len(valid) > 500, "the classifier list did not parse (%d)" % len(valid)
48
+ bad = [c for c in declared if c not in valid]
49
+ for c in declared:
50
+ print("%s %s" % ("ok " if c not in bad else "BAD ", c))
51
+ # One invented trove 400s the whole upload, after the files are already sent.
52
+ assert not bad, "not on https://pypi.org/classifiers/: %s" % bad
53
+ PYP
54
+ - name: Build the sdist and the wheel
55
+ run: |
56
+ python -m pip install --quiet build twine
57
+ python -m build
58
+ twine check dist/*
59
+ - name: The artifact must carry the version the tag was cut from
60
+ run: |
61
+ set -euo pipefail
62
+ REF="${{ github.event.inputs.tag || github.ref_name }}"
63
+ TAG="${REF#v}"
64
+ SRC="$(python -c 'import subproto; print(subproto.__version__)')"
65
+ WHEEL="$(basename dist/*.whl)"
66
+ echo "tag=$TAG source=$SRC wheel=$WHEEL"
67
+ test "$SRC" = "$TAG" || { echo "subproto.__version__ disagrees with the tag"; exit 1; }
68
+ test "$WHEEL" = "subproto-$TAG-py3-none-any.whl" \
69
+ || { echo "the built wheel is not tagged $TAG"; exit 1; }
70
+ - name: Install the built wheel offline and run it
71
+ run: |
72
+ set -euo pipefail
73
+ python -m venv .smoke
74
+ .smoke/bin/python -m pip install --quiet --no-index --find-links dist subproto
75
+ .smoke/bin/python -m pip check
76
+ cd "$(mktemp -d)"
77
+ SUBPROTO_HOME="$PWD" .smoke/bin/subproto --version
78
+ SUBPROTO_HOME="$PWD" .smoke/bin/subproto doctor --port 0 > /dev/null
79
+ - uses: pypa/gh-action-pypi-publish@release/v1
80
+ with:
81
+ verbose: true
@@ -0,0 +1,9 @@
1
+ {
2
+ "data_dir": "~/.subproto",
3
+ "port": 8787,
4
+ "store_bodies": false,
5
+ "record_features": true,
6
+ "laya_url": null,
7
+ "openai_upstream": "https://api.openai.com",
8
+ "anthropic_upstream": "https://api.anthropic.com"
9
+ }