Add orynq-ai-auditability community ability (supersedes #249)#257
Add orynq-ai-auditability community ability (supersedes #249)#257realdecimalist wants to merge 4 commits intoopenhome-dev:devfrom
Conversation
…o-end on-chain certification proven Supersedes openhome-dev#249 (which remains closed, CHANGES_REQUESTED). Major architectural and sandbox-compatibility improvements validated end-to-end on Preprod v5 on 2026-04-24. Design changes -------------- - Pure Background Daemon category. No matching_hotwords; main.py is a non-functional stub required only by the CLI validator's REQUIRED_FILES check. All capture + anchoring runs silently in background.py. - Auto-anchor: after every poll that grew the chain, _maybe_anchor_auto() evaluates four gates (no_growth / rate_limit / backoff / api_key) and uploads the canonical v2 envelope to the Materios gateway. Rate-limited to once per minute; fails open to local-only mode if no Bearer token is configured. User takes no action after one-time token setup. - Sponsored-receipt flow: gateway fires fire-and-forget callback to the sponsored submitter, which signs submit_receipt_v2 on-chain. No signing key on the device. - Capture coverage expanded to ANY non-empty message (user / assistant / tool / function roles, text OR structured dict/list content) via canonical JSON (sort_keys, compact separators). Regression: previously tool_call dicts and capability invocations were silently dropped. Sandbox + deploy-cycle hardening -------------------------------- - Removed 'getattr' (forbidden builtin) in favour of hasattr + direct access. - Renamed journal file from orynq_audit_chain.json.tmp to orynq_audit_chain_tmp.json (the SDK silently drops .tmp extensions). - Omitted mode='w' on write_file (silently dropped on real hardware; default 'a+' works). - Bounded-retry journal verify: 5 attempts with exponential backoff (50->100->200->400ms) accommodates the write/read eventual-consistency window; a single read returns empty. - Static templated confirmation strings in any speak() path that carries structured data (hashes, receipt IDs). text_to_text_response() is not safe for factual confirmations — the LLM safety layer refuses. - Auth routing helper selects Authorization: Bearer when the key is prefixed matra_, else falls back to legacy x-api-key. - Gateway URL uses the verified double-prefix /preprod-blobs/blobs/{hash}/... (nginx strips the outer prefix; backend routes expect /blobs/{hash}/...). Verification + diagnostics -------------------------- - _verify_chain(chain) ported from main.py into background.py as a pure helper. Tests (kept out of the PR, local-dev only) cover content-hash tamper, previous-hash tamper, direct chain-hash tamper, partial-verify-after-compaction, and empty-chain success. - Per-poll verbose logs added for diagnosability: [OrynqAudit] poll: hist_len=N seen_prev=M new_msgs=K added=X chain_len=Y [OrynqAudit] anchor skip: no_growth|rate_limit|backoff|no_api_key (...) [OrynqAudit] anchor start: chain_len=N last_len=M [OrynqAudit] upload OK hash=<64-hex> [OrynqAudit] history shrunk, resetting pointer End-to-end proof 2026-04-24 --------------------------- Deployed as OpenHome ability 3789 (Background Daemon, agent 578906). Two full cycles captured during a single voice session, both certified: - Session open — content 0xc7a028f0... -> receipt 0xa95eb1d2...8782, cert 0xbb5f1a1c... - Weather Q&A — content 0x2bfad367... -> receipt 0xa560d10c...df6e7, cert 0x7beff7f944bd937b... Submitter 15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5 (Materios Preprod v5 sponsored operator). Known follow-ups ---------------- - background.py uses requests.post()/put() synchronously from an async path; should migrate to aiohttp or asyncio.to_thread() to avoid worst-case event-loop starvation on slow networks. Not urgent — uploads typically complete ~1-2s and rate-limit caps to 1/min. - OpenHome CLI validator still insists on main.py + at least one --triggers string even for --category background_daemon. The stub main.py + dummy-trigger-which-the-server-clears workaround is a platform contradiction worth a separate upstream issue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🔀 Branch Merge CheckPR direction: ✅ Passed — |
✅ Community PR Path Check — PassedAll changed files are inside the |
✅ Ability Validation Passed |
🔍 Lint Results✅
|
…ostics — verified end-to-end on DevKit voice
Since the initial PR commit, live testing on a DevKit revealed three
hardening issues. Fixed in this commit, confirmed in a real voice
session 2026-04-24 with the Background Daemon category:
1. Cross-session state corruption — `last_seen_index` was user-scoped
but indexed session-scoped `get_full_message_history()`. Each new
session reset the pointer via the "history shrunk" branch and
nothing was captured. Replaced with `session_pointers: {session_id:
int}` keyed on `worker.session_id` (fallback to synthesized id if
unexposed). State bumped to `state_version: 2` with tolerant
migration from the old schema.
2. `call(worker, background_daemon_mode)` lacked a default on the
second arg. For category=skill invocations the platform calls
`call(worker)` and hits TypeError, preventing the daemon from
starting. Added `background_daemon_mode: bool = False` so the
signature works for both skill and background_daemon paths.
3. No observability when the daemon silently fails to load. Added:
- Step-by-step `[OrynqAudit] call() STEP 1-5` logs in the entry
point, wrapped per-step in try/except so a failure before log openhome-dev#1
still surfaces (to the extent logging is reachable)
- `[OrynqAudit] watch_loop STEP A-D` logs at the start of the
coroutine including resolved session_id + fallback source
- `orynq_audit_heartbeat.json` written on step C — its existence in
user-data file storage is a persistent non-log signal that
`call()` fired + watch_loop started
- Per-message `extend[i/total]: ADD/skip role=<x> reason=<r>`
logging with skip counters and content-hash prefix
- Per-poll `poll#N: hist_len=M prev_seen=P shrunk=<bool> …` dump
- Per-gate anchor-skip reasons (`no_growth|rate_limit|backoff|
no_api_key`) and an `anchor start` line before uploads
End-to-end verification (2026-04-24, OpenHome agent 578906, DevKit):
- BD ability auto-loaded on session init (bd_mode=True), no hotword
- 47-turn conversation captured on first poll (chain 0 → 47)
- Forward-journal save succeeded (17522-byte .tmp verified)
- Auto-anchor uploaded content_hash 0x3b83d1e816494b0161015880dd17bb56
5a463a8c3b2168614fb502c4aaee39a4 (chain_len=335, 103596 bytes)
- On-chain receipt 0x463c3b1c730b6ef7821c45a70139bb6faf1b98b303d29b00b
983195b715efb32 landed in block ~22:00:06 UTC, cert-daemon attested
within ~4s (certHash 0x75fe3b4bbb8d64bb361c3a60e4145554e8760957c348d
e677ebe9ad936bfe1f0)
- Per-session-pointer fix proven across 8 distinct session_ids tracked
in the same file without cross-session contamination
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update: per-session pointers + step-by-step diagnostics, verified end-to-end on DevKit voicePushed commit
End-to-end verification (2026-04-24, OpenHome agent 578906, DevKit)
All three green bot checks (branch direction, community path, ability validation, lint/flake8) pass on the new commit. |
|
Hey @Ju-usc, the rest of the code looks good! One thing to fix before we can approve — for all JSON file writes, please use the canonical delete + write pattern instead of writing directly. The SDK's Anywhere you write a JSON file, replace: await self.capability_worker.write_file(filename, json.dumps(data), False)with: if await self.capability_worker.check_if_file_exists(filename, False):
await self.capability_worker.delete_file(filename, False)
await self.capability_worker.write_file(filename, json.dumps(data), False)Please address this and push your fix — we'll take another look. |
Summary
New community ability: Orynq — Passive AI Auditability. Pure Background Daemon that hashes every conversation turn into a SHA-256 rolling chain and auto-anchors to Materios + Cardano with zero user action. Supersedes #249 (closed with CHANGES_REQUESTED, never reopened).
End-to-end proven on Preprod v5 — 2026-04-24
Deployed to OpenHome agent 578906 as ability id
3789, categorybackground_daemon. Full cycle (capture → upload → on-chain receipt → cert-daemon attestation) confirmed twice during a single voice session.Verify the receipts live: fluxpointstudios.com/materios/explorer#reciepts — paste any receipt ID or content hash below to view on Preprod v5.
Session-open anchor
0xc7a028f035145908df39c5fcee837b8acebdd40288f063764c204ec3237de3590xa95eb1d2875d81dddf573352974373e2b6df306d2fc43eed4bfa3c8a607787820xbb5f1a1c11f0231c394c64b0bc6ac70cb25234e472bcf2fe460119b087c42d72Weather Q&A anchor
0x2bfad36760aaa5992b658df86bed6217c020cf16d5dde719c03d2b5f8eaf16a70xa560d10cc433223d112439517243426863e6e503f107bbda39d191a10b8df6e70x7beff7f944bd937b077968dcd858c1e2bc5bb648234fb3f01664077568266ff2Submitter:
15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5(Materios Preprod v5 sponsored operator). Zero user action, zero signing key on the device.What changed beyond #249
Design
main.pyis a non-functional stub required only by the CLI validator's REQUIRED_FILES check; all capture + anchoring runs silently inbackground.py.Sandbox + deploy-cycle hardening (learned on real hardware)
getattr(forbidden builtin) in favour ofhasattr+ direct access.orynq_audit_chain.json.tmptoorynq_audit_chain_tmp.json(SDK silently drops.tmpextensions).mode="w"onwrite_file(silently dropped on real DevKit; default"a+"works).text_to_text_response()is not safe for factual confirmations (LLM safety layer refuses with "I can't confirm specific transactions…").Authorization: Bearerwhen token ismatra_-prefixed, else legacyx-api-key./preprod-blobs/blobs/{hash}/…(nginx strips the outer prefix; backend expects/blobs/{hash}/…).Verification + diagnostics
_verify_chain(chain)now lives inbackground.pyas a pure helper (was in main.py). Locally test-covered for content-hash tamper, previous-hash tamper, direct chain-hash tamper, partial-verify-after-compaction.uzair401's review (#249) — status
All six axes of the voice-UX audit prompt are now addressed by the passive refactor:
speak(). Silent by design.Test plan
python3 validate_ability.py community/orynq-ai-auditability/openhome-cli deploy --category background_daemonattaches cleanly to a test personality[OrynqAudit] daemon startedappears in logs; subsequent polls loghist_len=… new_msgs=… added=…materios_gateway_api_key, first new-turn poll triggers an upload and a receipt lands on chain within ~6sKnown follow-ups (not blocking)
background.pyuses syncrequests.post()/put()inside an async loop — should migrate toaiohttp/asyncio.to_thread()for worst-case event-loop starvation. Not urgent; uploads typically 1–2s and rate-limit caps at 1/min.main.py+ at least one--triggersstring even for--category background_daemon. Stub + dummy-trigger-which-the-server-clears works but is a platform contradiction worth a separate upstream issue.🤖 Generated with Claude Code