Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
6f34059
plan: elevate hexagonal architecture to PRIORITY 50, add P50-0 for tr…
ahouseholder Mar 9, 2026
404171c
docs(architecture): outline TODO for ADR on hexagonal architecture fo…
ahouseholder Mar 9, 2026
cb35f80
refactor(P50-0): extract trigger service layer; split triggers.py int…
ahouseholder Mar 9, 2026
28a0380
refactor(ARCH-1.1): move MessageSemantics to vultron/core/models/even…
ahouseholder Mar 9, 2026
f3699fe
plan: mark ARCH-1.1 complete; add implementation notes
ahouseholder Mar 9, 2026
ed79e81
ARCH-1.2: Introduce InboundPayload; remove AS2 type from DispatchActi…
ahouseholder Mar 9, 2026
9dd57c4
plan: mark ARCH-1.2 complete; add implementation notes
ahouseholder Mar 9, 2026
c95d49b
arch: ARCH-1.3 — create wire/as2/parser.py and wire/as2/extractor.py …
ahouseholder Mar 9, 2026
99f96ee
docs: update IMPLEMENTATION_NOTES.md with hexagonal architecture use …
ahouseholder Mar 9, 2026
b8521c6
test: update 3 test files for refactored handler dl parameter API
ahouseholder Mar 10, 2026
967f248
refactor: ARCH-1.4 inject DataLayer via port; move handler map to ada…
ahouseholder Mar 10, 2026
24a7f66
plan: mark ARCH-1.4 complete; update notes and test count
ahouseholder Mar 10, 2026
8a61be6
docs: update PRIORITIES.md with details on hexagonal architecture ref…
ahouseholder Mar 10, 2026
eaa9e43
plan: gap analysis refresh #21 — PRIORITY-50 complete, ARCH-CLEANUP a…
ahouseholder Mar 10, 2026
dab495f
refactor: ARCH-CLEANUP-1 — delete backward-compat shims
ahouseholder Mar 10, 2026
524684f
arch(ARCH-CLEANUP-2): move AS2 structural enums to vultron/wire/as2/e…
ahouseholder Mar 10, 2026
27c0156
plan: mark ARCH-CLEANUP-2 complete
ahouseholder Mar 10, 2026
445c307
arch: replace isinstance AS2 checks with string type comparisons (V-1…
ahouseholder Mar 10, 2026
a6e2e5c
plan: mark ARCH-CLEANUP-3 complete; add implementation notes
ahouseholder Mar 10, 2026
11d07f0
docs(adr): add ADR-0009 adopting hexagonal architecture (ports and ad…
ahouseholder Mar 10, 2026
f51bf06
refactor(P60-1): move vultron/as_vocab to vultron/wire/as2/vocab
ahouseholder Mar 10, 2026
12f5889
plan: mark P60-1 complete; update notes
ahouseholder Mar 10, 2026
a55238f
refactor: move vultron/behaviors/ to vultron/core/behaviors/ (P60-2)
ahouseholder Mar 10, 2026
ce9cf45
docs: update hexagonal architecture documentation and add use case be…
ahouseholder Mar 10, 2026
738f044
docs: update stale file paths in AGENTS.md and specs after hexagonal …
ahouseholder Mar 10, 2026
616b97a
docs: align architecture docs with completed hexagonal refactor
ahouseholder Mar 10, 2026
867da39
docs: add MCP adapter spec and fix stale paths in notes/
ahouseholder Mar 10, 2026
ada9d5e
docs: mark captured items in plan/ and lint-fix whitespace in docs/
ahouseholder Mar 10, 2026
1344a48
docs: update implementation notes and priorities for DataLayer refact…
ahouseholder Mar 10, 2026
0271213
docs(notes): add transitions, DataLayer-port, and cleanup-task notes
ahouseholder Mar 10, 2026
0c57378
docs(specs): update status, add OID prefix, fix AR-09 PROD_ONLY, add …
ahouseholder Mar 10, 2026
9fed8ba
docs(plan): mark captured IMPLEMENTATION_NOTES items with strikethrough
ahouseholder Mar 10, 2026
77a4e5d
plan: refresh gap analysis #22 — add P70, TECHDEBT-11/12, supersede T…
ahouseholder Mar 10, 2026
d729b07
docs(ideas): remove outdated MCP integration proposal from IDEAS.md
ahouseholder Mar 10, 2026
972fa08
feat(arch): stub vultron/adapters/ package (P60-3)
ahouseholder Mar 10, 2026
b9a63d6
feat: add actor_participant_index to VulnerabilityCase (SC-PRE-2)
ahouseholder Mar 10, 2026
a1ef0c5
plan: mark SC-PRE-2 complete; add implementation notes
ahouseholder Mar 10, 2026
0a5d07e
feat(SC-3.2): record embargo acceptance in CaseParticipant and case e…
ahouseholder Mar 10, 2026
008d504
plan: mark SC-3.2 complete; update test count to 838
ahouseholder Mar 10, 2026
16f6502
feat(SC-3.3): add embargo acceptance guard to update_case handler (CM…
ahouseholder Mar 10, 2026
1fcd2b5
feat: refactor vultron/api to separate concerns and align with port a…
ahouseholder Mar 10, 2026
7c226ac
refactor(tests): relocate test dirs to mirror new source layout (TECH…
ahouseholder Mar 10, 2026
52e41c7
fix: replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_U…
ahouseholder Mar 10, 2026
b570879
techdebt: apply NonEmptyString/OptionalNonEmptyString across vocab ob…
ahouseholder Mar 10, 2026
e0d1553
feat: backfill pre-case events in create_case BT (TECHDEBT-10)
ahouseholder Mar 10, 2026
67e51b6
docs: clarify implementation phase priorities in PRIORITIES.md
ahouseholder Mar 10, 2026
0eeef77
techdebt: configure pyright for gradual static type checking (TECHDEB…
ahouseholder Mar 10, 2026
7b33d4b
feat(TECHDEBT-3): standardize object IDs to URI form
ahouseholder Mar 10, 2026
315dde1
docs: update architecture review with new violations and remediation …
ahouseholder Mar 10, 2026
a5bcbf7
plan: add Priority 65 (architecture violations) to implementation plan
ahouseholder Mar 10, 2026
664d699
arch: P65-1 move DataLayer Protocol to core/ports/activity_store
ahouseholder Mar 10, 2026
f47215a
plan: mark P65-1 complete; add implementation notes
ahouseholder Mar 10, 2026
b36f092
docs: update implementation notes to emphasize use of Pydantic models…
ahouseholder Mar 10, 2026
44b2fc6
docs: remove outdated notes on TinyDbDataLayer's use of dicts
ahouseholder Mar 10, 2026
77a3298
P65-1/P65-2: type DataLayer ports with StorableRecord and inject disp…
ahouseholder Mar 10, 2026
f8248dc
plan+arch: mark P65-2 done; implement P65-5 (remove adapter imports f…
ahouseholder Mar 10, 2026
ca804ee
docs: add pre-implementation notes for P65-3 and outline the need for…
ahouseholder Mar 10, 2026
bf6a014
docs: clarify IMPLEMENTATION_NOTES vs IMPLEMENTATION_HISTORY distinction
ahouseholder Mar 11, 2026
3f10e2d
docs: sync specs, notes, and AGENTS.md with P65-1/P65-2/P65-5 completion
ahouseholder Mar 11, 2026
ad2d264
refactor: move and update core behaviors and models to align with hex…
ahouseholder Mar 11, 2026
eebafbc
refactor: remove raw_activity from handlers; use InboundPayload field…
ahouseholder Mar 11, 2026
623d0eb
docs: mark P65-3 complete; add implementation history entry
ahouseholder Mar 11, 2026
4fd7c85
docs: update IDEAS.md with domain event hierarchy and refactor tasks …
ahouseholder Mar 11, 2026
1698557
docs: capture IDEAS.md and IMPLEMENTATION_NOTES.md insights into spec…
ahouseholder Mar 11, 2026
a7fd8fc
docs: update IDEAS.md to reflect captured insights and implementation…
ahouseholder Mar 11, 2026
206fca2
plan: refresh #23 — mark P65-3 done; add VultronEvent tasks P65-6a/6b
ahouseholder Mar 11, 2026
78523da
refactor(P65-4): decouple behavior_dispatcher from wire layer
ahouseholder Mar 11, 2026
4958d21
P65-6a: define VultronEvent typed event hierarchy in core/models/events/
ahouseholder Mar 11, 2026
00cc5b7
P65-6b: Remove AS2 wire imports from core/behaviors/ — rich domain types
ahouseholder Mar 11, 2026
45d8951
refactor: remove relic `as_` prefix from core model fields for clarity
ahouseholder Mar 11, 2026
5083aa7
refactor: remove relic `as_` prefix from core model fields for clarity
ahouseholder Mar 11, 2026
0678d36
docs: update implementation notes on performance testing and architec…
ahouseholder Mar 11, 2026
4447aa7
docs: update implementation notes to emphasize use-case centric namin…
ahouseholder Mar 11, 2026
59e85ca
refactor(tests): P65-7 remove wire AS2 imports from core BT tests (V-…
ahouseholder Mar 11, 2026
b3027e7
docs(plan): mark P65-7 complete; append history entry
ahouseholder Mar 11, 2026
b4e8ba0
docs: capture all IMPLEMENTATION_NOTES items into permanent locations
ahouseholder Mar 11, 2026
1d4e4f0
docs: update implementation notes to reflect recent architectural dec…
ahouseholder Mar 11, 2026
1b36b84
plan: refresh #24 — P65 complete; add ARCH-DOCS-1, TECHDEBT-13/14, P7…
ahouseholder Mar 11, 2026
d19252a
docs: update architecture-review.md — mark V-03-R, V-15–19, V-22–23 f…
ahouseholder Mar 11, 2026
fd9324c
plan: mark ARCH-DOCS-1 complete; append history entry
ahouseholder Mar 11, 2026
b43fe9a
refactor: wire-boundary cleanup TECHDEBT-13 (a/b/c)
ahouseholder Mar 11, 2026
fd05cb2
feat(ports): add DeliveryQueue and DnsResolver Protocol stubs (P70-3)
ahouseholder Mar 11, 2026
288c110
refactor(P70-2): relocate OfferStatusEnum and VultronObjectType to core
ahouseholder Mar 11, 2026
0f83d94
plan: mark P70-2 complete; update history and plan status
ahouseholder Mar 11, 2026
721c785
refactor(P70-4): move TinyDbDataLayer to adapters/driven/activity_store
ahouseholder Mar 11, 2026
8254ee8
refactor: rename activity_store to datalayer and update imports
ahouseholder Mar 11, 2026
8a3ce4b
docs: update docker README to reflect current services and usage
ahouseholder Mar 12, 2026
80882ac
docs: update implementation notes to address documentation generation…
ahouseholder Mar 12, 2026
f4e42ac
refactor(P70-5): remove api/v2/datalayer shims; update all callers
ahouseholder Mar 12, 2026
babd55d
plan: mark P75-1 complete — VultronEvent event types already implemen…
ahouseholder Mar 12, 2026
7fbcf3e
docs: update implementation notes to address core model enrichment an…
ahouseholder Mar 12, 2026
1a6fd46
fix: pass wire objects from handlers to use cases to fix round-trip t…
ahouseholder Mar 13, 2026
f66694e
docs: mark P75-2 complete in plan and implementation history
ahouseholder Mar 13, 2026
175827c
refactor: append Activity suffix to all activity classes in vultron.w…
ahouseholder Mar 13, 2026
8109deb
docs: update implementation notes to clarify dispatching and naming c…
ahouseholder Mar 13, 2026
ccee64e
plan: insert P75-2a/b/c between P75-2 and P75-3 to resolve dispatch/h…
ahouseholder Mar 13, 2026
765c1e0
docs: expand implementation notes to clarify separation of responsibi…
ahouseholder Mar 13, 2026
c8e0d78
P75-2a: enrich domain models with missing wire-layer pass-through fields
ahouseholder Mar 13, 2026
4966996
docs: clarify redundancy in domain models and propose a unified class…
ahouseholder Mar 13, 2026
33d713e
refactor: P75-2b rename DispatchActivity→DispatchEvent, remove wire f…
ahouseholder Mar 13, 2026
440d33d
refactor: TECHDEBT-14 split vultron_types.py into per-type modules
ahouseholder Mar 13, 2026
18496b2
plan: mark TECHDEBT-14 complete
ahouseholder Mar 13, 2026
ac6f77f
docs: document flaky test as technical debt and propose resolution steps
ahouseholder Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 106 additions & 75 deletions AGENTS.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ mypy: ## Run mypy for type checking
# edit $(PROJECT_HOME)/.mypy.ini to configure mypy options
uv run mypy

# pyright static type checking
.PHONY: pyright
pyright: ## Run pyright for static type checking
uv run pyright

# run all linters
.PHONY: lint
lint: black mdlint flake8-lint mypy ## Run all linters (black, markdownlint)
Expand Down
219 changes: 219 additions & 0 deletions docs/adr/0009-hexagonal-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
---
status: accepted
date: 2026-03-10
deciders:
- vultron maintainers
consulted:
- project stakeholders
informed:
- contributors
---

# Adopt Hexagonal Architecture (Ports and Adapters) for Vultron

## Context and Problem Statement

The Vultron codebase grew incrementally from a prototype, causing concerns
from different layers to accumulate in the same modules. ActivityStreams 2.0
(AS2) structural types leaked into domain logic; semantic extraction was
scattered across multiple files; handler functions instantiated the
persistence layer directly; and the root `vultron/` package mixed domain
enums with wire-format enums. An architectural review (`notes/architecture-review.md`)
catalogued twelve violations of the intended separation of concerns, ranging
in severity from minor to critical. A clear architecture decision was needed
to guide remediation and prevent the same violations from re-accumulating.

## Decision Drivers

- **Wire format replaceability**: AS2 is the current wire format because its
vocabulary matches Vultron's domain vocabulary. That match must not collapse
the boundary between wire concerns and domain concerns. If AS2 is replaced
or extended, the change must be absorbable in the wire layer alone.
- **Testability**: Core domain logic should be testable with plain domain
objects, without constructing AS2 activities or making HTTP requests.
- **Single extraction seam**: The mapping from AS2 vocabulary to domain
semantics must live in exactly one place so that future wire format changes
have a single point of entry.
- **Dependency injection over direct instantiation**: Driven adapters
(persistence, delivery) must be injectable to support testing and alternate
backends.
- **Alignment with existing clean points**: Several existing patterns are
already architecturally sound (the `DataLayer` Protocol, `DispatchEvent`
wrapper, `MessageSemantics` enum vocabulary, FastAPI 202 + BackgroundTasks)
and should be preserved.

## Considered Options

- **Continue organic layering** — Impose light conventions without a formal
architecture pattern. Enforce import rules via linting. Let the structure
evolve.
- **Adopt hexagonal architecture (Ports and Adapters)** — Define explicit
layers (`core/`, `wire/`, `adapters/`), ports (abstract interfaces), and
adapters (thin translation layers). Use a separate wire layer for AS2
parsing and semantic extraction.
- **Clean layered architecture (no hexagon)** — Impose strict top-down
layering (controller → service → repository) without the explicit
driving/driven adapter distinction.

## Decision Outcome

Chosen option: **Adopt hexagonal architecture (Ports and Adapters)**, because
it directly addresses the identified violations, provides the single
extraction seam the project needs, and cleanly separates the wire format from
the domain — a property that is especially important given that AS2 and the
Vultron domain vocabulary are semantically isomorphic and will otherwise
naturally collapse.

### Layer Model

Four layers are defined:

```text
wire (AS2 JSON)
AS2 Parser (vultron/wire/as2/parser.py)
Semantic Extractor (vultron/wire/as2/extractor.py)
Handler Dispatch (vultron/behavior_dispatcher.py)
Domain Logic (vultron/core/ — no AS2 awareness)
```

The `wire/` package is structurally separate from both `core/` and
`adapters/` to reflect its status as a distinct inbound pipeline stage, not
an adapter of a particular external system.

### Key Rules

1. `core/` MUST NOT import from `wire/`, `adapters/`, or any framework.
2. `core/` functions MUST accept and return domain types, not AS2 types.
3. `MessageSemantics` (`SemanticIntent`) is a domain type, defined in
`core/`.
4. The semantic extractor is the **only** place AS2 vocabulary is mapped to
domain concepts.
5. Driven adapters are injected via port interfaces, never instantiated
directly.
6. The `wire/` layer is replaceable as a unit without touching `core/`.

### Consequences

- Good, because domain logic is testable with plain Pydantic objects and no
AS2 or HTTP dependencies.
- Good, because a future wire format (binary encoding, revised federation
standard) can be introduced by adding `wire/new_protocol/` without touching
`core/` or `adapters/`.
- Good, because the single extraction seam (`wire/as2/extractor.py`) means
new activity types require exactly one file change to add a new pattern.
- Good, because dependency injection via port interfaces enables alternative
backends (in-memory, TinyDB, PostgreSQL) to be swapped without modifying
handler code.
- Bad, because the transition from the organic prototype structure to the
hexagonal layout requires incremental refactoring across multiple modules.
- Bad, because the semantic match between AS2 vocabulary and the Vultron
domain vocabulary means some violations are subtle and may re-accumulate
without active review.

## Validation

- Violations V-01 through V-12 (see `notes/architecture-review.md`)
serve as the acceptance criteria: each is tracked and remediated
incrementally.
- The review checklist in `notes/architecture-ports-and-adapters.md`
(Core, Wire, Adapters, Connectors, Tests sections) is applied during code
review.
- Import boundary rules are enforced by code review and, where practical,
by `pylint`/`flake8` import order checks.

## Violation Inventory and Remediation Status

The architectural review identified twelve violations. Each has been assigned
to a remediation task.

### Remediated (ARCH-1.x and ARCH-CLEANUP-x)

| ID | Severity | Summary | Addressed by |
|------|----------|---------------------------------------------------------|-------------------|
| V-01 | Major | `MessageSemantics` co-located with AS2 enums in `enums.py` | ARCH-1.1, ARCH-CLEANUP-2 |
| V-02 | Critical | `DispatchEvent.payload` typed as `as_Activity` | ARCH-1.2 |
| V-03 | Critical | `behavior_dispatcher.py` imports AS2 type directly | ARCH-1.2 |
| V-04 | Major | `find_matching_semantics` called twice (extractor + decorator) | ARCH-1.3 |
| V-05 | Major | Pattern matching logic split across `activity_patterns.py` and `semantic_map.py` | ARCH-1.3 |
| V-06 | Major | `parse_activity` in HTTP router instead of wire layer | ARCH-1.3 |
| V-07 | Major | `inbox_handler.py` imports and uses AS2 `VOCABULARY` | ARCH-1.3 |
| V-08 | Critical | `handle_inbox_item` collapses three pipeline stages | ARCH-1.3 |
| V-09 | Major | `semantic_handler_map.py` lazy-imports adapter-layer handlers | ARCH-1.4 |
| V-10 | Major | All handlers call `get_datalayer()` directly | ARCH-1.4 |
| V-11 | Major | Handlers use `isinstance` checks against AS2 types | ARCH-CLEANUP-3 |
| V-12 | Minor | Dispatcher test constructs AS2 types for core test inputs | ARCH-CLEANUP-3 |

Shims left at `vultron/activity_patterns.py`, `vultron/semantic_map.py`, and
`vultron/semantic_handler_map.py` for backward compatibility were removed in
ARCH-CLEANUP-1. AS2 structural enums were moved from `vultron/enums.py` to
`vultron/wire/as2/enums.py` in ARCH-CLEANUP-2.

### Completed (PRIORITY-60 package relocation)

Package relocations that have been completed as part of PRIORITY-60:

- **P60-1** ✅: `vultron/as_vocab/` moved to `vultron/wire/as2/vocab/`. All
internal and external imports updated; old `vultron/as_vocab/` deleted.
- **P60-2** ✅: `vultron/behaviors/` moved to `vultron/core/behaviors/`. All
internal and external imports updated; old `vultron/behaviors/` deleted.

### Remaining (PRIORITY-60 — in progress)

The following structural move is deferred to PRIORITY-60 and is tracked
in `plan/IMPLEMENTATION_PLAN.md`:

- **P60-3**: Stub the `vultron/adapters/` package per the target layout in
`notes/architecture-ports-and-adapters.md`.

## Pros and Cons of the Options

### Continue organic layering

- Good: no migration cost; conventions can be enforced incrementally.
- Bad: violations re-accumulate naturally because AS2 and domain vocabulary
look identical; without a formal pattern, there is no shared vocabulary for
"this is a violation" across reviewers.
- Bad: import chains between layers remain implicit and hard to enforce.

### Adopt hexagonal architecture (chosen)

- Good: explicit layer vocabulary (core / wire / adapters / ports) that all
contributors can reference.
- Good: violations have a clear label and a known remediation pattern.
- Good: enables future extension points (MCP adapter, alternative wire
formats, connector plugins) without core changes.
- Neutral: incremental migration is possible; the codebase does not need to
be fully restructured before benefits are realized.
- Bad: transition work is non-trivial and must be done incrementally without
breaking the running system.

### Clean layered architecture (no hexagon)

- Good: familiar pattern for most Python web developers.
- Bad: does not provide the driving/driven adapter distinction needed to
support CLI, MCP, and HTTP inbox as parallel entry points.
- Bad: does not explicitly model the wire layer as separate from adapters,
which is the core architectural tension in this project.

## More Information

- `notes/architecture-ports-and-adapters.md` — canonical layer model,
file layout target, code patterns, and review checklist.
- `notes/architecture-review.md` — full violation inventory (V-01 to V-12)
and remediation plans (R-01 to R-06).
- `specs/architecture.md` — testable requirements derived from this decision
(ARCH-01-001 through ARCH-03-001 and beyond).
- `plan/PRIORITIES.md` — PRIORITY 50 (hexagonal architecture starting with
triggers.py), PRIORITY 60 (continue package relocation).
- `plan/IMPLEMENTATION_PLAN.md` — task-level tracking for ARCH-1.1 through
ARCH-1.4, ARCH-CLEANUP-1 through ARCH-CLEANUP-3, and P60-1 through P60-3.
- Related ADRs:
- [ADR-0005](0005-activitystreams-vocabulary-as-vultron-message-format.md)
— rationale for choosing AS2 as wire format.
- [ADR-0007](0007-use-behavior-dispatcher.md) — introduces the
`DispatchEvent` wrapper and `ActivityDispatcher` Protocol that this
architecture refines.
123 changes: 123 additions & 0 deletions docs/adr/0010-standardize-object-ids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
status: accepted
date: 2026-03-10
deciders:
- vultron maintainers
consulted:
- project stakeholders
informed:
- contributors
---

# Standardize Object IDs to URI Form

## Context and Problem Statement

The Vultron ActivityStreams 2.0 (AS2) object model requires every object to
have an `id` field. The `generate_new_id()` utility in
`vultron/wire/as2/vocab/base/utils.py` currently returns a bare UUID-4 string
(e.g., `2196cbb2-fb6f-407c-b473-1ed8ae806578`) as the default `as_id` for
new objects. The AS2 specification and the Vultron object-IDs spec
(`specs/object-ids.md` OID-01-001) require that `id` values be full URI
identifiers. Bare UUIDs are not valid AS2 identifiers and will cause
interoperability problems once federation is implemented.

## Decision Drivers

- **AS2 compliance**: ActivityStreams 2.0 requires `id` to be an absolute
IRI. Bare UUIDs are not IRIs.
- **Federation readiness**: Federated delivery requires globally unique,
resolvable identifiers. Bare UUIDs cannot be resolved across systems.
- **Blackboard key safety**: py_trees blackboard keys derived from full-URI
IDs must not use the raw URI (which contains slashes). Using the last path
segment or UUID suffix as the key avoids hierarchical key-parsing issues
(OID-03-001).
- **DataLayer compatibility**: The DataLayer uses `as_id` as the primary
lookup key. Changing the default ID format is a forward-looking migration;
any existing bare-UUID records are a prototype artifact.

## Considered Options

1. **`urn:uuid:` prefix** — `urn:uuid:{uuid4}` (e.g.,
`urn:uuid:2196cbb2-fb6f-407c-b473-1ed8ae806578`)
2. **Configurable HTTPS base URL** — `https://{base}/{type}/{uuid4}` driven
by a `VULTRON_BASE_URL` environment variable
3. **Continue using bare UUIDs** — defer the change

## Decision Outcome

Chosen option: **Configurable HTTPS base URL with `urn:uuid:` fallback**,
because:

- HTTPS URLs are the canonical AS2 `id` form and are directly resolvable.
- A configurable base URL (via `VULTRON_BASE_URL` env var) allows deployment
flexibility without hard-coding a hostname.
- When `VULTRON_BASE_URL` is not set, the helper defaults to `urn:uuid:`
form, which is a valid absolute IRI and avoids any need for a running HTTP
server during development and testing.
- The `make_id()` helper in `vultron/api/v2/data/utils.py` (which already
uses HTTPS base-URL form) becomes the shared canonical ID factory for all
layers, satisfying OID-01-004.

### Consequences

- Good: all newly created objects carry globally unique, AS2-compliant `id`
values.
- Good: `VULTRON_BASE_URL` enables environment-specific ID namespaces
(dev / staging / production) without code changes.
- Good: `urn:uuid:` default keeps development and test setups self-contained.
- Neutral: existing bare-UUID records in prototype data stores remain usable
during the prototype phase; a migration script is not required until
production deployment (OID-04-002, PROTO-01-001).
- Bad: `generate_new_id()` now produces IDs that contain colons, which must
not be used directly as py_trees blackboard keys (use the UUID suffix
portion only — see OID-03-001).

## Validation

- Unit tests in `test/wire/as2/vocab/base/test_utils.py` assert that
`generate_new_id()` returns a value starting with `urn:uuid:` or a
configured HTTPS prefix.
- Unit tests in `test/api/v2/data/test_utils.py` confirm that `make_id()`
produces HTTPS-prefixed IDs and that `BASE_URL` is read from
`VULTRON_BASE_URL` when set.
- Code review: no new code should assign a bare UUID directly to `as_id`
without wrapping it in a URI prefix.

## Pros and Cons of the Options

### `urn:uuid:` prefix

- Good: simple — no environment configuration required.
- Good: valid absolute IRI; accepted by AS2 parsers.
- Good: no slashes in the ID, so the full ID can be used as a path segment
without percent-encoding.
- Neutral: not directly HTTP-resolvable.
- Bad: does not support the future federation model where IDs are resolvable
URLs on actor servers.

### Configurable HTTPS base URL

- Good: IDs are HTTP-resolvable, matching the ActivityPub federation model.
- Good: per-environment namespacing via `VULTRON_BASE_URL`.
- Bad: IDs contain slashes; API routes must URL-encode or use query
parameters for ID-based lookups.
- Bad: requires a configured hostname to produce meaningful IDs.

### Continue using bare UUIDs

- Good: no code changes required.
- Bad: violates AS2 spec; blocks federation.
- Bad: violates `specs/object-ids.md` OID-01-001.

## More Information

- `specs/object-ids.md` — normative requirements OID-01 through OID-04.
- `notes/codebase-structure.md` — "Technical Debt: Object IDs Should Be
URL-Like, Not Bare UUIDs" section.
- `plan/IMPLEMENTATION_PLAN.md` — TECHDEBT-3.
- Related ADRs:
- [ADR-0005](0005-activitystreams-vocabulary-as-vultron-message-format.md)
— rationale for using AS2 as the wire format.
- [ADR-0009](0009-hexagonal-architecture.md) — hexagonal architecture that
separates wire format concerns from domain logic.
2 changes: 2 additions & 0 deletions docs/adr/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ General information about architectural decision records is available at <https:
- [ADR-0006 Use CalVer for Project Versioning](0006-use-calver-for-project-versioning.md)
- [ADR-0007 Introduce a Behavior Dispatcher](0007-use-behavior-dispatcher.md)
- [ADR-0008 Use py_trees for Handler BT Integration](0008-use-py-trees-for-handler-bt-integration.md)
- [ADR-0009 Adopt Hexagonal Architecture (Ports and Adapters)](0009-hexagonal-architecture.md)
- [ADR-0010 Standardize Object IDs to URI Form](0010-standardize-object-ids.md)

## Proposed ADRs

Expand Down
Loading
Loading