openproj 0.39.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.
- openproj-0.39.0/.gcloudignore +30 -0
- openproj-0.39.0/.gitattributes +1 -0
- openproj-0.39.0/.github/shards/coedit +43 -0
- openproj-0.39.0/.github/shards/editor +39 -0
- openproj-0.39.0/.github/shards/graph +36 -0
- openproj-0.39.0/.github/shards/rest +44 -0
- openproj-0.39.0/.github/shards/views +35 -0
- openproj-0.39.0/.github/workflows/ci.yml +257 -0
- openproj-0.39.0/.github/workflows/release.yml +83 -0
- openproj-0.39.0/.gitignore +22 -0
- openproj-0.39.0/.pre-commit-config.yaml +40 -0
- openproj-0.39.0/AGENTS.md +595 -0
- openproj-0.39.0/Dockerfile +53 -0
- openproj-0.39.0/LICENSE +41 -0
- openproj-0.39.0/PKG-INFO +158 -0
- openproj-0.39.0/README.md +132 -0
- openproj-0.39.0/deploy/RUNBOOK.md +469 -0
- openproj-0.39.0/deploy/boot.py +87 -0
- openproj-0.39.0/docs/EDITOR.md +1152 -0
- openproj-0.39.0/docs/QUEUE.md +461 -0
- openproj-0.39.0/docs/architecture.md +226 -0
- openproj-0.39.0/docs/data-model.md +304 -0
- openproj-0.39.0/docs/deferred-push.md +301 -0
- openproj-0.39.0/docs/drawings.md +753 -0
- openproj-0.39.0/docs/hackmd-observed.md +380 -0
- openproj-0.39.0/docs/probes/README.md +198 -0
- openproj-0.39.0/docs/probes/ci-durations-4core.txt +5279 -0
- openproj-0.39.0/docs/probes/ci-durations-after-cache.txt +761 -0
- openproj-0.39.0/docs/probes/ci-durations.txt +1137 -0
- openproj-0.39.0/docs/probes/ci-speed.md +1189 -0
- openproj-0.39.0/docs/probes/coedit-under-load.md +206 -0
- openproj-0.39.0/docs/probes/concurrency-audit.md +904 -0
- openproj-0.39.0/docs/probes/hackmd_probe.py +184 -0
- openproj-0.39.0/docs/probes/load/adversarial-tail.json +973 -0
- openproj-0.39.0/docs/probes/load/adversarial.json +902 -0
- openproj-0.39.0/docs/probes/load/coedit-churn.json +5789 -0
- openproj-0.39.0/docs/probes/load/coedit-same-room.json +17539 -0
- openproj-0.39.0/docs/probes/load/detail-cost.json +41 -0
- openproj-0.39.0/docs/probes/load/diverge.json +350 -0
- openproj-0.39.0/docs/probes/load/index-warm.json +15 -0
- openproj-0.39.0/docs/probes/load/lockout.json +406 -0
- openproj-0.39.0/docs/probes/load/mixed-rtt600.json +398 -0
- openproj-0.39.0/docs/probes/load/mixed.json +286 -0
- openproj-0.39.0/docs/probes/load/read-load.json +974 -0
- openproj-0.39.0/docs/probes/load/same-contention.json +288 -0
- openproj-0.39.0/docs/probes/load/write-different.json +1261 -0
- openproj-0.39.0/docs/probes/load/write-different.md +168 -0
- openproj-0.39.0/docs/probes/load/write-same.json +1048 -0
- openproj-0.39.0/docs/probes/load/write-same.md +282 -0
- openproj-0.39.0/docs/quickstart.md +141 -0
- openproj-0.39.0/docs/shape-up.md +78 -0
- openproj-0.39.0/gcloud_deploy.sh +425 -0
- openproj-0.39.0/pyproject.toml +66 -0
- openproj-0.39.0/seed/README.md +82 -0
- openproj-0.39.0/seed/config/cycles.yaml +13 -0
- openproj-0.39.0/seed/config/defaults.yaml +23 -0
- openproj-0.39.0/seed/config/holidays.yaml +22 -0
- openproj-0.39.0/seed/config/people.yaml +36 -0
- openproj-0.39.0/seed/cycles/0037.md +55 -0
- openproj-0.39.0/seed/issues/issue-778899.md +9 -0
- openproj-0.39.0/seed/issues/issue-a1b2c3.md +14 -0
- openproj-0.39.0/seed/issues/issue-b3c4d5.md +23 -0
- openproj-0.39.0/seed/issues/issue-c9d0e1.md +24 -0
- openproj-0.39.0/seed/issues/issue-d4e5f6.md +10 -0
- openproj-0.39.0/seed/issues/issue-e6f7a8.md +26 -0
- openproj-0.39.0/seed/notes/note-11aa22.md +22 -0
- openproj-0.39.0/seed/notes/note-33bb44.md +16 -0
- openproj-0.39.0/seed/notes/note-55cc66.md +15 -0
- openproj-0.39.0/seed/notes/note-66dd77.md +36 -0
- openproj-0.39.0/seed/notes/note-77ee88.md +27 -0
- openproj-0.39.0/seed/notes/note-88ff99.md +25 -0
- openproj-0.39.0/seed/pitches/pitch-0a0001--testing-rank-reproducibility.md +49 -0
- openproj-0.39.0/seed/pitches/pitch-0b0001--porting-throughflow.md +49 -0
- openproj-0.39.0/seed/pitches/pitch-0c0001--porting-the-bed.md +48 -0
- openproj-0.39.0/seed/pitches/pitch-0d0001--aroma-transport-convergence.md +48 -0
- openproj-0.39.0/seed/pitches/pitch-0e0001--burner-radiation-port.md +51 -0
- openproj-0.39.0/seed/pitches/pitch-0f0001--scan-operator-on-the-gpu-backend.md +76 -0
- openproj-0.39.0/seed/pitches/pitch-0f0002--chaff-optics-in-the-burner.md +64 -0
- openproj-0.39.0/seed/pitches/pitch-0f0003--tap-deck-rewrite.md +69 -0
- openproj-0.39.0/seed/products/prod-0f0001--kiln4py.md +27 -0
- openproj-0.39.0/seed/products/prod-0f0002--hearth.md +22 -0
- openproj-0.39.0/seed/projects/proj-000001--whole-roast.md +45 -0
- openproj-0.39.0/seed/projects/proj-000002--scan-backend.md +54 -0
- openproj-0.39.0/seed/tasks/task-0a1001--ci-for-the-standalone-driver-v1-5.md +47 -0
- openproj-0.39.0/seed/tasks/task-0a1002--implement-deterministic-means.md +44 -0
- openproj-0.39.0/seed/tasks/task-0b1001--port-the-air-side-throughflow.md +47 -0
- openproj-0.39.0/seed/tasks/task-0b1002--port-the-bed-side-throughflow.md +48 -0
- openproj-0.39.0/seed/tasks/task-0c1001--port-the-bed-solver.md +49 -0
- openproj-0.39.0/seed/tasks/task-0c1002--coordinate-with-kettleworth.md +47 -0
- openproj-0.39.0/seed/tasks/task-0d1001--flume-blend-coefficients.md +47 -0
- openproj-0.39.0/seed/tasks/task-0d1002--transport-module-halo-exchanges.md +46 -0
- openproj-0.39.0/seed/tasks/task-0d1003--drum-convergence-study.md +47 -0
- openproj-0.39.0/seed/tasks/task-0e1001--port-the-near-ir-burner.md +48 -0
- openproj-0.39.0/seed/tasks/task-0e1002--port-the-far-ir-burner.md +49 -0
- openproj-0.39.0/seed/tasks/task-0f1001--lower-the-scan-to-the-gpu-backend.md +54 -0
- openproj-0.39.0/seed/tasks/task-0f1002--bench-the-scan-against-the-cpu-reference.md +53 -0
- openproj-0.39.0/seed/tasks/task-0f1003--port-the-tap-deck-reader.md +42 -0
- openproj-0.39.0/seed/tasks/task-0f1004--wire-the-tap-deck-writer.md +46 -0
- openproj-0.39.0/seed/tasks/task-0f1005--retire-the-f2py-shim.md +45 -0
- openproj-0.39.0/src/openproj/__init__.py +3 -0
- openproj-0.39.0/src/openproj/auth.py +147 -0
- openproj-0.39.0/src/openproj/cli.py +782 -0
- openproj-0.39.0/src/openproj/coedit.py +405 -0
- openproj-0.39.0/src/openproj/github.py +290 -0
- openproj-0.39.0/src/openproj/index.py +834 -0
- openproj-0.39.0/src/openproj/model.py +3188 -0
- openproj-0.39.0/src/openproj/pusher.py +214 -0
- openproj-0.39.0/src/openproj/query.py +294 -0
- openproj-0.39.0/src/openproj/render/__init__.py +230 -0
- openproj-0.39.0/src/openproj/render/controls.py +4052 -0
- openproj-0.39.0/src/openproj/render/cycles.py +2239 -0
- openproj-0.39.0/src/openproj/render/deck.py +1712 -0
- openproj-0.39.0/src/openproj/render/detail.py +3435 -0
- openproj-0.39.0/src/openproj/render/editor.py +1752 -0
- openproj-0.39.0/src/openproj/render/env.py +165 -0
- openproj-0.39.0/src/openproj/render/export.py +71 -0
- openproj-0.39.0/src/openproj/render/graph.py +1519 -0
- openproj-0.39.0/src/openproj/render/help.py +369 -0
- openproj-0.39.0/src/openproj/render/hill.py +478 -0
- openproj-0.39.0/src/openproj/render/icons.py +297 -0
- openproj-0.39.0/src/openproj/render/markdown.py +614 -0
- openproj-0.39.0/src/openproj/render/records.py +340 -0
- openproj-0.39.0/src/openproj/render/rows.py +179 -0
- openproj-0.39.0/src/openproj/render/shell.py +3446 -0
- openproj-0.39.0/src/openproj/render/slides.py +949 -0
- openproj-0.39.0/src/openproj/render/styles.py +1360 -0
- openproj-0.39.0/src/openproj/render/table.py +4104 -0
- openproj-0.39.0/src/openproj/render/timeline.py +1016 -0
- openproj-0.39.0/src/openproj/render/tokens.py +814 -0
- openproj-0.39.0/src/openproj/schedule.py +570 -0
- openproj-0.39.0/src/openproj/store.py +2134 -0
- openproj-0.39.0/src/openproj/themes.py +164 -0
- openproj-0.39.0/src/openproj/vendor.py +238 -0
- openproj-0.39.0/src/openproj/web.py +4502 -0
- openproj-0.39.0/static/SHA256SUMS +8 -0
- openproj-0.39.0/static/VENDOR.md +594 -0
- openproj-0.39.0/static/ace-LICENSE.txt +24 -0
- openproj-0.39.0/static/ace.js +23 -0
- openproj-0.39.0/static/cytoscape.min.js +32 -0
- openproj-0.39.0/static/elk-LICENSE.txt +264 -0
- openproj-0.39.0/static/elk.bundled.js +6696 -0
- openproj-0.39.0/static/excalidraw-LICENSE.txt +21 -0
- openproj-0.39.0/static/excalidraw-fonts-LICENSE.txt +655 -0
- openproj-0.39.0/static/excalidraw.js +562 -0
- openproj-0.39.0/static/inter-LICENSE.txt +93 -0
- openproj-0.39.0/static/inter-latin-wght-normal.woff2 +0 -0
- openproj-0.39.0/static/keybinding-vim.js +8 -0
- openproj-0.39.0/static/mermaid-LICENSE.txt +21 -0
- openproj-0.39.0/static/mermaid.min.js +3636 -0
- openproj-0.39.0/static/yjs-LICENSE.txt +23 -0
- openproj-0.39.0/static/yjs.bundle.mjs +8 -0
- openproj-0.39.0/tests/browser.py +476 -0
- openproj-0.39.0/tests/cascade.py +379 -0
- openproj-0.39.0/tests/conftest.py +35 -0
- openproj-0.39.0/tests/fixtures/corpus/README.md +74 -0
- openproj-0.39.0/tests/fixtures/corpus/config/cycles.yaml +9 -0
- openproj-0.39.0/tests/fixtures/corpus/config/defaults.yaml +11 -0
- openproj-0.39.0/tests/fixtures/corpus/config/holidays.yaml +15 -0
- openproj-0.39.0/tests/fixtures/corpus/cycles/0037.md +44 -0
- openproj-0.39.0/tests/fixtures/corpus/cycles/0038.md +39 -0
- openproj-0.39.0/tests/fixtures/corpus/issues/issue-8e1a37.md +38 -0
- openproj-0.39.0/tests/fixtures/corpus/issues/issue-9f2b48.md +41 -0
- openproj-0.39.0/tests/fixtures/corpus/notes/note-a03c59.md +39 -0
- openproj-0.39.0/tests/fixtures/corpus/notes/note-b14d6a.md +33 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-1b3f9a--mpi-on-ci-verify-with-serial.md +72 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-2a7f3e--transport-port-blend-coeffs.md +143 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-3c9a41--throughflow-1d-cpu-single-node.md +123 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-48ea9e--transport-needs-exchange.md +69 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-5e7b1c--ci-standalone-driver-v1-5.md +133 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-6f2d18--scan-operator-on-the-gpu-backend.md +64 -0
- openproj-0.39.0/tests/fixtures/corpus/pitches/pitch-7b3e94--year-end-backend-hardening.md +66 -0
- openproj-0.39.0/tests/fixtures/corpus/products/prod-6d1a70--kiln4py.md +20 -0
- openproj-0.39.0/tests/fixtures/corpus/products/prod-7c2b81--hearth.md +37 -0
- openproj-0.39.0/tests/fixtures/corpus/projects/proj-7e57a0--testing.md +20 -0
- openproj-0.39.0/tests/fixtures/corpus/projects/proj-9a4c25--hearth-backend-for-the-bed-solver.md +47 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-0e4b7a--add-unit-tests-for-bed2drum.md +85 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-2b6c94--cleanup-mesh-shape-compute-coeffs-interface.md +42 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-31f6c4--verify-the-throughflow-scheme-in-kiln4py-using-recorded-test-data.md +37 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-3a52d8--compile-the-wrapper-with-f2py-to-generate-the-bedstep-f2py-module.md +36 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-3d84e9--create-the-interface-for-running-throughflow-in-kiln4py.md +42 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-3e07b2--generate-python-bindings-with-f2py-for-bedstep-setup-config-and-bedstep-run.md +37 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-53a9f0--reproduce-2gpu-seam-artefact.md +33 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-58d7c6--per-field-delta-tolerances.md +27 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-5a4e39--read-the-2014-stable-summation-paper.md +28 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-5c1d84--try-deterministic-means.md +30 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-5f062b--downgrade-numpy-for-global-sums.md +28 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-6a5c02--lower-the-scan-operator.md +41 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-6b7d31--bench-the-scan-against-the-cpu-reference.md +36 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-7c8e40--freeze-the-backend-api.md +54 -0
- openproj-0.39.0/tests/fixtures/corpus/tasks/task-7d9f52--rerun-the-backend-suite-after-the-shutdown.md +42 -0
- openproj-0.39.0/tests/fixtures/roundtrip.md +39 -0
- openproj-0.39.0/tests/js/drive.js +921 -0
- openproj-0.39.0/tests/js/yjs-seed.js +64 -0
- openproj-0.39.0/tests/load/adversarial.py +1026 -0
- openproj-0.39.0/tests/load/churn.py +1328 -0
- openproj-0.39.0/tests/load/coedit_same_room.py +834 -0
- openproj-0.39.0/tests/load/corpus.py +160 -0
- openproj-0.39.0/tests/load/counters.py +138 -0
- openproj-0.39.0/tests/load/detail_cost.py +166 -0
- openproj-0.39.0/tests/load/diverge.py +233 -0
- openproj-0.39.0/tests/load/divergence.py +215 -0
- openproj-0.39.0/tests/load/drive.py +177 -0
- openproj-0.39.0/tests/load/harness.py +427 -0
- openproj-0.39.0/tests/load/herd.py +111 -0
- openproj-0.39.0/tests/load/index_warm.py +131 -0
- openproj-0.39.0/tests/load/lockout.py +282 -0
- openproj-0.39.0/tests/load/lost_line.py +115 -0
- openproj-0.39.0/tests/load/measure.py +164 -0
- openproj-0.39.0/tests/load/merge_probe.py +83 -0
- openproj-0.39.0/tests/load/micro.py +127 -0
- openproj-0.39.0/tests/load/probe_ceiling.py +89 -0
- openproj-0.39.0/tests/load/probe_emoji.py +118 -0
- openproj-0.39.0/tests/load/probe_fanout.py +109 -0
- openproj-0.39.0/tests/load/probe_manyrooms.py +157 -0
- openproj-0.39.0/tests/load/probe_saverace.py +144 -0
- openproj-0.39.0/tests/load/probe_seats.py +128 -0
- openproj-0.39.0/tests/load/probe_shutdown.py +129 -0
- openproj-0.39.0/tests/load/probe_twoinstances.py +112 -0
- openproj-0.39.0/tests/load/probe_writecost.py +178 -0
- openproj-0.39.0/tests/load/queueing.py +212 -0
- openproj-0.39.0/tests/load/readload.py +526 -0
- openproj-0.39.0/tests/load/room.py +340 -0
- openproj-0.39.0/tests/load/rooms.py +139 -0
- openproj-0.39.0/tests/load/run.py +362 -0
- openproj-0.39.0/tests/load/run.sh +78 -0
- openproj-0.39.0/tests/load/serve_load.py +109 -0
- openproj-0.39.0/tests/load/server.py +70 -0
- openproj-0.39.0/tests/load/users.py +591 -0
- openproj-0.39.0/tests/load/verify.py +435 -0
- openproj-0.39.0/tests/load/write_same.py +738 -0
- openproj-0.39.0/tests/load/writer_ceiling.py +190 -0
- openproj-0.39.0/tests/pages.py +336 -0
- openproj-0.39.0/tests/plans.py +156 -0
- openproj-0.39.0/tests/test_auth.py +602 -0
- openproj-0.39.0/tests/test_card.py +601 -0
- openproj-0.39.0/tests/test_cascade.py +1373 -0
- openproj-0.39.0/tests/test_cli.py +912 -0
- openproj-0.39.0/tests/test_coedit.py +4888 -0
- openproj-0.39.0/tests/test_config.py +259 -0
- openproj-0.39.0/tests/test_credential.py +275 -0
- openproj-0.39.0/tests/test_deck.py +1356 -0
- openproj-0.39.0/tests/test_delete.py +400 -0
- openproj-0.39.0/tests/test_edges.py +222 -0
- openproj-0.39.0/tests/test_editor.py +8753 -0
- openproj-0.39.0/tests/test_exclusion.py +400 -0
- openproj-0.39.0/tests/test_facets.py +570 -0
- openproj-0.39.0/tests/test_gitdoor.py +641 -0
- openproj-0.39.0/tests/test_graph_layout.py +740 -0
- openproj-0.39.0/tests/test_harness.py +108 -0
- openproj-0.39.0/tests/test_headers.py +130 -0
- openproj-0.39.0/tests/test_help.py +352 -0
- openproj-0.39.0/tests/test_hill.py +918 -0
- openproj-0.39.0/tests/test_identity.py +198 -0
- openproj-0.39.0/tests/test_index.py +1278 -0
- openproj-0.39.0/tests/test_injection.py +944 -0
- openproj-0.39.0/tests/test_issues.py +491 -0
- openproj-0.39.0/tests/test_mermaid.py +430 -0
- openproj-0.39.0/tests/test_model.py +400 -0
- openproj-0.39.0/tests/test_notes.py +656 -0
- openproj-0.39.0/tests/test_parse.py +268 -0
- openproj-0.39.0/tests/test_payload.py +235 -0
- openproj-0.39.0/tests/test_product.py +579 -0
- openproj-0.39.0/tests/test_records.py +741 -0
- openproj-0.39.0/tests/test_remote.py +2084 -0
- openproj-0.39.0/tests/test_render.py +6256 -0
- openproj-0.39.0/tests/test_schedule.py +994 -0
- openproj-0.39.0/tests/test_search.py +470 -0
- openproj-0.39.0/tests/test_seats.py +1340 -0
- openproj-0.39.0/tests/test_socket_offer.py +108 -0
- openproj-0.39.0/tests/test_status_gate.py +314 -0
- openproj-0.39.0/tests/test_store.py +1551 -0
- openproj-0.39.0/tests/test_table.py +5840 -0
- openproj-0.39.0/tests/test_teaching.py +609 -0
- openproj-0.39.0/tests/test_themes.py +317 -0
- openproj-0.39.0/tests/test_validate.py +1046 -0
- openproj-0.39.0/tests/test_web.py +6292 -0
- openproj-0.39.0/tests/test_writes.py +693 -0
- openproj-0.39.0/tests/wsclient.py +105 -0
- openproj-0.39.0/tools/README.md +87 -0
- openproj-0.39.0/tools/build-excalidraw.mjs +282 -0
- openproj-0.39.0/tools/excalidraw-entry.js +22 -0
- openproj-0.39.0/tools/excalidraw-package-lock.json +3442 -0
- openproj-0.39.0/tools/excalidraw-package.json +13 -0
- openproj-0.39.0/uv.lock +915 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# What NOT to upload to Cloud Build. Without this file gcloud uploads the whole
|
|
2
|
+
# directory — .git is 20 MB and .venv is 101 MB, none of which the image uses,
|
|
3
|
+
# on every single build.
|
|
4
|
+
#
|
|
5
|
+
# Deliberately not the default gcloudignore behaviour of honouring .gitignore:
|
|
6
|
+
# .venv is git-ignored and would be excluded, but so is nothing else that
|
|
7
|
+
# matters, and an ignore file that silently follows another file is one more
|
|
8
|
+
# place a build can lose something it needed. Everything here is explicit.
|
|
9
|
+
|
|
10
|
+
.git/
|
|
11
|
+
.gitignore
|
|
12
|
+
.worktrees/
|
|
13
|
+
.venv/
|
|
14
|
+
**/__pycache__/
|
|
15
|
+
**/*.pyc
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
|
|
19
|
+
# Not part of the running service.
|
|
20
|
+
#
|
|
21
|
+
# `docs/` was here and is not: the Help page reads those files off the disk at
|
|
22
|
+
# render time, so they ARE part of the running service now. The build failed at
|
|
23
|
+
# `COPY docs/` with "file not found in build context or excluded by
|
|
24
|
+
# .dockerignore" — on a commit whose whole test suite was green, because nothing
|
|
25
|
+
# in CI builds an image. `test_the_container_carries_the_documents` reads this
|
|
26
|
+
# file as well as the Dockerfile for exactly that reason: two files decide
|
|
27
|
+
# whether a document reaches production and only one of them was being checked.
|
|
28
|
+
tests/
|
|
29
|
+
deploy/RUNBOOK.md
|
|
30
|
+
gcloud_deploy.sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uv.lock merge=binary
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# coedit — 106.5s exact, anchored on tests/test_coedit.py (76.4s).
|
|
2
|
+
#
|
|
3
|
+
# Seconds are per FILE and EXACT, from docs/probes/ci-durations-4core.txt — every
|
|
4
|
+
# phase of every test on the 4-core public runner, run 32754826551. The older
|
|
5
|
+
# tables in docs/probes/ were taken on the 2-core box a PRIVATE repository gets,
|
|
6
|
+
# AND at `--durations-min=0.05`, and both halves of that were wrong for this cut:
|
|
7
|
+
# the leg order inverted when the cores doubled, and the 50ms floor hid 37s of
|
|
8
|
+
# the suite on exactly the leg that had 1103 tests. Modelling that hidden cost
|
|
9
|
+
# was tried and the fit did not hold — see docs/probes/ci-speed.md § 0b. The
|
|
10
|
+
# threshold is zero here, so there is nothing left to model.
|
|
11
|
+
#
|
|
12
|
+
# The cut is what a longest-first pass makes of those numbers, and it lands at
|
|
13
|
+
# 106.4-106.5s a leg: a 0.1s spread, against 93.0-119.8s on the cut it replaces.
|
|
14
|
+
# `tests/test_editor.py` is 101.6s of the 106.4s it sits in, so this is 5s off
|
|
15
|
+
# the floor and there is nothing further to win by moving files.
|
|
16
|
+
#
|
|
17
|
+
# **Balance is not what to spend the next hour on.** Runs of the SAME lists put a
|
|
18
|
+
# leg 44s apart — `views` measured 76.57s and 120.37s across four — so no re-cut
|
|
19
|
+
# is visible in one run, and four were not enough to separate this cut from the
|
|
20
|
+
# one it replaced (120.4s against 122.8s, in either direction depending on which
|
|
21
|
+
# runs you take). What would actually move the gate is the floor, and the floor
|
|
22
|
+
# is one file.
|
|
23
|
+
#
|
|
24
|
+
# Real sockets, a real uvicorn on a real port, and literal `sleep(0.5)` budgets.
|
|
25
|
+
# This leg exists so those stay serial, in file order, in one process, on a
|
|
26
|
+
# machine nothing else is running AT THE SAME TIME — the condition every one of
|
|
27
|
+
# those margins was measured under. Ballast is fine and always was: the other
|
|
28
|
+
# files here run before or after it in the same process, never beside it.
|
|
29
|
+
#
|
|
30
|
+
# **What must never happen is this leg being internally parallelised**, and that
|
|
31
|
+
# is also why `pytest-xdist` cannot help the gate even where it helps a leg: the
|
|
32
|
+
# critical path is bounded by a leg that is not allowed to split, so making the
|
|
33
|
+
# other four faster buys total machine seconds and zero wall clock. Measured, in
|
|
34
|
+
# docs/probes/ci-speed.md § 0b.
|
|
35
|
+
tests/test_coedit.py
|
|
36
|
+
tests/test_hill.py
|
|
37
|
+
tests/test_deck.py
|
|
38
|
+
tests/test_gitdoor.py
|
|
39
|
+
tests/test_cli.py
|
|
40
|
+
tests/test_headers.py
|
|
41
|
+
tests/test_product.py
|
|
42
|
+
tests/test_identity.py
|
|
43
|
+
tests/test_socket_offer.py
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# editor — 106.4s exact, anchored on tests/test_editor.py (101.6s).
|
|
2
|
+
#
|
|
3
|
+
# Seconds are per FILE and EXACT, from docs/probes/ci-durations-4core.txt — every
|
|
4
|
+
# phase of every test on the 4-core public runner, run 32754826551. The older
|
|
5
|
+
# tables in docs/probes/ were taken on the 2-core box a PRIVATE repository gets,
|
|
6
|
+
# AND at `--durations-min=0.05`, and both halves of that were wrong for this cut:
|
|
7
|
+
# the leg order inverted when the cores doubled, and the 50ms floor hid 37s of
|
|
8
|
+
# the suite on exactly the leg that had 1103 tests. Modelling that hidden cost
|
|
9
|
+
# was tried and the fit did not hold — see docs/probes/ci-speed.md § 0b. The
|
|
10
|
+
# threshold is zero here, so there is nothing left to model.
|
|
11
|
+
#
|
|
12
|
+
# The cut is what a longest-first pass makes of those numbers, and it lands at
|
|
13
|
+
# 106.4-106.5s a leg: a 0.1s spread, against 93.0-119.8s on the cut it replaces.
|
|
14
|
+
# `tests/test_editor.py` is 101.6s of the 106.4s it sits in, so this is 5s off
|
|
15
|
+
# the floor and there is nothing further to win by moving files.
|
|
16
|
+
#
|
|
17
|
+
# **Balance is not what to spend the next hour on.** Runs of the SAME lists put a
|
|
18
|
+
# leg 44s apart — `views` measured 76.57s and 120.37s across four — so no re-cut
|
|
19
|
+
# is visible in one run, and four were not enough to separate this cut from the
|
|
20
|
+
# one it replaced (120.4s against 122.8s, in either direction depending on which
|
|
21
|
+
# runs you take). What would actually move the gate is the floor, and the floor
|
|
22
|
+
# is one file.
|
|
23
|
+
#
|
|
24
|
+
# THE FLOOR. 105 tests, 53 of the suite's real-Chrome call sites, each a fresh
|
|
25
|
+
# browser laying out a page carrying 594 KB of inlined Ace. 101.6s on its own, so
|
|
26
|
+
# no arrangement of these lists puts a leg below it and the gate cannot go under
|
|
27
|
+
# about 102s of pytest however many machines it is given. It is also the reason
|
|
28
|
+
# `pytest-xdist` was measured and refused: `-n 2` on this leg is 27% SLOWER, not
|
|
29
|
+
# faster, because the cost is process startup and two Chromes on four cores
|
|
30
|
+
# contend. Going below 101.6s means splitting the file, and its natural seam is
|
|
31
|
+
# still `?editor=ace` against `?editor=plain`.
|
|
32
|
+
tests/test_editor.py
|
|
33
|
+
tests/test_writes.py
|
|
34
|
+
tests/test_notes.py
|
|
35
|
+
tests/test_issues.py
|
|
36
|
+
tests/test_harness.py
|
|
37
|
+
tests/test_config.py
|
|
38
|
+
tests/test_model.py
|
|
39
|
+
tests/test_auth.py
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# graph — 106.5s exact, anchored on tests/test_graph_layout.py (32.2s).
|
|
2
|
+
#
|
|
3
|
+
# Seconds are per FILE and EXACT, from docs/probes/ci-durations-4core.txt — every
|
|
4
|
+
# phase of every test on the 4-core public runner, run 32754826551. The older
|
|
5
|
+
# tables in docs/probes/ were taken on the 2-core box a PRIVATE repository gets,
|
|
6
|
+
# AND at `--durations-min=0.05`, and both halves of that were wrong for this cut:
|
|
7
|
+
# the leg order inverted when the cores doubled, and the 50ms floor hid 37s of
|
|
8
|
+
# the suite on exactly the leg that had 1103 tests. Modelling that hidden cost
|
|
9
|
+
# was tried and the fit did not hold — see docs/probes/ci-speed.md § 0b. The
|
|
10
|
+
# threshold is zero here, so there is nothing left to model.
|
|
11
|
+
#
|
|
12
|
+
# The cut is what a longest-first pass makes of those numbers, and it lands at
|
|
13
|
+
# 106.4-106.5s a leg: a 0.1s spread, against 93.0-119.8s on the cut it replaces.
|
|
14
|
+
# `tests/test_editor.py` is 101.6s of the 106.4s it sits in, so this is 5s off
|
|
15
|
+
# the floor and there is nothing further to win by moving files.
|
|
16
|
+
#
|
|
17
|
+
# **Balance is not what to spend the next hour on.** Runs of the SAME lists put a
|
|
18
|
+
# leg 44s apart — `views` measured 76.57s and 120.37s across four — so no re-cut
|
|
19
|
+
# is visible in one run, and four were not enough to separate this cut from the
|
|
20
|
+
# one it replaced (120.4s against 122.8s, in either direction depending on which
|
|
21
|
+
# runs you take). What would actually move the gate is the floor, and the floor
|
|
22
|
+
# is one file.
|
|
23
|
+
#
|
|
24
|
+
# The files whose cost is starting something — a fresh headless Chrome per canvas
|
|
25
|
+
# assertion, a fresh node per injection census — rather than rendering. This is
|
|
26
|
+
# the group that gained most from four cores: it was 131.2s on two and 93.0s on
|
|
27
|
+
# four, which is most of why the old cut inverted.
|
|
28
|
+
tests/test_graph_layout.py
|
|
29
|
+
tests/test_facets.py
|
|
30
|
+
tests/test_injection.py
|
|
31
|
+
tests/test_web.py
|
|
32
|
+
tests/test_themes.py
|
|
33
|
+
tests/test_payload.py
|
|
34
|
+
tests/test_records.py
|
|
35
|
+
tests/test_exclusion.py
|
|
36
|
+
tests/test_credential.py
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# rest — 106.4s exact, anchored on tests/test_table.py (38.9s).
|
|
2
|
+
#
|
|
3
|
+
# Seconds are per FILE and EXACT, from docs/probes/ci-durations-4core.txt — every
|
|
4
|
+
# phase of every test on the 4-core public runner, run 32754826551. The older
|
|
5
|
+
# tables in docs/probes/ were taken on the 2-core box a PRIVATE repository gets,
|
|
6
|
+
# AND at `--durations-min=0.05`, and both halves of that were wrong for this cut:
|
|
7
|
+
# the leg order inverted when the cores doubled, and the 50ms floor hid 37s of
|
|
8
|
+
# the suite on exactly the leg that had 1103 tests. Modelling that hidden cost
|
|
9
|
+
# was tried and the fit did not hold — see docs/probes/ci-speed.md § 0b. The
|
|
10
|
+
# threshold is zero here, so there is nothing left to model.
|
|
11
|
+
#
|
|
12
|
+
# The cut is what a longest-first pass makes of those numbers, and it lands at
|
|
13
|
+
# 106.4-106.5s a leg: a 0.1s spread, against 93.0-119.8s on the cut it replaces.
|
|
14
|
+
# `tests/test_editor.py` is 101.6s of the 106.4s it sits in, so this is 5s off
|
|
15
|
+
# the floor and there is nothing further to win by moving files.
|
|
16
|
+
#
|
|
17
|
+
# **Balance is not what to spend the next hour on.** Runs of the SAME lists put a
|
|
18
|
+
# leg 44s apart — `views` measured 76.57s and 120.37s across four — so no re-cut
|
|
19
|
+
# is visible in one run, and four were not enough to separate this cut from the
|
|
20
|
+
# one it replaced (120.4s against 122.8s, in either direction depending on which
|
|
21
|
+
# runs you take). What would actually move the gate is the floor, and the floor
|
|
22
|
+
# is one file.
|
|
23
|
+
#
|
|
24
|
+
# NO LONGER "everything else". It is the table, the query language, the hover
|
|
25
|
+
# card and the resolved cascade, plus what balances them. The name is kept
|
|
26
|
+
# anyway: renaming a matrix value renames a status check, the one check branch
|
|
27
|
+
# protection requires is `check`, which fans these in, and a rename is a
|
|
28
|
+
# branch-protection edit in exchange for a word.
|
|
29
|
+
#
|
|
30
|
+
# **Where a new test file goes.** It used to be "here, unless it is slow enough
|
|
31
|
+
# to be worth naming", and that rule went with the property that justified it. It
|
|
32
|
+
# goes on whichever leg the newest table in docs/probes/ says is cheapest, and
|
|
33
|
+
# `test_every_test_file_is_in_exactly_one_ci_shard` in tests/test_harness.py is
|
|
34
|
+
# what stops it going on no leg at all.
|
|
35
|
+
tests/test_table.py
|
|
36
|
+
tests/test_mermaid.py
|
|
37
|
+
tests/test_search.py
|
|
38
|
+
tests/test_card.py
|
|
39
|
+
tests/test_cascade.py
|
|
40
|
+
tests/test_edges.py
|
|
41
|
+
tests/test_teaching.py
|
|
42
|
+
tests/test_index.py
|
|
43
|
+
tests/test_parse.py
|
|
44
|
+
tests/test_store.py
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# views — 106.4s exact, anchored on tests/test_render.py (76.4s).
|
|
2
|
+
#
|
|
3
|
+
# Seconds are per FILE and EXACT, from docs/probes/ci-durations-4core.txt — every
|
|
4
|
+
# phase of every test on the 4-core public runner, run 32754826551. The older
|
|
5
|
+
# tables in docs/probes/ were taken on the 2-core box a PRIVATE repository gets,
|
|
6
|
+
# AND at `--durations-min=0.05`, and both halves of that were wrong for this cut:
|
|
7
|
+
# the leg order inverted when the cores doubled, and the 50ms floor hid 37s of
|
|
8
|
+
# the suite on exactly the leg that had 1103 tests. Modelling that hidden cost
|
|
9
|
+
# was tried and the fit did not hold — see docs/probes/ci-speed.md § 0b. The
|
|
10
|
+
# threshold is zero here, so there is nothing left to model.
|
|
11
|
+
#
|
|
12
|
+
# The cut is what a longest-first pass makes of those numbers, and it lands at
|
|
13
|
+
# 106.4-106.5s a leg: a 0.1s spread, against 93.0-119.8s on the cut it replaces.
|
|
14
|
+
# `tests/test_editor.py` is 101.6s of the 106.4s it sits in, so this is 5s off
|
|
15
|
+
# the floor and there is nothing further to win by moving files.
|
|
16
|
+
#
|
|
17
|
+
# **Balance is not what to spend the next hour on.** Runs of the SAME lists put a
|
|
18
|
+
# leg 44s apart — `views` measured 76.57s and 120.37s across four — so no re-cut
|
|
19
|
+
# is visible in one run, and four were not enough to separate this cut from the
|
|
20
|
+
# one it replaced (120.4s against 122.8s, in either direction depending on which
|
|
21
|
+
# runs you take). What would actually move the gate is the floor, and the floor
|
|
22
|
+
# is one file.
|
|
23
|
+
#
|
|
24
|
+
# `tests/test_render.py` draws every page and then measures it, and it is the
|
|
25
|
+
# second-largest file in the suite. `tests/test_seats.py` is beside it rather
|
|
26
|
+
# than on `coedit` because a seat band is drawn by the same rendering path, and
|
|
27
|
+
# because `coedit` had no room for it.
|
|
28
|
+
tests/test_render.py
|
|
29
|
+
tests/test_help.py
|
|
30
|
+
tests/test_seats.py
|
|
31
|
+
tests/test_delete.py
|
|
32
|
+
tests/test_status_gate.py
|
|
33
|
+
tests/test_schedule.py
|
|
34
|
+
tests/test_remote.py
|
|
35
|
+
tests/test_validate.py
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Pull requests before the merge, and `main` after it. Gating only the PR leaves
|
|
4
|
+
# the branch everything is cut from unverified the moment two separately-green
|
|
5
|
+
# PRs land and interact.
|
|
6
|
+
#
|
|
7
|
+
# EVERY pull request, and not only the ones based on `main`. The filter that used
|
|
8
|
+
# to be here was `branches: [main]`, and what it did to a stacked PR is the worst
|
|
9
|
+
# available failure: #92 was opened against the `thinking` branch, GitHub matched
|
|
10
|
+
# no trigger, and `gh pr checks` answered "no checks reported" — which is the same
|
|
11
|
+
# answer it gives while a run is still queuing. A branch built on an unmerged one
|
|
12
|
+
# therefore sat un-gated and looked exactly like a branch waiting its turn.
|
|
13
|
+
#
|
|
14
|
+
# AGENTS.md already documents the sibling trap, where a `CONFLICTING` PR has no
|
|
15
|
+
# `refs/pull/<n>/merge` for the workflow to check out, and says to ask
|
|
16
|
+
# `gh pr view <n> --json mergeable` before asking anything else. That question
|
|
17
|
+
# answers `MERGEABLE`/`CLEAN` here, so the documented check does not find this
|
|
18
|
+
# one. Removing the filter is what makes the check trustworthy again.
|
|
19
|
+
#
|
|
20
|
+
# `push` stays pinned to `main`: a push run gates the branch everything is cut
|
|
21
|
+
# from, and every other branch already reaches this workflow through its PR.
|
|
22
|
+
on:
|
|
23
|
+
pull_request:
|
|
24
|
+
push:
|
|
25
|
+
branches: [main]
|
|
26
|
+
|
|
27
|
+
# A PR's third push makes the first two runs' answers worthless, and they go on
|
|
28
|
+
# holding a runner while the answer that matters queues behind them. With six
|
|
29
|
+
# jobs per run instead of one this matters six times as much.
|
|
30
|
+
concurrency:
|
|
31
|
+
group: ci-${{ github.ref }}
|
|
32
|
+
cancel-in-progress: true
|
|
33
|
+
|
|
34
|
+
# Three job kinds, and the reason there are three is measured in
|
|
35
|
+
# `docs/probes/ci-speed.md`: a job on this repository costs about a dozen seconds
|
|
36
|
+
# of setup, not the ninety everybody assumes, so buying a whole extra machine is
|
|
37
|
+
# cheaper than any arrangement of processes on one machine.
|
|
38
|
+
#
|
|
39
|
+
# **The runner is probed, not assumed, and the answer changed under this file.**
|
|
40
|
+
# It said 2-core / 8 GB, citing run 32633361097, and added "`ubuntu-latest` is
|
|
41
|
+
# 4-core only for public repositories and this one is private". The repository
|
|
42
|
+
# went public on 2026-08-23 and the box is now **4-core / 15.6 GB**, probed again
|
|
43
|
+
# in run 32753487844 — the probe step below prints it on every leg, because "the
|
|
44
|
+
# runner" is five runners and nothing promises they stay the same size.
|
|
45
|
+
#
|
|
46
|
+
# That did not change the answer and it did change the sums. Five machines still
|
|
47
|
+
# beat processes on one, and the shard cut was re-made from
|
|
48
|
+
# `docs/probes/ci-durations-4core.txt` because the old one had INVERTED: the leg
|
|
49
|
+
# order on 2 cores was editor > views > graph > rest > coedit and on 4 cores it
|
|
50
|
+
# is rest > views > coedit > editor > graph. The Chrome-heavy legs took the extra
|
|
51
|
+
# cores and the plain-Python one did not.
|
|
52
|
+
jobs:
|
|
53
|
+
# Ruff alone, on its own machine, finishing in about twenty-five seconds.
|
|
54
|
+
#
|
|
55
|
+
# Worth ZERO seconds of wall clock on a green run, and that is worth saying
|
|
56
|
+
# plainly because the theory it looks like it comes from — "ruff runs serially
|
|
57
|
+
# in front of a twenty-minute suite" — was measured at 0s and is false. What it
|
|
58
|
+
# buys is how fast a RED run answers: a misplaced import is reported in
|
|
59
|
+
# twenty-five seconds instead of after the whole suite. The shards deliberately
|
|
60
|
+
# do NOT `needs: [lint]`; a lint error and a test failure are different
|
|
61
|
+
# questions and you want both answers from one push.
|
|
62
|
+
lint:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v7
|
|
66
|
+
|
|
67
|
+
# Pinned to an exact release rather than a major, and alone in that: astral
|
|
68
|
+
# stopped publishing moving major tags after v7, so `@v10` is a 404 and not
|
|
69
|
+
# a pin. Bump this deliberately; the other two follow their major.
|
|
70
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
71
|
+
with:
|
|
72
|
+
# 3.12 because that is what the Dockerfile ships and what ruff targets.
|
|
73
|
+
# A gate that is green on a version the deploy never runs is not a gate.
|
|
74
|
+
python-version: "3.12"
|
|
75
|
+
enable-cache: true
|
|
76
|
+
|
|
77
|
+
# `--locked`, as in the Dockerfile: it fails on a `pyproject.toml` edited
|
|
78
|
+
# without relocking, instead of quietly testing a resolution that will
|
|
79
|
+
# never be the one deployed. Doing it here as well as in the shards is not
|
|
80
|
+
# duplication for its own sake — this is the job that reaches the sync step
|
|
81
|
+
# first, so a bump-without-relock is red in twenty-five seconds instead of
|
|
82
|
+
# in whatever the slowest shard costs.
|
|
83
|
+
- run: uv sync --locked --group dev
|
|
84
|
+
|
|
85
|
+
- run: uv run ruff check .
|
|
86
|
+
|
|
87
|
+
# No `ruff format --check` yet, on purpose: 32 of the 103 files predate any
|
|
88
|
+
# formatter and would fail it today, so adding it now commits a gate that
|
|
89
|
+
# is red on arrival — and a gate that is red on arrival teaches everybody
|
|
90
|
+
# to read past red. One `uv run ruff format .`, as its own commit touching
|
|
91
|
+
# nothing else, is what makes this line safe to add.
|
|
92
|
+
#
|
|
93
|
+
# Nothing here checks the shard lists, on purpose. They are hand-written
|
|
94
|
+
# and a hand-written list of files fails OPEN — add `tests/test_new.py`,
|
|
95
|
+
# forget the list, and it runs on no machine at all while every job stays
|
|
96
|
+
# green — but that is already held, once, by
|
|
97
|
+
# `test_every_test_file_is_in_exactly_one_ci_shard` in `tests/test_harness.py`,
|
|
98
|
+
# which reads the same files and answers on a laptop as well as here. An
|
|
99
|
+
# invariant written twice gets guarded once.
|
|
100
|
+
|
|
101
|
+
# The suite, cut five ways across five machines.
|
|
102
|
+
#
|
|
103
|
+
# The cut is in `.github/shards/<name>`, one path per line, and it is made from
|
|
104
|
+
# `docs/probes/ci-durations-4core.txt` — this hardware's table, not either of
|
|
105
|
+
# the 2-core ones, because the legs did not merely shrink when the cores
|
|
106
|
+
# doubled, they changed order. And at `--durations-min=0`, because the 0.05
|
|
107
|
+
# threshold the older tables used hid 37s of the suite, almost none of it
|
|
108
|
+
# evenly: 16s of that was on the leg with 1103 tests. 532.3s of exact rows over
|
|
109
|
+
# 42 files. Per leg the cut is 106.4-106.5s, a spread of 0.1s, against
|
|
110
|
+
# 93.0-119.8s for the lists it replaces.
|
|
111
|
+
#
|
|
112
|
+
# **That is a 13s improvement in expectation and it is invisible in any single
|
|
113
|
+
# run.** Two runs of identical lists put one leg 23s apart. Quote a median over
|
|
114
|
+
# several runs or quote nothing, and do not spend an afternoon re-cutting: what
|
|
115
|
+
# bounds this gate is `tests/test_editor.py` at 101.6s, which is one file.
|
|
116
|
+
#
|
|
117
|
+
# FIVE, and five is still the ceiling on four cores — re-derived rather than
|
|
118
|
+
# inherited. The break-even for a shard is `T/(K·(K−1)) > setup`; at T≈501s of
|
|
119
|
+
# attributed cost and ~12s of setup that allows six, and balance stops it
|
|
120
|
+
# first, exactly as it did before. `tests/test_editor.py` is 94.4s on its own —
|
|
121
|
+
# 105 tests, 53 of the suite's real-Chrome call sites, every one laying out a
|
|
122
|
+
# page carrying 594 KB of inlined Ace — and no file-level scheme goes below
|
|
123
|
+
# that floor. A perfect five-way is 100.3s of attributed cost; a perfect
|
|
124
|
+
# six-way would be 83.6s and cannot be reached, because the sixth machine lands
|
|
125
|
+
# on this one file at 94.4s. Six machines to save six seconds, and a seventh
|
|
126
|
+
# billing rounding to pay for it. Going below 94.4s means splitting that one
|
|
127
|
+
# file, and its natural seam is still `?editor=ace` against `?editor=plain`.
|
|
128
|
+
#
|
|
129
|
+
# `coedit` is a shard for the opposite reason to `editor`: it is the only leg
|
|
130
|
+
# with real sockets, a real uvicorn on a real port, and literal `sleep(0.5)`
|
|
131
|
+
# budgets. It must not be reordered or internally parallelised, and its own
|
|
132
|
+
# machine is how that stays true for free. That is also the one constraint the
|
|
133
|
+
# re-cut was not allowed to optimise away, and it is why a `-n 2` inside each
|
|
134
|
+
# shard could never be a blanket answer even if it paid on the other four.
|
|
135
|
+
suite:
|
|
136
|
+
strategy:
|
|
137
|
+
# One red shard must not cancel the other four. The whole point of a
|
|
138
|
+
# three-minute gate is getting every piece of bad news from one push.
|
|
139
|
+
fail-fast: false
|
|
140
|
+
matrix:
|
|
141
|
+
# Longest first: legs start roughly in this order, and `editor` is the
|
|
142
|
+
# critical path, so it should never be the one waiting for a runner.
|
|
143
|
+
shard: [editor, views, graph, coedit, rest]
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
steps:
|
|
146
|
+
# Full checkout, never sparse — the obvious optimisation here is wrong. 27
|
|
147
|
+
# of the 47 files in `tests/` import from another file in `tests/`
|
|
148
|
+
# (`test_editor.py` alone imports `test_web`, `test_store`,
|
|
149
|
+
# `test_injection`, `pages` and `browser`), and `tests/test_remote.py:375`
|
|
150
|
+
# asks for a fixture defined in `test_store` by name. A shard missing an
|
|
151
|
+
# import hub fails at COLLECTION, which is a shard reporting nothing rather
|
|
152
|
+
# than a shard reporting a failure. The whole tree is one second.
|
|
153
|
+
- uses: actions/checkout@v7
|
|
154
|
+
|
|
155
|
+
# Pinned to an exact release rather than a major, and alone in that: astral
|
|
156
|
+
# stopped publishing moving major tags after v7, so `@v10` is a 404 and not
|
|
157
|
+
# a pin. Bump this deliberately; the other two follow their major.
|
|
158
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
159
|
+
with:
|
|
160
|
+
# 3.12 because that is what the Dockerfile ships and what ruff targets.
|
|
161
|
+
# A gate that is green on a version the deploy never runs is not a gate.
|
|
162
|
+
python-version: "3.12"
|
|
163
|
+
enable-cache: true
|
|
164
|
+
|
|
165
|
+
# Thirty-four tests drive the shipped JavaScript through `tests/js/drive.js`
|
|
166
|
+
# and skip themselves when node is missing, so without this step the suite
|
|
167
|
+
# goes green by not running them — green for the wrong reason, which is the
|
|
168
|
+
# one failure a gate must never have. node is needed to test, never to
|
|
169
|
+
# build or run: nothing here is a build step, and there is no npm. Every
|
|
170
|
+
# shard installs it, including the ones nobody thinks need it: a shard that
|
|
171
|
+
# silently stops driving JS is exactly the failure this step exists to
|
|
172
|
+
# prevent, and eight seconds of cached tarball is not worth reasoning about
|
|
173
|
+
# per leg.
|
|
174
|
+
- uses: actions/setup-node@v7
|
|
175
|
+
with:
|
|
176
|
+
node-version: "24"
|
|
177
|
+
|
|
178
|
+
# WHAT THIS MACHINE ACTUALLY IS. The paragraph above `jobs:` said 2-core /
|
|
179
|
+
# 8 GB and cited a run id, which was the right way to settle it and is now
|
|
180
|
+
# the wrong answer: that measurement was taken while this repository was
|
|
181
|
+
# PRIVATE, and a public repository gets a different box. The number decided
|
|
182
|
+
# the whole shape of this file — five machines rather than `-n auto` on one
|
|
183
|
+
# — so it is not a number to inherit from a document. It is printed on
|
|
184
|
+
# every leg rather than probed once, because "the runner" is five runners
|
|
185
|
+
# and nothing promises they are the same size.
|
|
186
|
+
- run: |
|
|
187
|
+
echo "nproc: $(nproc)"
|
|
188
|
+
echo "memory: $(awk '/MemTotal/{printf "%.1f GB", $2/1048576}' /proc/meminfo)"
|
|
189
|
+
echo "chrome: $(google-chrome --version 2>/dev/null || echo absent)"
|
|
190
|
+
echo "node: $(node --version)"
|
|
191
|
+
echo "runner: ${RUNNER_OS} ${RUNNER_ARCH}"
|
|
192
|
+
|
|
193
|
+
# `--locked`, as in the Dockerfile: it fails on a `pyproject.toml` edited
|
|
194
|
+
# without relocking, instead of quietly testing a resolution that will
|
|
195
|
+
# never be the one deployed.
|
|
196
|
+
- run: uv sync --locked --group dev
|
|
197
|
+
|
|
198
|
+
# The pixel tests look for `google-chrome` on PATH and ubuntu-latest ships
|
|
199
|
+
# one, so headless Chrome needs no step of its own here. If that stops
|
|
200
|
+
# being true they skip with a stated reason rather than fail, and `-ra`
|
|
201
|
+
# (set in pyproject, deliberately not `-q`) prints every skip — so a suite
|
|
202
|
+
# that has quietly stopped checking pixels says so in the log instead of
|
|
203
|
+
# just looking green.
|
|
204
|
+
#
|
|
205
|
+
# `$files` is unquoted on purpose: the shard file is one path per line and
|
|
206
|
+
# the words are meant to split into separate pytest arguments. `sed` strips
|
|
207
|
+
# the `#` commentary the lists carry, matching what the census test in
|
|
208
|
+
# `tests/test_harness.py` accepts — a shard file the test reads and this
|
|
209
|
+
# line cannot is a trap set for whoever adds the next comment. It is a
|
|
210
|
+
# command substitution rather than `xargs` because `xargs` is allowed to
|
|
211
|
+
# split a long list into two invocations, and two pytest runs in one step
|
|
212
|
+
# would report two summary lines and one exit code. The emptiness check is
|
|
213
|
+
# what makes a missing or all-comment list red here: bare `pytest` with no
|
|
214
|
+
# paths falls back to `testpaths` and runs the WHOLE suite in every shard,
|
|
215
|
+
# which is slow rather than wrong, and therefore invisible.
|
|
216
|
+
#
|
|
217
|
+
# `--durations=25` stays after the measurement flags came out. The probe
|
|
218
|
+
# was `--durations=0 --durations-min=0.05`, which is 732 rows and is what
|
|
219
|
+
# `docs/probes/ci-durations*.txt` hold; 25 is the number that keeps this
|
|
220
|
+
# line useful without keeping the probe. What decides the critical path is
|
|
221
|
+
# shard BALANCE, and balance drifts every time a test file is added — 25
|
|
222
|
+
# rows per leg is enough to see which file grew and re-cut the lists,
|
|
223
|
+
# without anybody having to re-run a measurement workflow to find out. It
|
|
224
|
+
# is display-only: pytest times every phase whether or not it prints them.
|
|
225
|
+
- run: |
|
|
226
|
+
files=$(sed 's/#.*//' .github/shards/${{ matrix.shard }})
|
|
227
|
+
[ -n "$files" ] || { echo "shard ${{ matrix.shard }} names no test files"; exit 1; }
|
|
228
|
+
uv run pytest -q --durations=25 $files
|
|
229
|
+
|
|
230
|
+
# The one required status check, and the reason it is a separate job.
|
|
231
|
+
#
|
|
232
|
+
# Branch protection on `main` requires exactly one context, literally `check`,
|
|
233
|
+
# with `enforce_admins: true`. A `strategy.matrix` on a job named `check`
|
|
234
|
+
# renames the contexts to `check (editor)`, `check (views)` and so on, and
|
|
235
|
+
# separate jobs remove the name entirely. GitHub does not fail a PR for a
|
|
236
|
+
# required context that stopped existing in the way people expect: the PR sits
|
|
237
|
+
# pending for ever, or — if the old name is dropped from the required list
|
|
238
|
+
# without the new ones being added — every shard is red and the merge button is
|
|
239
|
+
# green. That is the sharding failure mode that ships a bug instead of blocking
|
|
240
|
+
# one, so the name does not move. The shards are called `suite`, and this job
|
|
241
|
+
# keeps the name the protection rule already knows.
|
|
242
|
+
check:
|
|
243
|
+
needs: [suite, lint]
|
|
244
|
+
# `!cancelled()` and NOT `always()`. `always()` runs this gate even while the
|
|
245
|
+
# run is being torn down, which is precisely what `cancel-in-progress` above
|
|
246
|
+
# does on every second push — reporting a red required check on a run nobody
|
|
247
|
+
# was waiting for. `!cancelled()` still runs when a shard FAILS, which is the
|
|
248
|
+
# case that has to be caught.
|
|
249
|
+
if: '!cancelled()'
|
|
250
|
+
runs-on: ubuntu-latest
|
|
251
|
+
steps:
|
|
252
|
+
# `needs.suite.result` on a matrix job is the aggregate: `success` only if
|
|
253
|
+
# every leg succeeded. Skipped and cancelled are not `success`, so both
|
|
254
|
+
# `[ … ]` tests fail closed on anything that is not a clean green.
|
|
255
|
+
- run: |
|
|
256
|
+
[ "${{ needs.suite.result }}" = success ] || exit 1
|
|
257
|
+
[ "${{ needs.lint.result }}" = success ] || exit 1
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI, so that `uvx openproj new …` is the whole of what somebody has
|
|
4
|
+
# to type. It already works without this file — `uvx --from
|
|
5
|
+
# git+https://github.com/jcanton/openproj openproj new …` builds the wheel from
|
|
6
|
+
# a public clone in about four seconds — and the difference PyPI buys is only
|
|
7
|
+
# that a person or an agent does not have to know the repository URL to reach
|
|
8
|
+
# the tool. That is a real difference for a command whose whole reason to exist
|
|
9
|
+
# is that nobody should have to look anything up first.
|
|
10
|
+
#
|
|
11
|
+
# On a tag and nothing else. `main` is pushed to many times a day and a version
|
|
12
|
+
# on PyPI cannot be replaced or re-used — deleting a release only burns the
|
|
13
|
+
# number — so the thing that publishes has to be a deliberate act, and a tag is
|
|
14
|
+
# the deliberate act this project already makes.
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
tags: ["v*"]
|
|
18
|
+
# Publishing by hand, for the first release, and for the day a tag's run is
|
|
19
|
+
# cancelled by an outage after the tag exists and cannot be pushed again.
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
# One at a time, and never cancelled mid-flight: `cancel-in-progress` on a job
|
|
23
|
+
# that uploads to an index is a way to produce half a release.
|
|
24
|
+
concurrency:
|
|
25
|
+
group: release
|
|
26
|
+
cancel-in-progress: false
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
release:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: pypi
|
|
32
|
+
permissions:
|
|
33
|
+
# Trusted Publishing: PyPI is told to trust *this workflow in this
|
|
34
|
+
# repository*, and the job exchanges a GitHub OIDC token for a
|
|
35
|
+
# short-lived upload token at publish time. Nothing long-lived is stored,
|
|
36
|
+
# so there is no API token in this repository's secrets to leak, to
|
|
37
|
+
# rotate, or to forget to rotate.
|
|
38
|
+
#
|
|
39
|
+
# It needs one thing done by hand, once, by somebody signed in to PyPI:
|
|
40
|
+
# add a pending publisher at https://pypi.org/manage/account/publishing/
|
|
41
|
+
# naming project `openproj`, owner `jcanton`, repository `openproj`,
|
|
42
|
+
# workflow `release.yml`, environment `pypi`. Until that exists this job
|
|
43
|
+
# fails at the last step with `invalid-publisher`, having built and
|
|
44
|
+
# checked the distributions perfectly well.
|
|
45
|
+
id-token: write
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v7
|
|
48
|
+
|
|
49
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
50
|
+
with:
|
|
51
|
+
# 3.12, as in `ci.yml` and the Dockerfile. The wheel is pure Python and
|
|
52
|
+
# `requires-python = ">=3.12"`, so the building interpreter does not
|
|
53
|
+
# end up in the artefact — this is here so the whole repository agrees
|
|
54
|
+
# about one version rather than because this job needs a specific one.
|
|
55
|
+
python-version: "3.12"
|
|
56
|
+
enable-cache: true
|
|
57
|
+
|
|
58
|
+
# The tag and the version in `pyproject.toml` are two copies of one fact,
|
|
59
|
+
# and the failure when they disagree is the expensive kind: a `v0.39.0` tag
|
|
60
|
+
# publishing 0.38.2, with the number now spent and 0.39.0 never on the
|
|
61
|
+
# index. Checked before anything is built, because after the upload there
|
|
62
|
+
# is nothing to do about it.
|
|
63
|
+
- name: The tag and the version must agree
|
|
64
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
65
|
+
run: |
|
|
66
|
+
tagged="${GITHUB_REF_NAME#v}"
|
|
67
|
+
declared="$(uv run --no-project python -c \
|
|
68
|
+
'import tomllib,pathlib;print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
69
|
+
if [ "$tagged" != "$declared" ]; then
|
|
70
|
+
echo "tag $GITHUB_REF_NAME says $tagged and pyproject.toml says $declared" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
echo "both say $declared"
|
|
74
|
+
|
|
75
|
+
- run: uv build
|
|
76
|
+
|
|
77
|
+
# Read back before it is published, the refusal every write path in this
|
|
78
|
+
# project makes. `twine check` is what catches a README that renders as
|
|
79
|
+
# plain text on the project page — which cannot be fixed in place
|
|
80
|
+
# afterwards, only in the next version.
|
|
81
|
+
- run: uvx twine check dist/*
|
|
82
|
+
|
|
83
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.venv/
|
|
4
|
+
.env
|
|
5
|
+
.DS_Store
|
|
6
|
+
|
|
7
|
+
out/
|
|
8
|
+
.worktrees/
|
|
9
|
+
|
|
10
|
+
# What `uv build` writes. The release workflow builds these on a runner and
|
|
11
|
+
# uploads them from there; a copy on somebody's laptop is a build artefact, and
|
|
12
|
+
# one committed by accident is a wheel in the repository that nothing rebuilds.
|
|
13
|
+
dist/
|
|
14
|
+
|
|
15
|
+
# The writer's flock (store.LOCK_FILE). A running server writes its pid into
|
|
16
|
+
# one of these beside the plan it is serving, and a copy of seed/ was carrying a
|
|
17
|
+
# committed one — see the commit that removed it.
|
|
18
|
+
openproj.lock
|
|
19
|
+
|
|
20
|
+
# The subagent-driven-development workspace: ledger, briefs and review packages for a
|
|
21
|
+
# plan in flight. Scratch by construction — git log is the record once the branch lands.
|
|
22
|
+
.superpowers/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# `uvx pre-commit install` turns these on. Deliberately not run from CI: CI runs
|
|
2
|
+
# ruff out of the locked dev group, and two ruffs on two versions disagreeing
|
|
3
|
+
# about one file is a worse failure than either check missing.
|
|
4
|
+
#
|
|
5
|
+
# There is no hook here that runs the test suite. It takes four and a half
|
|
6
|
+
# minutes, and a pre-commit hook that slow does not get waited for — it gets
|
|
7
|
+
# `--no-verify`d, and then uninstalled, by exactly the person it was written to
|
|
8
|
+
# help. The suite is the merge gate's job; this file is only for what is cheap
|
|
9
|
+
# enough to never think about.
|
|
10
|
+
repos:
|
|
11
|
+
# Same version as the ruff resolved in `uv.lock` for the dev group. Bump the
|
|
12
|
+
# two together: a hook that formats a file the way CI's ruff then rejects is
|
|
13
|
+
# the whole problem this is supposed to prevent.
|
|
14
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
15
|
+
rev: v0.16.2
|
|
16
|
+
hooks:
|
|
17
|
+
- id: ruff-check
|
|
18
|
+
args: [--fix]
|
|
19
|
+
# This repo is not format-clean yet (32 files of 103), and the hook only
|
|
20
|
+
# touches what you staged — so the first commit to an old file carries
|
|
21
|
+
# that file's reformat into an unrelated diff. `uv run ruff format .` as
|
|
22
|
+
# its own commit gets the noise out of everybody's review at once, and is
|
|
23
|
+
# the same commit that lets CI start checking format.
|
|
24
|
+
- id: ruff-format
|
|
25
|
+
|
|
26
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
27
|
+
rev: v6.0.0
|
|
28
|
+
hooks:
|
|
29
|
+
- id: trailing-whitespace
|
|
30
|
+
- id: end-of-file-fixer
|
|
31
|
+
# Conflict markers reach a branch that two other worktrees are cut from,
|
|
32
|
+
# and history here does not move — so a marker committed is a marker
|
|
33
|
+
# fixed forward, in public, forever.
|
|
34
|
+
- id: check-merge-conflict
|
|
35
|
+
# 1000, not the default 500. Vendoring libraries is policy here rather
|
|
36
|
+
# than an accident (`static/VENDOR.md`), and `static/cytoscape.min.js` is
|
|
37
|
+
# already 365K; a guard that fires on the documented practice is a guard
|
|
38
|
+
# people learn to skip. This is for the stray screenshot or database dump.
|
|
39
|
+
- id: check-added-large-files
|
|
40
|
+
args: [--maxkb=1000]
|