Skip to content

fix(actors): reject foreign-authority ids at POST /actors/; add POST /actors/{id}/peers/ - #2916

Merged
ahouseholder merged 3 commits into
mainfrom
bug/2549-foreign-actor-store
Sep 1, 2026
Merged

fix(actors): reject foreign-authority ids at POST /actors/; add POST /actors/{id}/peers/#2916
ahouseholder merged 3 commits into
mainfrom
bug/2549-foreign-actor-store

Conversation

@sei-ahouseholder

@sei-ahouseholder sei-ahouseholder commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes #2549

Summary

  • Root cause: POST /actors/ accepted foreign-authority ids. actor_slug() drops scheme and netloc, so finder:7999/.../alice and vendor:7999/.../alice mapped to the same SQLite file. hosted_actor_ids() then reported remote actors as locally hosted, breaking outbound delivery.
  • Fix (ADR-0081): _is_foreign_authority() compares the request id's netloc against the serving node's base_url; POST /actors/ returns 422 for any id whose authority does not match.
  • New route: POST /actors/{actor_id}/peers/ stores a peer as a CoreActor record inside the named hosted actor's own store — an address-book entry, not a phantom hosted actor.
  • Demo seeding updated: all seed_containers* helpers and cli.py seed command now use seed_peer() for Phase 2 cross-container peer registrations.
  • Engine guard: get_actor_engine collision guard remains a WARNING (not exception); the route-level rejection is the primary guard. Warning-not-raise preserves the multi-node test harness where two nodes in one process legitimately share a slug.

Changes

  • vultron/adapters/driving/fastapi/routers/actors/_routes.py_is_foreign_authority(), 422 guard in create_actor(), new POST /{actor_id:path}/peers/ endpoint
  • vultron/demo/utils.pyseed_peer() utility function
  • vultron/demo/helpers/seeding.py — all seed_containers* helpers updated to use seed_peer() for Phase 2
  • vultron/demo/cli.pyseed command uses seed_peer() for peer registration
  • vultron/adapters/driven/datalayer_sqlite/engine.pyactor_slug() docstring updated
  • vultron/adapters/driven/actor_hosts.pycanonical_actor_uri() docstring updated
  • docs/adr/0081-peer-knowledge-in-hosted-actor-store.md — new ADR

Verification

  • Full suite: uv run pytest — 7899 passed, 0 failed (+103 new tests net)
  • AC1: docs/adr/0081-peer-knowledge-in-hosted-actor-store.md added ✅
  • AC2: POST /actors/ with foreign-authority id → 422 (test_a_foreign_absolute_id_is_rejected) ✅
  • AC3: hosted_actor_ids() enumerates only hosted actors (peers live in host stores, not as hosted actors) ✅
  • AC4: create_actor rejects foreign-authority ids with clear error message ✅
  • AC5: collision warning preserved as WARNING (condition "no legitimate path" not yet satisfied — test harness is still a legitimate path) ✅
  • AC6/AC7: all detector tests updated to reference ADR-0081 ✅
  • TestRegisterPeer — 5 new tests: 201 on creation, 200 on repeat, peer absent from GET /actors/, 422 for non-http peer id, 404 for unknown host ✅
  • TestGetActorEngineCollisionGuard — warning (not raise) on cross-authority slug collision ✅

🤖 Generated with Claude Code

…/actors/{id}/peers/

Closes #2549

Root cause: POST /actors/ accepted any actor_id, including ids under foreign
authorities.  actor_slug() drops scheme and netloc, so two ids that share only
the final path segment (e.g. finder:7999/.../alice and vendor:7999/.../alice)
mapped to the same SQLite file.  hosted_actor_ids() then reported remote actors
as locally hosted, and subsequent delivery failed in unexpected ways.

Fix (ADR-0081):
- _is_foreign_authority() helper compares the request id's netloc against the
  serving node's base_url; POST /actors/ returns 422 for any id whose authority
  does not match.
- New POST /actors/{actor_id}/peers/ endpoint stores a peer as a CoreActor
  record inside the named hosted actor's own store (address-book entry, not a
  phantom hosted actor).
- Demo seeding helpers (seed_containers*, cli.py seed command) updated to use
  seed_peer() for Phase 2 cross-container registrations.
- Engine-level collision guard remains a WARNING; the route-level rejection is
  the primary guard.  Warning-not-raise preserves the multi-node test harness
  where two nodes in one process legitimately share a slug.

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

@sei-ahouseholder sei-ahouseholder left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR Triage: #2916 — fix(actors): reject foreign-authority ids at POST /actors/; add POST /actors/{id}/peers/

