Skip to content

feat(keys): safe-to-repeat messaging and calendar key issue, operation ID, check mode (ANYM-495) - #282

Open
egerev wants to merge 11 commits into
mainfrom
feat/admin-cli-issue
Open

egerev wants to merge 11 commits into
mainfrom
feat/admin-cli-issue

Conversation

@egerev

@egerev egerev commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Safe-to-repeat key issue (ANYM-495). Stacked on #281 (ANYM-494): review the commits after b0a237d; merge after #281.

  • messaging issue-key --token-file FILE --operation-id ID (both or neither): stores only the digest of a caller-written token, never writes or prints it. Same ID + same request = existing record's public fields with "replayed": true; another request = operation_conflict; token held by another key = token_in_use. key-info --operation-id reads it back.
  • calendar agent-key the same (--token-file/--operation-id instead of --output), plus --days 1-183 (default 90 unchanged); agent-info --operation-id.
  • --check on messaging issue-key, extend-key, revoke-key, list-keys, key-info and calendar agent-key, extend-agent, revoke-agent, list-agents, agent-info: validates arguments, writes nothing, answers ok or schema_not_ready.
  • Schema: messaging migration 044_key_operation_id.sql (-- rollout: expand): new table api_key_operations (operation ID primary key, request sha256, key ID unique), no foreign key, so it takes no lock on api_keys (the migrate transaction still reruns schema.sql, whose existing CREATE INDEX IF NOT EXISTS holds share locks on messages/deliveries/download_grants until commit, as every migrate does; production.md says so); runtime ceiling, backup/restore range and backup manifest (V44_TABLES, table count 70→71) to 44; calendar schema_v3.sql: principal_operations, same shape, no lock on principals.
  • Docs: ACCESS.md (Repeatable issue), services/calendar/docs/SERVICE.md, docs/operations/production.md (migration 044), schema 43→44 in README, RELEASING, CLAUDE.md, services/messaging/README, .claude/rules/migrations.md.

Deployed-version compatibility: expand-only. Deployed schema-43 messaging and version-2 calendar code never read the new tables; a schema-43 runtime refuses schema 44, so 044 ships with its release (pinned backup/restore runtimes first). Deployed images: c4a7285; migrations are applied by hand (production.py stops autodeploy when the SQL inventory changes). Invocations without the new options behave as before. Data: the new tables hold operation IDs, request digests and key/principal IDs, never a token, key or key hash; they enter the backup manifest; calendar db-roles (SELECT ON ALL TABLES) would give the gateway/worker roles read access to principal_operations on its next run, no more than they already read in principals.

Tests: tests/test_key_operation.py (messaging: two keys of one parent operation, replay after either commit, first call, replay, conflict, crash after commit then replay, check on current and N-1 = schema 43) and services/calendar/tests/service/test_agent_operation.py (same + --days bounds/default, N-1 = migrate(through=2)). Race tests prove the overlap: they poll pg_stat_activity until the second issue waits on the winner's lock. The migration tests assert that applying 044 / calendar 3 holds no lock on api_keys / principals (pg_locks) and adds no foreign key. Touched messaging, backup and calendar tests pass locally on PostgreSQL 18.

Spec impact

No acceptance criterion changes. The card's Boundary/Expected paths said "nullable column, unique index"; the operation ID now lives in a new additive table with a unique key (no lock on the authentication tables); settled on the Tiden branch below. The idempotency contract (same ID + same request = replay, other request = operation_conflict, token held = token_in_use) is unchanged.

Release step

Migrations by hand after merge, under production.md's backup gate: messaging migrate (044 is expand; production.py still pauses on the SQL inventory change) and calendar migrate to 3, Order: back up; apply messaging 044 before activating the new messaging generation (a new managed generation needs schema 44 at activation, release.py:29; the running schema-43 generation keeps serving, since it does not recheck); calendar 3 in either order. Upgrade the pinned backup/restore runtimes first (they reject schema 44). Installed host production.py, gateway and backup pins are read on the host at release time, not inferred from source.

Spec

Spec: ANYM-495
Delivers: ANYM-495
Tiden branch: review/2026-10-24-anym-495-storage
Settle text: ANYM-495 Expected paths, Boundary and History name the side tables (api_key_operations, principal_operations, no lock on api_keys/principals) instead of "one nullable column with a unique index"; acceptance criteria unchanged.
Owner note: nothing changes in daily use; the migrations add two empty tables. The admin CLI (ANYM-496/497) will use these commands so a crashed key issue can be rerun without a second key.
Change path: OWNER (section 2: new migration 044 and calendar migration 3; additive only, no destructive SQL, no role SQL)

