fix(otlp): span-raised Guard incidents reach the hosted tab; one source per tool call; content profile; opt-in span rescrub - #6009
Conversation
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
eb35154 to
ea7b1e0
Compare
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
08236ca to
df2bb4a
Compare
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
df2bb4a to
1b55191
Compare
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
2 similar comments
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
4ce83fe to
cf01d93
Compare
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
2 similar comments
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
…l; content profile; opt-in rescrub Closes the engineering gaps left on #5938 after 0.12.878 (REQ-OBS-OTG-001, AC-OBS-OTG-001.7 to .10). Hosted parity: a session materialised from received spans was listed under the Free-install id-prefix guess (openclaw), so a hosted runtime filter dropped it and Guard offered OpenClaw's controls for a remote agent; the incident's observation label never reached the Guard body. routes/guard.py and the detector pass now use the runtime the telemetry names, answer state=unsupported with a sentence, and carry incident.observation. The Guard tab prints "seen after it ran". Proven by decrypting the real sync_system_snapshot blob. One source per tool call (clawmetry/otlp_sources.py): machine observation, then trace span, then log record. A call both signals name by call id gets one event id; the span wins in either arrival order (payload replacement, chain intact). A call only one signal reports is recorded. Calls without a call id keep the first-signal rule. The event half of a batch runs under one lock. Content profile (clawmetry/otlp_content.py): CLAWMETRY_OTLP_CONTENT full|redacted|metadata, default redacted (unchanged). Secret masking and personal-data filtering stay separate switches; posture line added. Rescrub (clawmetry/span_rescrub.py): clawmetry maintenance rescrub-spans, dry run unless --apply, content columns only, through the daemon when it holds the store. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gURwssjXXXdN4ftTqnycU
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2qvyzQXsAhnjfrCt4izc8
…lp_batch return
write_events() was not tracking the flush failure flag, so
otlp_intake could not honour the REQ-OBS-OIA-001 rule that a
flush failure must produce a 503 (not 200) acknowledgement.
put_otlp_batch() was returning only {"records": n} plus the
events sub-counts from otlp_sources.write_events, silently
dropping the four fields otlp_intake expects:
records_rejected, records_duplicate_in_batch,
records_already_stored, records_failed.
This caused four test_otlp_durable_ack.py failures introduced
when the PR refactored the events write path into otlp_sources.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R2qvyzQXsAhnjfrCt4izc8
8c81e38 to
8e02006
Compare
|
Auto-rebase onto current main pushed; CI now running. If still not green in 10min, may need manual attention. Generated by Claude Code |
The MOAT Verifier (test_every_endpoint_hit_fast_path) seeded events at fixture-setup time and queried them up to 8+ minutes later. Events seeded at T=0 fell outside the 300s lookback by the time the test ran (~480s in), so _try_local_store_token_velocity returned None and the endpoint fell back to the legacy JSONL walker without a _source tag. The 2-minute token-accumulation window is unchanged; only the context window for the tool-chain consecutive-run heuristic grows to 30 min. This also improves production chain detection for sessions with tool bursts that started more than 5 minutes ago. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2qvyzQXsAhnjfrCt4izc8
|
MOAT Verifier fix pushed ( Root cause: This PR added new test files that increased the total MOAT Verifier run from under 240 seconds (where events were still in-window) to 484 seconds (where they were not). Main was not affected because its test suite was shorter. Fix: Extended the context window in Generated by Claude Code |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
|
MOAT Verifier now passes (job 104516827026, 18:45–18:50). Waiting on E2E Gate to complete before merging. Generated by Claude Code |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Publishes #5971 (agent supply chain inventory) and #6009 (hosted parity for trace-derived Guard incidents, per-signal dedup, content profile), with their CHANGELOG entries. Claude-Session: https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Refs #5938. Closes the four engineering gaps from the 0.12.878 re-audit. Builds on #5953; does not touch #5963.
Product record: REQ-OBS-OTG-001, extended with AC-OBS-OTG-001.7 to .10 before code: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/requirements/ff78b7cc-7a85-4f0f-a155-3fc78c6cd8b6 . Blueprint section "Received traces reach Guard" in Runtime and Session Observability updated (hosted path, source precedence, content profile, rescrub, one new ADR).
1. Hosted parity (AC-OBS-OTG-001.7)
Traced path: span, tool event, detector pass,
loop_signalsrow, the/api/guard/sessionsbody built on the node, theguardSessionsslice of the encrypted snapshot, the hostedcm-cloud-guardinterceptor.What dropped it: a session materialised from spans was labelled by the session-id prefix reader, which answers
openclawfor any id on a Free install. On the hosted tab a runtime filter therefore hid the session, Guard offered OpenClaw's Pause/Stop/Kill for an agent that only sends spans, and the incident was filed underopenclaw. The Guard body also droppedobservation, so no surface could say "seen after it ran".Fix:
routes/guard.pyand the detector pass inclawmetry/sync.pyuse the runtime the telemetry names (metadata.runtimewhenmetadata.sourceisotlp_spans). Such a session answersstate: unsupported,controllable: false, no actions, with a sentence.incident.observationrides the body. The Guard tab prints "seen after it ran". No cloud change: the interceptor passes rows through unchanged.Proof:
test_a_span_raised_incident_reaches_the_hosted_guard_tab_through_the_encrypted_snapshotruns the real receiver, detector pass andsync_system_snapshot. It captures the blob the daemon would POST, checks the incident is not readable in it, then decrypts it and reads the row the way the interceptor does, including?runtime=. Also replayed by hand: the realcm-cloud-guardJS from clawmetry-cloudorigin/main(4eb835bd), run under node against that decrypted snapshot, served the row for?runtime=support_agent(flagged,file_blast_radius,prevented: false, not controllable) and nothing for?runtime=openclaw.2. One source per tool call (AC-OBS-OTG-001.3, .8)
clawmetry/otlp_sources.pyowns the event half ofput_otlp_batch. Precedence: machine observation, then trace span, then log record.gen_ai.tool.call.id,tool.call.id,tool_call_id,call_id,tool_use_id) gets one event id derived from session and call id. The span's copy wins in either arrival order. A log copy after the span is skipped; a span after the log copy replaces the row's payload. The event hash chain covers identity and time, not payload, so it stays valid (tested).3. Content minimisation (AC-OBS-OTG-001.9)
CLAWMETRY_OTLP_CONTENT=full|redacted(default, unchanged) |metadata, inclawmetry/otlp_content.py.full: content is kept and personal data is not filtered. Secrets are still masked.metadata: prompts, responses, tool arguments, results, status messages and every attribute outside a metadata allowlist are replaced with[WITHHELD:content-profile]. The row carriesclawmetry.contentand the withheld keys. The ledger identity columns are kept as sent (AC-OBS-006.2).CLAWMETRY_REDACT; personal-data filtering stays onCLAWMETRY_REDACT_PII.clawmetry.contentandclawmetry.redactionattributes.otlp_contentline naming the profile and the personal data never detected (names, street addresses, dates of birth, record numbers).metadata, the behavioural detectors cannot read arguments that were never stored.4. Old spans (AC-OBS-OTG-001.10)
clawmetry maintenance rescrub-spansinclawmetry/span_rescrub.py. It is a dry run that only counts unless--applyis given.--applyrewrites content columns only. Ids, times, cost, tokens, model and the received-content hash are untouched.CLAWMETRY_REDACT=0, and says copies already sent in a snapshot or exported are not changed.rescrub_spans, allowlisted, not memoised) or directly when no daemon runs.Verified against a scratch daemon in a scratch HOME. Dry run: 1 would change, nothing written.
--apply: 1 rewritten, and the daemon logged the rewrite. Dry run again: 0. The user's daemon and~/.clawmetrywere not touched.Tests
tests/test_otlp_span_parity_sources_content.py(15 tests), added to the CI OTLP receiver job. Onorigin/main, 13 fail. The two that pass there are guards: chain validity after replacement, and the personal-data switch staying separate.tests/test_otlp_trace_guard_redaction.py: one assertion now checksdata._otlp_signalinstead of theotlp:span:id prefix, because keyed spans take the sharedotlp:call:id.test_security_posture_registry.pytotals tests, fail identically onorigin/main. Everymake lintguard except the pre-existing ruff backlog passes; the new files are ruff-clean.docs/acceptance_criteria.jsongains .7 to .10 (no duplicate ids), baseline tightened (184/254),docs/MODULE_MAP.mdregenerated.Merge order with #5963
Both PRs rewrite the event half of
put_otlp_batch: this one moves it intoclawmetry/otlp_sources.py, and #5963 adds refused/failed counters to it. Whichever merges second needs a rebase. If #5963 goes first, port itsevents_rejected/events_failed/events_flush_failedcounters intootlp_sources.write_events. The trace receiver edits sit in different hunks (_otel_to_rowreturn, the log tool-event ids).No new HTTP route, so no
cloud_route_policyPR is needed. No CHANGELOG entry.🤖 Generated with Claude Code
https://claude.ai/code/session_01Jm9d7s4fN55hN3YzQo75o9