Linked issues: #2549 (foreign-authority actor store collision)
Changed files: 18 files — adapters/driven, adapters/driving/fastapi, demo
CI status: ❌ failing (Tests/pytest — 20 failures; all other checks pass)
Merge state: ⚠️ MERGEABLE (BLOCKED — required check failing)
Base branch: main
Needs integration tests: yes


Findings

# Phase Severity Description Outcome
phase11-test-suite-failing-0 ci-status ❌ FAIL 20 tests fail in 7 test/demo/ files: _create_actor() helpers post foreign-authority IDs to POST /actors/ (now 422). test_case_proposal_round_trip.py, test_multi_actor_seed.py, test_pcr_bootstrap.py, test_pcr_engage_case.py, test_pcr_late_joiner.py, test_seed.py, test_sync_ledger_replication.py were not migrated to seed_peer(). fix-now
phase8-docstring-raises-vs-warns-0 code-review ❌ FAIL actor_slug() docstring (engine.py:102) says :func:\get_actor_engine` raises if it ever occursbut the function logs a WARNING, not an exception. Contradicts both actual behavior andget_actor_engine()`'s own docstring. fix-now
phase3-pr-body-format-0 pr-body-format ⚠️ IMPROVE Closes #2549 is at the bottom after ## Test plan — must precede any ## header. Also missing Changes section and Verification section with actual test counts. fix-now
phase9-mkdocs-title-inconsistency-0 notes-docs-currency ⚠️ IMPROVE mkdocs.yml line 319: ADR-0081 title missing "Own" ("Hosted Actor Store" vs "Hosted Actor's Own Store"). fix-now

Total: 2 FAIL · 2 IMPROVE · 0 NEW-ISSUE


Triage artifact: .claude/pr-2916-triage.json
Next step: run /pr-execute or /pr-ship to apply fixes.

@sei-ahouseholder

Copy link
Copy Markdown
Contributor Author

PR Triage: #2916 — fix(actors): reject foreign-authority ids at POST /actors/; add POST /actors/{id}/peers/

Linked issues: #2549 (foreign-authority actor store collision)
Changed files: 18 files — adapters/driven, adapters/driving/fastapi, demo
CI status: ❌ failing (Tests/pytest — 20 failures; all other checks pass)
Merge state: ⚠️ MERGEABLE (BLOCKED — required check failing)
Base branch: main
Needs integration tests: yes


Findings

# Phase Severity Description Outcome
phase11-test-suite-failing-0 ci-status ❌ FAIL 20 tests fail in 7 test/demo/ files: _create_actor() helpers post foreign-authority IDs to POST /actors/ (now 422). test_case_proposal_round_trip.py, test_multi_actor_seed.py, test_pcr_bootstrap.py, test_pcr_engage_case.py, test_pcr_late_joiner.py, test_seed.py, test_sync_ledger_replication.py were not migrated to seed_peer(). fix-now
phase8-docstring-raises-vs-warns-0 code-review ❌ FAIL actor_slug() docstring (engine.py:102) says :func:\get_actor_engine` raises if it ever occursbut the function logs a WARNING, not an exception. Contradicts both actual behavior andget_actor_engine()`'s own docstring. fix-now
phase3-pr-body-format-0 pr-body-format ⚠️ IMPROVE Closes #2549 is at the bottom after ## Test plan — must precede any ## header. Also missing Changes section and Verification section with actual test counts. fix-now
phase9-mkdocs-title-inconsistency-0 notes-docs-currency ⚠️ IMPROVE mkdocs.yml line 319: ADR-0081 title missing "Own" ("Hosted Actor Store" vs "Hosted Actor's Own Store"). fix-now

Total: 2 FAIL · 2 IMPROVE · 0 NEW-ISSUE


Triage artifact: .claude/pr-2916-triage.json
Next step: run /pr-execute or /pr-ship to apply fixes.

ahouseholder and others added 2 commits September 1, 2026 14:01
- ci-status — migrate 7 test/demo/ files from _create_actor(wrong_client,
  foreign_base_api) to _register_peer() (ADR-0081); update TestSeedCLIWith-
  DeterministicId and TestSeedCommand to mock seed_peer alongside seed_actor
- code-review — actor_slug() docstring: "raises" → "warns" (engine.py:102)
- notes-docs-currency — mkdocs.yml ADR-0081 title: add missing "Own"
- pr-body-format — Closes #2549 moved to top; added Changes + Verification

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

@sei-ahouseholder sei-ahouseholder left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR Execute: #2916 — fix(actors): reject foreign-authority ids at POST /actors/; add POST /actors/{id}/peers/

Fixes applied: 1 commit (32e7719b)
Issues filed: 0
Deferred (awaiting your input): 0
Tests run: unit + integration
CI status: ✅ passing (22/22 checks)
Base sync: ✅ merged main @ 8d40c313 — 0 conflicts resolved