Reviews

  • r1: Astra (gpt-6-astra): 0 blocking, 5 major, 3 minor; fixed in 61bc724.
  • r2: Sol scoped recheck (gpt-6.1-sol xhigh): 2, 3, 4, 6, 7 CLOSED; 1, 5 OPEN + 1 new minor; fixed in f720476.
  • r3: Sol scoped recheck: minor CLOSED; majors 1 and 5 OPEN; fixed in 5c19098.
  • r4: Sol scoped recheck of 5c19098: major 5 CLOSED; major 1 OPEN: the commit's WAL flush runs after transaction_timeout stops, so a stalled flush can hold the api_keys/principals lock past 5 s. Docs corrected in c1fabf7 (bound up to COMMIT).
  • r5: Sol rebuttal recheck of c1fabf7: major 1 kept: ACCESS EXCLUSIVE blocks plain authentication SELECTs during a stalled commit flush, which an ordinary key-write commit does not. Superseded by the redesign 1d64c27 (orchestrator option A: side tables, no lock on api_keys/principals).
  • r1 on the redesign: Astra (gpt-6-astra): 0 blocking, 2 major, 1 minor; fixed in 4ce0777 (check = execution validation for revoke key file and repeated calendar, with regression tests; production.md names the bootstrap share locks; this body).
  • r2 on the redesign: Sol scoped recheck (gpt-6.1-sol xhigh) of 4ce0777: 1, 2, 3 CLOSED; APPROVED 0/0/0.
  • Fable product round (fable, owner-level PR): 0 blocking, 1 major, 8 minor. F1 (major: what --check ok means; errors and connection failures never schema_not_ready), F4, F5, F7, F8 answered in ACCESS.md and SERVICE.md (5051acd, docs only; the code already behaves so, tests cited in the commit); F9 settled on the Tiden branch above. F2 (calendar issue ceiling 183 vs extend 365), F3 (conflict names the differing fields), F6 (align calendar error names with messaging) and a would: create|replay field in check answers are left to ANYM-496/497 (card candidates; outside ANYM-495's criteria).
  • Fable scoped recheck of F1: CLOSED; APPROVE 0 blocking, 0 major, 1 minor (token length 32-256 vs 32-512 and an unnamed connection failure; deferred with F6 to ANYM-496).

Model: claude-opus-5-5 high

🤖 Generated with Claude Code

@egerev
egerev marked this pull request as ready for review October 24, 2026 09:02
egerev and others added 11 commits October 24, 2026 17:58
…sue-key and calendar agent-key; schema 44 and calendar migration 3 (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…play/conflict/crash/check/migration (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…schema 44 and calendar migration 3 (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…check runs the issue validation, bounded calendar key read, timed migration locks, named principal columns survive migration 3 (ANYM-495)

Astra r1 findings 1-7 and the CI cached-plan failure: ON CONFLICT DO NOTHING with a read-back
instead of a unique violation; issue-key/agent-key --check run the issue's own validation read-only,
key-info/agent-info --check read and bound the key file; calendar read_key opens nonblocking with
no symlink, checks a regular owner-only file and reads at most 4097 bytes; both migrations get a
statement timeout and the messaging header; the calendar principal lookup names its columns so a
prepared plan survives the new columns. Tests: real schema-43 shape, race, omitted conflict fields,
parent-operation recovery, check negatives, FIFO/symlink/size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…out, check validates arguments on N-1, calendar group/calendar-list conflicts and identical concurrent replay tests (ANYM-495)

Sol r2 recheck: finding 1 (total lock hold) and 5 (calendar test gaps), new minor (N-1 check).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…hold; race tests prove the second issue waits on the winner's lock (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…MMIT; the commit's WAL flush is outside it (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…he pool holders, migrate, start, verify; abort restarts them (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
… migrations 044 and calendar 3 take no lock on api_keys or principals and need no outage; 044 is expand (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…epeated calendar); production.md states the bootstrap share locks of every migrate; table pins 70→71 (ANYM-495)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
…en shape, piped stdin, replay fields, rerun days (ANYM-495)

Fable r1 F1, F4, F5, F7, F8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: egerev <egor@gosync.ai>
@egerev
egerev force-pushed the feat/admin-cli-issue branch from 4ce0777 to 7115766 Compare October 24, 2026 09:59
@egerev
egerev enabled auto-merge October 24, 2026 09:59
@egerev
egerev added this pull request to the merge queue Oct 24, 2026
@egerev
egerev removed this pull request from the merge queue due to a manual request Oct 24, 2026

This branch has not been deployed

No deployments
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