Repository navigation
Paper alert: scrape only registered adaptive-paper exporters, and alert only on a registered silent one - #284
Conversation
…rt only on a registered silent one
EquitiesPaperMetricsMissing was absent(paper_trial_active) over a static
127.0.0.1:18890 target. On every host without a running trial it fired
permanently, and this workstation has carried silence 87eabf8a for it.
- Scrape job adaptive-paper now reads file_sd_configs:
@CONFIG_ROOT@/adaptive-paper-targets.json. configure.py ships the list
as [] and never overwrites an existing one.
- metrics.py --file-sd registers its own 127.0.0.1:<port> target after the
listener binds. The update uses a sibling flock, a temporary file in the
same directory, fsync and os.replace. The exporter refuses to start
(exit 2) on a list that is not a file_sd target list.
- It deregisters only on a clean SIGTERM or SIGINT stop of a finished
trial, or when there is no trial.json. A crashed or killed exporter, or
a trial at starting, needs_attention, held_overnight, or finished with a
failed status, stays registered. --deregister removes a target after
recovery.
- The rule is now up{job="adaptive-paper"} unless on(job, instance)
paper_trial_active, for 2m. It fires for a registered exporter that is
down, and for another process answering on its port. It stays silent
with nothing registered. Both descriptions are updated.
- Tests:
- A promtool 3.14.0 `test rules` case covers five cases. Mutants
absent() and up==0 both fail it.
- FileSdRegistrationTests run real exporter processes. Six fix-revert
mutants are killed.
- configure.py shipping [] and keeping an existing list.
- Evidence pins for the eight changed files are refreshed. The historical
broker-path receipt is unchanged, and the README says it predates this.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The local full suite (5,401 tests) failed one check: tests.test_adaptive_paper_source_hashes_manifest. source-hashes.json still held the pre-change sha256 of metrics.py and tests/test_adaptive_paper_metrics.py. Both entries are refreshed, along with the file's own evidence.json pin; no receipt or convergence record pins source-hashes.json's digest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Foundation review from native-agent-stack-9a, of head Verified locally (WSL2):
Requested before ack:
Optional: I'll ack once 1 and 2 are in. Merge after WINDOW DONE for v2026.09.25.2, as planned. |
…he runner's passing statuses (#284 review) Foundation-lane review of 6daaf72: (1) An exporter started without --file-sd is never scraped, so no alert covers it. Serving now requires --file-sd (argparse exit 2). The explicit --no-file-sd, mutually exclusive with it, opts into an unscraped exporter for local inspection. The README example passes --file-sd. (2) trial_finished uses the runner's allowlist of passing statuses for phase finished: passed and completed_no_signals. A finished trial with any other, missing or unknown status stays registered. New tests cover None, not_ready, an unknown status, and both passing statuses. (3) The SIGTERM handler sets SIG_IGN before raising, and the clean-stop path ignores repeated SIGTERM and SIGINT. Mutants for (1) and (2) are killed. 37 exporter tests pass on system Python and on the pinned runtime; 18 alert tests and the source-hashes manifest test pass. The rule description, both READMEs, source-hashes.json and evidence.json are updated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Foundation ACK from native-agent-stack-9a, for head
Locally: |
…iry of silence 87eabf8a (#287) Adds observability/backends/paper-alert-file-sd-host-20260925.json. - #284's lines were applied surgically to the live config, and configure.py was not re-run. The WSL2 rendering carries shifted loopback ports (collector 28889/28888, qdrant 26333, vllm 18231, httpcheck 24333), and the renderer cannot override them. - Both staged files passed promtool 3.14.0 before install. After a SIGHUP, reloadConfigSuccess was true. - The Prometheus API read back the new expr, inactive, with 0 adaptive-paper targets, and an empty ALERTS query. - Silence 87eabf8a (alertname=EquitiesPaperMetricsMissing, due to end 2026-10-02) was expired early; Alertmanager then held 0 alerts for it. The live firing path still waits for a registered exporter, since the paper keys return HTTP 401. Co-authored-by: Scout <scout@local> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e-render (#290) * Observability renderer: host loopback port overrides that survive a re-render All WSL 2 distributions share one network namespace, so the workstation moved five scraped services off their template ports (collector 18889 and 18888, Qdrant 16333, vLLM 8231, the 14333 HTTP check). configure.py had no input for that, so the host's live config was hand-adapted and any re-render would reset those ports; the #284 rollout (#287) had to be applied surgically for that reason. configure.py --port-overrides FILE takes a JSON object of template port to host port and rewrites every 127.0.0.1:<port> address in the configs and units in one pass. It keeps the map as <config-root>/port-overrides.json, which later renders reuse. It refuses the whole render (exit 2, nothing written) for a stale override, for a port that also appears outside a 127.0.0.1:<port> address (Grafana's http_port, Loki's listen ports), for a host port that collides with another rendered address, and for invalid input. The map is validated against the full render before any file is written. Rendering main with the workstation's map into a scratch root reproduces the live files: 14 of 15 byte-identical, and ecosystem-prometheus.yml equal as parsed YAML. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Port overrides: address the independent review - Medium: the bare-port check scanned the caller's root paths, so a data root such as .../18888-data refused an 18888 override with a misleading cause. The checks now ignore the four root paths, and the refusal names the file that has the bare port. - Low: a non-ASCII digit key (such as U+00B2) passed str.isdigit() and crashed int() with exit 1. Keys must now be ASCII digits (exit 2). - Low: the README lists malformed input among the refusals. - Tests: each template output without overrides equals the template with only its path tokens replaced; a root path containing the port digits renders; a mixed valid and stale map is refused; a non-ASCII digit key is refused. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Scout <scout@local> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#242 and #284 (#350) * Paper engine: fix the retroactive cross-family review findings on #221, #242 and #284 GPT-6 (gpt-6-astra, effort max) reviewed three merged paper-engine PRs on main ca8e37f; #243 passed. - #221: activity-based gap recovery booked fills natively without persisting them to the durable ledger (native 3, durable 1); a continuing LULD pause auto-cleared after twelve minutes and let cancel_expired cancel the exit it protected; cash and realized P&L depended on REST-snapshot versus stream delivery order. - #242: replay_compare scheduled a named cancel stamped before its submit and silently skipped it; cancelling the transport task cleared the cancel identity before the HTTP worker sent the DELETE. - #284: a clean exporter stop released the port before deregistering, so it could remove a replacement exporter's target; configure.py's initialization could overwrite a concurrent registration; malformed nested labels passed validation and would make Prometheus reject the whole target list. Each fix was implemented test-first by a GPT-6 implementer in its own worktree; every new regression failed before its fix. Coordinator integration: source-hashes.json and evidence pins refreshed; the 20260923g sim-paper-compare receipt replayed from its retained pages with the new code (identical apart from run-local fields and runner_sha256, updated as in #242); sim-capacity's frozen replay_compare source re-pinned with a dated limitation (AST check: the 8 definitions sim-capacity uses are unchanged). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Paper engine repair round: persist recovered fills through the transport's real observation hook Independent verification of cad7faf found #221 finding 1 unresolved in production: reconcile_fills persisted recovered fills only through a port.controller attribute that no real Alpaca port has (AlpacaPaperTransport and the native-faults FaultTransport are wired with sink_observation=controller.observe); only the test-only SimulatedPort carried it. - native_adapter.py: persist through the port's sink_observation (the hook the transport already uses for stream rows), after validating that the recovered activities tile the fill contiguously (fail closed otherwise). - simulation.py: SimulatedPort exposes the same sink_observation hook, so tests and production share one path. - Tests: a real-shaped port (no .controller) regression that failed on cad7faf with Decimal('1') != Decimal('3'), restart persistence, incomplete tiling; the two accounting-replay guards in safety.py; fast build_decisions-level tests for the cancel-before-submit refusal and its equality boundary. Implemented by a GPT-6 implementer test-first; 514 tests across the five affected modules pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Scout <scout@local> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ealized-loss delivery-order major; follow-up to #350) (#355) * Paper engine: fix the retroactive cross-family review findings on #221, #242 and #284 GPT-6 (gpt-6-astra, effort max) reviewed three merged paper-engine PRs on main ca8e37f; #243 passed. - #221: activity-based gap recovery booked fills natively without persisting them to the durable ledger (native 3, durable 1); a continuing LULD pause auto-cleared after twelve minutes and let cancel_expired cancel the exit it protected; cash and realized P&L depended on REST-snapshot versus stream delivery order. - #242: replay_compare scheduled a named cancel stamped before its submit and silently skipped it; cancelling the transport task cleared the cancel identity before the HTTP worker sent the DELETE. - #284: a clean exporter stop released the port before deregistering, so it could remove a replacement exporter's target; configure.py's initialization could overwrite a concurrent registration; malformed nested labels passed validation and would make Prometheus reject the whole target list. Each fix was implemented test-first by a GPT-6 implementer in its own worktree; every new regression failed before its fix. Coordinator integration: source-hashes.json and evidence pins refreshed; the 20260923g sim-paper-compare receipt replayed from its retained pages with the new code (identical apart from run-local fields and runner_sha256, updated as in #242); sim-capacity's frozen replay_compare source re-pinned with a dated limitation (AST check: the 8 definitions sim-capacity uses are unchanged). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Paper engine repair round: persist recovered fills through the transport's real observation hook Independent verification of cad7faf found #221 finding 1 unresolved in production: reconcile_fills persisted recovered fills only through a port.controller attribute that no real Alpaca port has (AlpacaPaperTransport and the native-faults FaultTransport are wired with sink_observation=controller.observe); only the test-only SimulatedPort carried it. - native_adapter.py: persist through the port's sink_observation (the hook the transport already uses for stream rows), after validating that the recovered activities tile the fill contiguously (fail closed otherwise). - simulation.py: SimulatedPort exposes the same sink_observation hook, so tests and production share one path. - Tests: a real-shaped port (no .controller) regression that failed on cad7faf with Decimal('1') != Decimal('3'), restart persistence, incomplete tiling; the two accounting-replay guards in safety.py; fast build_decisions-level tests for the cancel-before-submit refusal and its equality boundary. Implemented by a GPT-6 implementer test-first; 514 tests across the five affected modules pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Paper engine: book realized P&L and gross loss per exact execution in accounting replay The accounting replay summed a status delta's executions into one notional before computing P&L, so a sell filled at a gain and a loss inside one delta netted the loss away, and the result depended on whether the stream or the REST snapshot arrived first. Replay now books each execution that tiles the delta separately (QuantConnect/Lean 985ef30: SecurityPortfolioModel.cs per-fill closing P&L; TradeStatistics.cs accumulates losses separately), falling back to the delta notional only while executions are incomplete. Two regressions: mixed-profit gross loss across stream-first, REST-first, duplicate and restart deliveries, and basis preservation at each booking. With the fix reverted, 4 cases fail (rest_first, duplicates, restart, basis). Paper suites (safety, runner, native, transport): 407 tests OK. Implemented by GPT-6 (gpt-6-astra, effort max) test-first; verified by the coordinator. Pins: source-hashes.json safety.py and manifests/evidence.json. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Paper engine repair round (#355 review): replay executions in execution order, versioned marker Independent review of c81a3d5 (needs_changes): - Finding 1 (major): the replay booked fills in observation order. Stream fills now carry Alpaca's own trade_updates timestamp (event_at_ns; FILL recovery forwards transaction_time_ns), the ledger keeps it in a new optional executions.at_ns column (added on open for existing ledgers), and replay fills each symbol's timed execution slots in execution order, tie-broken by event id then cumulative quantity. Adoptions, untimed executions and uncovered provisional bookings keep their journal positions. - Finding 3: the marker is execution_accounting:v2:<client_id>, so ledgers replayed under the observation-order rule re-derive once. - Finding 4: a test for an uncovered REST-only booking beside another order's late executions. - Finding 2 (adopted open orders) is covered by #356's startup persistence. The A/B/C case gives cash 70, realized -15, gross loss 25, and 1 share at 115 under stream, REST-ahead, recovered, late-buy and nanosecond deliveries. Reverting the three production files fails 10 cases (4 new tests, 2 strengthened). Paper suites: 411 OK; trading gates 26 OK; validate.py passed. Implemented by GPT-6 (gpt-6-astra, effort max) test-first; verified by the coordinator. Pins: source-hashes.json (safety, transport, native_adapter) and manifests/evidence.json. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Re-pin the changed paper test files in adaptive-paper/source-hashes.json source-hashes.json also pins test files; CI's test_adaptive_paper_source_hashes_manifest caught the stale entries. Pins only, no code change: the changed tests' entries and their manifests/evidence.json registration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Reduce #355 to the reviewed-safe subset; defer execution-order replay The re-check of cc6d7cb found two new majors in the execution-order replay: - Moving an earlier-executed fill into another order's slot can make an uncovered REST-only sell go short. The replay then raises, the fill rolls back and the transport freezes, although the real history is consistent. - Removing the early exit let that failure block fills in every symbol and added a full replay to every fill. Under the bounded review loop (one review, one repair, then land with residuals), this commit lands the parts both reviews confirmed. It keeps: - c81a3d5's per-execution P&L and gross loss within a delta; - the versioned execution_accounting:v2 marker (F3); - the uncovered-fallback test (F4), with its oracle stated for observation-order replay. It reverts: - the slot-substitution replay and the early-exit removal; - the executions.at_ns column, which also avoids the unversioned schema change (re-check F3); - the stream and recovery timestamp plumbing. Execution-time ordering is a recorded follow-up, with the reviewer's constraints: never reorder into a short (fall back to observation order), a targeted replay trigger, and a schema version bump. Revert checks: the v1 marker fails the F3 test; skipping the uncovered fallback errors the F4 test. Paper suites: 409 OK; source-hashes and trading gates: 33 OK; validate.py passed. Pins: safety.py and its test file only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Scout <scout@local> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Why
EquitiesPaperMetricsMissingwasabsent(paper_trial_active)over a static127.0.0.1:18890scrape target. It fired permanently on every host with no trial running, and this workstation has carried Alertmanager silence 87eabf8a for it, expiring about 2026-10-02.This PR is lane:shared: it changes foundation-owned backend files and a trading-owned exporter. The split and the review points were agreed with the foundation lane (9a).
What
Scrape job and exporter registration
adaptive-papernow readsfile_sd_configsfrom@CONFIG_ROOT@/adaptive-paper-targets.json.configure.pyships the file as[]and keeps an existing one.metrics.py --file-sd PATHregisters the exporter's own127.0.0.1:<port>after the listener binds.flockand writes a temporary file in the same directory, thenfsyncandos.replace.file_sdtarget list refuses start-up (exit 2) rather than leaving the exporter unmonitored.Deregistration
trial.jsonphasefinishedwithout a failed status, or notrial.json.starting,needs_attentionorheld_overnight;needs_attentionorfailed.metrics.py --deregister --port N --file-sd PATHremoves a target after the operator has recovered the trial.startinggap: stopping the exporter of a stuck trial now raises the alert rather than hiding the trial.The rule
up{job="adaptive-paper"} unless on(job, instance) paper_trial_active, for 2m.EcosystemServiceUnavailabledescription and the new rule's description are both updated.Checks (this workstation)
tests.test_observability_backends_alerts: 18 OK, 0 skipped underuvwith PyYAML.test rulescase over the rendered rule covering five scenarios: registered and down, which fires after 2m; registered and answering; registered but another process answering, which fires; nothing registered; and deregistered, where the stale series clears the pending alert.absent(paper_trial_active)andup{job="adaptive-paper"} == 0both fail it.tests.test_adaptive_paper_metrics: 35 OK on system Python and on the pinned adaptive-paper runtime.FileSdRegistrationTestsrun real exporter processes. They passed 5 of 5 repeated runs.scripts/validate.py,evidence_manifest.py --checkandvalidate_convergence.py --all-recordedpass. The evidence pins of the eight changed files are refreshed.python3 -m unittest, system Python:tests.test_adaptive_paper_source_hashes_manifeststill pinned the old sha256 ofmetrics.pyand its test.source-hashes.jsonentries. Nothing pins that file's own digest, and the manifest test passes.tests.test_pre_commit_gatetest_blocks_synthetic_key_and_passes_clean_commit, with "gitleaks: another scan holds the per-user lock". Another session's concurrent gitleaks scan held the lock, so this was environmental and not this change.tests.test_pre_commit_gatepassed: 3 OK.--file-sd;--no-file-sdis the explicit opt-out.trial_finisheduses the runner's allowlist of passing statuses.Not changed
observability/backends/broker-path-rules-receipt.jsonstays as historical evidence of the static target and theabsent()rule. The README says it predates this change.After merge (host steps; I'll record both)
configure.pyand reload Prometheus. Confirm through the Prometheus API that the rule returns empty while nothing is registered.🤖 Generated with Claude Code