Fixed

Finding Commit
phase11-test-suite-failing-0: 20 failing tests — _create_actor() used foreign-authority client in 7 demo test files 32e7719b
phase8-docstring-raises-vs-warns-0: actor_slug() docstring said "raises" but get_actor_engine() warns 32e7719b
phase3-pr-body-format-0: PR body missing Closes at top, Changes section, and Verification counts 32e7719b
phase9-mkdocs-title-inconsistency-0: mkdocs.yml ADR-0081 title missing "Own" 32e7719b

Execute artifact: .claude/pr-2916-execute.json
Next step: run /pr-verify, or /pr-ship will continue automatically.

@sei-ahouseholder sei-ahouseholder left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR Verify: #2916 — fix(actors): reject foreign-authority ids at POST /actors/; add POST /actors/{id}/peers/

Overall verdict: ✅ READY-TO-MERGE

CI: ✅ 22/22 checks passing
Merge state: MERGEABLE / BLOCKED (pending required review — repo policy, not a defect)
Conflict markers: clean


Finding Verdicts

Finding Severity Outcome Verdict
phase11-test-suite-failing-0: 20 failing tests across 7 demo test files FAIL fixed @ 32e7719b ✅ CONFIRMED
phase8-docstring-raises-vs-warns-0: actor_slug() docstring said "raises" FAIL fixed @ 32e7719b ✅ CONFIRMED
phase3-pr-body-format-0: PR body format violations IMPROVE fixed @ 32e7719b ✅ CONFIRMED
phase9-mkdocs-title-inconsistency-0: mkdocs.yml ADR-0081 title missing "Own" IMPROVE fixed @ 32e7719b ✅ CONFIRMED

All FAIL findings confirmed at HEAD. CI green. Branch synced with main @ 8d40c313. No unresolved items.

Verify artifact cleanup complete.
PR #2916 is READY-TO-MERGE.

@ahouseholder
ahouseholder merged commit 3d3a805 into main Sep 1, 2026
22 checks passed
@ahouseholder
ahouseholder deleted the bug/2549-foreign-actor-store branch September 1, 2026 14:24
sei-ahouseholder pushed a commit that referenced this pull request Sep 1, 2026
… PRs

Replace two `ADR-0073 decision 5` references introduced by PRs #2916 and
#2927 while resolving merge conflicts with origin/main:

- test/adapters/driving/fastapi/routers/test_actors.py:447
- vultron/adapters/driving/fastapi/routers/actors/_routes.py:166

Both are replaced with ADR-0073#peer-records-in-knowers-store.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sei-ahouseholder pushed a commit that referenced this pull request Sep 1, 2026
Renumbers this branch's ADR 0081 -> 0082. PR #2916 landed its own ADR-0081
("Peer Knowledge Lives in the Hosted Actor's Own Store, Not as a Hosted Actor")
on main first, and that number already has inbound references across vultron/
and test/, so the unlanded ADR is the one that moves.

Conflicts, both in adr-index generator output, resolved by taking main's side
and regenerating:
- docs/adr/index.md — regenerated with `adr-index --write`; now lists both
  ADR-0081 (peer knowledge) and ADR-0082 (wire/core boundary).
- mkdocs.yml — nav labels are hand-crafted and only checked for completeness,
  so the ADR-0082 entry was added by hand in number order.

All 51 references to the wire/core-boundary ADR were rewritten across 22 files
(specs, notes, docs, AGENTS.md, and the ARCH-22 ratchet test docstring). Every
occurrence was inspected individually first: references to main's ADR-0081 in
vultron/ and test/ are deliberately untouched.

Known exception: plan/history/2608/learning/CONCERN-2830.md still cites
docs/adr/0081-wire-core-boundary-pairing-registry.md. HM-01-005 makes history
entries write-once (MUST NOT be modified), and that spec has no sanctioned
amendment path for an external renumbering, so the stale path is left in place
rather than violating the rule. Recorded on #2952.

Verification after the merge:
- Full unit suite: no failures, no errors (pre-existing xfails only).
- mypy 1315 files clean; pyright 0 errors; black clean; flake8 vultron/ test/
  clean (CI scope).
- adr-index --check: index and nav in sync.
- mkdocs build --strict: 101 warnings on this branch and 101 on origin/main —
  identical, pre-existing, tracked as #2904. This branch adds none.
- test/architecture/test_codebase_docs_paths.py (new ratchet from main) passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 301+ diff lines or 7+ ACs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

POST /actors/ mints a local store for a foreign-authority id; actor_slug collides across authorities

2 participants