feat(ingest): open the front door — ingest key, generated contract, setup prompt, live status - #5684
Conversation
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Visual diffBot run failed before producing screenshots. Check the workflow logs. This check is non-blocking. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
1b36e87 to
90f8eb5
Compare
a399006 to
1eb88dc
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. |
|
✨ auto-fixed: merged updated base branch (feat/build-your-own-ui) into branch — base was at old SHA 90f8eb5, now at e270a12 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. |
✅ 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. |
e270a12 to
0339012
Compare
7f72f7d to
7c8a51a
Compare
|
|
Checked this rather than acting on it — the finding is a false positive, but it was pointing at a real defect.
Why the bot couldn't see it, and the actual defect. The module opens with a per-blueprint route index, and that index had drifted:
Fixed in a798db0: every count corrected, the missing routes named, and a guard added asserting the documented count matches the decorators below it (mutation-proven by understating |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
ba455d6 to
c042dad
Compare
11d922a to
3483d37
Compare
|
awaiting confirmation — non-trivial rebase, needs human review (conflicts in: CHANGELOG.md, CLAUDE.md, clawmetry/apikeys.py, clawmetry/cli.py, dashboard.py, docs/MODULE_MAP.md, examples/custom-ui/index.html, routes/public_api.py, tests/test_public_api_keys.py) This PR is stacked on Generated by Claude Code |
a798db0 to
511a170
Compare
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
|
blocked on author decision — skipping (auto-mergeability sweep) This PR is stacked on Generated by Claude Code |
511a170 to
fcdece8
Compare
New modules added in cb77f96 (otlp_intake, otlp_sources, otlp_content, cost_basis_surfaces, price_book_edit, price_book_usage, span_rescrub, store_errors) caused the lint-module-map guard to fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154SEoKpixxxoFgQNvd3rgJ
|
| # prompts, where a wrong header name is a silent failure an agent will | ||
| # write confidently. | ||
| from clawmetry.ingest_contract import ( # noqa: F401 (re-exported) | ||
| HEADER_ENV, | ||
| HEADER_KEY, | ||
| HEADER_RUNTIME, | ||
| MAX_BODY_BYTES, |
There was a problem hiding this comment.
ingest_auth.py imports HEADER_KEY, HEADER_RUNTIME, HEADER_ENV, and MAX_BODY_BYTES from clawmetry.ingest_contract (lines 59-65), but these constants do not exist in the contract module. This will cause ImportError when ingest_auth.py is imported at runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lq5A3wY8cF5kEU8XXuvKQR
|
|
| # setup prompts and the landing reference are generated from that same | ||
| # declaration, so the thing the server enforces and the thing we tell | ||
| # people to send cannot drift apart -- which matters most for the setup | ||
| # prompts, where a wrong header name is a silent failure an agent will |
There was a problem hiding this comment.
ingest_auth.py imports HEADER_KEY, HEADER_RUNTIME, HEADER_ENV, and MAX_BODY_BYTES from clawmetry.ingest_contract (lines 59-65), but these constants are not defined in that module. This will cause ImportError when ingest_auth.py is imported at runtime.
| from typing import Optional | ||
|
|
||
| from clawmetry.ingest_contract import ( | ||
| CONTRACT_VERSION, |
There was a problem hiding this comment.
setup_prompt.py imports HEADER_KEY, HEADER_RUNTIME, HEADER_ENV, MAX_BODY_BYTES, and CONTRACT_VERSION from clawmetry.ingest_contract (lines 46-51), but these constants (except CONTRACT_VERSION) are not defined in that module. This will cause ImportError when setup_prompt.py is imported at runtime.
ingest_auth.py and setup_prompt.py both import HEADER_KEY, HEADER_RUNTIME, HEADER_ENV and MAX_BODY_BYTES from clawmetry.ingest_contract, but those constants were never defined there -- causing an ImportError on every POST to /v1/traces and a test failure in test_ingest_contract_drift. Also adds HEADERS (for the setup-prompt generator), SURFACES (for the reference doc and the drift test), RESPONSES (checked by test for 200/400/ 401/403/413), and GENAI_READ / GENAI_NOT_READ convenience aliases (iterated as (attr, _) tuples by the parametrized drift tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lq5A3wY8cF5kEU8XXuvKQR
|
✨ auto-fixed: added missing Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) mergeable_state=dirty: merge conflict between feat/ingest-key and its base feat/build-your-own-ui (base has moved since this PR was opened). Resolving requires author context about both stacked branches. 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. |
|
awaiting confirmation — non-trivial rebase, needs human review (conflicts in: stacked base Automated sweeper identified this as Generated by Claude Code |
Resolves merge conflict in docs/MODULE_MAP.md (module count: kept 281 from HEAD as feat/ingest-key has additional modules ingest_auth.py, setup_prompt.py beyond what feat/build-your-own-ui introduced). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVbQ3pgBwKmfVmb8FAo68
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Replace 14 em-dashes across the four CHANGELOG entries added by this PR (ingest-status, setup-prompt, ingest-contract, ingest-key) with commas, colons, and parentheticals per the style rule. Verified by test_changelog_no_em_dashes.py locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVbQ3pgBwKmfVmb8FAo68
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
test_doc_states_what_we_do_not_accept asserts the generated docs/INGEST.md contains "does not accept", "syslog" and "CEF" so the reference answers the recurring syslog question without a meeting. The gen script was missing the section; add _render_non_goals() and include it in render(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVbQ3pgBwKmfVmb8FAo68
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Resolved 8 conflicted files by keeping PR's ingest-key additions (SCOPE_INGEST, write:ingest scope, setup-prompt CLI command, ingest_auth gate in before_request, UI write-scope warning). Regenerated MODULE_MAP after merge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188yXdii4pWbUxWSbtkaMPt
|
✨ auto-fixed: merged 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. |
…ze log injection CodeQL findings on #5684: - routes/meta.py:1358 (CWE-209): exception text in HTTP 400 body could expose internal parse-library error details to callers. Drop the `detail` key. - clawmetry/ingest_auth.py:111 (CWE-117): user-supplied key value flows into a log call even through apikeys.redact(). Strip newlines explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVbQ3pgBwKmfVmb8FAo68
✅ 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. |
Closes #5679, #5682, #5681, #5680.
Requirement: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/requirements/7fb88c5c-228e-4c48-9f16-da570740fc36
Stacked on #5676 (
feat/build-your-own-ui) — this is the write half of that key system, so the base is that branch, not main. Merge #5676 first.The gap
An agent is observable by ClawMetry only if the daemon runs on the same machine as the agent.
/v1/*trusts loopback and otherwise wants the gateway token; the write API trusts loopback or one static secret shared by the whole install;ingest.clawmetry.comtakes E2E-sealed daemon snapshots and nothing else. That excludes CI runners, containers, serverless, hosted agent products and teammates' laptops — where a lot of agent spend actually happens.Three commits, in dependency order.
1. The ingest key (#5679)
then
x-clawmetry-key: cmk_…on the OTLP endpoints. Samecmk_shape, same store, sameclawmetry key list|revokeas the read keys — not a second key system. Two routing headers come with it, written into the resource attributes the mappers already read (service.name,deployment.environment), so a header is exactly as powerful as the equivalent exporter setting and no mapper learns a second way to answer the question.The posture: an ingest key can only push (
write:ingestgrants noq/1shape); it is never given a CORS header and cannot carry a browser origin; read and write cannot be mixed on one key, refused at creation with a sentence rather than at request time with a code; and there is one gate, not two —_check_authsteps aside for a keyed/v1/request exactly as it does for/api/q/.Verified against a real dashboard: all three doors return 200 (loopback zero-config unchanged, gateway token unchanged, ingest key), every refusal returns its own status with its own sentence, and a span pushed with
x-clawmetry-runtime: my-enginelands asagent_type=my_enginewhile the same span pushed bare still lands asopenclaw. That live run found the one real bug here:GET /api/q/1was answering200to a write-only key —verify()accepts it since it is a valid key, and nothing asked whether it held any read scope. Now403, pinned by a test.2. The ingest contract, generated (#5682)
Four things describe this surface — the server,
docs/INGEST.md, the setup prompts, and the landing reference — and none shared a source.clawmetry/ingest_contract.pydeclares it as data;ingest_auth.pyimports its constants from there;scripts/gen_ingest_doc.pyrenders the doc with--check.The guard earns the file. It checks both directions: every attribute declared read must be named in the mapper, and every attribute declared unread must genuinely be absent. Writing that check is what surfaced #5685 — we were advertising a GenAI convention we did not implement, and cached tokens were priced as free.
The doc also states what ClawMetry does not accept — no syslog, CEF, GELF or raw text — pinned by a test, because a reference that only says what works is not one anyone can plan against.
Two cache attributes are listed as not read here, which is the honest state of this branch: the fix is #5686, open against main while this is stacked elsewhere. The unread-side guard makes whichever merges second update the list — which is why both directions are checked.
3. The setup prompt (#5681)
clawmetry setup-prompt <runtime>andGET /api/setup-prompt, rendered from the same contract. Half of it is negative space, because that is the useful half: the key goes in one header and nowhere else; the placeholder is a placeholder and the real key must be asked for, never invented; both encodings are already accepted and need no "fixing"; a key not in the prompt does not exist; and this is observability — it watches, it does not change what runs. It ends by making the agent verify and report a real event count.Two guards worth keeping. The first reads backwards: every
x-clawmetry-*token in the prompt must be a declared header. Checking only that the right headers appear was too weak — proven by mutation, where swapping the config block's header forx-clawmetry-apikeyleft every other assertion green because the correct name still appeared in the prose. The second is general: every registered subcommand must be incli.py's_subcmdsallowlist, because a parser with no entry there falls through to the dashboard's argparse and dies with "invalid choice", reading as though the command was never written.setup-promptdid exactly that when first added — the same two-list trap CLAUDE.md documents for runtimes — and asserting only thatsetup-promptis present would not have prevented the next one.The prompt module names no runtime and hardcodes no vendor value, pinned by a test: runtime-specific OTel knowledge stays in profiles, which paid runtimes register from clawmetry-pro.
4. "Did it work?" (#5680)
GET /api/onboarding/ingest-statusanswers the question that kills setup funnels, from real data, and the first-run gate renders it as a live strip. When nothing has arrived it says so and what to do, includingclawmetry setup-promptfor an off-box agent. It never blocks — a confirmation, not a step, in a flow whose selling point is having no steps.Three judgement calls worth naming:
has_data_this_process. Folding them together would tell a working install it was broken after every restart._dispatch, never raw files, so it answers identically on a laptop and in a container with no~/.openclaw. Memoised 2s because it's polled: ~200 ms first call, ~0.2 ms after.Verified in both states against a running dashboard: strip renders inside the card in the real page; empty store returns
connected:falsewith an actionablenext_step; the populated store here returns 25,349 events across 9 runtimes. The strip first landed outside.obg-card— on the overlay backdrop rather than in the dialog — so a guard now asserts its position.Tests
77 guards across
test_ingest_key.py(25),test_ingest_contract_drift.py(26),test_setup_prompt.py(17) andtest_ingest_status.py(9), all registered inci.ymlsince CI runs explicit file lists. Every non-obvious guard was mutation-tested — and two of them were strengthened after the mutation passed, which is the only reason they are worth having.Not in scope
Regional endpoint split, a base "any format" endpoint, per-key rate limiting (belongs on the hosted side where plan budgets live). The hosted endpoint is clawmetry-cloud#2343, gated on the trust-class work in clawmetry-cloud#2344 — pushed telemetry is plaintext by construction, and that needs naming before it ships rather than after it becomes a P0.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YMHxYeQR1QLunY3PzURqRd