Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .github/workflows/collect-reconciliation-evidence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
collect:
name: Read ${{ matrix.profile }} baseline evidence
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -83,7 +83,7 @@ jobs:
test -n "$service_url"
echo "service_url=$service_url" >> "$GITHUB_OUTPUT"

- name: Create and run internal reconciliation request
- name: Schedule one-shot internal reconciliation request
id: scheduler
if: ${{ steps.selection.outputs.selected == 'true' }}
env:
Expand All @@ -94,14 +94,25 @@ jobs:
test -n "$SERVICE_URL"
job_name="ibkr-reconcile-${PROFILE}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
requested_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
case "$PROFILE" in
soxl_soxx_trend_income) delay_minutes=3 ;;
tqqq_growth_income) delay_minutes=5 ;;
global_etf_rotation) delay_minutes=7 ;;
russell_top50_leader_rotation) delay_minutes=9 ;;
*)
echo "Unsupported reconciliation profile: $PROFILE" >&2
exit 1
;;
esac
schedule="$(date -u -d "+${delay_minutes} minutes" '+%M %H %d %m *')"
if gcloud scheduler jobs describe "$job_name" --project "$GCP_PROJECT_ID" --location "$GCP_REGION" >/dev/null 2>&1; then
echo "Refusing to replace an existing reconciliation job: $job_name" >&2
exit 1
fi
gcloud scheduler jobs create http "$job_name" \
--project "$GCP_PROJECT_ID" \
--location "$GCP_REGION" \
--schedule '0 0 1 1 *' \
--schedule "$schedule" \
--time-zone 'Etc/UTC' \
--uri "${SERVICE_URL}/reconcile" \
--http-method POST \
Expand All @@ -113,7 +124,6 @@ jobs:
echo "job_name=$job_name"
echo "requested_at=$requested_at"
} >> "$GITHUB_OUTPUT"
gcloud scheduler jobs run "$job_name" --project "$GCP_PROJECT_ID" --location "$GCP_REGION"

- name: Read private no-order reconciliation candidate
if: ${{ steps.selection.outputs.selected == 'true' }}
Expand All @@ -128,7 +138,7 @@ jobs:
test -n "$REQUESTED_AT"
mkdir -p reports
report_uri=''
for attempt in $(seq 1 60); do
for attempt in $(seq 1 96); do
report_uri="$(gcloud logging read "resource.type=\"cloud_run_revision\" AND resource.labels.service_name=\"${SERVICE}\" AND timestamp>=\"${REQUESTED_AT}\" AND textPayload:\"execution_report gs://\"" --project "$GCP_PROJECT_ID" --freshness=15m --limit=10 --format='value(textPayload)' 2>/dev/null | awk '$1 == "execution_report" {print $2; exit}')"
if [ -n "$report_uri" ]; then
break
Expand Down
4 changes: 2 additions & 2 deletions docs/ibkr_reconciliation_baseline_enrollment.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ URI、部署 Cloud Run、连接券商或提交订单。实际启用仍需要单
## 收集两份候选收据

`Collect IBKR Reconciliation Evidence` 是显式手动工作流。由于这些 Cloud Run 服务只接受
内部入口,工作流会以部署身份创建一个名称绑定到本次运行的**一次性** Cloud Scheduler
任务,再由既有的最小权限 Scheduler 身份调用冻结服务的 `POST /reconcile`。它随后只从
内部入口,工作流会以部署身份创建一个名称绑定到本次运行、几分钟后只执行一次的 Cloud
Scheduler 任务,再由既有的最小权限 Scheduler 身份调用冻结服务的 `POST /reconcile`。它随后只从
私有运行报告提取脱敏 `ibkr_reconciliation_candidate.v1`,在 30 天内保留 artifact,并在
成功或失败时删除该一次性任务。它不调用 `/run`、不修改 GitHub 变量、不发布状态账本,也
不发送任何订单。
Expand Down
3 changes: 2 additions & 1 deletion tests/test_reconciliation_evidence_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ def test_reconciliation_evidence_uses_internal_one_shot_scheduler_and_cleans_up(

assert '"${SERVICE_URL}/reconcile"' in workflow
assert "gcloud scheduler jobs create http" in workflow
assert "gcloud scheduler jobs run" in workflow
assert 'schedule="$(date -u -d "+${delay_minutes} minutes"' in workflow
assert "gcloud scheduler jobs delete" in workflow
assert "gcloud storage cat \"$report_uri\"" in workflow
assert "gcloud scheduler jobs run" not in workflow
assert "/run" not in workflow
assert "curl " not in workflow