Skip to content

fix(hosts): rate-limit heartbeats and close enrollment and audit gaps - #100

Merged
wstein merged 4 commits into
mainfrom
fix/87-heartbeat-codes-audits
Oct 16, 2026
Merged

wstein merged 4 commits into
mainfrom
fix/87-heartbeat-codes-audits

Conversation

@wstein

@wstein wstein commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Implements #87: heartbeat rate limit, older enrollment codes, and denial audits.

  • 54e534c: heartbeats are rate-limited by a receive-time token bucket (capacity 3, one token per 5 s; the first heartbeat after hello is free), per owner decision H54:a. A refused heartbeat writes nothing and closes the connection; the close still records the slot update and one host_connection_unknown audit. The rule is in host-enrollment.md.
  • ee245a8: issuing an enrollment code revokes the host's earlier unconsumed codes.
  • e383084: the pre-check stays outside the transaction; a denial raised inside the transaction is audited after rollback.
  • 3065854: tests prove an in-transaction denial keeps its audit row (Hosts.revoke, ConfigTrust.approve).

Review: CLEAR (Opus 5.5) on 3065854 after three rounds. make check on this head is pending. Stacked on the pilot PR (H51:b).

🤖 Generated with Claude Code

@wstein

wstein commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Review CLEAR on 3065854 (Opus 5.5). Restacked onto pilot 75526bb (CI boundary fix); git range-diff shows every commit identical, so the review carries over. make check on this head: pending.

@wstein
wstein force-pushed the fix/87-heartbeat-codes-audits branch from 4129072 to 5e4002d Compare October 16, 2026 10:42
@wstein

wstein commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main 54a002f after #95 merged; git range-diff shows every patch identical (for #83 only one context line in docs/index.md differs), so the review carries to 5e4002d. make check on this head: pending.

@wstein
wstein marked this pull request as ready for review October 16, 2026 10:55
@wstein

wstein commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Ready per owner: review CLEAR on this head; local make check replaced by one final check on main after the stack merges (owner rule).

@wstein
wstein force-pushed the fix/87-heartbeat-codes-audits branch from 5e4002d to 6a19eeb Compare October 16, 2026 11:19
@wstein

wstein commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Restacked onto pilot 866e46c; git range-diff identical, review carries to this head.

Base automatically changed from pilot/installable-pilot-pr to main October 16, 2026 11:31
wstein and others added 4 commits October 16, 2026 13:32
Every heartbeat writes a transaction plus an undeletable audit row, so a
buggy or compromised agent could grow the audit table without bound and
starve owner writes. host-enrollment.md fixes the cadence at 10 s.

Owner decision H54:a: spacing is measured from when each heartbeat
arrives (monotonic receive time), not from the previous commit, so a slow
ack followed by a catch-up heartbeat is not punished. A token bucket
(capacity 3, one token per 5 s) allows that small burst; the first
heartbeat after hello is free. A heartbeat arriving with no token is
answered with heartbeat_too_fast and the connection is closed. It is
refused without a heartbeat write (no host_heartbeat audit, no lease
renewal); the close itself still records the slot update and one
host_connection_unknown audit. The rule is recorded in the contract's
live expiry section. Existing tests advance the monotonic clock.

Refs: #87

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Earlier unexpired codes stayed valid after a new issue, so redeeming a
stale code later retired the identity the agent had just enrolled with.
Issuing now marks the host's outstanding unconsumed codes revoked in the
same transaction, reusing the existing revoked_at column (no migration).

Refs: #87

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
authorize! called Accounts.authorize inside the transaction and then
Repo.rollback(:forbidden), which also rolled back the denial audit that
authentication.md section 4 treats as security evidence. The in-transaction
check now rolls back with a {:denied, action} marker and the caller records
the denial with Accounts.deny after the rollback.

The outside-transaction pre-check is kept in Hosts (as in ConfigTrust), so
non-Principal callers still get {:error, :forbidden} and ordinary denials
are audited without taking the immediate lock. The post-rollback deny only
covers a denial that appears inside the transaction.

Refs: #87

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Expire the credential between the pre-check and the transaction (clock
flips after the pre-check's reads) so only the in-transaction check
denies; covers Hosts.revoke and ConfigTrust.approve. Both tests fail
when authorize! rolls back with :forbidden again.

Refs: #87

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@wstein
wstein force-pushed the fix/87-heartbeat-codes-audits branch from 6a19eeb to cba2cc9 Compare October 16, 2026 11:33
@wstein

wstein commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Restacked onto main e82167b after #96/#91 merged; range-diff identical, review carries to cba2cc9.

@wstein
wstein added this pull request to stack #108 October 16, 2026 12:04
@wstein
wstein merged commit e3e13ae into main Oct 16, 2026
3 checks passed
@wstein
wstein deleted the fix/87-heartbeat-codes-audits branch October 16, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant