Skip to content

feat(cron): persist conservative transport receipts - #93009

Open
Kinkoolino-Hermes wants to merge 20 commits into
NousResearch:mainfrom
Kinkoolino-Hermes:fix/cron-transport-receipts-v2
Open

feat(cron): persist conservative transport receipts#93009
Kinkoolino-Hermes wants to merge 20 commits into
NousResearch:mainfrom
Kinkoolino-Hermes:fix/cron-transport-receipts-v2

Conversation

@Kinkoolino-Hermes

@Kinkoolino-Hermes Kinkoolino-Hermes commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a conservative, typed transport-receipt contract for cron delivery so Hermes can distinguish provider-confirmed delivery from ambiguous or definite failure outcomes.

A target is delivered only when every preregistered text/media component has an explicit provider acknowledgement. Timeouts, legacy success + message_id results, partial acknowledgements, post-ack persistence failures, and interrupted recovery remain unknown and never trigger a blind resend, mirror, session seed, or replay seed.

The implementation keeps requested and actual targets separate, binds evidence to the exact job/fire/component/ordinal/content hash, and revalidates the full typed receipt at the SQLite trust boundary before persistence. Public cron surfaces expose bounded summaries; sensitive editable configuration is available only through the authenticated management-detail endpoint for an explicitly selected profile.

Related Issue

Fixes #70945

Related but not duplicate:

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Add immutable TransportTarget / TransportReceipt types and additive receipt preservation on SendResult.
  • Add a normalized schema-v5 SQLite plan/attempt ledger with atomic migration, retention cascade, exact replay binding, partial evidence preservation, and persistence-boundary constructor revalidation.
  • Carry durable execution/fire identity through builtin, external-provider, standalone, relay, direct-tool, and background-pool paths; keep immutable nominal fire_at separate from mutable claim heartbeat time.
  • Preregister complete text/media plans before the first side effect and settle a target only after all planned components are acknowledged.
  • Emit explicit typed acknowledgements from Telegram and Matrix, including distinct requested/actual thread targets and native media IDs. Matrix text/E2EE/native-media exceptions after a possible dispatch become typed, non-retryable unknown without key-share/send retry; Telegram photo-to-document fallback is limited to an exact typed BadRequest("photo_invalid_dimensions") rejection.
  • Bound CLI, cronjob-tool, dashboard API, blueprint list/create, cron run-history, cron-fire proxy, and UI projections to status/categories/aggregates; use explicit job/run allowlists, fixed error categories, and bounded timestamp validation so prompts, raw targets, execution IDs, claim owners, raw exceptions, malformed values, and unknown runtime fields cannot leak.
  • Separate public job summaries from authenticated, concrete-profile management detail. Dashboard all-profile mode is read-only, and create/update/pause/resume/trigger/delete all require an explicit concrete profile before lookup or mutation.
  • Count targets_delivered only when every planned component is acknowledged at the exact preregistered platform/chat/thread; partial/fallback acknowledgements remain preserved evidence without falsely settling the requested target.
  • Add regression coverage for constructor bypasses, malformed/unhashable records, timeout/partial/post-ack failures, replay, recovery identity, public redaction, profile isolation, Blueprint siblings, Telegram/Matrix threading and media, and claimed-job pool fallback.

How to Test

  1. Run PYTHONDONTWRITEBYTECODE=1 uv run pytest -q tests/cron.
  2. Run the focused gateway/tool/CLI receipt, ledger, scheduler, profile-isolation, and public-surface suites.
  3. In web/, run npm run check.
  4. Run Ruff on all changed Python files and git diff --check.

Verified on the submitted head before publication:

  • Cron: 931 passed, 1 skipped.
  • CLI/tool/profile/public-surface gate: 371 passed.
  • Monitoring: 20 passed, 1 skipped.
  • Gateway Matrix/Telegram/delivery gate: 219 passed; the same eight unrelated Matrix E2EE/intake baseline failures reproduce on clean current main.
  • Web: typecheck passed; Vitest 278/278; ESLint 0 errors (26 existing warnings).
  • Independent full-spec review: PASS on the frozen 54-path artifact, with zero Blocker/Important findings.
  • Separate adversarial security/code-quality review: PASS on the same frozen artifact, with zero Blocker/Important findings.
  • After rebasing onto current main, all 54 changed file contents matched the reviewed patch byte-for-byte and 87 overlap/delta tests passed.
  • AST parsing, Ruff, git diff --check, scope review, and credential/private-key/token pattern scan: passed with zero findings.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — the complete Matrix file retains eight unrelated failures reproduced on clean current main; all affected-area gates pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Linux, Python 3.11, Node.js web toolchain

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A: no user-facing configuration or workflow is added
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A: no config keys changed
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A: contributor workflows are unchanged
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — SQLite, dataclasses, and timezone-aware ISO timestamps remain platform-neutral
  • I've updated tool descriptions/schemas if I changed tool behavior — dashboard/tool response types are bounded and updated; no new tool arguments

Screenshots / Logs

N/A — this is a transport-accounting and public-response hardening change; verification is covered by automated tests above.

— 🤖 Bot-authored PR description · LLM: gpt-5.6-sol · not written by a human

@alt-glitch alt-glitch added type/feature New feature or request comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 23, 2026

@andrexibiza andrexibiza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 11784164298d7b05253acb88349bea086589f0c3 against base f293e7206b4ddd66042329442c6afebc19a8808d, including the scheduler/ledger receipt boundary, Telegram + Matrix typed acknowledgements, media path, recovery/read-side projection, focused tests, existing review history, and adjacent cron/delivery work.

There is one correctness blocker on the current head:

Typed unknown / failed media receipts can still settle the target as delivered and trigger delivery-only side effects.

_send_media_via_adapter() appends every typed receipt to receipts_out, then separately records SendResult.success == False as a media error. The caller surfaces _media_errors into delivery_errors, but does not clear adapter_ok. It then calls _persist_target_text_receipts(..., components={"media"}). That helper considers a preregistered component satisfied whenever record_transport_receipt() persisted its expected key; it does not require receipt.outcome == "delivered". record_transport_receipt() intentionally persists unknown and failed outcomes and returns True when that write succeeds.

That combination is reachable through the real provider adapters added here: Telegram/Matrix can return the exact expected media receipt key with outcome="unknown" after an exception where the external write may already have happened, together with SendResult.success=False. The receipt is correctly preserved as ambiguous evidence, but the scheduler currently counts the key as fully persisted and leaves adapter_ok=True; the subsequent if adapter_ok: branch sets delivered=True and may seed/mirror the session. The execution ledger can later project the aggregate as unknown, but the target-level delivered transition and its side effects have already happened.

This violates the PR's stated invariant ('a target is delivered only when every preregistered text/media component has an explicit provider acknowledgement') and the #70945 lineage requirement that a later media failure be partial/not fully confirmed and that heuristic/ambiguous outcomes never become confirmed delivery.

Required closure:

  • receipt-plan satisfaction must require every expected component to persist with outcome == "delivered", not merely an expected receipt key;
  • a media SendResult.success=False / media error must make the target non-delivered while still preserving typed unknown/failed evidence;
  • do not seed or mirror the target unless all planned text/media receipts are delivered;
  • add an adversarial regression using a real receipt-shaped media result with the expected key but outcome="unknown" (and ideally failed) and assert target delivery is false, no seed/mirror occurs, the ambiguous receipt remains durable, and no blind resend is authorized.

The existing media regression catches a missing receipt key, but not an expected-key receipt whose terminal evidence is non-delivered, which is why this path survives the current suite.

Interlocks/provenance checked: #75876 (@Zhou-Ruichen) is the narrower live-adapter timeout/no-blind-retry precursor and remains complementary; #82782 (@brianrogstad) is the broader execution-accountability predecessor whose untyped whole-delivery evidence this PR is explicitly tightening; #90293 preserves @smfworks/Paula Rossi lineage for producer/model-route atomicity and evidence-poor execution outcomes and is adjacent rather than duplicate. #92931 is also complementary, not a substitute: it hardens cross-connection Bot Mode relay delivery with a lease-backed at-least-once outbox, while this PR is defining cron's provider-receipt trust boundary. I found no reason to collapse those ownership layers or to erase the earlier contributor lineages.

Everything else I inspected in the typed receipt shape is directionally sound: preregistration before side effects, exact target/component/ordinal binding, persistence-boundary reconstruction, conservative unknown handling for legacy evidence, and native Telegram/Matrix provider IDs are the right architecture. The blocker is specifically the media settlement predicate at the scheduler boundary.

Exact-head CI is also not yet a merge receipt: Branch CI is currently red in its validate job, with dependent quick-check/unit-test work canceled; the other observed exact-head workflows (lint, JS/TS, secrets, Dockerfile lint, history/contributor checks) were green. That CI failure should be resolved/re-run after the semantic fix above.

@Kinkoolino-Hermes
Kinkoolino-Hermes force-pushed the fix/cron-transport-receipts-v2 branch from 1178416 to 8dff48c Compare August 23, 2026 21:16
Hermes Agent and others added 5 commits August 24, 2026 17:21
Resolve tools/cronjob_tools.py by retaining the PR's bounded public projection while preserving upstream gateway-liveness notices for create/list results.
Allowlist the canonical EMPTY_PAYLOAD_ERROR at the public tool boundary while keeping unexpected exceptions generically redacted.
@Kinkoolino-Hermes
Kinkoolino-Hermes force-pushed the fix/cron-transport-receipts-v2 branch from 29e0c93 to b319446 Compare August 25, 2026 17:56
Preserve conservative transport-receipt semantics while integrating durable incident acknowledgements, delivery-target provenance, and the bounded cron tool projection.
Fail closed instead of coercing malformed attach_to_session values that could silently enable transcript mirroring.
Apply the same strict boolean boundary to create and update so registry callers cannot silently coerce or ignore malformed attachment intent.
…3009-20260830

# Conflicts:
#	plugins/platforms/telegram/adapter.py
#	tests/tools/test_cronjob_tools.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cron: persist conservative transport receipts without treating timeouts as delivery

3 participants