earnings-watch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- earnings_watch-0.1.0/.github/workflows/ci.yml +15 -0
- earnings_watch-0.1.0/.github/workflows/release.yml +235 -0
- earnings_watch-0.1.0/.gitignore +11 -0
- earnings_watch-0.1.0/CHANGELOG.md +17 -0
- earnings_watch-0.1.0/CONTRIBUTING.md +9 -0
- earnings_watch-0.1.0/Dockerfile +27 -0
- earnings_watch-0.1.0/LICENSE +21 -0
- earnings_watch-0.1.0/Makefile +20 -0
- earnings_watch-0.1.0/NOTICE +3 -0
- earnings_watch-0.1.0/PKG-INFO +105 -0
- earnings_watch-0.1.0/README.md +87 -0
- earnings_watch-0.1.0/SECURITY.md +7 -0
- earnings_watch-0.1.0/compose.yaml +27 -0
- earnings_watch-0.1.0/docs/ARCHITECTURE.md +19 -0
- earnings_watch-0.1.0/docs/DESIGN.md +13 -0
- earnings_watch-0.1.0/docs/IMPLEMENTATION.md +54 -0
- earnings_watch-0.1.0/docs/OPERATIONS.md +98 -0
- earnings_watch-0.1.0/docs/PERMISSIONS.md +13 -0
- earnings_watch-0.1.0/docs/PROVIDERS.md +39 -0
- earnings_watch-0.1.0/earnings-watch.example.toml +9 -0
- earnings_watch-0.1.0/examples/sources.yaml +11 -0
- earnings_watch-0.1.0/pyproject.toml +43 -0
- earnings_watch-0.1.0/scripts/check-boundaries.py +42 -0
- earnings_watch-0.1.0/scripts/check-conformance.py +92 -0
- earnings_watch-0.1.0/scripts/snapshot-surface.py +30 -0
- earnings_watch-0.1.0/server.json +30 -0
- earnings_watch-0.1.0/src/earnings_watch/__init__.py +7 -0
- earnings_watch-0.1.0/src/earnings_watch/browser.py +65 -0
- earnings_watch-0.1.0/src/earnings_watch/calendar.py +117 -0
- earnings_watch-0.1.0/src/earnings_watch/cli.py +423 -0
- earnings_watch-0.1.0/src/earnings_watch/composition.py +11 -0
- earnings_watch-0.1.0/src/earnings_watch/documents.py +184 -0
- earnings_watch-0.1.0/src/earnings_watch/engine/__init__.py +0 -0
- earnings_watch-0.1.0/src/earnings_watch/engine/coverage.py +115 -0
- earnings_watch-0.1.0/src/earnings_watch/engine/reconcile.py +408 -0
- earnings_watch-0.1.0/src/earnings_watch/engine/timing.py +387 -0
- earnings_watch-0.1.0/src/earnings_watch/engine/timing_search.py +128 -0
- earnings_watch-0.1.0/src/earnings_watch/engine/verify.py +676 -0
- earnings_watch-0.1.0/src/earnings_watch/errors.py +10 -0
- earnings_watch-0.1.0/src/earnings_watch/exports.py +162 -0
- earnings_watch-0.1.0/src/earnings_watch/heal.py +52 -0
- earnings_watch-0.1.0/src/earnings_watch/http_server.py +92 -0
- earnings_watch-0.1.0/src/earnings_watch/ics.py +49 -0
- earnings_watch-0.1.0/src/earnings_watch/legacy.py +319 -0
- earnings_watch-0.1.0/src/earnings_watch/llm/__init__.py +0 -0
- earnings_watch-0.1.0/src/earnings_watch/llm/doctor.py +75 -0
- earnings_watch-0.1.0/src/earnings_watch/llm/research_server.py +58 -0
- earnings_watch-0.1.0/src/earnings_watch/llm/runner.py +353 -0
- earnings_watch-0.1.0/src/earnings_watch/maintenance.py +332 -0
- earnings_watch-0.1.0/src/earnings_watch/mcp_server.py +155 -0
- earnings_watch-0.1.0/src/earnings_watch/model/__init__.py +0 -0
- earnings_watch-0.1.0/src/earnings_watch/model/events.py +333 -0
- earnings_watch-0.1.0/src/earnings_watch/model/limits.py +12 -0
- earnings_watch-0.1.0/src/earnings_watch/model/sources.py +531 -0
- earnings_watch-0.1.0/src/earnings_watch/model/urls.py +28 -0
- earnings_watch-0.1.0/src/earnings_watch/notify.py +113 -0
- earnings_watch-0.1.0/src/earnings_watch/outbox.py +70 -0
- earnings_watch-0.1.0/src/earnings_watch/polling.py +97 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/__init__.py +21 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/base.py +21 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/definitions/generic_ir.toml +4 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/definitions/hkexnews.toml +4 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/definitions/sec_edgar.toml +5 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/generic_ir.py +56 -0
- earnings_watch-0.1.0/src/earnings_watch/providers/sec_edgar.py +25 -0
- earnings_watch-0.1.0/src/earnings_watch/registration.py +71 -0
- earnings_watch-0.1.0/src/earnings_watch/release.py +238 -0
- earnings_watch-0.1.0/src/earnings_watch/research.py +50 -0
- earnings_watch-0.1.0/src/earnings_watch/service.py +160 -0
- earnings_watch-0.1.0/src/earnings_watch/settings.py +58 -0
- earnings_watch-0.1.0/src/earnings_watch/store/__init__.py +0 -0
- earnings_watch-0.1.0/src/earnings_watch/store/database.py +690 -0
- earnings_watch-0.1.0/src/earnings_watch/store/schema.sql +22 -0
- earnings_watch-0.1.0/src/earnings_watch/templates/__init__.py +0 -0
- earnings_watch-0.1.0/src/earnings_watch/transport.py +224 -0
- earnings_watch-0.1.0/src/earnings_watch/worker.py +33 -0
- earnings_watch-0.1.0/tests/conformance-baseline.json +11 -0
- earnings_watch-0.1.0/tests/conftest.py +57 -0
- earnings_watch-0.1.0/tests/schema-v1.sql +21 -0
- earnings_watch-0.1.0/tests/surface.json +526 -0
- earnings_watch-0.1.0/tests/test_audit_regressions.py +252 -0
- earnings_watch-0.1.0/tests/test_browser.py +79 -0
- earnings_watch-0.1.0/tests/test_coverage_engine.py +143 -0
- earnings_watch-0.1.0/tests/test_engine_safety_cases.py +204 -0
- earnings_watch-0.1.0/tests/test_events_state_engine.py +151 -0
- earnings_watch-0.1.0/tests/test_fetch_verify_engine.py +275 -0
- earnings_watch-0.1.0/tests/test_legacy_and_cli.py +415 -0
- earnings_watch-0.1.0/tests/test_maintenance_documents.py +222 -0
- earnings_watch-0.1.0/tests/test_mcp.py +125 -0
- earnings_watch-0.1.0/tests/test_polling_worker.py +139 -0
- earnings_watch-0.1.0/tests/test_runners_and_surface.py +137 -0
- earnings_watch-0.1.0/tests/test_sources_universe_engine.py +209 -0
- earnings_watch-0.1.0/tests/test_store_service.py +200 -0
- earnings_watch-0.1.0/tests/test_timing_evidence_engine.py +132 -0
- earnings_watch-0.1.0/tests/test_timing_resolution_engine.py +5 -0
- earnings_watch-0.1.0/tests/test_transport.py +95 -0
- earnings_watch-0.1.0/uv.lock +1923 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: astral-sh/setup-uv@v6
|
|
14
|
+
- run: make bootstrap
|
|
15
|
+
- run: make check
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags: ['v*.*.*']
|
|
5
|
+
workflow_dispatch: {}
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
concurrency:
|
|
9
|
+
group: release-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
jobs:
|
|
12
|
+
released:
|
|
13
|
+
if: github.ref_type == 'tag'
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
outputs:
|
|
16
|
+
exists: ${{ steps.check.outputs.exists }}
|
|
17
|
+
steps:
|
|
18
|
+
- id: check
|
|
19
|
+
env:
|
|
20
|
+
GH_TOKEN: ${{ github.token }}
|
|
21
|
+
run: |
|
|
22
|
+
if gh release view "$GITHUB_REF_NAME" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
|
23
|
+
echo exists=true >> "$GITHUB_OUTPUT"
|
|
24
|
+
else
|
|
25
|
+
echo exists=false >> "$GITHUB_OUTPUT"
|
|
26
|
+
fi
|
|
27
|
+
test:
|
|
28
|
+
needs: released
|
|
29
|
+
if: needs.released.outputs.exists != 'true'
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v6
|
|
34
|
+
- run: make bootstrap
|
|
35
|
+
- run: make check
|
|
36
|
+
- uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: distributions
|
|
39
|
+
path: dist/*
|
|
40
|
+
images:
|
|
41
|
+
needs: test
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
permissions:
|
|
44
|
+
contents: read
|
|
45
|
+
packages: write
|
|
46
|
+
strategy:
|
|
47
|
+
matrix:
|
|
48
|
+
target: [serve, worker]
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- uses: docker/setup-qemu-action@v3
|
|
52
|
+
- uses: docker/setup-buildx-action@v3
|
|
53
|
+
id: builder
|
|
54
|
+
with:
|
|
55
|
+
use: false
|
|
56
|
+
- uses: docker/login-action@v3
|
|
57
|
+
with:
|
|
58
|
+
registry: ghcr.io
|
|
59
|
+
username: ${{ github.actor }}
|
|
60
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
61
|
+
- id: metadata
|
|
62
|
+
shell: bash
|
|
63
|
+
run: |
|
|
64
|
+
name=earnings-watch
|
|
65
|
+
if [ '${{ matrix.target }}' = worker ]; then name=earnings-watch-worker; fi
|
|
66
|
+
echo name="$name" >> "$GITHUB_OUTPUT"
|
|
67
|
+
echo version="${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
68
|
+
- id: build
|
|
69
|
+
uses: docker/build-push-action@v6
|
|
70
|
+
with:
|
|
71
|
+
builder: ${{ steps.builder.outputs.name }}
|
|
72
|
+
context: .
|
|
73
|
+
target: ${{ matrix.target }}
|
|
74
|
+
platforms: linux/amd64,linux/arm64
|
|
75
|
+
push: true
|
|
76
|
+
tags: ghcr.io/${{ github.repository_owner }}/${{ steps.metadata.outputs.name }}:candidate-${{ github.sha }}
|
|
77
|
+
labels: |
|
|
78
|
+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
|
79
|
+
org.opencontainers.image.revision=${{ github.sha }}
|
|
80
|
+
org.opencontainers.image.version=${{ steps.metadata.outputs.version }}
|
|
81
|
+
- env:
|
|
82
|
+
DIGEST: ${{ steps.build.outputs.digest }}
|
|
83
|
+
run: |
|
|
84
|
+
mkdir -p digests
|
|
85
|
+
printf '%s\n' "$DIGEST" > 'digests/${{ steps.metadata.outputs.name }}'
|
|
86
|
+
- uses: actions/upload-artifact@v4
|
|
87
|
+
with:
|
|
88
|
+
name: digest-${{ matrix.target }}
|
|
89
|
+
path: digests/*
|
|
90
|
+
smoke:
|
|
91
|
+
needs: images
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
strategy:
|
|
94
|
+
matrix:
|
|
95
|
+
image: [earnings-watch, earnings-watch-worker]
|
|
96
|
+
arch: [amd64, arm64]
|
|
97
|
+
steps:
|
|
98
|
+
- uses: docker/setup-qemu-action@v3
|
|
99
|
+
- uses: actions/download-artifact@v4
|
|
100
|
+
with:
|
|
101
|
+
pattern: digest-*
|
|
102
|
+
path: digests
|
|
103
|
+
merge-multiple: true
|
|
104
|
+
- env:
|
|
105
|
+
IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
|
|
106
|
+
ARCH: ${{ matrix.arch }}
|
|
107
|
+
run: |
|
|
108
|
+
digest="$(cat 'digests/${{ matrix.image }}')"
|
|
109
|
+
docker run --rm --platform "linux/$ARCH" --read-only --cap-drop ALL --security-opt no-new-privileges --tmpfs /data:uid=1000,gid=1000,mode=0700 --tmpfs /tmp --entrypoint sh "$IMAGE@$digest" -c 'earnings-watch init && earnings-watch doctor && earnings-watch upcoming --format json'
|
|
110
|
+
if [ '${{ matrix.image }}' = earnings-watch-worker ]; then
|
|
111
|
+
docker run --rm --platform "linux/$ARCH" --entrypoint claude "$IMAGE@$digest" --version
|
|
112
|
+
docker run --rm --platform "linux/$ARCH" --entrypoint codex "$IMAGE@$digest" --version
|
|
113
|
+
fi
|
|
114
|
+
promote:
|
|
115
|
+
needs: smoke
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
permissions:
|
|
118
|
+
contents: read
|
|
119
|
+
packages: write
|
|
120
|
+
steps:
|
|
121
|
+
- uses: docker/setup-buildx-action@v3
|
|
122
|
+
- uses: docker/login-action@v3
|
|
123
|
+
with:
|
|
124
|
+
registry: ghcr.io
|
|
125
|
+
username: ${{ github.actor }}
|
|
126
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
127
|
+
- uses: actions/download-artifact@v4
|
|
128
|
+
with:
|
|
129
|
+
pattern: digest-*
|
|
130
|
+
path: digests
|
|
131
|
+
merge-multiple: true
|
|
132
|
+
- env:
|
|
133
|
+
OWNER: ${{ github.repository_owner }}
|
|
134
|
+
run: |
|
|
135
|
+
set -eu
|
|
136
|
+
version="${GITHUB_REF_NAME#v}"
|
|
137
|
+
for name in earnings-watch earnings-watch-worker; do
|
|
138
|
+
image="ghcr.io/$OWNER/$name"
|
|
139
|
+
if docker buildx imagetools inspect "$image:$version" >/dev/null 2>&1; then
|
|
140
|
+
echo "Release version already exists; use a new version."
|
|
141
|
+
exit 1
|
|
142
|
+
fi
|
|
143
|
+
done
|
|
144
|
+
for name in earnings-watch earnings-watch-worker; do
|
|
145
|
+
image="ghcr.io/$OWNER/$name"
|
|
146
|
+
digest="$(cat "digests/$name")"
|
|
147
|
+
docker buildx imagetools create --prefer-index=false --tag "$image:$version" --tag "$image:latest" "$image@$digest"
|
|
148
|
+
done
|
|
149
|
+
github-release:
|
|
150
|
+
needs: [test, promote]
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
permissions:
|
|
153
|
+
contents: write
|
|
154
|
+
steps:
|
|
155
|
+
- uses: actions/download-artifact@v4
|
|
156
|
+
with:
|
|
157
|
+
name: distributions
|
|
158
|
+
path: dist
|
|
159
|
+
- env:
|
|
160
|
+
GH_TOKEN: ${{ github.token }}
|
|
161
|
+
run: |
|
|
162
|
+
cd dist
|
|
163
|
+
sha256sum * > SHA256SUMS
|
|
164
|
+
gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --generate-notes ./*
|
|
165
|
+
pypi:
|
|
166
|
+
needs: [released, github-release]
|
|
167
|
+
if: >-
|
|
168
|
+
always() && github.ref_type == 'tag' && vars.PYPI_PUBLISH == 'true' &&
|
|
169
|
+
(needs.github-release.result == 'success' || needs.released.outputs.exists == 'true')
|
|
170
|
+
runs-on: ubuntu-latest
|
|
171
|
+
environment: pypi
|
|
172
|
+
permissions:
|
|
173
|
+
contents: read
|
|
174
|
+
id-token: write
|
|
175
|
+
steps:
|
|
176
|
+
- uses: actions/checkout@v4
|
|
177
|
+
- uses: astral-sh/setup-uv@v6
|
|
178
|
+
- env:
|
|
179
|
+
GH_TOKEN: ${{ github.token }}
|
|
180
|
+
run: |
|
|
181
|
+
set -eu
|
|
182
|
+
gh release download "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --dir dist --pattern '*.whl' --pattern '*.tar.gz' --pattern SHA256SUMS
|
|
183
|
+
(cd dist && sha256sum --check SHA256SUMS)
|
|
184
|
+
uv tool run --from twine twine check --strict dist/*.whl dist/*.tar.gz
|
|
185
|
+
uv venv /tmp/earnings-wheel-check
|
|
186
|
+
uv pip install --python /tmp/earnings-wheel-check/bin/python dist/*.whl
|
|
187
|
+
EARNINGS_WATCH_DATA_DIR=/tmp/earnings-release-state /tmp/earnings-wheel-check/bin/earnings-watch init
|
|
188
|
+
EARNINGS_WATCH_DATA_DIR=/tmp/earnings-release-state /tmp/earnings-wheel-check/bin/earnings-watch doctor
|
|
189
|
+
rm dist/SHA256SUMS
|
|
190
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086
|
|
191
|
+
with:
|
|
192
|
+
attestations: true
|
|
193
|
+
skip-existing: true
|
|
194
|
+
registry:
|
|
195
|
+
needs: [released, github-release, pypi]
|
|
196
|
+
if: >-
|
|
197
|
+
always() && github.ref_type == 'tag' && vars.MCP_REGISTRY_PUBLISH == 'true' &&
|
|
198
|
+
(needs.github-release.result == 'success' || needs.released.outputs.exists == 'true')
|
|
199
|
+
runs-on: ubuntu-latest
|
|
200
|
+
permissions:
|
|
201
|
+
contents: read
|
|
202
|
+
id-token: write
|
|
203
|
+
env:
|
|
204
|
+
MCP_PUBLISHER_VERSION: v1.8.1
|
|
205
|
+
steps:
|
|
206
|
+
- uses: actions/checkout@v4
|
|
207
|
+
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6
|
|
208
|
+
- name: Verify the publisher and the published package
|
|
209
|
+
env:
|
|
210
|
+
GH_TOKEN: ${{ github.token }}
|
|
211
|
+
run: |
|
|
212
|
+
set -eu
|
|
213
|
+
version="${GITHUB_REF_NAME#v}"
|
|
214
|
+
curl --fail --silent --show-error "https://pypi.org/pypi/earnings-watch/$version/json" -o /tmp/package.json
|
|
215
|
+
gh release download "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --pattern SHA256SUMS --dir /tmp/earnings-registry-assets
|
|
216
|
+
python3 - <<'PY'
|
|
217
|
+
import json
|
|
218
|
+
from pathlib import Path
|
|
219
|
+
published = json.loads(Path('/tmp/package.json').read_text())
|
|
220
|
+
expected = dict(line.split()[::-1] for line in Path('/tmp/earnings-registry-assets/SHA256SUMS').read_text().splitlines())
|
|
221
|
+
actual = {item['filename']: item['digests']['sha256'] for item in published['urls']}
|
|
222
|
+
if not expected or expected != actual:
|
|
223
|
+
raise SystemExit('PyPI files differ from the accepted GitHub release')
|
|
224
|
+
PY
|
|
225
|
+
archive=mcp-publisher_linux_amd64.tar.gz
|
|
226
|
+
base_url="https://github.com/modelcontextprotocol/registry/releases/download/$MCP_PUBLISHER_VERSION"
|
|
227
|
+
curl --fail --location --silent --show-error -o "$archive" "$base_url/$archive"
|
|
228
|
+
curl --fail --location --silent --show-error -o "$archive.sigstore.json" "$base_url/$archive.sigstore.json"
|
|
229
|
+
cosign verify-blob --bundle "$archive.sigstore.json" --certificate-identity "https://github.com/modelcontextprotocol/registry/.github/workflows/release.yml@refs/tags/$MCP_PUBLISHER_VERSION" --certificate-oidc-issuer https://token.actions.githubusercontent.com "$archive"
|
|
230
|
+
tar xzf "$archive" mcp-publisher
|
|
231
|
+
jq --arg version "$version" '.version = $version | .packages[].version = $version' server.json > server-release.json
|
|
232
|
+
mv server-release.json server.json
|
|
233
|
+
- run: |
|
|
234
|
+
./mcp-publisher login github-oidc
|
|
235
|
+
./mcp-publisher publish
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
Initial standalone CLI, Python API and MCP server. Shared SQLite facts with personal follows, delegated calendar plans and durable delivery state; deterministic date/timing verification; official release polling, document links and opt-in downloads; standalone and gateway HTTP authentication; separate serving and maintenance images.
|
|
6
|
+
|
|
7
|
+
Candidate audit fixes preserve revisions and download receipts across catalogue
|
|
8
|
+
retirement, cover the full release-polling window, keep accepted documents after
|
|
9
|
+
partial provider failures, align date boundaries to UTC, treat disabled models
|
|
10
|
+
as intentional, and harden interrupted HTTP requests and download integrity.
|
|
11
|
+
Existing schema-1 candidates upgrade explicitly through `init`; see operations.
|
|
12
|
+
|
|
13
|
+
- Preserve legacy polling, document verification and reviewed overrides during
|
|
14
|
+
migration; block external effects until explicit reconciliation, and export
|
|
15
|
+
current-state compatibility projections with retained rollback evidence.
|
|
16
|
+
- Bind concurrent image builds to their own builders so one completed job cannot
|
|
17
|
+
stop another job's qualification or release build.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Report bugs with synthetic inputs and redacted diagnostics. Do not attach account tokens, personal follows, calendar records or captured private pages.
|
|
4
|
+
|
|
5
|
+
## How changes land
|
|
6
|
+
|
|
7
|
+
This repository is a release mirror. Maintained source is reviewed separately and exported as complete releases. Pull requests are reviewed and reimplemented in that source, then credited in release notes; they are not merged directly into the mirror because the next export would replace them.
|
|
8
|
+
|
|
9
|
+
Run `make bootstrap` and `make check`. Keep network, models and account effects behind fake boundaries in offline tests. Add a regression test when changing date, period, timing, persistence or authentication behavior. Public fixtures must be fictional.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
FROM node:24-bookworm-slim AS clis
|
|
2
|
+
RUN npm install --prefix /opt/clis @anthropic-ai/claude-code@2.1.312 @openai/codex@0.164.0
|
|
3
|
+
FROM ghcr.io/astral-sh/uv:0.13.5 AS uv
|
|
4
|
+
FROM python:3.12-slim-bookworm AS base
|
|
5
|
+
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/home/earnings EARNINGS_WATCH_DATA_DIR=/data PATH=/app/.venv/bin:$PATH
|
|
6
|
+
RUN useradd --uid 1000 --create-home earnings && mkdir /data && chown earnings /data
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
FROM base AS dependencies
|
|
9
|
+
COPY --from=uv /uv /usr/local/bin/uv
|
|
10
|
+
COPY pyproject.toml uv.lock README.md LICENSE NOTICE ./
|
|
11
|
+
COPY src ./src
|
|
12
|
+
RUN uv sync --locked --no-dev --no-editable
|
|
13
|
+
FROM base AS serve
|
|
14
|
+
COPY --from=dependencies /app/.venv /app/.venv
|
|
15
|
+
USER 1000:1000
|
|
16
|
+
VOLUME /data
|
|
17
|
+
HEALTHCHECK --interval=60s --timeout=10s CMD ["earnings-watch", "health", "--json"]
|
|
18
|
+
ENTRYPOINT ["earnings-watch"]
|
|
19
|
+
CMD ["serve", "--http", "--host", "0.0.0.0"]
|
|
20
|
+
FROM serve AS worker
|
|
21
|
+
USER root
|
|
22
|
+
RUN apt-get update && apt-get install -y --no-install-recommends libstdc++6 ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
23
|
+
COPY --from=clis /usr/local/bin/node /usr/local/bin/node
|
|
24
|
+
COPY --from=clis /opt/clis /opt/clis
|
|
25
|
+
ENV PATH=/opt/clis/node_modules/.bin:$PATH
|
|
26
|
+
USER 1000:1000
|
|
27
|
+
CMD ["worker"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 benjamin-di
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.PHONY: bootstrap check test live conformance
|
|
2
|
+
bootstrap:
|
|
3
|
+
uv sync --locked
|
|
4
|
+
check:
|
|
5
|
+
uv lock --check
|
|
6
|
+
uv run --locked ruff check src tests scripts
|
|
7
|
+
uv run --locked ruff format --check src tests scripts
|
|
8
|
+
uv run --locked mypy src
|
|
9
|
+
uv run --locked python scripts/check-boundaries.py
|
|
10
|
+
uv run --locked pytest -m 'not live' -q
|
|
11
|
+
uv build --no-sources
|
|
12
|
+
git diff --check
|
|
13
|
+
|
|
14
|
+
test:
|
|
15
|
+
uv run --locked pytest -q
|
|
16
|
+
live:
|
|
17
|
+
uv run --locked pytest -m live private/ -q
|
|
18
|
+
|
|
19
|
+
conformance:
|
|
20
|
+
uv run --locked python scripts/check-conformance.py
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: earnings-watch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verified issuer earnings calendars and results documents, through CLI and MCP
|
|
5
|
+
Project-URL: Homepage, https://github.com/benjamin-di/earnings-watch
|
|
6
|
+
Author: benjamin-di
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: fastmcp==4.1.3
|
|
12
|
+
Requires-Dist: html2text>=2024.2
|
|
13
|
+
Requires-Dist: httpx<1,>=0.28
|
|
14
|
+
Requires-Dist: pydantic<3,>=2.11
|
|
15
|
+
Requires-Dist: pyyaml<7,>=6
|
|
16
|
+
Requires-Dist: tzdata>=2024.1
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Earnings Watch
|
|
20
|
+
|
|
21
|
+
<!-- mcp-name: io.github.benjamin-di/earnings-watch -->
|
|
22
|
+
|
|
23
|
+
Verified issuer earnings calendars and results documents through a CLI, Python API and MCP server. Models propose dates; deterministic checks require quoted evidence and consistent issuer, period and timing. A call time never silently becomes a publication time.
|
|
24
|
+
|
|
25
|
+
Calendar updates are optional. Earnings Watch produces an operation plan; your MCP client uses its external Outlook app to apply it and acknowledges confirmed writes. No Microsoft credentials or calendar SDK are required by this package.
|
|
26
|
+
|
|
27
|
+
Requires Python 3.12+. Install a release wheel with `uv tool install path/to/earnings_watch-0.1.0-py3-none-any.whl`, or develop from a source checkout:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
make bootstrap
|
|
31
|
+
uv run earnings-watch init
|
|
32
|
+
uv run earnings-watch doctor
|
|
33
|
+
uv run earnings-watch sources import examples/sources.yaml
|
|
34
|
+
uv run earnings-watch watch EXAMPLE
|
|
35
|
+
uv run earnings-watch upcoming --format json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The example uses reserved domains and fictional data; replace its source URLs and issuer metadata before fetching. There is no bundled issuer universe or market-data subscription.
|
|
39
|
+
|
|
40
|
+
## Maintenance and models
|
|
41
|
+
|
|
42
|
+
Serving reads SQLite and updates personal follows/ledgers; it never fetches issuer pages or calls a model. Run maintenance separately:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
uv run earnings-watch sweep --dry-run
|
|
46
|
+
uv run earnings-watch sweep
|
|
47
|
+
uv run earnings-watch sweep --docs-only
|
|
48
|
+
uv run earnings-watch worker
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The default backend is `none`: it preserves existing dates and still polls official release providers. To extract new dates, install and sign into your own Claude Code or Codex CLI, then configure it in `earnings-watch.toml`:
|
|
52
|
+
|
|
53
|
+
```toml
|
|
54
|
+
[llm]
|
|
55
|
+
backends = ["claude-code", "codex", "none"]
|
|
56
|
+
max_calls_per_run = 20
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Supported minimum versions are Claude Code 2.1.312 and Codex 0.164.0. Run `earnings-watch doctor --live` to check capabilities and an optional model canary. The package has no login flow. Model usage follows your CLI account's limits and terms. Missing credentials or capacity may try the next backend; a timed-out run is not repeated in the same tick.
|
|
60
|
+
|
|
61
|
+
## MCP
|
|
62
|
+
|
|
63
|
+
A local client configuration, after initialization:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{"mcpServers":{"earnings":{"command":"earnings-watch","args":["serve","--stdio"]}}}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Tool | Purpose |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `earnings_upcoming` | Bounded upcoming dates with timing uncertainty |
|
|
72
|
+
| `earnings_get_events` | Evidence, revisions and recent history |
|
|
73
|
+
| `earnings_documents` | Official result-document links and verification status |
|
|
74
|
+
| `earnings_changes` | Cursor-based public change feed |
|
|
75
|
+
| `earnings_watchlist` | Your follows and registration states |
|
|
76
|
+
| `earnings_watch` | Follow an issuer; queue registration or resolve ambiguity |
|
|
77
|
+
| `earnings_unwatch` | Remove a manual follow |
|
|
78
|
+
| `earnings_calendar_plan` | Plan optional external calendar operations |
|
|
79
|
+
| `earnings_calendar_ack` | Record confirmed operations from a signed plan |
|
|
80
|
+
| `earnings_status` | Catalogue and maintenance status |
|
|
81
|
+
|
|
82
|
+
Prompts: `weekly_earnings_brief`, `results_brief`, `sync_to_my_outlook`. Resource: `earnings://upcoming.ics`. Exact times require verified clock and timezone evidence; otherwise calendar entries are all-day. Outlook operations never include attendees.
|
|
83
|
+
|
|
84
|
+
HTTP standalone mode uses a separate bearer token per person at `/users/<id>/mcp`. Gateway mode accepts a configured backend bearer and trusted person header at `/mcp`. These modes are separate trust boundaries; see [permissions](docs/PERMISSIONS.md).
|
|
85
|
+
|
|
86
|
+
## Docker Compose
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
cp earnings-watch.example.toml earnings-watch.toml
|
|
90
|
+
docker compose build
|
|
91
|
+
docker compose run --rm server init
|
|
92
|
+
docker compose run --rm server users add example
|
|
93
|
+
docker compose up -d
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Save the returned token privately and use it only on that person's HTTP path. The server binds to loopback by default. Both images run as uid 1000; Compose drops capabilities, prevents privilege escalation and mounts the root filesystem read-only. The worker image includes pinned model CLIs; their auth volume is optional with `backends=["none"]`. Configure CLI sign-in outside the read-only worker before enabling model maintenance. Protect auth files and the data volume from other users.
|
|
97
|
+
|
|
98
|
+
Use `--config PATH`, `--person ID`, and `--json` with the CLI. Settings come from TOML and `EARNINGS_WATCH_*` environment variables. See [operations](docs/OPERATIONS.md), [architecture](docs/ARCHITECTURE.md), [providers](docs/PROVIDERS.md) and [design decisions](docs/DESIGN.md).
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
`make check` verifies lock drift, lint, formatting, types, dependency boundaries, offline tests and wheel/source builds. Tests use synthetic issuers and fake network/model/account boundaries. Real-model and real-page checks are separate operator gates. Read [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
|
|
103
|
+
|
|
104
|
+
See [implementation status](docs/IMPLEMENTATION.md) for completed capabilities,
|
|
105
|
+
release gates and the production adapter work still required before cutover.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Earnings Watch
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.benjamin-di/earnings-watch -->
|
|
4
|
+
|
|
5
|
+
Verified issuer earnings calendars and results documents through a CLI, Python API and MCP server. Models propose dates; deterministic checks require quoted evidence and consistent issuer, period and timing. A call time never silently becomes a publication time.
|
|
6
|
+
|
|
7
|
+
Calendar updates are optional. Earnings Watch produces an operation plan; your MCP client uses its external Outlook app to apply it and acknowledges confirmed writes. No Microsoft credentials or calendar SDK are required by this package.
|
|
8
|
+
|
|
9
|
+
Requires Python 3.12+. Install a release wheel with `uv tool install path/to/earnings_watch-0.1.0-py3-none-any.whl`, or develop from a source checkout:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
make bootstrap
|
|
13
|
+
uv run earnings-watch init
|
|
14
|
+
uv run earnings-watch doctor
|
|
15
|
+
uv run earnings-watch sources import examples/sources.yaml
|
|
16
|
+
uv run earnings-watch watch EXAMPLE
|
|
17
|
+
uv run earnings-watch upcoming --format json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The example uses reserved domains and fictional data; replace its source URLs and issuer metadata before fetching. There is no bundled issuer universe or market-data subscription.
|
|
21
|
+
|
|
22
|
+
## Maintenance and models
|
|
23
|
+
|
|
24
|
+
Serving reads SQLite and updates personal follows/ledgers; it never fetches issuer pages or calls a model. Run maintenance separately:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
uv run earnings-watch sweep --dry-run
|
|
28
|
+
uv run earnings-watch sweep
|
|
29
|
+
uv run earnings-watch sweep --docs-only
|
|
30
|
+
uv run earnings-watch worker
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The default backend is `none`: it preserves existing dates and still polls official release providers. To extract new dates, install and sign into your own Claude Code or Codex CLI, then configure it in `earnings-watch.toml`:
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[llm]
|
|
37
|
+
backends = ["claude-code", "codex", "none"]
|
|
38
|
+
max_calls_per_run = 20
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Supported minimum versions are Claude Code 2.1.312 and Codex 0.164.0. Run `earnings-watch doctor --live` to check capabilities and an optional model canary. The package has no login flow. Model usage follows your CLI account's limits and terms. Missing credentials or capacity may try the next backend; a timed-out run is not repeated in the same tick.
|
|
42
|
+
|
|
43
|
+
## MCP
|
|
44
|
+
|
|
45
|
+
A local client configuration, after initialization:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{"mcpServers":{"earnings":{"command":"earnings-watch","args":["serve","--stdio"]}}}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Tool | Purpose |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `earnings_upcoming` | Bounded upcoming dates with timing uncertainty |
|
|
54
|
+
| `earnings_get_events` | Evidence, revisions and recent history |
|
|
55
|
+
| `earnings_documents` | Official result-document links and verification status |
|
|
56
|
+
| `earnings_changes` | Cursor-based public change feed |
|
|
57
|
+
| `earnings_watchlist` | Your follows and registration states |
|
|
58
|
+
| `earnings_watch` | Follow an issuer; queue registration or resolve ambiguity |
|
|
59
|
+
| `earnings_unwatch` | Remove a manual follow |
|
|
60
|
+
| `earnings_calendar_plan` | Plan optional external calendar operations |
|
|
61
|
+
| `earnings_calendar_ack` | Record confirmed operations from a signed plan |
|
|
62
|
+
| `earnings_status` | Catalogue and maintenance status |
|
|
63
|
+
|
|
64
|
+
Prompts: `weekly_earnings_brief`, `results_brief`, `sync_to_my_outlook`. Resource: `earnings://upcoming.ics`. Exact times require verified clock and timezone evidence; otherwise calendar entries are all-day. Outlook operations never include attendees.
|
|
65
|
+
|
|
66
|
+
HTTP standalone mode uses a separate bearer token per person at `/users/<id>/mcp`. Gateway mode accepts a configured backend bearer and trusted person header at `/mcp`. These modes are separate trust boundaries; see [permissions](docs/PERMISSIONS.md).
|
|
67
|
+
|
|
68
|
+
## Docker Compose
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
cp earnings-watch.example.toml earnings-watch.toml
|
|
72
|
+
docker compose build
|
|
73
|
+
docker compose run --rm server init
|
|
74
|
+
docker compose run --rm server users add example
|
|
75
|
+
docker compose up -d
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Save the returned token privately and use it only on that person's HTTP path. The server binds to loopback by default. Both images run as uid 1000; Compose drops capabilities, prevents privilege escalation and mounts the root filesystem read-only. The worker image includes pinned model CLIs; their auth volume is optional with `backends=["none"]`. Configure CLI sign-in outside the read-only worker before enabling model maintenance. Protect auth files and the data volume from other users.
|
|
79
|
+
|
|
80
|
+
Use `--config PATH`, `--person ID`, and `--json` with the CLI. Settings come from TOML and `EARNINGS_WATCH_*` environment variables. See [operations](docs/OPERATIONS.md), [architecture](docs/ARCHITECTURE.md), [providers](docs/PROVIDERS.md) and [design decisions](docs/DESIGN.md).
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
`make check` verifies lock drift, lint, formatting, types, dependency boundaries, offline tests and wheel/source builds. Tests use synthetic issuers and fake network/model/account boundaries. Real-model and real-page checks are separate operator gates. Read [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
|
|
85
|
+
|
|
86
|
+
See [implementation status](docs/IMPLEMENTATION.md) for completed capabilities,
|
|
87
|
+
release gates and the production adapter work still required before cutover.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Use GitHub's private vulnerability reporting for security issues. Never publish credentials, database files, calendar handles or raw private diagnostics in an issue.
|
|
4
|
+
|
|
5
|
+
Keep the data/auth volumes private and use TLS for remote HTTP. Standalone per-person tokens and gateway backend credentials have different authority; see [permissions](docs/PERMISSIONS.md). Do not expose gateway mode directly to untrusted callers.
|
|
6
|
+
|
|
7
|
+
Security-relevant fixes receive a new release. Treat model output, captured pages, document links and mirror contributions as untrusted inputs. Financial dates and document verification are evidence checks, not investment advice or financial-content certification.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
services:
|
|
2
|
+
server:
|
|
3
|
+
image: ghcr.io/benjamin-di/earnings-watch:0.1.0
|
|
4
|
+
build:
|
|
5
|
+
context: .
|
|
6
|
+
target: serve
|
|
7
|
+
ports: ["127.0.0.1:8787:8787"]
|
|
8
|
+
volumes: ["data:/data", "./earnings-watch.toml:/app/earnings-watch.toml:ro"]
|
|
9
|
+
read_only: true
|
|
10
|
+
cap_drop: [ALL]
|
|
11
|
+
security_opt: ["no-new-privileges:true"]
|
|
12
|
+
tmpfs: ["/tmp:mode=1777"]
|
|
13
|
+
restart: unless-stopped
|
|
14
|
+
worker:
|
|
15
|
+
image: ghcr.io/benjamin-di/earnings-watch-worker:0.1.0
|
|
16
|
+
build:
|
|
17
|
+
context: .
|
|
18
|
+
target: worker
|
|
19
|
+
volumes: ["data:/data", "./earnings-watch.toml:/app/earnings-watch.toml:ro", "cli-auth:/home/earnings"]
|
|
20
|
+
read_only: true
|
|
21
|
+
cap_drop: [ALL]
|
|
22
|
+
security_opt: ["no-new-privileges:true"]
|
|
23
|
+
tmpfs: ["/tmp:mode=1777"]
|
|
24
|
+
restart: unless-stopped
|
|
25
|
+
volumes:
|
|
26
|
+
data:
|
|
27
|
+
cli-auth:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
One SQLite database owns both shared public facts and person-scoped state. Shared tables hold sources, events, revisions, polling state and documents. Personal tables hold follows, calendar ledgers, delivery receipts and downloads. Personal access always requires an explicit person selected at the authentication boundary.
|
|
4
|
+
|
|
5
|
+
CLI, MCP and Python callers share `PersonService`. It receives a read-only library reader and a personal store. The serving surface has no network, model or provider dependency. FastMCP runs synchronous service calls outside its event loop. SQL connections use WAL, five-second busy timeouts and explicit transactions; personal acknowledgements use immediate write transactions.
|
|
6
|
+
|
|
7
|
+
Maintenance runs separately under one process lock. Its stages register pending issuers, capture pages, extract claims, verify evidence, reconcile facts, poll official releases and prepare personal notification outboxes. A failed or incomplete extraction cannot establish absence. Removal caps guard catalogue commits. Models and web pages cannot write directly to the database.
|
|
8
|
+
|
|
9
|
+
Only `store/` imports SQLite. `transport.py` owns HTTP, including DNS validation and IP pinning with original-host TLS SNI. `llm/` owns model processes. The pure engine depends on models and the standard library. An AST gate enforces capability boundaries.
|
|
10
|
+
|
|
11
|
+
Calendar plans read follows, events, revision sequence and ledger in one snapshot. A signed handle binds that snapshot, person, sink, operations and thirty-minute expiry. Acknowledgement validates the handle and revisions in the same transaction that records confirmed writes. Retries of an acknowledged handle return its original receipt.
|
|
12
|
+
|
|
13
|
+
Event revisions increase across removal and reintroduction, so change consumers
|
|
14
|
+
and ICS clients never see a sequence reset. Personal download receipts describe
|
|
15
|
+
historical effects and survive catalogue retirement. They reference a document's
|
|
16
|
+
stable identifier without requiring that document to remain in the current
|
|
17
|
+
catalogue; new receipts validate current catalogue membership before insertion.
|
|
18
|
+
|
|
19
|
+
The library deliberately shares public facts. A public query can reveal that a source was registered or refreshed; it never returns another person's follows, tokens or calendar ledger. Operators who need to hide interest in an issuer must use separate deployments.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Design decisions
|
|
2
|
+
|
|
3
|
+
Earnings Watch describes the task and retains the `earnings_` MCP namespace. It combines an evidence-backed issuer calendar with document discovery; it is not a price feed or a forecast of earnings dates.
|
|
4
|
+
|
|
5
|
+
The design uses three familiar patterns: a pure validation/reconciliation core, a shared catalogue with personal projections, and a durable outbox. SQLite keeps backup and installation simple for one host. A database server, built-in OAuth issuer and plugin loader would add operational boundaries without improving this workload.
|
|
6
|
+
|
|
7
|
+
Model access uses user-owned agent CLIs. A raw API integration would introduce a second authentication and billing path. Extraction runs with tools disabled. Research has only search and scoped page/observation tools; page evidence must still pass deterministic validation. Browser automation is an extension boundary rather than a requirement for reading static issuer pages.
|
|
8
|
+
|
|
9
|
+
Maintenance is asynchronous because requests and model subprocesses have independent deadlines. The serving service remains synchronous and bounded. This is a deliberate deviation from a synchronous-core preference: I/O orchestration lives outside the pure engine and read service.
|
|
10
|
+
|
|
11
|
+
Side effects have explicit boundaries. R0 queries produce facts or plans. R1 operations update only personal state. Outbound notification delivery requires a separate notifier invocation. Calendar plan/ack delegates account effects to the client's optional Outlook app. No calendar feature grants arbitrary web content authority to invite people.
|
|
12
|
+
|
|
13
|
+
A single authoritative database replaces hand-edited state files. YAML, JSON, CSV and ICS are projections. Legacy import/export supports migration rehearsal; an uncertain legacy delivery remains unknown until an operator reconciles it. A failed image rollout does not restore database state.
|