Skip to content

feat(aauth): keypair format standardization (#36) + grant revocation primitives (#34)#41

Open
markmhendrickson wants to merge 19 commits into
mainfrom
feat/aauth-keypair-revocation
Open

feat(aauth): keypair format standardization (#36) + grant revocation primitives (#34)#41
markmhendrickson wants to merge 19 commits into
mainfrom
feat/aauth-keypair-revocation

Conversation

@markmhendrickson
Copy link
Copy Markdown
Owner

Closes #36. Closes #34.

#36 — Standardize daemon keypair on-disk format + JWKS endpoint

  • lib/daemon_runtime/aauth_signer.py: from_key_file now probes <name>.jwk.json (canonical JWK) first, falls back to <name>.json (legacy PEM). Auto-detects format by kty+d fields. New _load_private_key_jwk reconstructs an EC P-256 private key from base64url x/y/d scalars via cryptography.
  • execution/scripts/mint_daemon_keypair.py: generates an ES256 P-256 keypair and writes the canonical <name>.jwk.json (mode 0600) into ateles-private/keys/. Refuses to overwrite an existing file.
  • docs/aauth/keys.md: canonical layout, legacy format, the existing 8 keypairs, rotation flow, and the planned JWKS endpoint.

#34 — Revocation primitives (suspend/revoke + per-capability)

  • lib/daemon_runtime/grant_checker.py: GrantChecker loads agent_grant entities by aauth_sub and exposes is_active()/is_suspended()/is_revoked()/check_capability(cap). Module-level suspend_grant/restore_grant/revoke_grant write status back via the Neotoma corrections API with reason + timestamp. Permissive fallback when Neotoma is unreachable (advisory until PS-layer enforcement, Implement R3 r3_conditional support in Neotoma's AAuth verifier #30).
  • execution/scripts/manage_grants.py: CLI — list [--sub], suspend <id> [--reason], restore <id>, revoke <id> [--reason].
  • lib/daemon_runtime/__init__.py: exports the new symbols.
  • Formica + neotoma-agent: grant check at startup (revoked → abort, suspended → warn + disable dispatch); grant-suspend guard in the dispatch path; ATELES_PARTICIPATION_REF passed in subprocess env (prep for ateles#19 part 4: auto-stamp retrieval_event from mcpsrv_neotoma #23 retrieval attribution).

Verification

  • All modules pass ast.parse.
  • grant_checker public API surface and __init__ exports validated.
  • HEAD commit is GPG-signed.

Out of scope / follow-ups

🤖 Generated with Claude Code

markmhendrickson and others added 19 commits May 26, 2026 11:35
Two-phase design: Phase 1 sends a shallow Telegram briefing at 05:30
Madrid time with today's calendar + known attendee context; Phase 2
spawns per-meeting Claude agents for deep participant research,
agenda/goals/talking-points, Neotoma task creation, and checkpoint_brief
storage. Fixes log filename sanitization for event titles containing '/'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Onychomys now knows to retrieve Cotinga's checkpoint_briefs from Neotoma
when asked about today's meetings, rather than re-running the daemon.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n at 05:30

Cotinga now runs at 05:00 to store checkpoint_briefs before the digest.
morning-brief runs at 05:30, polls Neotoma for Cotinga's briefs (up to
20min), spawns a one-shot Claude agent in Onychomys voice to compose the
digest, and sends to Telegram.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t Madrid time)

Removes the 48-hour lookahead window that was including tomorrow's events
in the morning briefing. Also picks up plist updates (log paths, PATH,
NEOTOMA_BASE_URL).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eotoma stub creation

Deep-prep agents now search Gmail (gws) and LinkedIn for contact details
when an attendee isn't found in Neotoma, then store a fully enriched
person entity rather than a bare stub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…personal/PR#2

- Add .claude/skills/analyze-meeting/SKILL.md: 11-step meeting analysis
  (participants via Calendar/Neotoma, recap email via gws, proposed_github_issue
  entities, default repo markmhendrickson/ateles)
- Add execution/scripts/import_audio_from_desktop.py: migrated from personal repo;
  replaces --analyze flag with --no-analyze; emits ANALYZE_MEETING_TRIGGER lines
  for the calling agent to fan out /analyze-meeting per transcription
- Add execution/scripts/backfill_analyze_private_docs.py: batch driver for
  /analyze backfill over docs/private; dedupes against Neotoma; emits
  ANALYZE_TRIGGER JSON lines
- Add execution/scripts/meeting-recording-control.sh: copied from personal repo;
  required by strix.py at startup (absence caused crash loop)
- Fix execution/daemons/strix/com.ateles.strix.plist: update python path to
  .venv/bin/python3, add VIRTUAL_ENV + PYTHONPATH so launchd finds venv packages
  (rumps), update log paths to ~/Library/Logs/ateles/strix.log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ooltip

Remove the two-item dropdown; clicking the status bar icon now directly
toggles recording on/off. Hover shows "Strix: recording active — click to
stop" or "Strix: idle — click to start recording".

Implementation: a one-shot Timer fires 100ms after startup to reach into
the NSStatusItem, clear its menu, and wire a PyObjC ClickTarget as the
direct action target (sendActionOn_ mask covers both mouse buttons).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…end failures; load openclaw .env in send.mjs

- Cotinga and morning-brief now acquire a PID lockfile on startup and
  release it on exit, preventing launchd double-launch races
- telegram_send() now logs exit code and stderr when send.mjs fails,
  instead of silently swallowing errors
- send.mjs now also loads ~/repos/openclaw/.env so TELEGRAM_BOT_TOKEN
  is found without manual env var configuration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s with summary

After recording stops, the control script now sends:
- 📝 [strix] Transcription done (Xm Ys).\n<first 400 chars of transcript>
- 🎙️ [strix] Diarization + transcription done (Xm Ys).\n<...> when diarization ran
- ❌ [strix] Transcription failed. if transcription errors

Also adds a _telegram() helper in the script and fixes the venv python
path to prefer .venv/bin/python3 (matching the launchd plist).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace send.mjs subprocess with direct Python urllib Bot API call —
  subprocess path silently failed on every launchd run; now logs message_id
  or explicit HTTP/exception error on failure
- Remove StreamHandler(stdout) from logger — plist routes stdout+stderr to
  same file, causing every log line to appear twice
- Filter shallow briefing to meetings-only (skip personal calendar blocks
  with no external attendees)
- Remove *bold* Markdown wrapping on event titles — emoji+spaces break
  Telegram's legacy Markdown parser
- Fix 'Deep briefs will arrive' footer: only shown when meetings present
- Fix --thread-id → --topic flag in both telegram_send() and deep-prep
  prompt (send.mjs uses --topic)
- Expand deep-prep prompt with company research, recent news/publications,
  Neotoma/Ateles overlap analysis, and live convergence sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- meeting-recording-control.sh: fall back to personal venv when ateles
  .venv lacks numpy/audio deps; find personal venv automatically
- Symlink record_meeting_audio.py, transcribe_audio.py,
  extract_meeting_frames.py from personal repo into ateles/execution/scripts
- tyto.py: fix Priority.ERROR→BLOCKER, add _load_env(), handle system.mp4
  track name, find transcribe_audio.py in personal repo fallback
- tyto plist: use personal venv python, set correct PATH (nvm, local/bin),
  point to prod Neotoma
- strix.py: control script path resolves correctly to ateles repo

Recording pipeline now fully operational: start/stop/transcribe/Neotoma store.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ine filter

- Fetch from start-of-day (not now) so overnight events (flights, etc.) are included
- Replace meetings-only filter with routine-block filter: show all events except
  personal blocks (Wake, Work, Busy, Prepare for bed, Lights out, etc.)
- 📌 icon for solo events, 🤝 for events with attendees; deep-prep only for latter
- "Deep briefs" footer only shown when at least one attendee-meeting has deep-prep
- Add _is_routine() with emoji-stripping for reliable title matching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Deduplicate events by normalised title (strips non-alphanumeric, lowercases)
  so multi-calendar duplicates (e.g. all-day + timed version of same event) only appear once
- Detect all-day events via start.date vs start.dateTime; show "all day" instead of "00:00"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- meeting-recording-control.sh: stop loading personal/.env (it has
  RECORD_MEETING_SKIP_TRANSCRIBE=1 which disabled transcription)
- Default RECORD_MEETING_DEVICE to "System-wide capture" (Rogue Amoeba
  virtual device that captures all system audio regardless of output routing,
  even when AirPods are active) instead of BlackHole
- Default RECORD_MEETING_MIC to "Studio Display Microphone" instead of
  system default (AirPods Max) which has noise cancellation issues
- Default RECORD_MEETING_SKIP_TRANSCRIBE to 0 (enable transcription)
- All three defaults are overridable via env vars; explicitly exported
  so they reach the record_meeting_audio.py subprocess

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…primitives (#34)

Issue #36 — Standardize daemon keypair on-disk format:
- aauth_signer.py: from_key_file probes <name>.jwk.json (canonical JWK)
  first, falls back to <name>.json (legacy PEM); auto-detects by kty+d
  fields. New _load_private_key_jwk reconstructs EC P-256 key from
  base64url x/y/d scalars via cryptography.
- mint_daemon_keypair.py: generates ES256 P-256 keypair, writes canonical
  <name>.jwk.json (mode 0600) to ateles-private/keys/.
- docs/aauth/keys.md: canonical layout, legacy format, rotation, JWKS plan.

Issue #34 — Revocation primitives (suspend/revoke + per-capability):
- grant_checker.py: GrantChecker loads agent_grant entities by aauth_sub;
  is_active/is_suspended/is_revoked/check_capability; suspend_grant,
  restore_grant, revoke_grant write state via corrections API with reason
  + timestamp. Permissive fallback when Neotoma unreachable.
- manage_grants.py: CLI list/suspend/restore/revoke.
- daemon_runtime __init__: export GrantChecker, AgentGrant, grant helpers.
- formica.py + neotoma_agent.py: grant check at startup (revoked=abort,
  suspended=warn+disable dispatch); grant-suspend guard in dispatch path;
  ATELES_PARTICIPATION_REF passed in subprocess env (#23 prep).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace single primary-calendar fetch with parallel multi-calendar
fetch using ThreadPoolExecutor. Queries markmhendrickson@gmail.com,
Tontitos, and Family calendars; merges and deduplicates by event id;
sorts by start time. Excludes Birthdays and holiday feeds (noise).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates 53 documentation files from the personal monorepo into ateles
as part of phasing out ~/repos/personal. Subdirectory structure preserved
(cursor_rules, developer, health, on_demand, outreach, testing).

personal/docs/data_types.md renamed to legacy_data_types_inventory.md to
avoid collision with ateles's existing swarm-governance data_types.md
(different document — record-count inventory vs. schema reference).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Moves git-hooks, linters (non-colliding), nosync utilities, and standalone
helper scripts from personal/scripts/ into ateles/scripts/. ateles's newer
check_file_naming.py and security_audit.py are kept (personal copies were
stale). update_readme_release_status.py and setup_claude_skills.sh land at
scripts/ to match existing ateles doc/linter references.

Dropped as obsolete: submodule-management scripts (auth/ensure/init_submodules)
and MCP-config generators tied to personal's old submodule layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
move_airdrop_to_desktop.py + setup_airdrop_to_desktop.sh. The
com.markmhendrickson.airdrop-to-desktop launchd plist will be repointed
to ateles/scripts/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Standardize daemon keypair on-disk format + JWKS endpoint Revocation primitives beyond key deletion (suspend/revoke + per-capability)

1 participant