beqforge 0.2.4__tar.gz → 0.2.7__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.
- {beqforge-0.2.4 → beqforge-0.2.7}/.github/workflows/build-designer-image.yml +6 -3
- {beqforge-0.2.4 → beqforge-0.2.7}/.github/workflows/build-executable.yml +40 -20
- {beqforge-0.2.4 → beqforge-0.2.7}/AGENTS.md +2 -1
- {beqforge-0.2.4 → beqforge-0.2.7}/PKG-INFO +50 -11
- {beqforge-0.2.4 → beqforge-0.2.7}/README.md +49 -10
- {beqforge-0.2.4 → beqforge-0.2.7}/TODO.md +2 -20
- {beqforge-0.2.4 → beqforge-0.2.7}/beq_common/__init__.py +1 -1
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/AGENTS.md +17 -6
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/designer.py +203 -9
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/diagnose.py +98 -8
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/explain.py +30 -1
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/pipeline.py +125 -7
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/record.py +6 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/AGENTS.md +5 -1
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/__init__.py +15 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/cache.py +182 -4
- beqforge-0.2.7/beqoptimiser/parameters.py +499 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser.md +51 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/packaging/designer/beqforge-designer.cmd +6 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/packaging/designer/smoke.py +22 -3
- beqforge-0.2.7/plans/default-record-and-log-locations.md +46 -0
- beqforge-0.2.7/plans/design-version.md +143 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/done-design-and-pipeline.md +87 -2
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/parameter-space-optimisation.md +65 -1
- beqforge-0.2.7/tests/fixtures/design_golden.json +50 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_designer.py +265 -12
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_designer_server.py +42 -3
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_diagnose.py +49 -1
- beqforge-0.2.7/tests/test_design_golden.py +232 -0
- beqforge-0.2.7/tests/test_design_target_fit.py +112 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_designer_server_startup.py +51 -6
- beqforge-0.2.7/tests/test_optimiser_parameters.py +233 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/design_beq.py +2 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/designer_server.py +73 -20
- beqforge-0.2.7/tools/experiments/parameter_benchmark.py +152 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/smoke_test_exe.py +20 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.dockerignore +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.gitattributes +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.github/dependabot.yml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.github/workflows/build-device-check.yml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.github/workflows/tests.yml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.gitignore +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/.gitignore +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/beqforge.iml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/editor.xml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/jsonSchemas.xml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/misc.xml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/modules.xml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/.idea/vcs.xml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/CLAUDE.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/LICENCE.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beq_common/AGENTS.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beq_common/biquad.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beq_common/provenance.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beq_common/publication.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beq_common/types.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/__init__.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/accept.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/beqd.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/biquad.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/cache.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/charts.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/cli.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/design.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/extraction.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/filters.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/harness.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/identify.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/material.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/reference.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/rolloff.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge/verify.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge-device-check.spec +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge.spec +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/AGENTS.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/__init__.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/analyse.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/audio.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/catalogue.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/cli.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/coefficients.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/engines.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/evidence.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/manifest.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/measurement.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/profiles.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/qualification.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/report.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/transactions.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqforge_device_check/verify.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/cli.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/core.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/data/seed-manifest.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/beqoptimiser/data/seed.json.gz +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/device-check-examples/minidsp-2x4hd-usb-loopback.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/device-check-examples/simulation.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/device-check.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/README.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/assets/aggregate-48000.png +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/assets/aggregate-96000.png +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/assets/example-1.png +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/assets/example-2.png +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/entry-results.json.gz +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/docs/optimiser-report/statistics.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/evidence_validation.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/negative_corpus.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/packaging/designer/Dockerfile +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/packaging/device-check/NOTICE.txt +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/F2-catalogue-filter-optimisation.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/F2-device-precision-validation.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/F2-package-consolidation.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/R2a-server-stage-cache.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/done-baseline-evidence.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/research-design-decisions.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/test-portability-and-runtime.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/texture-yardstick-sub-band.md +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/plans/texture-yardstick-sub-band.patch +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/pyproject.toml +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/__init__.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/conftest.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/fixtures/optimiser_seed_entry.json +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_cli.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_accept.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_cache.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_cache_stores.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_channel_evidence.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_charts.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_corpus.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_design.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_device_rate.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_evidence.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_evidence_excess.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_exclusions.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_explain.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_extraction.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_filters.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_frozen_cache.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_guard.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_harness.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_headroom.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_hum.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_identify.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_jacobian.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_parallel.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_pipeline.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_playback.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_playback_headroom.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_publication.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_record.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_record_fingerprint.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_reference.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_references.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_ripple_band.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_rolloff.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_strategy_config.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_timings.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_design_verify.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_designer_image.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_analyse.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_catalogue.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_catalogue_predict.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_cli.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_engines.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_evidence.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_examples.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_manifest.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_measurement.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_qualification.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_transactions.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_device_check_verify.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_extract_layout.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_optimiser.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_optimiser_cache.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_optimiser_report.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tests/test_package_boundaries.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/__init__.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/build_optimiser_seed.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/a1_sweep.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/compare_records.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/compare_verdicts.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/inject_variants.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p14_compare.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p14_jacobian.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p14_run_pipeline.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p14_surrogate.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p18_compare_greedy.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p18_compare_seeded.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p18_compare_structure.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p18_greedy.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p18_seeded.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p18_structure.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/p20_smooth_drift.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/probe.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/refit_probe.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/request_timing.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/score_injected.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/experiments/t8_floor_rules.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/extract.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/ledger_template.html +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/negative_corpus.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/optimiser_catalogue_report.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/prepare_device_check_helper.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/render_ledger.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/replay.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/seed_optimiser_cache.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/smoke_test_device_check.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/smoke_test_install.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/summarise.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/tools/validate_evidence.py +0 -0
- {beqforge-0.2.4 → beqforge-0.2.7}/uv.lock +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
name: Designer image
|
|
2
2
|
|
|
3
3
|
# The designer as a container, beside a beqdesigner pipeline service (README, "Running the
|
|
4
|
-
# designer in a container"). Every change to what goes into it is built and smoke-tested
|
|
5
|
-
#
|
|
4
|
+
# designer in a container"). Every change to what goes into it is built and smoke-tested.
|
|
5
|
+
# A release publishes linux/amd64 and linux/arm64 to GHCR as ghcr.io/<owner>/beqforge-designer:
|
|
6
|
+
# build-executable.yml calls this for a vX.Y.Z tag once the release tests have passed, as it
|
|
7
|
+
# does for the executables and PyPI, so an image never ships ahead of its tests.
|
|
6
8
|
# A manual run with `publish` publishes the image of an existing release from a later commit,
|
|
7
9
|
# only if the designer's runtime code is identical to that release's tag (as when the image
|
|
8
10
|
# recipe arrived after the release).
|
|
9
11
|
on:
|
|
10
12
|
push:
|
|
11
13
|
branches: [main]
|
|
12
|
-
tags: ['v*']
|
|
13
14
|
paths:
|
|
14
15
|
- 'packaging/designer/**'
|
|
15
16
|
- '.dockerignore'
|
|
@@ -30,6 +31,8 @@ on:
|
|
|
30
31
|
description: Publish the image as beq_common.__version__ (its vX.Y.Z tag must exist, with identical designer code)
|
|
31
32
|
type: boolean
|
|
32
33
|
default: false
|
|
34
|
+
# the release: build-executable.yml, from a tag, after its test gate
|
|
35
|
+
workflow_call:
|
|
33
36
|
|
|
34
37
|
permissions:
|
|
35
38
|
contents: read
|
|
@@ -34,9 +34,10 @@ jobs:
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
const deadline = Date.now() + 15 * 60 * 1000;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
while (true) {
|
|
38
|
+
let runs;
|
|
39
|
+
try {
|
|
40
|
+
runs = await github.paginate(github.rest.actions.listWorkflowRuns, {
|
|
40
41
|
...context.repo,
|
|
41
42
|
workflow_id: 'tests.yml',
|
|
42
43
|
head_sha: context.sha,
|
|
@@ -44,27 +45,34 @@ jobs:
|
|
|
44
45
|
event: 'push',
|
|
45
46
|
per_page: 100,
|
|
46
47
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
run.status === 'completed' && run.conclusion === 'success'
|
|
52
|
-
);
|
|
53
|
-
if (passed) {
|
|
54
|
-
core.info(`Reusing successful main tests: ${passed.html_url}`);
|
|
55
|
-
await core.summary.addLink('Reused main test run', passed.html_url).write();
|
|
56
|
-
core.setOutput('reusable', 'true');
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// a transient API failure is no reason to rerun the whole suite, so keep trying until the deadline
|
|
50
|
+
if (Date.now() >= deadline) {
|
|
51
|
+
core.warning(`Unable to check main tests; running fresh release tests: ${error.message}`);
|
|
57
52
|
return;
|
|
58
53
|
}
|
|
59
|
-
|
|
60
|
-
core.info('No successful main test run available; running fresh release tests.');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
core.info('Main tests are still running; checking again in 30 seconds.');
|
|
54
|
+
core.info(`Unable to check main tests (${error.message}); trying again in 30 seconds.`);
|
|
64
55
|
await new Promise(resolve => setTimeout(resolve, 30_000));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const matching = runs.filter(run =>
|
|
59
|
+
run.head_sha === context.sha && run.head_branch === 'main' && run.event === 'push'
|
|
60
|
+
);
|
|
61
|
+
const passed = matching.find(run =>
|
|
62
|
+
run.status === 'completed' && run.conclusion === 'success'
|
|
63
|
+
);
|
|
64
|
+
if (passed) {
|
|
65
|
+
core.info(`Reusing successful main tests: ${passed.html_url}`);
|
|
66
|
+
await core.summary.addLink('Reused main test run', passed.html_url).write();
|
|
67
|
+
core.setOutput('reusable', 'true');
|
|
68
|
+
return;
|
|
65
69
|
}
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
if (!matching.some(run => run.status !== 'completed') || Date.now() >= deadline) {
|
|
71
|
+
core.info('No successful main test run available; running fresh release tests.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
core.info('Main tests are still running; checking again in 30 seconds.');
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 30_000));
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
tests:
|
|
@@ -234,6 +242,18 @@ jobs:
|
|
|
234
242
|
env:
|
|
235
243
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
236
244
|
|
|
245
|
+
image:
|
|
246
|
+
name: Publish designer image
|
|
247
|
+
needs: [validated_tests, package]
|
|
248
|
+
# see create_release for why the needed results are checked explicitly
|
|
249
|
+
if: >-
|
|
250
|
+
!cancelled() && startsWith(github.ref, 'refs/tags/') &&
|
|
251
|
+
needs.validated_tests.result == 'success' && needs.package.result == 'success'
|
|
252
|
+
uses: ./.github/workflows/build-designer-image.yml
|
|
253
|
+
permissions:
|
|
254
|
+
contents: read
|
|
255
|
+
packages: write
|
|
256
|
+
|
|
237
257
|
publish:
|
|
238
258
|
name: Publish Python package to PyPI
|
|
239
259
|
needs: [validated_tests, package, build, create_release]
|
|
@@ -60,7 +60,8 @@ or fixed ports), with coverage (`--cov`, configured in `pyproject.toml`, `multip
|
|
|
60
60
|
children included) uploaded to Codecov by each test job using the `CODECOV_TOKEN` secret;
|
|
61
61
|
release runs inherit it. Feature branch pushes test on Linux only and a newer push cancels the branch's
|
|
62
62
|
previous run; `main`, tags and manual runs test Linux, macOS and Windows and are never cancelled. `.github/workflows/build-executable.yml` builds release executables and distributions;
|
|
63
|
-
tag pushes publish to PyPI after validation and successful executable builds
|
|
63
|
+
tag pushes publish to PyPI after validation and successful executable builds, and the designer
|
|
64
|
+
image to GHCR after validation. Tags must match
|
|
64
65
|
the shared version exactly (`vX.Y.Z`). Manual runs build artifacts without publishing.
|
|
65
66
|
PyPI uses the `pypi` environment and Trusted Publishing; setup details are in the README.
|
|
66
67
|
Device-check preview executable packaging has its own workflow, not a separate Python release;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: beqforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Audio-derived BEQ design, published filter optimisation and DSP validation
|
|
5
5
|
License-File: LICENCE.md
|
|
6
6
|
Requires-Python: <3.15,>=3.13
|
|
@@ -146,7 +146,7 @@ straight off disk. A stale record is skipped with a warning rather than failing
|
|
|
146
146
|
|
|
147
147
|
`beqforge serve-designer` runs this pipeline as a live, HTTP-bound filter designer for
|
|
148
148
|
[`beqdesigner`](https://github.com/3ll3d00d/beqdesigner), implementing its
|
|
149
|
-
`design/designer-interface.md` v1.
|
|
149
|
+
`design/designer-interface.md` v1.4 contract (§7.1's HTTP binding) rather than the file-based
|
|
150
150
|
`.npz` → `design` → `--beq` export flow above:
|
|
151
151
|
|
|
152
152
|
```bash
|
|
@@ -183,15 +183,19 @@ An unusable directory causes a startup error naming the option and path.
|
|
|
183
183
|
relative to that root plus the SHA-256 of its decoded column, instead of inline base64 audio.
|
|
184
184
|
The server resolves, decodes and checks each reference; an unusable reference gets HTTP 422
|
|
185
185
|
naming the array, rather than a design decline. Inline requests remain supported.
|
|
186
|
-
`GET /health` reports `contract_version
|
|
186
|
+
`GET /health` reports `contract_version`, whether `shared_root` is enabled, and 1.4's design
|
|
187
|
+
version, settings and build. Each request
|
|
187
188
|
logs read, parse, decode, design and response timings; `tools/experiments/request_timing.py`
|
|
188
189
|
measures cold and warm requests.
|
|
189
190
|
|
|
190
191
|
**Several designs at once.** `--workers N` (1-8, default 1) runs N designs at once, each in
|
|
191
192
|
its own worker process; further requests queue. `GET /health` reports it as `parallel`, so a
|
|
192
193
|
caller can send each server as many designs as it takes rather than be configured with that
|
|
193
|
-
number.
|
|
194
|
-
if set, stays each design's own).
|
|
194
|
+
number. Concurrent designs share the machine's logical processors, SMT threads included, each
|
|
195
|
+
at most the width one design uses alone (`BEQ_FIT_WORKERS`, if set, stays each design's own).
|
|
196
|
+
`--cores N` caps the processors the designs use between them, for a machine with other work to
|
|
197
|
+
do: concurrent designs split N evenly, and N must be at least `--workers`. The share changes
|
|
198
|
+
how long a fit takes, never its answer.
|
|
195
199
|
|
|
196
200
|
Every `serve-designer` configuration option can also be set through an environment variable.
|
|
197
201
|
Explicit CLI options take precedence; for repeatable options, CLI values replace the entire
|
|
@@ -214,6 +218,7 @@ their existing CLI configuration.
|
|
|
214
218
|
| `--cache-dir` | `BEQFORGE_CACHE_DIR` | path |
|
|
215
219
|
| `--shared-root` | `BEQFORGE_SHARED_ROOT` | path |
|
|
216
220
|
| `--workers` | `BEQFORGE_WORKERS` | integer, 1-8 |
|
|
221
|
+
| `--cores` | `BEQFORGE_CORES` | integer, at least `--workers` |
|
|
217
222
|
| `--quiet` | `BEQFORGE_QUIET` | boolean |
|
|
218
223
|
|
|
219
224
|
Booleans accept `true`/`false`, `yes`/`no`, `on`/`off` or `1`/`0`, ignoring case.
|
|
@@ -246,21 +251,51 @@ diagnosis, in this order:
|
|
|
246
251
|
carried `bass_management`, otherwise the assumed LR4 80 Hz model, and it says which).
|
|
247
252
|
Clipping is reported, never used to reject a filter. The typed `gain_reduction_db` field is
|
|
248
253
|
filled only when the request carries `bass_management`, as the contract requires.
|
|
254
|
+
* `fit` — how closely the filter, as the device plays it, delivers the boost the content
|
|
255
|
+
supports: the typical error, the share of that boost within ±0.5 dB, and the worst miss,
|
|
256
|
+
where and which way. It is a match to that target, not a verdict on whether the target is
|
|
257
|
+
right; `correction` says how much of what is missing that target restores.
|
|
249
258
|
* `alternatives` — why each other candidate was rejected or not chosen.
|
|
250
259
|
|
|
251
260
|
The commentary then continues with `target_notes`, `verdict_notes` (what the checks themselves
|
|
252
|
-
noticed), `effective_params` and `beqforge_revision` (which build answered). The
|
|
261
|
+
noticed), `effective_params` and `beqforge_revision` (which build answered). The same fit is
|
|
262
|
+
in typed fields (contract 1.3): `residual_db` is the worst absolute error over
|
|
263
|
+
`residual_band_hz`, the band the fit was actually scored on, measured on the published filter
|
|
264
|
+
as the device plays it; `residual_at_hz` and `residual_direction` say where and which way;
|
|
265
|
+
`residual_rms_db` and `residual_within_tolerance` (against `residual_tolerance_db`) weight
|
|
266
|
+
each frequency by the boost asked for there, as `confidence` does. The raw
|
|
253
267
|
recovered and shaping fractions are kept in the run record, not the response. A decline's `decline_message` gives the reason first, then `| found: …` with
|
|
254
268
|
the same diagnosis, then the build in brackets. `beqforge/designer.py` is the
|
|
255
269
|
pure `design(request) -> response` adapter, independently testable without a socket;
|
|
256
270
|
`tools/designer_server.py` is the HTTP transport around it.
|
|
257
271
|
|
|
272
|
+
**Which answer a build gives (contract 1.4).** Every response, design or decline, and
|
|
273
|
+
`GET /health` carry `design_version`, `design_settings` and `designer_build`, whatever
|
|
274
|
+
`contract_version` the request names. `design_version` is an integer bumped only by a change
|
|
275
|
+
that can alter an answer for an unchanged request: the candidates, the rejected designs, a
|
|
276
|
+
decline or a fit figure. Wording, speed and
|
|
277
|
+
packaging don't bump it, so the executable and the image of one release report the same
|
|
278
|
+
version and different `designer_build`s (the string also in `beqforge_revision`). Releases
|
|
279
|
+
before it: 0.0.x and 0.1.0 are 0, 0.2.0 is 1, 0.2.1 is 2, 0.2.2 is 3, and 0.2.3 to 0.2.5
|
|
280
|
+
are 4, as is 0.2.7, the first release to report it. One version is one algorithm, not one
|
|
281
|
+
filter on every machine: the `counterfactual` and `parametric` fits can land on a different
|
|
282
|
+
filter on a different processor, so compare versions, never figures. `design_settings` is a 12-hex digest of the server-wide settings that can change an
|
|
283
|
+
answer (`--strategy`, `--exclude`, `--goal-tolerance`, `--goal-tilt`, `--content-edge`,
|
|
284
|
+
`--device-rate`, `--coefficient-bits`, `--integer-bits`) that differ from their defaults, so a
|
|
285
|
+
server started with none of them always sends the same digest. `/health` also reports those
|
|
286
|
+
settings as `design_settings_detail`, for reading when two designers disagree. A caller keys
|
|
287
|
+
each title on (`design_version`, `design_settings`) to tell which titles a new release would
|
|
288
|
+
answer differently.
|
|
289
|
+
|
|
258
290
|
### Running the designer in a container
|
|
259
291
|
|
|
260
292
|
`ghcr.io/3ll3d00d/beqforge-designer:<version>` runs `beqforge serve-designer` for a beqdesigner
|
|
261
293
|
pipeline service beside it (beqdesigner's `docker/compose.example.yaml` runs both). It is
|
|
262
|
-
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml`
|
|
294
|
+
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml` for
|
|
263
295
|
each `vX.Y.Z` tag, from the same commit as that release; `latest` follows the newest release.
|
|
296
|
+
The release workflow (`build-executable.yml`) calls it only once the release tests have passed,
|
|
297
|
+
as it does for the executables and PyPI; pushes to `main` and pull requests build and
|
|
298
|
+
smoke-test the image without publishing it.
|
|
264
299
|
A manual run of that workflow with `publish` set publishes the current version's image from a
|
|
265
300
|
later commit, and refuses unless the designer's code is identical to that version's tag (0.2.0's
|
|
266
301
|
image was published this way, the recipe having arrived after the release).
|
|
@@ -304,8 +339,10 @@ beqforge-windows-x86_64.exe serve-designer --host 0.0.0.0 --port 8420 ^
|
|
|
304
339
|
```
|
|
305
340
|
|
|
306
341
|
The launcher sets the image's defaults through the environment variables above, each only if
|
|
307
|
-
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420
|
|
308
|
-
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache
|
|
342
|
+
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420`,
|
|
343
|
+
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache` and
|
|
344
|
+
`BEQFORGE_RECORD_DIR=%LOCALAPPDATA%\beqforge\records`, so every request's run record is kept.
|
|
345
|
+
Arguments pass through to `serve-designer`.
|
|
309
346
|
|
|
310
347
|
* **Listen on the network.** The CLI's default host is `127.0.0.1`, which a container cannot
|
|
311
348
|
reach; the launcher uses `0.0.0.0`. Windows Firewall asks the first time: allow private
|
|
@@ -317,7 +354,8 @@ not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420` and
|
|
|
317
354
|
`http://host.docker.internal:8420/design`; from another machine, use this one's LAN address.
|
|
318
355
|
Check `http://<address>:8420/health` from there first.
|
|
319
356
|
* **Several at once.** Set `BEQFORGE_WORKERS` (or pass `--workers 2`) for a machine that can
|
|
320
|
-
design more than one title at a time; `/health` reports it as `parallel`.
|
|
357
|
+
design more than one title at a time; `/health` reports it as `parallel`. To leave room for
|
|
358
|
+
other work, set `BEQFORGE_CORES` to the most processors the designer may use between them.
|
|
321
359
|
* **The rest.** The exe is unsigned, so SmartScreen warns on first run. `serve-designer` does not need `ffmpeg`. Close the window, or
|
|
322
360
|
press Ctrl+C, to stop.
|
|
323
361
|
|
|
@@ -334,7 +372,8 @@ one locally: `uv pip install pyinstaller && uv run pyinstaller beqforge.spec`.
|
|
|
334
372
|
|
|
335
373
|
The same workflow builds and checks the Python wheel and source distribution, verifies all
|
|
336
374
|
five dependency profiles, and reuses a successful branch-push run of `tests.yml` on `main`
|
|
337
|
-
at the exact tagged commit. If matching tests are still running,
|
|
375
|
+
at the exact tagged commit. If matching tests are still running, or GitHub's API fails to answer, it
|
|
376
|
+
keeps checking for up to 15 minutes;
|
|
338
377
|
without a successful result, it runs the reusable tests workflow against the release commit.
|
|
339
378
|
GitHub release creation and executable builds require those tests to pass on Linux, macOS
|
|
340
379
|
and Windows, including device-check tests and isolated dependency profiles. PyPI publication
|
|
@@ -120,7 +120,7 @@ straight off disk. A stale record is skipped with a warning rather than failing
|
|
|
120
120
|
|
|
121
121
|
`beqforge serve-designer` runs this pipeline as a live, HTTP-bound filter designer for
|
|
122
122
|
[`beqdesigner`](https://github.com/3ll3d00d/beqdesigner), implementing its
|
|
123
|
-
`design/designer-interface.md` v1.
|
|
123
|
+
`design/designer-interface.md` v1.4 contract (§7.1's HTTP binding) rather than the file-based
|
|
124
124
|
`.npz` → `design` → `--beq` export flow above:
|
|
125
125
|
|
|
126
126
|
```bash
|
|
@@ -157,15 +157,19 @@ An unusable directory causes a startup error naming the option and path.
|
|
|
157
157
|
relative to that root plus the SHA-256 of its decoded column, instead of inline base64 audio.
|
|
158
158
|
The server resolves, decodes and checks each reference; an unusable reference gets HTTP 422
|
|
159
159
|
naming the array, rather than a design decline. Inline requests remain supported.
|
|
160
|
-
`GET /health` reports `contract_version
|
|
160
|
+
`GET /health` reports `contract_version`, whether `shared_root` is enabled, and 1.4's design
|
|
161
|
+
version, settings and build. Each request
|
|
161
162
|
logs read, parse, decode, design and response timings; `tools/experiments/request_timing.py`
|
|
162
163
|
measures cold and warm requests.
|
|
163
164
|
|
|
164
165
|
**Several designs at once.** `--workers N` (1-8, default 1) runs N designs at once, each in
|
|
165
166
|
its own worker process; further requests queue. `GET /health` reports it as `parallel`, so a
|
|
166
167
|
caller can send each server as many designs as it takes rather than be configured with that
|
|
167
|
-
number.
|
|
168
|
-
if set, stays each design's own).
|
|
168
|
+
number. Concurrent designs share the machine's logical processors, SMT threads included, each
|
|
169
|
+
at most the width one design uses alone (`BEQ_FIT_WORKERS`, if set, stays each design's own).
|
|
170
|
+
`--cores N` caps the processors the designs use between them, for a machine with other work to
|
|
171
|
+
do: concurrent designs split N evenly, and N must be at least `--workers`. The share changes
|
|
172
|
+
how long a fit takes, never its answer.
|
|
169
173
|
|
|
170
174
|
Every `serve-designer` configuration option can also be set through an environment variable.
|
|
171
175
|
Explicit CLI options take precedence; for repeatable options, CLI values replace the entire
|
|
@@ -188,6 +192,7 @@ their existing CLI configuration.
|
|
|
188
192
|
| `--cache-dir` | `BEQFORGE_CACHE_DIR` | path |
|
|
189
193
|
| `--shared-root` | `BEQFORGE_SHARED_ROOT` | path |
|
|
190
194
|
| `--workers` | `BEQFORGE_WORKERS` | integer, 1-8 |
|
|
195
|
+
| `--cores` | `BEQFORGE_CORES` | integer, at least `--workers` |
|
|
191
196
|
| `--quiet` | `BEQFORGE_QUIET` | boolean |
|
|
192
197
|
|
|
193
198
|
Booleans accept `true`/`false`, `yes`/`no`, `on`/`off` or `1`/`0`, ignoring case.
|
|
@@ -220,21 +225,51 @@ diagnosis, in this order:
|
|
|
220
225
|
carried `bass_management`, otherwise the assumed LR4 80 Hz model, and it says which).
|
|
221
226
|
Clipping is reported, never used to reject a filter. The typed `gain_reduction_db` field is
|
|
222
227
|
filled only when the request carries `bass_management`, as the contract requires.
|
|
228
|
+
* `fit` — how closely the filter, as the device plays it, delivers the boost the content
|
|
229
|
+
supports: the typical error, the share of that boost within ±0.5 dB, and the worst miss,
|
|
230
|
+
where and which way. It is a match to that target, not a verdict on whether the target is
|
|
231
|
+
right; `correction` says how much of what is missing that target restores.
|
|
223
232
|
* `alternatives` — why each other candidate was rejected or not chosen.
|
|
224
233
|
|
|
225
234
|
The commentary then continues with `target_notes`, `verdict_notes` (what the checks themselves
|
|
226
|
-
noticed), `effective_params` and `beqforge_revision` (which build answered). The
|
|
235
|
+
noticed), `effective_params` and `beqforge_revision` (which build answered). The same fit is
|
|
236
|
+
in typed fields (contract 1.3): `residual_db` is the worst absolute error over
|
|
237
|
+
`residual_band_hz`, the band the fit was actually scored on, measured on the published filter
|
|
238
|
+
as the device plays it; `residual_at_hz` and `residual_direction` say where and which way;
|
|
239
|
+
`residual_rms_db` and `residual_within_tolerance` (against `residual_tolerance_db`) weight
|
|
240
|
+
each frequency by the boost asked for there, as `confidence` does. The raw
|
|
227
241
|
recovered and shaping fractions are kept in the run record, not the response. A decline's `decline_message` gives the reason first, then `| found: …` with
|
|
228
242
|
the same diagnosis, then the build in brackets. `beqforge/designer.py` is the
|
|
229
243
|
pure `design(request) -> response` adapter, independently testable without a socket;
|
|
230
244
|
`tools/designer_server.py` is the HTTP transport around it.
|
|
231
245
|
|
|
246
|
+
**Which answer a build gives (contract 1.4).** Every response, design or decline, and
|
|
247
|
+
`GET /health` carry `design_version`, `design_settings` and `designer_build`, whatever
|
|
248
|
+
`contract_version` the request names. `design_version` is an integer bumped only by a change
|
|
249
|
+
that can alter an answer for an unchanged request: the candidates, the rejected designs, a
|
|
250
|
+
decline or a fit figure. Wording, speed and
|
|
251
|
+
packaging don't bump it, so the executable and the image of one release report the same
|
|
252
|
+
version and different `designer_build`s (the string also in `beqforge_revision`). Releases
|
|
253
|
+
before it: 0.0.x and 0.1.0 are 0, 0.2.0 is 1, 0.2.1 is 2, 0.2.2 is 3, and 0.2.3 to 0.2.5
|
|
254
|
+
are 4, as is 0.2.7, the first release to report it. One version is one algorithm, not one
|
|
255
|
+
filter on every machine: the `counterfactual` and `parametric` fits can land on a different
|
|
256
|
+
filter on a different processor, so compare versions, never figures. `design_settings` is a 12-hex digest of the server-wide settings that can change an
|
|
257
|
+
answer (`--strategy`, `--exclude`, `--goal-tolerance`, `--goal-tilt`, `--content-edge`,
|
|
258
|
+
`--device-rate`, `--coefficient-bits`, `--integer-bits`) that differ from their defaults, so a
|
|
259
|
+
server started with none of them always sends the same digest. `/health` also reports those
|
|
260
|
+
settings as `design_settings_detail`, for reading when two designers disagree. A caller keys
|
|
261
|
+
each title on (`design_version`, `design_settings`) to tell which titles a new release would
|
|
262
|
+
answer differently.
|
|
263
|
+
|
|
232
264
|
### Running the designer in a container
|
|
233
265
|
|
|
234
266
|
`ghcr.io/3ll3d00d/beqforge-designer:<version>` runs `beqforge serve-designer` for a beqdesigner
|
|
235
267
|
pipeline service beside it (beqdesigner's `docker/compose.example.yaml` runs both). It is
|
|
236
|
-
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml`
|
|
268
|
+
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml` for
|
|
237
269
|
each `vX.Y.Z` tag, from the same commit as that release; `latest` follows the newest release.
|
|
270
|
+
The release workflow (`build-executable.yml`) calls it only once the release tests have passed,
|
|
271
|
+
as it does for the executables and PyPI; pushes to `main` and pull requests build and
|
|
272
|
+
smoke-test the image without publishing it.
|
|
238
273
|
A manual run of that workflow with `publish` set publishes the current version's image from a
|
|
239
274
|
later commit, and refuses unless the designer's code is identical to that version's tag (0.2.0's
|
|
240
275
|
image was published this way, the recipe having arrived after the release).
|
|
@@ -278,8 +313,10 @@ beqforge-windows-x86_64.exe serve-designer --host 0.0.0.0 --port 8420 ^
|
|
|
278
313
|
```
|
|
279
314
|
|
|
280
315
|
The launcher sets the image's defaults through the environment variables above, each only if
|
|
281
|
-
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420
|
|
282
|
-
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache
|
|
316
|
+
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420`,
|
|
317
|
+
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache` and
|
|
318
|
+
`BEQFORGE_RECORD_DIR=%LOCALAPPDATA%\beqforge\records`, so every request's run record is kept.
|
|
319
|
+
Arguments pass through to `serve-designer`.
|
|
283
320
|
|
|
284
321
|
* **Listen on the network.** The CLI's default host is `127.0.0.1`, which a container cannot
|
|
285
322
|
reach; the launcher uses `0.0.0.0`. Windows Firewall asks the first time: allow private
|
|
@@ -291,7 +328,8 @@ not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420` and
|
|
|
291
328
|
`http://host.docker.internal:8420/design`; from another machine, use this one's LAN address.
|
|
292
329
|
Check `http://<address>:8420/health` from there first.
|
|
293
330
|
* **Several at once.** Set `BEQFORGE_WORKERS` (or pass `--workers 2`) for a machine that can
|
|
294
|
-
design more than one title at a time; `/health` reports it as `parallel`.
|
|
331
|
+
design more than one title at a time; `/health` reports it as `parallel`. To leave room for
|
|
332
|
+
other work, set `BEQFORGE_CORES` to the most processors the designer may use between them.
|
|
295
333
|
* **The rest.** The exe is unsigned, so SmartScreen warns on first run. `serve-designer` does not need `ffmpeg`. Close the window, or
|
|
296
334
|
press Ctrl+C, to stop.
|
|
297
335
|
|
|
@@ -308,7 +346,8 @@ one locally: `uv pip install pyinstaller && uv run pyinstaller beqforge.spec`.
|
|
|
308
346
|
|
|
309
347
|
The same workflow builds and checks the Python wheel and source distribution, verifies all
|
|
310
348
|
five dependency profiles, and reuses a successful branch-push run of `tests.yml` on `main`
|
|
311
|
-
at the exact tagged commit. If matching tests are still running,
|
|
349
|
+
at the exact tagged commit. If matching tests are still running, or GitHub's API fails to answer, it
|
|
350
|
+
keeps checking for up to 15 minutes;
|
|
312
351
|
without a successful result, it runs the reusable tests workflow against the release commit.
|
|
313
352
|
GitHub release creation and executable builds require those tests to pass on Linux, macOS
|
|
314
353
|
and Windows, including device-check tests and isolated dependency profiles. PyPI publication
|
|
@@ -30,7 +30,6 @@ are retained so records and code comments remain traceable. Completed items are
|
|
|
30
30
|
| 11 | F4 follow-up | parked | Both fitter-objective selection spikes lost passes/true positives. Judge several fits only with a demonstrated usable alternative discarded before `_judge`; a smaller residual is insufficient evidence. |
|
|
31
31
|
| 12 | F3 follow-up | parked | A fifth section moved no verdict when tried and costs more fitting. Reopen if an evidence-supported target has a demonstrated usable fit beyond four sections and no usable fit within four. |
|
|
32
32
|
| 13 | T2 | parked | The steep-variant audit found no swept cap binding a weak winner. Derive caps only when a binding cap demonstrably prevents a better supported result. |
|
|
33
|
-
| 14 | Residual (former TODO 5) | open | Correct the reported target error and its actual measurement band without changing fit selection. Primarily audit accuracy; any escalation/objective change is a separate, higher-impact decision. Non-parametric reporting is already allowed upstream. |
|
|
34
33
|
| 15 | Authored features | parked | Manual `--exclude` works; no validated automatic detector exists. Reopen with real labelled features and negatives showing that detection preserves legitimate bass. |
|
|
35
34
|
| 16 | Dynamic processing (former TODO 11) | parked | No suspected real case has scoped a useful tripwire. Reopen when compression/limiting demonstrably compromises this correction; non-LTI processing alone does not prove every BEQ is invalid. |
|
|
36
35
|
| 17 | Tilted passband reference (goal/corpus follow-up) | parked | No real in-sub-band case needs a sloping reference yet; Dossier's abstention was decided. Reopen with one. Goal-aware corpus labels belong to the open E10 protocol work. Nearest so far: 13 Going on 30's mix tilts about −7 dB/oct above 50 Hz (bass is coherent across channels, ~200 Hz is not) and finds its plateau only through the one-sided fallback. That reference reads a 36 dB deficit below 25 Hz; each channel's own plateau reads 30-34 dB. A reference built from the channels would be 2-5 dB less generous there (see plans/done-design-and-pipeline.md, 11 October 2026). |
|
|
@@ -43,7 +42,8 @@ are retained so records and code comments remain traceable. Completed items are
|
|
|
43
42
|
| 24 | Chart peak bias | parked | Deliberate catalogue-compatible per-bin maximum; it affects presentation only. Reopen if a reader needs an alternative percentile view; keep it distinct from the 95th-percentile evidence envelope. |
|
|
44
43
|
| 25 | Documentation citations | open | Remove or repoint orphan references and stale descriptions of shipped behaviour. Low filter impact, but prevents future changes being based on obsolete assumptions. |
|
|
45
44
|
| 26 | Texture yardstick retune (library review) | parked | The cubic (row 2) keeps the 200 Hz band only because the sub-band cap admitted one synthetic negative, `broadband/5`: chance unevenness in noise bursts, at a 2.6 dB boost. With 45 gated cases a single case moves the gate's upper bound from 0.079 to 0.118. Carrie is still declined (0.85x about a cubic, 1.33x with the cap). Reopen when the beqdesigner library review (~1,290 titles) is complete. Then retune against its declines: the band edge, the trend degree, and whether the gate weights a false acceptance by its boost. The library holds no known negative, so it can expose wrong declines but not measure false acceptances. Any retune still needs the corpus or a fresh E10 set for that, ideally with negatives built from real programme texture (row 5). See [the outcome](plans/texture-yardstick-sub-band.md#outcome-17-october-2026). |
|
|
46
|
-
| 27 |
|
|
45
|
+
| 27 | Keep run records and logs by default | open | `serve-designer` keeps no run record and no log file unless told to, so the Windows designer's 432 library titles (111 declines) have no records and can be reviewed only from their responses. Default records and a rotating log to the platform's per-user data directory (as the `.cmd` already does for the cache), set and declare `/records` in the image, report `record_dir` on `/health`. See [the handoff](plans/default-record-and-log-locations.md). |
|
|
46
|
+
| 28 | Fit sensitivity to arithmetic | open | The same request can get a different filter on a different processor. On the synthetic BW4 24 Hz title, `counterfactual` gave 2 sections here and 3 on Linux and macOS CI; `parametric`'s boost was 15.21 dB on this AVX-512 machine, 15.78 dB with Haswell BLAS kernels and 16.49 dB on CI. `flatten` matched everywhere. It's repeatable on one machine, so last-digit differences grow into a different local optimum. Measure it on real titles first: run the baseline set under `OPENBLAS_CORETYPE=Haswell` (or on another CPU) and compare verdicts and corrected curves. Then decide whether the fit needs a deterministic tie-break or a conditioning fix, or whether only the golden's coverage does. See [the correction](plans/design-version.md#correction-after-the-v026-release-tests). |
|
|
47
47
|
|
|
48
48
|
## Review evidence
|
|
49
49
|
|
|
@@ -92,10 +92,6 @@ Evidence behind the order and the parked decisions:
|
|
|
92
92
|
filtered/1 reached 5.6 dB over 0.28 octave. Real-title candidates stayed below 0.5 dB.
|
|
93
93
|
Audit this with steep policy; it is not evidence specifically of single-bin holes or
|
|
94
94
|
permission to add a blanket gate.
|
|
95
|
-
* [`designer._to_design_candidate`](beqforge/designer.py) forwards `fit_error_db` and the
|
|
96
|
-
run's residual band, while [`filters._fit_structure`](beqforge/filters.py) returns a
|
|
97
|
-
target-error/device-drift objective and [`design._result`](beqforge/design.py) recomputes
|
|
98
|
-
pure target error. The actual fit score band can widen beyond the reported one.
|
|
99
95
|
* The sibling [`designer-interface.md`](../beqdesigner/design/designer-interface.md)
|
|
100
96
|
§§2–4 already adopts clipping cost, bass management, ordinal confidence, non-parametric
|
|
101
97
|
residuals and decline/report rules. It deliberately excludes device preference.
|
|
@@ -124,20 +120,6 @@ supplying a dict is not enough to claim it was modelled. No hardware capture is
|
|
|
124
120
|
these software comparisons. Caller propagation requires a change in beqdesigner, outside
|
|
125
121
|
this repo; both its batch job and library call omit the available argument today.
|
|
126
122
|
|
|
127
|
-
### Residual reporting
|
|
128
|
-
|
|
129
|
-
`Candidate.fit_error_db` reaches the response's `residual_db`. For fitted candidates it is
|
|
130
|
-
normally the larger of target error and exact-coefficient quantisation change; after pruning
|
|
131
|
-
it can be pure target error. The contract already permits residuals for every method with an
|
|
132
|
-
explicit target and defines them as maximum absolute target error. Recompute the reported
|
|
133
|
-
error from the canonical published cascade against its priced target on a stated band; make
|
|
134
|
-
the evaluation rate and coefficient treatment explicit. Carry or recompute the actual band:
|
|
135
|
-
`_fit_all` can widen it above `params.residual_band_hz`, and parametric proposals carry their
|
|
136
|
-
own effective design settings. Keep the optimisation objective, drift screen and escalation
|
|
137
|
-
unchanged in a reporting fix. Changing what `residual_target_db` compares against is a
|
|
138
|
-
separate decision change and needs the full regression workflow. A small residual says the
|
|
139
|
-
fit matched the constructed target, not that the target is correct.
|
|
140
|
-
|
|
141
123
|
### Fresh validation protocol and steep-filter policy
|
|
142
124
|
|
|
143
125
|
The legacy corpus is development evidence after repeated tuning, not a held-out estimate.
|
|
@@ -40,10 +40,10 @@ still holds: no other API, no other service, and the server is a thin transport
|
|
|
40
40
|
| `beqforge/record.py` | The run record — a fingerprinted, gzipped JSON of everything a run produced, written every run. Ours, not beqdesigner's. |
|
|
41
41
|
| `beqforge/beqd.py` | Export to a `.beq` beqdesigner project. A separate job from the record: idiomatic in their UI, allowed to be lossy. |
|
|
42
42
|
| `beqforge/cli.py` | `beqforge <subcommand> ...` — the installed entry point. Strips the subcommand off argv and hands the rest to the matching script in `tools/` unchanged. |
|
|
43
|
-
| `beqforge/explain.py` | The plain-language account a designer response leads with — `found` (reference plateau, which channels carry it and which are silent, down to where there is real bass content, judged band, goal), `correction` (the cascade, then frequency by frequency what was wanted, what the content supports and before → after), `clipping` (the sub-feed peak and how far to turn the sub down, on the request's or the assumed bass management) and `alternatives` (why each other candidate lost). Reads the `Report`; measures and decides nothing. |
|
|
44
|
-
| `beqforge/designer.py` | The `design(request) -> response` adapter between beqdesigner's `designer-interface.md` v1.1 and `beqforge.pipeline.run` — request/response dataclasses (1.1: every candidate the judge failed goes back in `rejected` with its failures, review only, beside the answer or the decline), wire-format (de)serialisation and the field-by-field mapping onto `Report`/`Candidate`/`Verdict`. Also `validate_response`, an independent re-implementation of beqdesigner's own §3-§5 rules (that repo isn't importable — PyQt6), called by the server before a response ever goes on the wire. Pure and independently testable; no socket. |
|
|
43
|
+
| `beqforge/explain.py` | The plain-language account a designer response leads with — `found` (reference plateau, which channels carry it and which are silent, down to where there is real bass content, judged band, goal), `correction` (the cascade, then frequency by frequency what was wanted, what the content supports and before → after), `clipping` (the sub-feed peak and how far to turn the sub down, on the request's or the assumed bass management), `fit` (how closely the filter as played delivers the boost the content supports — a match to the target, not a verdict on it) and `alternatives` (why each other candidate lost). Reads the `Report`; measures and decides nothing. |
|
|
44
|
+
| `beqforge/designer.py` | The `design(request) -> response` adapter between beqdesigner's `designer-interface.md` v1.1 and `beqforge.pipeline.run` — request/response dataclasses (1.1: every candidate the judge failed goes back in `rejected` with its failures, review only, beside the answer or the decline; 1.3: `residual_db` and its companions come from `Candidate.target_fit`, never `fit_error_db`; 1.4: `design_version` (`DESIGN_VERSION`, bumped only by a change that can alter an answer, guarded by `tests/test_design_golden.py`), `design_settings` (`design_settings_digest`, the non-default server settings that can change an answer) and `designer_build` on every response), wire-format (de)serialisation and the field-by-field mapping onto `Report`/`Candidate`/`Verdict`. Also `validate_response`, an independent re-implementation of beqdesigner's own §3-§5 rules (that repo isn't importable — PyQt6), called by the server before a response ever goes on the wire. Pure and independently testable; no socket. |
|
|
45
45
|
| `tools/design_beq.py` | **The entry point.** One command, a filter and its reasoning. `--charts DIR` for the pictures; writes a `.run.json.gz` record beside the material unless `--no-record`. |
|
|
46
|
-
| `tools/designer_server.py` | `beqforge serve-designer` — the HTTP transport (`designer-interface.md` §7.1) around `beqforge/designer.py`. Server-wide flags for device realisation/strategies/exclusions; everything per-title comes from the request. A `ThreadingHTTPServer`, so `/health` answers during a design, with every design run in a long-lived worker process started with "spawn" (`_DesignWorker`): the fitter forks worker processes (`beqforge.filters.PARALLEL_FITS`) when a fit escalates past one section count, and forking a multi-threaded process risks a deadlock, so the process that forks must stay single-threaded — the worker's log records come back over a `SimpleQueue`, not a `Queue`, for that reason. A worker designs one title at a time; `--workers N` (1-8) runs N of them, each request taking the next idle one, and `/health` reports N as `parallel` so a caller can size its sends from the designer. With N > 1 each worker's fit pool is `filters.FIT_WORKERS
|
|
46
|
+
| `tools/designer_server.py` | `beqforge serve-designer` — the HTTP transport (`designer-interface.md` §7.1) around `beqforge/designer.py`. Server-wide flags for device realisation/strategies/exclusions; everything per-title comes from the request. A `ThreadingHTTPServer`, so `/health` answers during a design, with every design run in a long-lived worker process started with "spawn" (`_DesignWorker`): the fitter forks worker processes (`beqforge.filters.PARALLEL_FITS`) when a fit escalates past one section count, and forking a multi-threaded process risks a deadlock, so the process that forks must stay single-threaded — the worker's log records come back over a `SimpleQueue`, not a `Queue`, for that reason. A worker designs one title at a time; `--workers N` (1-8) runs N of them, each request taking the next idle one, and `/health` reports N as `parallel` so a caller can size its sends from the designer. With N > 1 each worker's fit pool is the logical processors less one, divided by N and capped at `filters.FIT_WORKERS` (`fit_share`), unless `BEQ_FIT_WORKERS` is set; `--cores N` instead caps the designs' total at N, split evenly, still at most `filters.FIT_WORKERS` each: concurrent designs are what can use SMT threads, and pool size changes timing, never an answer. A worker that dies answers 500 and is replaced; the others carry on. SIGTERM shuts down like Ctrl-C, stopping the worker's process group, fit pool included. Every response names its build (`beqforge_revision`); `--record-dir DIR` also writes each request's run record, named by a digest of the request audio, so a review-queue entry can be replayed. `--cache-dir DIR` keeps the stage cache there (`cache.DirStore`, one file per entry, atomic): a repeat request for a title skips the analysis — 110 s cold, 50 s warm on a 2-hour 8-channel title. Each request logs a timing line (read, parse, decode, design, respond, queued); `tools/experiments/request_timing.py` reports them. `--shared-root DIR` accepts audio by reference (contract 1.2, beqdesigner's `design/designer-by-reference.md` §3): an array names a WAV relative to DIR plus the SHA-256 of its decoded column, and `beqforge/reference.py` resolves, decodes and checks it; one that cannot be honoured is a 422 naming the array, never a decline. `GET /health` reports `contract_version`, `shared_root`, `parallel`, and 1.4's `design_version`, `design_settings`, `design_settings_detail` and `designer_build`. |
|
|
47
47
|
| `tools/replay.py` | Redraw charts and export to beqdesigner from a record — no rerun, no extraction. Refuses on a stale record unless `--force`. |
|
|
48
48
|
| `tools/extract.py` | ffmpeg → 1 kHz per-channel `.npz`. Requires an explicit supported channel layout; preserves layout provenance. Needs no beqdesigner. |
|
|
49
49
|
| `tools/summarise.py` | Sanity-check an extraction before using it. |
|
|
@@ -203,6 +203,16 @@ open questions it resolves. An item with no recorded outcome is not done.
|
|
|
203
203
|
|
|
204
204
|
## Gotchas
|
|
205
205
|
|
|
206
|
+
* A change that can alter an answer for an unchanged request (candidates, rejected designs, a
|
|
207
|
+
decline or a 1.3 fit figure) bumps `designer.DESIGN_VERSION` and regenerates
|
|
208
|
+
`tests/fixtures/design_golden.json` (`BEQFORGE_REGENERATE_GOLDEN=1 uv run pytest
|
|
209
|
+
tests/test_design_golden.py`) in the same commit. Callers decide what to redesign from it.
|
|
210
|
+
Wording, speed, packaging and new outputs don't bump it. If the probe shows a moved verdict
|
|
211
|
+
or target, the golden will usually fail too, but it covers only a few synthetic requests:
|
|
212
|
+
a bump follows from what the change can do, not from whether the golden noticed. It leaves
|
|
213
|
+
out `counterfactual` and `parametric` because their fits differ across processors
|
|
214
|
+
(TODO, "Fit sensitivity to arithmetic"): regenerating it on one machine doesn't make those
|
|
215
|
+
hold on CI.
|
|
206
216
|
* RBJ biquad formulae exist twice — `biquad.py`'s class hierarchy and `filters.py`'s
|
|
207
217
|
`_sos_from_parameters`. Fix both or neither; the test asserting they agree is what keeps that
|
|
208
218
|
honest.
|
|
@@ -425,18 +435,19 @@ least `min_judge_octaves` above the floor — no fixed top (historical review T6
|
|
|
425
435
|
|
|
426
436
|
### Publication, playback and verification
|
|
427
437
|
|
|
428
|
-
* **Robustness is measured
|
|
438
|
+
* **Robustness is measured six ways, and they answer different questions.** Do not add one
|
|
429
439
|
to another, or compare one against a limit meant for another:
|
|
430
440
|
|
|
431
441
|
| Quantity | What it measures | Where, over what |
|
|
432
442
|
| --- | --- | --- |
|
|
433
443
|
| Fit objective (`_fit_structure`'s cost) | the larger of the worst error against the target and the worst change exact-coefficient device quantisation makes to the response | the optimiser's raw parameters, over the fit's scoring band (`residual_band_hz`, widened to cover placements) |
|
|
434
|
-
| `fit_error_db`
|
|
444
|
+
| `fit_error_db` | that objective — **except** that when `_prune` drops a section it becomes the pure worst error against the target, without the quantisation term. Two meanings in one field; the escalation's `residual_target_db` compares against it, so it is left alone. Record-only outside the fitter, never in the response | as above |
|
|
445
|
+
| `TargetFit` / designer `residual_db` and its 1.3 companions | the cascade that plays against the target it was fitted to: worst absolute error, where and which way, RMS and share within `fit_tolerance_db` both weighted by the boost the target asks for. Reported, never gated; a match to the target, not evidence the target is right | published parameters, quantised coefficients at the device rate, design grid, over the band the fit was actually scored on (`_fit_band_hz`, or `residual_band_hz` for `parametric`) |
|
|
435
446
|
| Drift, p90 (`_published_drift`, `Verdict.drift_db`) | the 90th percentile, over `drift_samples` jitters of the *published* parameters by their rounding step, of the worst change quantisation makes — how fragile the neighbourhood is, not what the published filter does | published parameters (since the F1 follow-up both use them), 3-400 Hz design grid |
|
|
436
447
|
| Exposure (`_exposure_db`) | the fallback's ranking when no fit reaches the residual target: the residual, plus the drift if over `max_drift_db`. A sampled heuristic, not a bound on the published response | as its two terms |
|
|
437
448
|
| `device_error_db` and the corrected curve | what the device will actually do: the exact published, quantised response against the unquantised one (`device_error_db`), and that response applied to the programme (`verify`) — the only one that is not a model | published parameters, quantised coefficients, design grid / the programme |
|
|
438
449
|
|
|
439
|
-
Strict Jury stability (`unstable_sections`) is separate from all
|
|
450
|
+
Strict Jury stability (`unstable_sections`) is separate from all six and is a veto: a
|
|
440
451
|
cascade that is unstable at its published, quantised coefficients is never accepted, and its
|
|
441
452
|
drift and exposure are infinite.
|
|
442
453
|
|