sobres 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sobres-1.1.0/.claude/hooks/commit-gate.sh +34 -0
- sobres-1.1.0/.claude/settings.json +16 -0
- sobres-1.1.0/.dockerignore +24 -0
- sobres-1.1.0/.gitattributes +2 -0
- sobres-1.1.0/.github/ISSUE_TEMPLATE/hosting-inquiry.yml +38 -0
- sobres-1.1.0/.github/dependabot.yml +27 -0
- sobres-1.1.0/.github/pull_request_template.md +26 -0
- sobres-1.1.0/.github/scripts/check_release.py +138 -0
- sobres-1.1.0/.github/workflows/ci.yml +353 -0
- sobres-1.1.0/.github/workflows/codeql.yml +35 -0
- sobres-1.1.0/.github/workflows/live.yml +59 -0
- sobres-1.1.0/.github/workflows/pages.yml +61 -0
- sobres-1.1.0/.github/workflows/release.yml +283 -0
- sobres-1.1.0/.gitignore +33 -0
- sobres-1.1.0/.pre-commit-config.yaml +48 -0
- sobres-1.1.0/.trivyignore +3 -0
- sobres-1.1.0/AGENTS.md +80 -0
- sobres-1.1.0/CHANGELOG.md +272 -0
- sobres-1.1.0/CLAUDE.md +238 -0
- sobres-1.1.0/Dockerfile +68 -0
- sobres-1.1.0/LICENSE +21 -0
- sobres-1.1.0/PKG-INFO +485 -0
- sobres-1.1.0/README.md +388 -0
- sobres-1.1.0/docker-compose.yml +23 -0
- sobres-1.1.0/docs/DEPLOYING.md +110 -0
- sobres-1.1.0/docs/RELEASING.md +172 -0
- sobres-1.1.0/docs/ROADMAP.md +103 -0
- sobres-1.1.0/docs/allocation-lp-reference.md +78 -0
- sobres-1.1.0/docs/foundation-completion.md +81 -0
- sobres-1.1.0/docs/foundation-reuse-audit.md +25 -0
- sobres-1.1.0/docs/integration/main-sync.md +30 -0
- sobres-1.1.0/docs/integration/pr10-main-sync.md +18 -0
- sobres-1.1.0/docs/integration/pr11-main-sync.md +16 -0
- sobres-1.1.0/docs/integration/pr12-main-sync.md +19 -0
- sobres-1.1.0/docs/integration/pr13-main-sync.md +20 -0
- sobres-1.1.0/docs/integration/pr14-main-sync.md +14 -0
- sobres-1.1.0/docs/integration/pr15-main-sync.md +15 -0
- sobres-1.1.0/docs/integration/pr15-multivariable-spec.md +28 -0
- sobres-1.1.0/docs/integration/pr16-main-sync.md +32 -0
- sobres-1.1.0/docs/integration/pr9-main-sync.md +19 -0
- sobres-1.1.0/docs/reviews/pr-7/evidence/dependency-audit.txt +3 -0
- sobres-1.1.0/docs/reviews/pr-7/evidence/network-tests.txt +21 -0
- sobres-1.1.0/docs/reviews/pr-7/evidence/offline-tests.txt +65 -0
- sobres-1.1.0/docs/reviews/pr-7/evidence/openspec-validation.txt +43 -0
- sobres-1.1.0/docs/reviews/pr-7/evidence/probes.json +43 -0
- sobres-1.1.0/docs/reviews/pr-7/fixes.md +94 -0
- sobres-1.1.0/docs/reviews/pr-7/github-review.md +57 -0
- sobres-1.1.0/docs/reviews/pr-7/report.md +200 -0
- sobres-1.1.0/docs/reviews/pr-7/reproduce.py +190 -0
- sobres-1.1.0/docs/reviews/pr-7/self-test.md +197 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/audit.log +229 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-DEBUG.stderr +18 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-DEBUG.stdout +15 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-ERROR.stdout +15 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-INFO.stderr +11 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-INFO.stdout +15 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-csv-WARNING.stdout +15 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-DEBUG.stderr +18 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-DEBUG.stdout +1177 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-ERROR.stdout +1177 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-INFO.stderr +11 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-INFO.stdout +1177 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-json-WARNING.stdout +1177 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-DEBUG.stderr +18 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-DEBUG.stdout +31 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-ERROR.stdout +31 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-INFO.stderr +11 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-INFO.stdout +31 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/backtest-table-WARNING.stdout +31 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/build.log +18 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/capm.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/capm.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/cli-summary.json +332 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/duplicate-risk.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/duplicate-risk.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-DEBUG.stderr +10 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-DEBUG.stdout +7 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-ERROR.stdout +7 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-INFO.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-INFO.stdout +7 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-csv-WARNING.stdout +7 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-DEBUG.stderr +10 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-DEBUG.stdout +90 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-ERROR.stdout +90 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-INFO.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-INFO.stdout +90 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-json-WARNING.stdout +90 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-DEBUG.stderr +10 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-DEBUG.stdout +19 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-ERROR.stdout +19 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-INFO.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-INFO.stdout +19 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/frontier-table-WARNING.stdout +19 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/github.json +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/independent.json +66 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/install-security-live.jsonl +12 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-DEBUG.stderr +11 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-DEBUG.stdout +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-ERROR.stdout +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-INFO.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-INFO.stdout +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-WARNING.stderr +2 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-csv-WARNING.stdout +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-DEBUG.stderr +11 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-DEBUG.stdout +51 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-ERROR.stdout +51 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-INFO.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-INFO.stdout +51 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-WARNING.stderr +2 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-json-WARNING.stdout +51 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-DEBUG.stderr +11 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-DEBUG.stdout +20 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-ERROR.stdout +20 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-INFO.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-INFO.stdout +20 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-WARNING.stderr +2 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/markowitz-table-WARNING.stdout +20 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/nan-risk.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/nan-risk.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/nan-riskfree.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/nan-riskfree.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/negative-seed.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/negative-seed.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/performance.log +13 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-DEBUG.stderr +10 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-DEBUG.stdout +16 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-ERROR.stdout +16 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-INFO.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-INFO.stdout +16 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-csv-WARNING.stdout +16 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-DEBUG.stderr +10 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-DEBUG.stdout +88 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-ERROR.stdout +88 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-INFO.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-INFO.stdout +88 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-json-WARNING.stdout +88 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-DEBUG.stderr +10 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-DEBUG.stdout +28 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-ERROR.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-ERROR.stdout +28 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-INFO.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-INFO.stdout +28 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-WARNING.stderr +1 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/risk-table-WARNING.stdout +28 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/target-backtest-option.stderr +4 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/target-backtest-option.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/target-backtest.stderr +3 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/target-backtest.stdout +0 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/tests-python312.log +73 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/tests-python314.log +98 -0
- sobres-1.1.0/docs/reviews/pr-8/evidence/tty-skip.txt +12 -0
- sobres-1.1.0/docs/reviews/pr-8/fixes.md +134 -0
- sobres-1.1.0/docs/reviews/pr-8/github-review.json +55 -0
- sobres-1.1.0/docs/reviews/pr-8/github-review.md +53 -0
- sobres-1.1.0/docs/reviews/pr-8/pypi-issue.md +56 -0
- sobres-1.1.0/docs/reviews/pr-8/report.md +239 -0
- sobres-1.1.0/docs/reviews/pr-8/reproduce.py +244 -0
- sobres-1.1.0/docs/reviews/pr-8/self-test.md +154 -0
- sobres-1.1.0/docs/reviews/pr-8/verification/dependency-audit.log +11 -0
- sobres-1.1.0/docs/reviews/pr-8/verification/live-tests.log +10 -0
- sobres-1.1.0/docs/reviews/pr-8/verification/offline-tests.log +80 -0
- sobres-1.1.0/docs/reviews/pr-8/verification/performance.jsonl +14 -0
- sobres-1.1.0/docs/reviews/pr-8/verification/wheel-formats.log +49 -0
- sobres-1.1.0/docs/why-your-backtest-looks-too-good.md +83 -0
- sobres-1.1.0/frontend/index.html +23 -0
- sobres-1.1.0/frontend/openapi.json +7136 -0
- sobres-1.1.0/frontend/package-lock.json +3573 -0
- sobres-1.1.0/frontend/package.json +37 -0
- sobres-1.1.0/frontend/public/manifest.json +80 -0
- sobres-1.1.0/frontend/scripts/check-bundle.mjs +25 -0
- sobres-1.1.0/frontend/scripts/check-client.mjs +11 -0
- sobres-1.1.0/frontend/src/App.tsx +97 -0
- sobres-1.1.0/frontend/src/api/client.ts +128 -0
- sobres-1.1.0/frontend/src/api/schema.d.ts +6339 -0
- sobres-1.1.0/frontend/src/charts/BacktestCharts.tsx +62 -0
- sobres-1.1.0/frontend/src/charts/EChart.tsx +39 -0
- sobres-1.1.0/frontend/src/charts/FrontierChart.tsx +45 -0
- sobres-1.1.0/frontend/src/components/Disclaimer.tsx +10 -0
- sobres-1.1.0/frontend/src/components/JobProgress.tsx +37 -0
- sobres-1.1.0/frontend/src/components/Provenance.tsx +35 -0
- sobres-1.1.0/frontend/src/components/ResultTable.tsx +75 -0
- sobres-1.1.0/frontend/src/components/TokenGate.tsx +37 -0
- sobres-1.1.0/frontend/src/components/ui/button.tsx +18 -0
- sobres-1.1.0/frontend/src/components/ui/card.tsx +9 -0
- sobres-1.1.0/frontend/src/components/ui/switch.tsx +14 -0
- sobres-1.1.0/frontend/src/components/ui/tabs.tsx +16 -0
- sobres-1.1.0/frontend/src/components/ui/tooltip.tsx +17 -0
- sobres-1.1.0/frontend/src/forms/CommandForm.tsx +132 -0
- sobres-1.1.0/frontend/src/index.css +24 -0
- sobres-1.1.0/frontend/src/lib/format.ts +51 -0
- sobres-1.1.0/frontend/src/main.tsx +10 -0
- sobres-1.1.0/frontend/src/theme/ThemeProvider.tsx +96 -0
- sobres-1.1.0/frontend/src/theme/tokens.css +39 -0
- sobres-1.1.0/frontend/src/views/CommandView.tsx +72 -0
- sobres-1.1.0/frontend/src/views/DoctorView.tsx +69 -0
- sobres-1.1.0/frontend/src/views/HomeView.tsx +43 -0
- sobres-1.1.0/frontend/src/views/JobsView.tsx +22 -0
- sobres-1.1.0/frontend/src/views/PortfoliosView.tsx +98 -0
- sobres-1.1.0/frontend/src/views/ResultView.tsx +55 -0
- sobres-1.1.0/frontend/src/views/RunsView.tsx +96 -0
- sobres-1.1.0/frontend/src/views/SettingsView.tsx +110 -0
- sobres-1.1.0/frontend/tsconfig.json +22 -0
- sobres-1.1.0/frontend/vite.config.ts +26 -0
- sobres-1.1.0/openspec/changes/0000-release-engineering/proposal.md +87 -0
- sobres-1.1.0/openspec/changes/0000-release-engineering/specs/release-pipeline/spec.md +217 -0
- sobres-1.1.0/openspec/changes/0000-release-engineering/tasks.md +75 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/design.md +363 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/proposal.md +123 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/cli-shell/spec.md +121 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/command-registry/spec.md +101 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/currency/spec.md +144 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/market-data/spec.md +256 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/observability/spec.md +226 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/onboarding/spec.md +194 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/storage/spec.md +157 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/specs/testing/spec.md +174 -0
- sobres-1.1.0/openspec/changes/0001-foundation-data-and-cli/tasks.md +248 -0
- sobres-1.1.0/openspec/changes/0002-portfolio-optimization/design.md +169 -0
- sobres-1.1.0/openspec/changes/0002-portfolio-optimization/proposal.md +107 -0
- sobres-1.1.0/openspec/changes/0002-portfolio-optimization/review-decisions.md +43 -0
- sobres-1.1.0/openspec/changes/0002-portfolio-optimization/specs/portfolio-optimization/spec.md +306 -0
- sobres-1.1.0/openspec/changes/0002-portfolio-optimization/tasks.md +154 -0
- sobres-1.1.0/openspec/changes/0003-local-persistence/design.md +25 -0
- sobres-1.1.0/openspec/changes/0003-local-persistence/proposal.md +108 -0
- sobres-1.1.0/openspec/changes/0003-local-persistence/specs/persistence/spec.md +221 -0
- sobres-1.1.0/openspec/changes/0003-local-persistence/tasks.md +69 -0
- sobres-1.1.0/openspec/changes/0004-web-ui/design.md +166 -0
- sobres-1.1.0/openspec/changes/0004-web-ui/proposal.md +118 -0
- sobres-1.1.0/openspec/changes/0004-web-ui/specs/http-api/spec.md +211 -0
- sobres-1.1.0/openspec/changes/0004-web-ui/specs/web-ui/spec.md +286 -0
- sobres-1.1.0/openspec/changes/0004-web-ui/tasks.md +91 -0
- sobres-1.1.0/openspec/changes/0005-docker-distribution/design.md +25 -0
- sobres-1.1.0/openspec/changes/0005-docker-distribution/proposal.md +113 -0
- sobres-1.1.0/openspec/changes/0005-docker-distribution/specs/deployment/spec.md +272 -0
- sobres-1.1.0/openspec/changes/0005-docker-distribution/tasks.md +80 -0
- sobres-1.1.0/openspec/changes/0006-landing-page/design.md +25 -0
- sobres-1.1.0/openspec/changes/0006-landing-page/proposal.md +115 -0
- sobres-1.1.0/openspec/changes/0006-landing-page/specs/landing-page/spec.md +189 -0
- sobres-1.1.0/openspec/changes/0006-landing-page/tasks.md +70 -0
- sobres-1.1.0/openspec/changes/0007-equity-factor-analysis/design.md +68 -0
- sobres-1.1.0/openspec/changes/0007-equity-factor-analysis/proposal.md +85 -0
- sobres-1.1.0/openspec/changes/0007-equity-factor-analysis/specs/equity-analysis/spec.md +79 -0
- sobres-1.1.0/openspec/changes/0007-equity-factor-analysis/tasks.md +46 -0
- sobres-1.1.0/openspec/changes/0008-goal-planning/design.md +69 -0
- sobres-1.1.0/openspec/changes/0008-goal-planning/proposal.md +85 -0
- sobres-1.1.0/openspec/changes/0008-goal-planning/specs/goal-planning/spec.md +132 -0
- sobres-1.1.0/openspec/changes/0008-goal-planning/tasks.md +41 -0
- sobres-1.1.0/openspec/changes/0009-econometrics-forecasting/design.md +246 -0
- sobres-1.1.0/openspec/changes/0009-econometrics-forecasting/proposal.md +132 -0
- sobres-1.1.0/openspec/changes/0009-econometrics-forecasting/research.md +77 -0
- sobres-1.1.0/openspec/changes/0009-econometrics-forecasting/specs/econometrics/spec.md +231 -0
- sobres-1.1.0/openspec/changes/0009-econometrics-forecasting/tasks.md +90 -0
- sobres-1.1.0/openspec/changes/0010-currency-and-ppp/design.md +86 -0
- sobres-1.1.0/openspec/changes/0010-currency-and-ppp/proposal.md +124 -0
- sobres-1.1.0/openspec/changes/0010-currency-and-ppp/specs/fx-analytics/spec.md +125 -0
- sobres-1.1.0/openspec/changes/0010-currency-and-ppp/specs/purchasing-power/spec.md +149 -0
- sobres-1.1.0/openspec/changes/0010-currency-and-ppp/tasks.md +60 -0
- sobres-1.1.0/openspec/changes/0011-rebrand-sobres/proposal.md +99 -0
- sobres-1.1.0/openspec/changes/0011-rebrand-sobres/specs/packaging/spec.md +54 -0
- sobres-1.1.0/openspec/changes/0011-rebrand-sobres/tasks.md +62 -0
- sobres-1.1.0/openspec/changes/0012-foundation-review-fixes/design.md +24 -0
- sobres-1.1.0/openspec/changes/0012-foundation-review-fixes/proposal.md +26 -0
- sobres-1.1.0/openspec/changes/0012-foundation-review-fixes/specs/foundation-correctness/spec.md +112 -0
- sobres-1.1.0/openspec/changes/0012-foundation-review-fixes/tasks.md +21 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/.openspec.yaml +2 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/alignment-audit.md +145 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/design.md +205 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/proposal.md +146 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/review-draft.md +33 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/specs/development-workflow/spec.md +116 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/specs/template-alignment/spec.md +103 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/tasks.md +132 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/tracking.md +46 -0
- sobres-1.1.0/openspec/changes/0013-template-development-alignment/validation.md +110 -0
- sobres-1.1.0/openspec/changes/0014-onboarding-defaults/design.md +34 -0
- sobres-1.1.0/openspec/changes/0014-onboarding-defaults/proposal.md +71 -0
- sobres-1.1.0/openspec/changes/0014-onboarding-defaults/specs/cli-shell/spec.md +48 -0
- sobres-1.1.0/openspec/changes/0014-onboarding-defaults/specs/onboarding/spec.md +37 -0
- sobres-1.1.0/openspec/changes/0014-onboarding-defaults/tasks.md +13 -0
- sobres-1.1.0/openspec/changes/0015-risk-free-proxy/design.md +29 -0
- sobres-1.1.0/openspec/changes/0015-risk-free-proxy/proposal.md +56 -0
- sobres-1.1.0/openspec/changes/0015-risk-free-proxy/specs/portfolio-optimization/spec.md +37 -0
- sobres-1.1.0/openspec/changes/0015-risk-free-proxy/tasks.md +7 -0
- sobres-1.1.0/openspec/changes/0016-broker-execution/design.md +84 -0
- sobres-1.1.0/openspec/changes/0016-broker-execution/proposal.md +87 -0
- sobres-1.1.0/openspec/changes/0016-broker-execution/specs/broker-execution/spec.md +130 -0
- sobres-1.1.0/openspec/changes/0016-broker-execution/tasks.md +43 -0
- sobres-1.1.0/openspec/changes/0017-arm-publication/design.md +42 -0
- sobres-1.1.0/openspec/changes/0017-arm-publication/proposal.md +55 -0
- sobres-1.1.0/openspec/changes/0017-arm-publication/specs/landing-page/spec.md +25 -0
- sobres-1.1.0/openspec/changes/0017-arm-publication/specs/release-pipeline/spec.md +52 -0
- sobres-1.1.0/openspec/changes/0017-arm-publication/tasks.md +50 -0
- sobres-1.1.0/openspec/changes/archive/.gitkeep +0 -0
- sobres-1.1.0/openspec/config.yaml +34 -0
- sobres-1.1.0/openspec/project.md +341 -0
- sobres-1.1.0/openspec/specs/.gitkeep +0 -0
- sobres-1.1.0/pyproject.toml +134 -0
- sobres-1.1.0/scripts/export_openapi.py +51 -0
- sobres-1.1.0/scripts/r_reference.py +102 -0
- sobres-1.1.0/scripts/record_fixtures.py +263 -0
- sobres-1.1.0/scripts/schema_fixture.py +75 -0
- sobres-1.1.0/scripts/synthesize_fixtures.py +481 -0
- sobres-1.1.0/scripts/synthesize_market_proxy.py +85 -0
- sobres-1.1.0/site/index.html +187 -0
- sobres-1.1.0/site/lighthouserc.json +40 -0
- sobres-1.1.0/site/package-lock.json +1879 -0
- sobres-1.1.0/site/package.json +27 -0
- sobres-1.1.0/site/public/og-image.png +0 -0
- sobres-1.1.0/site/scripts/check-bundle.mjs +23 -0
- sobres-1.1.0/site/scripts/check-content.mjs +38 -0
- sobres-1.1.0/site/scripts/generate.mjs +29 -0
- sobres-1.1.0/site/scripts/og_image.py +51 -0
- sobres-1.1.0/site/scripts/prepare-audit.mjs +12 -0
- sobres-1.1.0/site/scripts/record_figures.py +183 -0
- sobres-1.1.0/site/src/data/backtest.json +2027 -0
- sobres-1.1.0/site/src/data/figures-meta.json +38 -0
- sobres-1.1.0/site/src/data/frontier.json +673 -0
- sobres-1.1.0/site/src/data/terminal.txt +24 -0
- sobres-1.1.0/site/src/figures.ts +107 -0
- sobres-1.1.0/site/src/main.ts +76 -0
- sobres-1.1.0/site/src/motion.ts +96 -0
- sobres-1.1.0/site/src/style.css +80 -0
- sobres-1.1.0/site/src/vite-env.d.ts +5 -0
- sobres-1.1.0/site/tsconfig.json +17 -0
- sobres-1.1.0/site/vite.config.ts +23 -0
- sobres-1.1.0/skills/openspec-pr-review/SKILL.md +49 -0
- sobres-1.1.0/src/sobres/__about__.py +1 -0
- sobres-1.1.0/src/sobres/__init__.py +5 -0
- sobres-1.1.0/src/sobres/api/__init__.py +19 -0
- sobres-1.1.0/src/sobres/api/app.py +496 -0
- sobres-1.1.0/src/sobres/api/auth.py +117 -0
- sobres-1.1.0/src/sobres/api/jobs.py +260 -0
- sobres-1.1.0/src/sobres/api/static/assets/BacktestCharts-C7phgH_S.js +1 -0
- sobres-1.1.0/src/sobres/api/static/assets/EChart-BHrkUjbY.js +1 -0
- sobres-1.1.0/src/sobres/api/static/assets/FrontierChart-B3J3HsGE.js +1 -0
- sobres-1.1.0/src/sobres/api/static/assets/echarts-BpE1uIOo.js +39 -0
- sobres-1.1.0/src/sobres/api/static/assets/index-DikqmPZ6.js +68 -0
- sobres-1.1.0/src/sobres/api/static/assets/index-pgZOLb7x.css +1 -0
- sobres-1.1.0/src/sobres/api/static/assets/motion-D99TpcCF.js +17 -0
- sobres-1.1.0/src/sobres/api/static/index.html +25 -0
- sobres-1.1.0/src/sobres/api/static/manifest.json +80 -0
- sobres-1.1.0/src/sobres/cli/__init__.py +0 -0
- sobres-1.1.0/src/sobres/cli/commands/__init__.py +29 -0
- sobres-1.1.0/src/sobres/cli/commands/analyze.py +390 -0
- sobres-1.1.0/src/sobres/cli/commands/cache.py +59 -0
- sobres-1.1.0/src/sobres/cli/commands/commands.py +26 -0
- sobres-1.1.0/src/sobres/cli/commands/config.py +85 -0
- sobres-1.1.0/src/sobres/cli/commands/data.py +135 -0
- sobres-1.1.0/src/sobres/cli/commands/db.py +103 -0
- sobres-1.1.0/src/sobres/cli/commands/deploy.py +102 -0
- sobres-1.1.0/src/sobres/cli/commands/doctor.py +105 -0
- sobres-1.1.0/src/sobres/cli/commands/econ.py +880 -0
- sobres-1.1.0/src/sobres/cli/commands/fx.py +436 -0
- sobres-1.1.0/src/sobres/cli/commands/init.py +227 -0
- sobres-1.1.0/src/sobres/cli/commands/optimize.py +925 -0
- sobres-1.1.0/src/sobres/cli/commands/plan.py +648 -0
- sobres-1.1.0/src/sobres/cli/commands/portfolio.py +122 -0
- sobres-1.1.0/src/sobres/cli/commands/ppp.py +398 -0
- sobres-1.1.0/src/sobres/cli/commands/run.py +174 -0
- sobres-1.1.0/src/sobres/cli/commands/serve.py +263 -0
- sobres-1.1.0/src/sobres/cli/commands/trade.py +892 -0
- sobres-1.1.0/src/sobres/cli/commands/upgrade.py +68 -0
- sobres-1.1.0/src/sobres/cli/commands/watchlist.py +112 -0
- sobres-1.1.0/src/sobres/cli/context.py +237 -0
- sobres-1.1.0/src/sobres/cli/main.py +223 -0
- sobres-1.1.0/src/sobres/cli/metric_table.py +45 -0
- sobres-1.1.0/src/sobres/cli/render.py +130 -0
- sobres-1.1.0/src/sobres/cli/window.py +27 -0
- sobres-1.1.0/src/sobres/config.py +221 -0
- sobres-1.1.0/src/sobres/core/__init__.py +5 -0
- sobres-1.1.0/src/sobres/core/allocate.py +114 -0
- sobres-1.1.0/src/sobres/core/backtest.py +241 -0
- sobres-1.1.0/src/sobres/core/conventions.py +59 -0
- sobres-1.1.0/src/sobres/core/errors.py +120 -0
- sobres-1.1.0/src/sobres/core/factors.py +265 -0
- sobres-1.1.0/src/sobres/core/forecast.py +1045 -0
- sobres-1.1.0/src/sobres/core/fx.py +170 -0
- sobres-1.1.0/src/sobres/core/goals.py +408 -0
- sobres-1.1.0/src/sobres/core/moments.py +221 -0
- sobres-1.1.0/src/sobres/core/optimize.py +545 -0
- sobres-1.1.0/src/sobres/core/ppp.py +303 -0
- sobres-1.1.0/src/sobres/core/rates.py +58 -0
- sobres-1.1.0/src/sobres/core/regression.py +145 -0
- sobres-1.1.0/src/sobres/core/returns.py +116 -0
- sobres-1.1.0/src/sobres/core/risk.py +216 -0
- sobres-1.1.0/src/sobres/core/simulate.py +145 -0
- sobres-1.1.0/src/sobres/core/timeseries.py +308 -0
- sobres-1.1.0/src/sobres/core/trading.py +318 -0
- sobres-1.1.0/src/sobres/core/validation.py +27 -0
- sobres-1.1.0/src/sobres/data/__init__.py +5 -0
- sobres-1.1.0/src/sobres/data/align.py +62 -0
- sobres-1.1.0/src/sobres/data/base.py +212 -0
- sobres-1.1.0/src/sobres/data/brokers/__init__.py +1 -0
- sobres-1.1.0/src/sobres/data/brokers/alpaca.py +340 -0
- sobres-1.1.0/src/sobres/data/brokers/base.py +174 -0
- sobres-1.1.0/src/sobres/data/brokers/fake.py +218 -0
- sobres-1.1.0/src/sobres/data/cache.py +213 -0
- sobres-1.1.0/src/sobres/data/currency.py +277 -0
- sobres-1.1.0/src/sobres/data/ecb_provider.py +157 -0
- sobres-1.1.0/src/sobres/data/fixtures.py +355 -0
- sobres-1.1.0/src/sobres/data/fred_provider.py +196 -0
- sobres-1.1.0/src/sobres/data/gaps.py +83 -0
- sobres-1.1.0/src/sobres/data/ken_french.py +212 -0
- sobres-1.1.0/src/sobres/data/ppp_provider.py +346 -0
- sobres-1.1.0/src/sobres/data/providers.py +77 -0
- sobres-1.1.0/src/sobres/data/storage/__init__.py +37 -0
- sobres-1.1.0/src/sobres/data/storage/adapters/__init__.py +8 -0
- sobres-1.1.0/src/sobres/data/storage/adapters/migrations.py +61 -0
- sobres-1.1.0/src/sobres/data/storage/adapters/schema.py +257 -0
- sobres-1.1.0/src/sobres/data/storage/adapters/sqlite.py +1233 -0
- sobres-1.1.0/src/sobres/data/storage/base.py +507 -0
- sobres-1.1.0/src/sobres/data/yfinance_provider.py +319 -0
- sobres-1.1.0/src/sobres/deploy.py +304 -0
- sobres-1.1.0/src/sobres/doctor.py +532 -0
- sobres-1.1.0/src/sobres/observability/__init__.py +9 -0
- sobres-1.1.0/src/sobres/observability/logging.py +152 -0
- sobres-1.1.0/src/sobres/observability/redaction.py +64 -0
- sobres-1.1.0/src/sobres/observability/tracing.py +224 -0
- sobres-1.1.0/src/sobres/py.typed +0 -0
- sobres-1.1.0/src/sobres/registry.py +583 -0
- sobres-1.1.0/src/sobres/results.py +215 -0
- sobres-1.1.0/src/sobres/settings.py +409 -0
- sobres-1.1.0/src/sobres/solvers/__init__.py +1 -0
- sobres-1.1.0/src/sobres/solvers/base.py +38 -0
- sobres-1.1.0/src/sobres/solvers/scipy.py +35 -0
- sobres-1.1.0/tests/__init__.py +0 -0
- sobres-1.1.0/tests/api/__init__.py +0 -0
- sobres-1.1.0/tests/api/conftest.py +43 -0
- sobres-1.1.0/tests/api/test_api.py +469 -0
- sobres-1.1.0/tests/api/test_parity.py +164 -0
- sobres-1.1.0/tests/architecture/__init__.py +0 -0
- sobres-1.1.0/tests/architecture/test_deployment_files.py +97 -0
- sobres-1.1.0/tests/architecture/test_encoding.py +83 -0
- sobres-1.1.0/tests/architecture/test_layering.py +212 -0
- sobres-1.1.0/tests/architecture/test_scenarios.py +65 -0
- sobres-1.1.0/tests/architecture/test_testing_spec.py +81 -0
- sobres-1.1.0/tests/cli/__init__.py +0 -0
- sobres-1.1.0/tests/cli/test_analyze.py +154 -0
- sobres-1.1.0/tests/cli/test_cache_commands.py +47 -0
- sobres-1.1.0/tests/cli/test_config_commands.py +59 -0
- sobres-1.1.0/tests/cli/test_data_commands.py +137 -0
- sobres-1.1.0/tests/cli/test_deploy.py +380 -0
- sobres-1.1.0/tests/cli/test_disclaimer.py +43 -0
- sobres-1.1.0/tests/cli/test_doctor.py +341 -0
- sobres-1.1.0/tests/cli/test_econ.py +384 -0
- sobres-1.1.0/tests/cli/test_fx.py +356 -0
- sobres-1.1.0/tests/cli/test_init.py +309 -0
- sobres-1.1.0/tests/cli/test_main.py +129 -0
- sobres-1.1.0/tests/cli/test_optimization_review.py +198 -0
- sobres-1.1.0/tests/cli/test_optimize.py +347 -0
- sobres-1.1.0/tests/cli/test_persistence.py +271 -0
- sobres-1.1.0/tests/cli/test_plan.py +470 -0
- sobres-1.1.0/tests/cli/test_ppp.py +232 -0
- sobres-1.1.0/tests/cli/test_registry.py +333 -0
- sobres-1.1.0/tests/cli/test_serve.py +187 -0
- sobres-1.1.0/tests/cli/test_settings.py +113 -0
- sobres-1.1.0/tests/cli/test_trade.py +348 -0
- sobres-1.1.0/tests/cli/test_upgrade.py +64 -0
- sobres-1.1.0/tests/cli/test_usage_examples.py +26 -0
- sobres-1.1.0/tests/cli/test_window.py +55 -0
- sobres-1.1.0/tests/conftest.py +135 -0
- sobres-1.1.0/tests/core/__init__.py +0 -0
- sobres-1.1.0/tests/core/test_allocate.py +52 -0
- sobres-1.1.0/tests/core/test_backtest.py +168 -0
- sobres-1.1.0/tests/core/test_conventions.py +35 -0
- sobres-1.1.0/tests/core/test_factors.py +183 -0
- sobres-1.1.0/tests/core/test_forecast.py +318 -0
- sobres-1.1.0/tests/core/test_fx.py +103 -0
- sobres-1.1.0/tests/core/test_goals.py +216 -0
- sobres-1.1.0/tests/core/test_moments.py +117 -0
- sobres-1.1.0/tests/core/test_optimize.py +274 -0
- sobres-1.1.0/tests/core/test_ppp.py +135 -0
- sobres-1.1.0/tests/core/test_psd.py +58 -0
- sobres-1.1.0/tests/core/test_rates.py +25 -0
- sobres-1.1.0/tests/core/test_regression.py +87 -0
- sobres-1.1.0/tests/core/test_returns.py +88 -0
- sobres-1.1.0/tests/core/test_review_corrections.py +146 -0
- sobres-1.1.0/tests/core/test_risk.py +125 -0
- sobres-1.1.0/tests/core/test_simulate.py +129 -0
- sobres-1.1.0/tests/core/test_textbook_cases.py +58 -0
- sobres-1.1.0/tests/core/test_timeseries.py +147 -0
- sobres-1.1.0/tests/core/test_trading.py +167 -0
- sobres-1.1.0/tests/data/__init__.py +0 -0
- sobres-1.1.0/tests/data/broker_conformance.py +98 -0
- sobres-1.1.0/tests/data/contracts.py +60 -0
- sobres-1.1.0/tests/data/storage_conformance.py +390 -0
- sobres-1.1.0/tests/data/test_align.py +74 -0
- sobres-1.1.0/tests/data/test_base.py +122 -0
- sobres-1.1.0/tests/data/test_brokers.py +252 -0
- sobres-1.1.0/tests/data/test_cache.py +220 -0
- sobres-1.1.0/tests/data/test_currency.py +220 -0
- sobres-1.1.0/tests/data/test_fixture_recording.py +99 -0
- sobres-1.1.0/tests/data/test_fred.py +188 -0
- sobres-1.1.0/tests/data/test_fx.py +111 -0
- sobres-1.1.0/tests/data/test_gaps.py +78 -0
- sobres-1.1.0/tests/data/test_ken_french.py +155 -0
- sobres-1.1.0/tests/data/test_migrations.py +85 -0
- sobres-1.1.0/tests/data/test_portability.py +71 -0
- sobres-1.1.0/tests/data/test_ppp_provider.py +81 -0
- sobres-1.1.0/tests/data/test_storage_conformance.py +89 -0
- sobres-1.1.0/tests/data/test_storage_port.py +224 -0
- sobres-1.1.0/tests/data/test_yfinance.py +236 -0
- sobres-1.1.0/tests/fixtures/README.md +95 -0
- sobres-1.1.0/tests/fixtures/bis/GB.csv +121 -0
- sobres-1.1.0/tests/fixtures/bis/JP.csv +121 -0
- sobres-1.1.0/tests/fixtures/bis/MX.csv +121 -0
- sobres-1.1.0/tests/fixtures/bis/US.csv +121 -0
- sobres-1.1.0/tests/fixtures/bis/XM.csv +121 -0
- sobres-1.1.0/tests/fixtures/bis/meta.json +7 -0
- sobres-1.1.0/tests/fixtures/ecb/CHF.csv +2562 -0
- sobres-1.1.0/tests/fixtures/ecb/GBP.csv +2562 -0
- sobres-1.1.0/tests/fixtures/ecb/JPY.csv +2562 -0
- sobres-1.1.0/tests/fixtures/ecb/USD.csv +2562 -0
- sobres-1.1.0/tests/fixtures/ecb/meta.json +17 -0
- sobres-1.1.0/tests/fixtures/fire_calculator/golden.json +26 -0
- sobres-1.1.0/tests/fixtures/fred/CPIAUCSL.json +736 -0
- sobres-1.1.0/tests/fixtures/fred/DEXUSEU.json +15664 -0
- sobres-1.1.0/tests/fixtures/fred/DGS10.json +15664 -0
- sobres-1.1.0/tests/fixtures/fred/DTB3.json +15664 -0
- sobres-1.1.0/tests/fixtures/fred/meta.json +17 -0
- sobres-1.1.0/tests/fixtures/ken_french/F-F_Momentum_Factor.CSV +1314 -0
- sobres-1.1.0/tests/fixtures/ken_french/F-F_Momentum_Factor_daily.CSV +26211 -0
- sobres-1.1.0/tests/fixtures/ken_french/F-F_Research_Data_5_Factors_2x3.CSV +829 -0
- sobres-1.1.0/tests/fixtures/ken_french/F-F_Research_Data_5_Factors_2x3_daily.CSV +15883 -0
- sobres-1.1.0/tests/fixtures/ken_french/F-F_Research_Data_Factors.CSV +1310 -0
- sobres-1.1.0/tests/fixtures/ken_french/F-F_Research_Data_Factors_daily.CSV +26303 -0
- sobres-1.1.0/tests/fixtures/ken_french/meta.json +16 -0
- sobres-1.1.0/tests/fixtures/oecd/AUS.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/CAN.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/CHE.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/DEU.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/ESP.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/GBR.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/JPN.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/MEX.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/PRT.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/USA.csv +15 -0
- sobres-1.1.0/tests/fixtures/oecd/meta.json +7 -0
- sobres-1.1.0/tests/fixtures/r_reference/allocation.json +42 -0
- sobres-1.1.0/tests/fixtures/schema/v1.sql +48 -0
- sobres-1.1.0/tests/fixtures/schema/v2.sql +97 -0
- sobres-1.1.0/tests/fixtures/synthetic/alpaca/account.json +12 -0
- sobres-1.1.0/tests/fixtures/synthetic/alpaca/meta.json +7 -0
- sobres-1.1.0/tests/fixtures/synthetic/alpaca/positions.json +1 -0
- sobres-1.1.0/tests/fixtures/synthetic/alpaca/quotes.json +38 -0
- sobres-1.1.0/tests/fixtures/synthetic/fred/GBRCPIALLMINMEI.json +736 -0
- sobres-1.1.0/tests/fixtures/synthetic/fred/IR3TIB01GBM156N.json +736 -0
- sobres-1.1.0/tests/fixtures/synthetic/yfinance/SPY.csv +2517 -0
- sobres-1.1.0/tests/fixtures/synthetic/yfinance/fundamentals.json +68 -0
- sobres-1.1.0/tests/fixtures/synthetic/yfinance/meta.json +14 -0
- sobres-1.1.0/tests/fixtures/worldbank/AUS.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/BRA.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/CAN.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/CHE.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/DEU.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/EMU.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/ESP.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/GBR.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/IND.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/JPN.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/MEX.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/PRT.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/USA.json +252 -0
- sobres-1.1.0/tests/fixtures/worldbank/meta.json +7 -0
- sobres-1.1.0/tests/fixtures/yfinance/AAPL.csv +2517 -0
- sobres-1.1.0/tests/fixtures/yfinance/GLD.csv +2517 -0
- sobres-1.1.0/tests/fixtures/yfinance/JNJ.csv +2517 -0
- sobres-1.1.0/tests/fixtures/yfinance/MSFT.csv +2517 -0
- sobres-1.1.0/tests/fixtures/yfinance/NVDA.csv +2517 -0
- sobres-1.1.0/tests/fixtures/yfinance/VOD.L.csv +2527 -0
- sobres-1.1.0/tests/fixtures/yfinance/XOM.csv +2517 -0
- sobres-1.1.0/tests/fixtures/yfinance/meta.json +57 -0
- sobres-1.1.0/tests/invariants/__init__.py +0 -0
- sobres-1.1.0/tests/invariants/test_every_command.py +693 -0
- sobres-1.1.0/tests/network/__init__.py +0 -0
- sobres-1.1.0/tests/network/test_live_providers.py +78 -0
- sobres-1.1.0/tests/test_cli_scaffold.py +30 -0
- sobres-1.1.0/tests/test_config.py +113 -0
- sobres-1.1.0/tests/test_errors.py +77 -0
- sobres-1.1.0/tests/test_logging.py +132 -0
- sobres-1.1.0/tests/test_observability_invariants.py +23 -0
- sobres-1.1.0/tests/test_packaging.py +96 -0
- sobres-1.1.0/tests/test_rebrand.py +49 -0
- sobres-1.1.0/tests/test_redaction.py +118 -0
- sobres-1.1.0/tests/test_release_script.py +117 -0
- sobres-1.1.0/tests/test_render.py +132 -0
- sobres-1.1.0/tests/test_settings_tier.py +11 -0
- sobres-1.1.0/tests/test_tracing.py +113 -0
- sobres-1.1.0/tests/ui/__init__.py +0 -0
- sobres-1.1.0/tests/ui/test_frontend_source.py +244 -0
- sobres-1.1.0/tests/ui/test_site_source.py +252 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code PreToolUse hook: before any `git commit` runs from a Bash tool call, run the
|
|
3
|
+
# same gate CI runs (ruff check, ruff format --check, mypy --strict, actionlint). Exit 2 blocks the
|
|
4
|
+
# commit and hands the output back to the agent, so a red PR is caught before it exists.
|
|
5
|
+
#
|
|
6
|
+
# Reads the tool call as JSON on stdin; only acts when the command contains "git commit".
|
|
7
|
+
set -uo pipefail
|
|
8
|
+
payload=$(cat)
|
|
9
|
+
command=$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tool_input", {}).get("command", ""))' 2>/dev/null || true)
|
|
10
|
+
case "$command" in
|
|
11
|
+
*"git commit"*) ;;
|
|
12
|
+
*) exit 0 ;;
|
|
13
|
+
esac
|
|
14
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
15
|
+
cd "$root" || exit 0
|
|
16
|
+
fail=0
|
|
17
|
+
run() {
|
|
18
|
+
local name=$1; shift
|
|
19
|
+
if ! output=$("$@" 2>&1); then
|
|
20
|
+
printf '\n[commit gate] %s failed:\n%s\n' "$name" "$output" >&2
|
|
21
|
+
fail=1
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
run "ruff check" ruff check .
|
|
25
|
+
run "ruff format --check" ruff format --check .
|
|
26
|
+
run "mypy --strict" python -m mypy
|
|
27
|
+
if command -v pre-commit >/dev/null 2>&1; then
|
|
28
|
+
run "actionlint" pre-commit run actionlint --all-files
|
|
29
|
+
fi
|
|
30
|
+
if [ "$fail" -ne 0 ]; then
|
|
31
|
+
printf '\n[commit gate] fix the findings above (ruff format . fixes formatting), then commit again.\n' >&2
|
|
32
|
+
exit 2
|
|
33
|
+
fi
|
|
34
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Only sources reach the build; nothing from a developer's machine does.
|
|
2
|
+
.git
|
|
3
|
+
.github
|
|
4
|
+
.venv
|
|
5
|
+
venv
|
|
6
|
+
**/__pycache__
|
|
7
|
+
**/*.pyc
|
|
8
|
+
.pytest_cache
|
|
9
|
+
.ruff_cache
|
|
10
|
+
.mypy_cache
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov
|
|
13
|
+
dist
|
|
14
|
+
build
|
|
15
|
+
*.egg-info
|
|
16
|
+
frontend/node_modules
|
|
17
|
+
src/sobres/api/static
|
|
18
|
+
legacy_code
|
|
19
|
+
openspec
|
|
20
|
+
docs
|
|
21
|
+
site
|
|
22
|
+
tests
|
|
23
|
+
.env
|
|
24
|
+
*.toml.bak
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Hosting inquiry
|
|
2
|
+
description: Ask about running sobres for you. This issue is public; do not include anything confidential.
|
|
3
|
+
title: "Hosting inquiry: "
|
|
4
|
+
labels: ["hosting-inquiry"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
**This issue is public.** Anyone can read it.
|
|
10
|
+
Do not paste API keys, account numbers, statements, holdings, or anything else you would not publish.
|
|
11
|
+
|
|
12
|
+
sobres is a single-user research tool. An inquiry is a conversation about whether
|
|
13
|
+
running it for you makes sense; it is not an order, a service-level agreement,
|
|
14
|
+
a payment contract, or a multi-tenant product, and it is not investment advice.
|
|
15
|
+
- type: dropdown
|
|
16
|
+
id: shape
|
|
17
|
+
attributes:
|
|
18
|
+
label: What are you looking for?
|
|
19
|
+
options:
|
|
20
|
+
- Help running the container myself
|
|
21
|
+
- Someone to run it for me
|
|
22
|
+
- Something else (describe below)
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: context
|
|
27
|
+
attributes:
|
|
28
|
+
label: Context
|
|
29
|
+
description: Where would it run, for how many people, and what would you use it for? Keep it general.
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
- type: checkboxes
|
|
33
|
+
id: confirm
|
|
34
|
+
attributes:
|
|
35
|
+
label: Confirm
|
|
36
|
+
options:
|
|
37
|
+
- label: I understand this issue is public and I have not included credentials, financial records or other confidential information.
|
|
38
|
+
required: true
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: github-actions
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
commit-message:
|
|
8
|
+
prefix: "ci"
|
|
9
|
+
groups:
|
|
10
|
+
# One PR per week for routine action bumps rather than five.
|
|
11
|
+
actions:
|
|
12
|
+
patterns: ["*"]
|
|
13
|
+
|
|
14
|
+
- package-ecosystem: pip
|
|
15
|
+
directory: "/"
|
|
16
|
+
schedule:
|
|
17
|
+
interval: weekly
|
|
18
|
+
commit-message:
|
|
19
|
+
prefix: "deps"
|
|
20
|
+
groups:
|
|
21
|
+
dev-dependencies:
|
|
22
|
+
dependency-type: development
|
|
23
|
+
patterns: ["*"]
|
|
24
|
+
# Runtime dependency majors are a deliberate decision, not a weekly chore.
|
|
25
|
+
ignore:
|
|
26
|
+
- dependency-name: "*"
|
|
27
|
+
update-types: ["version-update:semver-major"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## What
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. What does this change do? -->
|
|
4
|
+
|
|
5
|
+
## OpenSpec change
|
|
6
|
+
|
|
7
|
+
<!-- Which change under openspec/changes/ does this implement? Which tasks? -->
|
|
8
|
+
- Change: `openspec/changes/____`
|
|
9
|
+
- Tasks completed: <!-- e.g. B1, B2, B3 -->
|
|
10
|
+
|
|
11
|
+
## Scenarios covered
|
|
12
|
+
|
|
13
|
+
<!-- List the spec scenarios this PR implements, and the test proving each. -->
|
|
14
|
+
| Scenario | Test |
|
|
15
|
+
|---|---|
|
|
16
|
+
| | |
|
|
17
|
+
|
|
18
|
+
## Checklist
|
|
19
|
+
|
|
20
|
+
- [ ] Every new behavior has a scenario in a spec delta
|
|
21
|
+
- [ ] Every scenario has a test that references it
|
|
22
|
+
- [ ] `pytest -m "not network"` passes offline
|
|
23
|
+
- [ ] `ruff check`, `ruff format --check`, and `mypy` are clean
|
|
24
|
+
- [ ] No business logic added to `cli/`; no I/O added to `core/`
|
|
25
|
+
- [ ] No bare periods-per-year literals (use `core/conventions.py`)
|
|
26
|
+
- [ ] Task boxes checked off in the change's `tasks.md`
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Decide whether the current commit should be published.
|
|
3
|
+
|
|
4
|
+
PyPI versions are immutable: re-uploading an existing version is a hard error,
|
|
5
|
+
not an overwrite. So "publish on every push to main" can only mean "publish when
|
|
6
|
+
the version declared in ``__about__.py`` is not yet on the index".
|
|
7
|
+
|
|
8
|
+
Writes ``version``, ``target``, ``publish`` and ``reason`` as ``KEY=value`` lines on stdout,
|
|
9
|
+
which the workflow appends to ``$GITHUB_OUTPUT``.
|
|
10
|
+
|
|
11
|
+
stdout is therefore a machine interface: it carries ONLY ``key=value`` lines.
|
|
12
|
+
Every human-facing message goes to stderr. ``$GITHUB_OUTPUT`` rejects any line
|
|
13
|
+
that is not ``key=value``, so a stray informational print on stdout fails the
|
|
14
|
+
whole job -- which is exactly what happened on the first push to ``main``.
|
|
15
|
+
``tests/test_release_script.py`` enforces the contract.
|
|
16
|
+
|
|
17
|
+
Run locally to see what the next push to main would do::
|
|
18
|
+
|
|
19
|
+
python .github/scripts/check_release.py
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
import tomllib
|
|
29
|
+
import urllib.error
|
|
30
|
+
import urllib.request
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
34
|
+
ABOUT = REPO_ROOT / "src" / "sobres" / "__about__.py"
|
|
35
|
+
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
|
36
|
+
CHANGELOG = REPO_ROOT / "CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
INDEXES = {
|
|
39
|
+
"pypi": "https://pypi.org/pypi/{name}/json",
|
|
40
|
+
"testpypi": "https://test.pypi.org/pypi/{name}/json",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# PEP 440, restricted to the subset this project actually uses.
|
|
44
|
+
VERSION_RE = re.compile(r"^\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?(?:\.dev\d+)?$")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def note(message: str) -> None:
|
|
48
|
+
"""Human-facing progress line. stderr, never stdout -- stdout is $GITHUB_OUTPUT."""
|
|
49
|
+
print(message, file=sys.stderr)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def emit(key: str, value: str) -> None:
|
|
53
|
+
"""The only writer to stdout: one ``key=value`` line for $GITHUB_OUTPUT."""
|
|
54
|
+
print(f"{key}={value}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def fail(message: str) -> None:
|
|
58
|
+
"""Emit a GitHub Actions error annotation and exit non-zero."""
|
|
59
|
+
print(f"::error::{message}", file=sys.stderr)
|
|
60
|
+
raise SystemExit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def read_version() -> str:
|
|
64
|
+
match = re.search(
|
|
65
|
+
r'^__version__\s*=\s*"([^"]+)"', ABOUT.read_text(encoding="utf-8"), re.MULTILINE
|
|
66
|
+
)
|
|
67
|
+
if not match:
|
|
68
|
+
fail(f"Could not find __version__ in {ABOUT.relative_to(REPO_ROOT)}")
|
|
69
|
+
raise AssertionError("unreachable") # pragma: no cover
|
|
70
|
+
return match.group(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def read_dist_name() -> str:
|
|
74
|
+
return str(tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))["project"]["name"])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def released_versions(dist_name: str, target: str) -> set[str] | None:
|
|
78
|
+
"""Return every version on the index, or ``None`` if the project is new."""
|
|
79
|
+
url = INDEXES[target].format(name=dist_name)
|
|
80
|
+
request = urllib.request.Request(url, headers={"Accept": "application/json"})
|
|
81
|
+
try:
|
|
82
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
83
|
+
return set(json.load(response)["releases"])
|
|
84
|
+
except urllib.error.HTTPError as exc:
|
|
85
|
+
if exc.code == 404:
|
|
86
|
+
# First ever release: the project does not exist on the index yet.
|
|
87
|
+
return None
|
|
88
|
+
fail(f"{target} returned HTTP {exc.code} for {dist_name}")
|
|
89
|
+
except urllib.error.URLError as exc:
|
|
90
|
+
# A network blip must not silently look like "nothing is published",
|
|
91
|
+
# which would re-publish an existing version and fail at upload.
|
|
92
|
+
fail(f"Could not reach {target}: {exc.reason}")
|
|
93
|
+
raise AssertionError("unreachable") # pragma: no cover
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main() -> None:
|
|
97
|
+
target = os.environ.get("TARGET", "pypi")
|
|
98
|
+
if target not in INDEXES:
|
|
99
|
+
fail(f"TARGET must be one of {sorted(INDEXES)}, got {target!r}")
|
|
100
|
+
|
|
101
|
+
version = read_version()
|
|
102
|
+
if not VERSION_RE.match(version):
|
|
103
|
+
fail(
|
|
104
|
+
f"Version {version!r} is not a valid release version "
|
|
105
|
+
f"(expected e.g. 1.2.3, 1.2.3rc1 or 1.2.3.dev0)"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
dist_name = read_dist_name()
|
|
109
|
+
existing = released_versions(dist_name, target)
|
|
110
|
+
|
|
111
|
+
if existing is None:
|
|
112
|
+
note(f"{dist_name} is not on {target} yet -- this would be the first release.")
|
|
113
|
+
publish = True
|
|
114
|
+
elif version in existing:
|
|
115
|
+
note(f"{dist_name} {version} is already on {target}; nothing to publish.")
|
|
116
|
+
publish = False
|
|
117
|
+
else:
|
|
118
|
+
note(f"{dist_name} {version} is not on {target}; it will be published.")
|
|
119
|
+
publish = True
|
|
120
|
+
|
|
121
|
+
# Release discipline: a version that ships must be described. Checked only
|
|
122
|
+
# when actually publishing, so ordinary pushes are never blocked by it.
|
|
123
|
+
if publish and CHANGELOG.exists():
|
|
124
|
+
changelog = CHANGELOG.read_text(encoding="utf-8")
|
|
125
|
+
if f"## [{version}]" not in changelog and f"## {version}" not in changelog:
|
|
126
|
+
fail(
|
|
127
|
+
f"CHANGELOG.md has no section for {version}. "
|
|
128
|
+
f"Add a '## [{version}]' heading describing the release."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
emit("version", version)
|
|
132
|
+
emit("target", target)
|
|
133
|
+
emit("publish", str(publish).lower())
|
|
134
|
+
emit("reason", "new-version" if publish else "already-published")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
main()
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
# Callable by release.yml so a release runs the exact same gates, not a copy
|
|
8
|
+
# of them that can drift.
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
17
|
+
|
|
18
|
+
env:
|
|
19
|
+
FORCE_COLOR: "1"
|
|
20
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
lint:
|
|
24
|
+
name: Lint and format
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v7
|
|
28
|
+
- uses: actions/setup-python@v7
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
cache: pip
|
|
32
|
+
cache-dependency-path: pyproject.toml
|
|
33
|
+
- run: pip install -e ".[dev]"
|
|
34
|
+
- name: ruff check
|
|
35
|
+
run: ruff check --output-format=github .
|
|
36
|
+
- name: ruff format
|
|
37
|
+
run: ruff format --check --diff .
|
|
38
|
+
|
|
39
|
+
- name: Lint GitHub Actions workflows
|
|
40
|
+
# These workflows are code, and they fail in a way no unit test can
|
|
41
|
+
# catch: an invalid expression is rejected before any job starts, so the
|
|
42
|
+
# run reports failure with zero jobs and no log to read.
|
|
43
|
+
# Pinned release download rather than a third-party action or a
|
|
44
|
+
# curl-piped-to-bash installer.
|
|
45
|
+
env:
|
|
46
|
+
ACTIONLINT_VERSION: "1.7.7"
|
|
47
|
+
run: |
|
|
48
|
+
curl -sSfL -o actionlint.tar.gz \
|
|
49
|
+
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
|
|
50
|
+
tar xzf actionlint.tar.gz actionlint
|
|
51
|
+
./actionlint -color
|
|
52
|
+
|
|
53
|
+
typecheck:
|
|
54
|
+
name: Type check
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v7
|
|
58
|
+
- uses: actions/setup-python@v7
|
|
59
|
+
with:
|
|
60
|
+
python-version: "3.12"
|
|
61
|
+
cache: pip
|
|
62
|
+
cache-dependency-path: pyproject.toml
|
|
63
|
+
- run: pip install -e ".[dev]"
|
|
64
|
+
- name: mypy --strict
|
|
65
|
+
run: mypy
|
|
66
|
+
|
|
67
|
+
test:
|
|
68
|
+
name: Test ${{ matrix.python-version }} on ${{ matrix.os }}
|
|
69
|
+
runs-on: ${{ matrix.os }}
|
|
70
|
+
strategy:
|
|
71
|
+
fail-fast: false
|
|
72
|
+
matrix:
|
|
73
|
+
os: [ubuntu-latest]
|
|
74
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
75
|
+
include:
|
|
76
|
+
# Cross-platform coverage on the primary interpreter only. Path
|
|
77
|
+
# handling and the on-disk cache are the parts that break on Windows.
|
|
78
|
+
- os: macos-latest
|
|
79
|
+
python-version: "3.12"
|
|
80
|
+
- os: windows-latest
|
|
81
|
+
python-version: "3.12"
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v7
|
|
84
|
+
- uses: actions/setup-python@v7
|
|
85
|
+
with:
|
|
86
|
+
python-version: ${{ matrix.python-version }}
|
|
87
|
+
cache: pip
|
|
88
|
+
cache-dependency-path: pyproject.toml
|
|
89
|
+
- run: pip install -e ".[dev]"
|
|
90
|
+
- name: pytest
|
|
91
|
+
# Network-marked tests hit live providers; a vendor outage must never
|
|
92
|
+
# turn this repo red. Run them deliberately with `pytest -m network`.
|
|
93
|
+
run: pytest -m "not network" --cov --cov-report=xml --cov-report=term-missing --cov-fail-under=90
|
|
94
|
+
|
|
95
|
+
- name: Upload coverage
|
|
96
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
|
|
97
|
+
uses: actions/upload-artifact@v7
|
|
98
|
+
with:
|
|
99
|
+
name: coverage-xml
|
|
100
|
+
path: coverage.xml
|
|
101
|
+
if-no-files-found: error
|
|
102
|
+
|
|
103
|
+
frontend:
|
|
104
|
+
name: Frontend (types, client drift, bundle budget)
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v7
|
|
108
|
+
- uses: actions/setup-python@v7
|
|
109
|
+
with:
|
|
110
|
+
python-version: "3.12"
|
|
111
|
+
cache: pip
|
|
112
|
+
cache-dependency-path: pyproject.toml
|
|
113
|
+
- uses: actions/setup-node@v4
|
|
114
|
+
with:
|
|
115
|
+
node-version: "22"
|
|
116
|
+
cache: npm
|
|
117
|
+
cache-dependency-path: frontend/package-lock.json
|
|
118
|
+
- run: pip install -e ".[dev]"
|
|
119
|
+
- name: The committed OpenAPI document matches the registry
|
|
120
|
+
# The TypeScript client is generated from this file; drift is a build
|
|
121
|
+
# failure here rather than a runtime one in the browser.
|
|
122
|
+
run: python scripts/export_openapi.py --check
|
|
123
|
+
- run: npm ci
|
|
124
|
+
working-directory: frontend
|
|
125
|
+
- name: The generated client matches the OpenAPI document
|
|
126
|
+
run: npm run check:client
|
|
127
|
+
working-directory: frontend
|
|
128
|
+
- name: Type check, build, bundle budget
|
|
129
|
+
# `build` runs tsc --strict, vite, then the 300 KB gzip gate.
|
|
130
|
+
run: npm run build
|
|
131
|
+
working-directory: frontend
|
|
132
|
+
- name: Upload the built SPA
|
|
133
|
+
uses: actions/upload-artifact@v7
|
|
134
|
+
with:
|
|
135
|
+
name: frontend-static
|
|
136
|
+
path: src/sobres/api/static/
|
|
137
|
+
if-no-files-found: error
|
|
138
|
+
|
|
139
|
+
build:
|
|
140
|
+
name: Build and verify distributions
|
|
141
|
+
needs: [frontend]
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
steps:
|
|
144
|
+
- uses: actions/checkout@v7
|
|
145
|
+
with:
|
|
146
|
+
# setuptools-scm-style tooling and changelog checks want real history.
|
|
147
|
+
fetch-depth: 0
|
|
148
|
+
- uses: actions/setup-python@v7
|
|
149
|
+
with:
|
|
150
|
+
python-version: "3.12"
|
|
151
|
+
cache: pip
|
|
152
|
+
cache-dependency-path: pyproject.toml
|
|
153
|
+
|
|
154
|
+
- name: Fetch the built SPA
|
|
155
|
+
# The wheel ships the frontend at sobres/api/static so `pip install
|
|
156
|
+
# sobres[web]` needs no Node.
|
|
157
|
+
uses: actions/download-artifact@v8
|
|
158
|
+
with:
|
|
159
|
+
name: frontend-static
|
|
160
|
+
path: src/sobres/api/static/
|
|
161
|
+
|
|
162
|
+
- run: pip install build twine
|
|
163
|
+
- name: Build sdist and wheel
|
|
164
|
+
run: python -m build
|
|
165
|
+
|
|
166
|
+
- name: The wheel carries the SPA
|
|
167
|
+
run: unzip -l dist/*.whl | grep -q "sobres/api/static/index.html"
|
|
168
|
+
|
|
169
|
+
- name: twine check
|
|
170
|
+
run: twine check --strict dist/*
|
|
171
|
+
|
|
172
|
+
- name: Install the base wheel, no extras, in a clean environment
|
|
173
|
+
# `pip install sobres` with no extras is what most users get, and until
|
|
174
|
+
# now nothing tested it: every other job installs `.[dev]` and the job
|
|
175
|
+
# below installs `[web]`. Registering commands imports every module under
|
|
176
|
+
# cli/commands/, so a single module-scope import of an optional
|
|
177
|
+
# dependency breaks the whole CLI, `--version` included. That shipped.
|
|
178
|
+
env:
|
|
179
|
+
SOBRES_CONFIG_FILE: /tmp/base-home/config.toml
|
|
180
|
+
SOBRES_DB_URL: sqlite:////tmp/base-home/sobres.db
|
|
181
|
+
SOBRES_FIXTURE_DIR: ${{ github.workspace }}/tests/fixtures
|
|
182
|
+
run: |
|
|
183
|
+
python -m venv /tmp/smoke-base
|
|
184
|
+
/tmp/smoke-base/bin/pip install --upgrade pip
|
|
185
|
+
/tmp/smoke-base/bin/pip install "$(ls dist/*.whl)"
|
|
186
|
+
# The check is only meaningful if the extra is genuinely absent.
|
|
187
|
+
if /tmp/smoke-base/bin/python -c "import fastapi" 2>/dev/null; then
|
|
188
|
+
echo "::error::fastapi is installed in the base environment; this job proves nothing"
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
/tmp/smoke-base/bin/sobres --version
|
|
192
|
+
/tmp/smoke-base/bin/sobres --help > /dev/null
|
|
193
|
+
/tmp/smoke-base/bin/sobres init --non-interactive --offline
|
|
194
|
+
/tmp/smoke-base/bin/sobres doctor --offline
|
|
195
|
+
/tmp/smoke-base/bin/sobres data prices AAPL MSFT --start 2020-01-01 --end 2020-03-31 \
|
|
196
|
+
--format csv | head -3
|
|
197
|
+
|
|
198
|
+
- name: Install the built wheel in a clean environment
|
|
199
|
+
# Proves the artifact users actually download works -- catches missing
|
|
200
|
+
# package data, a broken entry point, or an import that only resolves
|
|
201
|
+
# from the source tree.
|
|
202
|
+
run: |
|
|
203
|
+
python -m venv /tmp/smoke
|
|
204
|
+
/tmp/smoke/bin/pip install --upgrade pip
|
|
205
|
+
/tmp/smoke/bin/pip install "$(ls dist/*.whl)[web]"
|
|
206
|
+
/tmp/smoke/bin/sobres --version
|
|
207
|
+
/tmp/smoke/bin/sobres serve --help > /dev/null
|
|
208
|
+
/tmp/smoke/bin/python -c "import sobres; print(sobres.__version__)"
|
|
209
|
+
/tmp/smoke/bin/python -c "import yfinance; assert callable(yfinance.Ticker)"
|
|
210
|
+
|
|
211
|
+
- name: The three-command path, in the clean environment
|
|
212
|
+
# Onboarding is tested end to end: init, doctor, then one data command
|
|
213
|
+
# served from the recorded fixtures rather than the network.
|
|
214
|
+
env:
|
|
215
|
+
SOBRES_CONFIG_FILE: /tmp/smoke-home/config.toml
|
|
216
|
+
SOBRES_DB_URL: sqlite:////tmp/smoke-home/sobres.db
|
|
217
|
+
SOBRES_FIXTURE_DIR: ${{ github.workspace }}/tests/fixtures
|
|
218
|
+
run: |
|
|
219
|
+
/tmp/smoke/bin/sobres init --non-interactive --offline
|
|
220
|
+
/tmp/smoke/bin/sobres doctor --offline
|
|
221
|
+
/tmp/smoke/bin/sobres data prices AAPL MSFT --start 2020-01-01 --end 2020-03-31 --format csv \
|
|
222
|
+
| head -5
|
|
223
|
+
/tmp/smoke/bin/sobres data prices AAPL MSFT --start 2020-01-01 --end 2020-03-31 --format json \
|
|
224
|
+
| /tmp/smoke/bin/python -c "import json,sys; d=json.load(sys.stdin); assert d['columns']==['AAPL','MSFT'], d['columns']; print(len(d['rows']), 'rows')"
|
|
225
|
+
|
|
226
|
+
- name: Confirm the sdist builds a wheel
|
|
227
|
+
run: |
|
|
228
|
+
python -m venv /tmp/sdist
|
|
229
|
+
/tmp/sdist/bin/pip install --upgrade pip build
|
|
230
|
+
/tmp/sdist/bin/pip wheel --no-deps -w /tmp/sdist-out dist/*.tar.gz
|
|
231
|
+
|
|
232
|
+
- name: Upload distributions
|
|
233
|
+
uses: actions/upload-artifact@v7
|
|
234
|
+
with:
|
|
235
|
+
name: dist
|
|
236
|
+
path: dist/
|
|
237
|
+
if-no-files-found: error
|
|
238
|
+
|
|
239
|
+
docker:
|
|
240
|
+
name: Container image (quickstart, size budget)
|
|
241
|
+
runs-on: ubuntu-latest
|
|
242
|
+
steps:
|
|
243
|
+
- uses: actions/checkout@v7
|
|
244
|
+
- uses: docker/setup-buildx-action@v3
|
|
245
|
+
- name: Build the image from this checkout
|
|
246
|
+
uses: docker/build-push-action@v6
|
|
247
|
+
with:
|
|
248
|
+
context: .
|
|
249
|
+
load: true
|
|
250
|
+
tags: sobres:ci
|
|
251
|
+
cache-from: type=gha
|
|
252
|
+
cache-to: type=gha,mode=max
|
|
253
|
+
|
|
254
|
+
- name: The CLI is the entrypoint, as a non-root user
|
|
255
|
+
run: |
|
|
256
|
+
docker run --rm sobres:ci --help | grep -q "Usage: sobres"
|
|
257
|
+
docker run --rm sobres:ci --version
|
|
258
|
+
test "$(docker run --rm --entrypoint id sobres:ci -u)" = "1000"
|
|
259
|
+
test "$(docker run --rm --entrypoint id sobres:ci -g)" = "1000"
|
|
260
|
+
|
|
261
|
+
- name: A missing data volume fails loudly
|
|
262
|
+
run: |
|
|
263
|
+
mkdir -p /tmp/ro && chmod 555 /tmp/ro
|
|
264
|
+
set +e
|
|
265
|
+
out=$(docker run --rm -v /tmp/ro:/data:ro sobres:ci db info 2>&1)
|
|
266
|
+
code=$?
|
|
267
|
+
set -e
|
|
268
|
+
echo "$out"
|
|
269
|
+
test "$code" -ne 0
|
|
270
|
+
echo "$out" | grep -q "/data"
|
|
271
|
+
|
|
272
|
+
- name: The documented quickstart, end to end
|
|
273
|
+
run: |
|
|
274
|
+
docker volume create sobres-ci
|
|
275
|
+
docker run -d --name sobres-ci -p 8787:8787 -v sobres-ci:/data sobres:ci serve --host 0.0.0.0
|
|
276
|
+
for _ in $(seq 1 30); do
|
|
277
|
+
if curl -fsS http://127.0.0.1:8787/api/v1/health > /tmp/health.json; then break; fi
|
|
278
|
+
sleep 1
|
|
279
|
+
done
|
|
280
|
+
cat /tmp/health.json
|
|
281
|
+
python3 -c "import json; d=json.load(open('/tmp/health.json')); assert d['app']=='sobres' and d['ready'], d"
|
|
282
|
+
curl -fsS http://127.0.0.1:8787/ | grep -qi "<div id=\"root\""
|
|
283
|
+
docker exec sobres-ci sobres deploy health --port 8787
|
|
284
|
+
docker logs sobres-ci 2>&1 | grep -q "token"
|
|
285
|
+
# A one-off command shares the same volume, then the container is replaced and data survives.
|
|
286
|
+
docker run --rm -v sobres-ci:/data sobres:ci portfolio save core --tickers AAPL MSFT --weights 0.6 0.4
|
|
287
|
+
docker stop -t 10 sobres-ci && docker rm sobres-ci
|
|
288
|
+
docker run --rm -v sobres-ci:/data sobres:ci portfolio list --format json | grep -q '"core"'
|
|
289
|
+
docker run --rm -v sobres-ci:/data sobres:ci doctor --offline
|
|
290
|
+
|
|
291
|
+
- name: The compressed image is under the 500 MB budget
|
|
292
|
+
run: |
|
|
293
|
+
size=$(docker save sobres:ci | gzip | wc -c)
|
|
294
|
+
echo "compressed image: $((size / 1024 / 1024)) MB"
|
|
295
|
+
test "$size" -lt $((500 * 1024 * 1024))
|
|
296
|
+
|
|
297
|
+
- name: The runtime stage carries no Node or build toolchain
|
|
298
|
+
run: |
|
|
299
|
+
! docker run --rm --entrypoint sh sobres:ci -c "command -v node || command -v npm || command -v gcc"
|
|
300
|
+
|
|
301
|
+
security:
|
|
302
|
+
name: Dependency audit
|
|
303
|
+
runs-on: ubuntu-latest
|
|
304
|
+
steps:
|
|
305
|
+
- uses: actions/checkout@v7
|
|
306
|
+
- uses: actions/setup-python@v7
|
|
307
|
+
with:
|
|
308
|
+
python-version: "3.12"
|
|
309
|
+
cache: pip
|
|
310
|
+
cache-dependency-path: pyproject.toml
|
|
311
|
+
- run: pip install -e ".[dev]" pip-audit
|
|
312
|
+
|
|
313
|
+
- name: Resolve the dependency tree
|
|
314
|
+
# Audit what this project actually pulls in, across every extra --
|
|
315
|
+
# `--exclude-editable` drops the local project (which is not on any
|
|
316
|
+
# index and so can never be audited), and `pip freeze` already omits
|
|
317
|
+
# pip/setuptools/wheel, whose advisories belong to the runner image
|
|
318
|
+
# rather than to this codebase. Auditing the ambient environment
|
|
319
|
+
# instead would fail on whatever pip version the runner happens to ship.
|
|
320
|
+
run: pip freeze --exclude-editable > requirements-audit.txt
|
|
321
|
+
|
|
322
|
+
- name: pip-audit
|
|
323
|
+
# A known vulnerability blocks merge and release by design. To ship past
|
|
324
|
+
# an unfixable transitive advisory, add `--ignore-vuln <ID>` here with a
|
|
325
|
+
# comment saying why -- see docs/RELEASING.md.
|
|
326
|
+
run: pip-audit --strict --progress-spinner=off -r requirements-audit.txt
|
|
327
|
+
|
|
328
|
+
# Single required status check. Branch protection points at this one job, so
|
|
329
|
+
# the matrix above can grow or shrink without editing protection rules.
|
|
330
|
+
all-green:
|
|
331
|
+
name: All checks passed
|
|
332
|
+
if: always()
|
|
333
|
+
needs: [lint, typecheck, test, frontend, build, docker, security]
|
|
334
|
+
runs-on: ubuntu-latest
|
|
335
|
+
steps:
|
|
336
|
+
# `if: always()` means this job runs even when a dependency failed, so it
|
|
337
|
+
# must inspect the results itself rather than rely on job ordering.
|
|
338
|
+
# Anything that is not `success` -- failed, cancelled, or skipped -- is a
|
|
339
|
+
# red gate; a skipped required job must never read as passing.
|
|
340
|
+
#
|
|
341
|
+
# The condition lives on the step, not in shell. A multi-line `${{ }}`
|
|
342
|
+
# interpolated into an `if` statement expands to a continuation line
|
|
343
|
+
# starting with `||`, which bash rejects as a syntax error -- so the gate
|
|
344
|
+
# would fail even when every job passed.
|
|
345
|
+
- name: Fail if any dependency did not succeed
|
|
346
|
+
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')
|
|
347
|
+
run: |
|
|
348
|
+
echo "::error::Required jobs did not all succeed: ${{ join(needs.*.result, ', ') }}"
|
|
349
|
+
exit 1
|
|
350
|
+
|
|
351
|
+
- name: All required jobs succeeded
|
|
352
|
+
run: |
|
|
353
|
+
echo "Results: ${{ join(needs.*.result, ', ') }}"
|