tasks(arch): adopt taskgraph for architecture work — blocker tasks for deferred OQs, level mapping, status fixes

Establishes the tasks/architecture/ blocker-task half of the Safe Exit
protocol. Each deferred OQ now has an external-trigger tracker task
representing its unblocking condition, and the OQ's Blocked on field names
the tracker task ID — closing the gap where the deferral field existed but
the task-graph half was unenforced.

Changes:
- Add 6 external-trigger tracker tasks for the deferred OQs (OQ-09, 10, 32,
  41, 44, 46) under tasks/architecture/, tagged [external-trigger, deferred-oq]
- Add structured Blocked on field to OQ-09 and OQ-10 (previously used legacy
  'deferred' status with the reason in the Resolution prose); index now
  surfaces all 6 deferred OQs with concrete conditions, no placeholders
- Document the architecture-task level mapping (level: implementation for
  ADRs/specs, decomposition for spec-to-ADR breakdown, planning for backlog
  seeding, research/review direct fits) and the deferred-OQ blocker-task
  pattern in docs/sdd_process.md
- Mark architecture/safe-exit-blocker-task-mechanism and
  architecture/oq-09-10-blocking-conditions completed (implemented by this pass)
- Fix 4 pre-existing ADR-050 implementation tasks: status: done → completed
  (taskgraph enum is 'completed', not 'done') — taskgraph validate now passes
  for all 100 tasks
This commit is contained in:
glm-5.2 committed 2026-07-06 16:34:01 +00:00
1 parent e6062230f9
commit df5d04af1d
16 files changed
+440 -14

No files matched your search

+2 -2
View File
@@ -160,13 +160,13 @@ filtering the tables above.
### OQ-09: WASM Target Boundaries
- **Blocked on**: _(no explicit blocking condition recorded — see full file)_
- **Blocked on**: A concrete server-side WASM use case, or a deliberate confirmation that WASM stays a client-side design constraint. Tracked as `architecture/oq-09-wasm-server-use-case` in `tasks/architecture/`.
- **Priority**: low
- **Full file**: [OQ-09](questions/009-wasm-target-boundaries.md)
### OQ-10: Git Adapter Scope — Smart Protocol Only or Full Server?
- **Blocked on**: _(no explicit blocking condition recorded — see full file)_
- **Blocked on**: Speccing the alknet-git crate — resolve this when that crate is specified, not deferred past it. Tracked as `architecture/oq-10-git-adapter-spec` in `tasks/architecture/`.
- **Priority**: low
- **Full file**: [OQ-10](questions/010-git-adapter-scope-smart-protocol-only-or-full-server.md)
@@ -4,5 +4,6 @@
- **Status**: deferred
- **Door type**: One-way (when applicable)
- **Priority**: low
- **Blocked on**: A concrete server-side WASM use case, or a deliberate confirmation that WASM stays a client-side design constraint. Tracked as `architecture/oq-09-wasm-server-use-case` in `tasks/architecture/`.
- **Resolution**: Not an active question — WASM compatibility is a design constraint (see ADR-009, overview.md design principles), not a deliverable. Specific WASM targeting decisions will be made when individual crates are implemented. **BiStream being a trait preserves the *client-side* stream door** — a browser can implement BiStream over WebTransport streams. **The *server-side* dispatch door is NOT preserved by ADR-007 and is a known, accepted closure**: `Connection` is a concrete quinn-bound struct (not a trait), the accept loop uses `tokio::spawn` (tokio does not run on WASM), and the call-protocol dispatch internals (`PendingRequestMap`, `CallAdapter`) use tokio `oneshot`/`mpsc` channels. A WASM server-side peer would require a `Connection` trait and a runtime-abstracted accept loop — not planned. The browser path is client-side via a JS SDK, not server-side Rust-to-WASM. This is an explicit one-way door, not an oversight.
- **Cross-references**: ADR-007, ADR-009
@@ -4,5 +4,6 @@
- **Status**: deferred
- **Door type**: Two-way
- **Priority**: low
- **Blocked on**: Speccing the alknet-git crate — resolve this when that crate is specified, not deferred past it. Tracked as `architecture/oq-10-git-adapter-spec` in `tasks/architecture/`.
- **Resolution**: Deferred per the cleanup plan. Start with git smart protocol over QUIC streams. ERC721 integration and full server capabilities are additive. **Composability fork (review #002 W18)**: whether git operations are registered in the `OperationRegistry` and callable via `env.invoke()`, or only available as raw smart protocol on `alknet/git`, is a separate decision from ERC721 scope. The path of least resistance (raw smart protocol only) forecloses agent composition of git operations — an agent handler that wants to compose `git/clone` cannot, because there's no `OperationSpec`, no `Handler`, no registration. To make git composable, a call-protocol projection (a set of `HandlerRegistration` bundles wrapping git operations behind the registry) must be built alongside or instead of the raw handler. Resolve this when speccing alknet-git, not deferred past it.
- **Cross-references**: ADR-001
+48
View File
@@ -420,6 +420,27 @@ cost-benefit framework in taskgraph's framework docs for the reasoning.
2. Fills in `## Summary` section
3. Commits changes to worktree branch
### Architecture work and the `level` field
Architecture work uses the same taskgraph format as implementation work —
there is no separate `architecture` level. The `level` enum
(`planning, decomposition, implementation, review, research`) already covers
architecture activities:
| Architecture activity | `level` | Rationale |
|---|---|---|
| Writing an ADR (the decision) | `implementation` | The ADR *is* the deliverable — the implementation of an architecture decision. Calling it `planning` would mislabel the artifact. |
| Decomposing a spec into ADRs + OQs | `decomposition` | Direct fit — breaking a large architecture area into decideable units. |
| Architecture review pass | `review` | Direct fit. |
| Research / POC before a decision | `research` | Direct fit. |
| Spec writing (overview, component spec) | `implementation` | The spec is the deliverable. |
| Seeding a backlog of OQs/ADRs for a crate | `planning` | The upfront graph-shaping pass. |
Architecture tasks live under `tasks/architecture/` and use the standard
task body sections (`## Description`, `## Work`, `## Verification`,
`## Out of scope`). The `## Summary` section is filled on completion, same as
implementation tasks.
## Safe Exit Protocol
When a task becomes untendable:
@@ -445,6 +466,33 @@ When a task becomes untendable:
3. Document in task notes
4. Notify coordinator
### Deferred OQs and the blocker-task half
The Safe Exit protocol applies to architecture decisions too, not just
implementation tasks. When an open question (OQ) is marked `deferred(scope)`,
the deferral has two halves that must stay in sync:
1. **The visibility half** (in `docs/architecture/`): the OQ's `Blocked on:`
field names the concrete blocking condition, and the `open-questions.md`
index surfaces it in the cross-theme **Deferred / Blocked** section so
"what's currently parked and why" is answerable at a glance. This is the
human-readable surface for the architect.
2. **The machine-readable half** (in `tasks/architecture/`): an
external-trigger tracker task represents the unblocking condition. It is
tagged `[external-trigger, deferred-oq]`, has `risk: trivial` and
`level: research` (it is not actionable work — it tracks whether the
external condition has arrived), and its `id` is referenced from the OQ's
`Blocked on:` text. This is the surface for the task graph: `taskgraph`
tools can reason about it, and downstream work that depends on the
decision can declare `depends_on: [architecture/oq-NN-...]`.
When the unblocking condition arrives (a use case materializes, a crate is
specced), the tracker task is marked `completed` and the OQ transitions from
`deferred(scope)` to `open` (or directly to `resolved` if no architecture
decision remains). The two halves serve different audiences and use one edge
type (`depends_on`) — the reverse lookup uses `taskgraph dependents`. Do not
add a `blocks:` field; it duplicates the edge and creates a sync hazard.
## Review Injection
Use graph analysis to determine where reviews should happen:
@@ -1,12 +1,13 @@
---
id: architecture/oq-09-10-blocking-conditions
name: Add explicit Blocked on conditions to OQ-09 (WASM) and OQ-10 (Git Adapter)
status: pending
status: completed
depends_on: []
scope: narrow
risk: low
impact: component
level: decomposition
tags: [convention]
---
## Description
@@ -39,8 +40,20 @@ Either:
The decision content stays unchanged — this is a metadata-structure fix, not a
re-resolution.
## Summary
Completed alongside `architecture/safe-exit-blocker-task-mechanism`. Added a
structured `Blocked on:` field to both OQ-09 and OQ-10 in their per-OQ files,
each pointing at its new external-trigger tracker task
(`architecture/oq-09-wasm-server-use-case`,
`architecture/oq-10-git-adapter-spec`). Kept the legacy `deferred` status
rather than reframing to `deferred(scope)` — the distinction is no longer
load-bearing now that both have explicit blocking conditions and tracker
tasks. The `open-questions.md` Deferred/Blocked section now surfaces all six
deferred OQs with concrete conditions inline — no placeholders remain.
## Verification
The "Deferred / Blocked" section of `docs/architecture/open-questions.md` should
show a concrete blocking condition for OQ-09 and OQ-10 instead of the "no
The "Deferred / Blocked" section of `docs/architecture/open-questions.md`
shows a concrete blocking condition for OQ-09 and OQ-10 instead of the "no
explicit blocking condition recorded" placeholder.
@@ -0,0 +1,58 @@
---
id: architecture/oq-09-wasm-server-use-case
name: External trigger — a concrete server-side WASM use case (or confirmation it stays a client-side constraint)
status: pending
depends_on: []
scope: single
risk: trivial
impact: component
level: research
tags: [external-trigger, deferred-oq]
---
## Description
External-trigger tracker for [OQ-09](../docs/architecture/questions/009-wasm-target-boundaries.md)
(WASM Target Boundaries). This is **not actionable work** — it tracks whether
a concrete server-side WASM use case has emerged, or whether the project
confirms that WASM compatibility remains a *client-side* design constraint
only (a browser can implement BiStream over WebTransport streams; the
server-side dispatch door is a known, accepted closure per ADR-007/009).
## Trigger condition
Either:
- **A concrete server-side WASM use case arrives** (a deployment that wants
to run an alknet server peer compiled to WASM, which would require a
`Connection` trait and a runtime-abstracted accept loop — currently not
planned), or
- **A deliberate confirmation** that WASM stays a client-side design
constraint, at which point OQ-09 transitions from `deferred` to `resolved`
with the accepted-closure framing already in its Resolution text.
The second path is the more likely one — the OQ exists mainly so the
server-side WASM door closure is documented rather than implicit.
## What unblocking looks like
When a decision is made (either direction):
1. Mark this task `status: completed`.
2. Move [OQ-09](../docs/architecture/questions/009-wasm-target-boundaries.md)
from `deferred` to either `resolved` (client-side-only confirmed) or `open`
(server-side use case arrives, requiring a Connection trait + WASM runtime
abstraction ADR).
## Why this is a task, not just an OQ field
OQ-09 predates the formalized `deferred(scope)` + blocking-condition pattern
and lacked a structured `Blocked on:` field (it used the legacy `deferred`
status with the deferral reason in the Resolution prose). This task
formalizes the blocking condition and gives the OQ a machine-readable
presence in the task graph.
## Verification
This task is "completed" when either a server-side WASM use case arrives
(move OQ-09 to `open`) or the client-side-only constraint is confirmed
(move OQ-09 to `resolved`).
@@ -0,0 +1,56 @@
---
id: architecture/oq-10-git-adapter-spec
name: External trigger — speccing alknet-git (resolve OQ-10 when that crate is specified, not deferred past it)
status: pending
depends_on: []
scope: single
risk: trivial
impact: component
level: research
tags: [external-trigger, deferred-oq]
---
## Description
External-trigger tracker for [OQ-10](../docs/architecture/questions/010-git-adapter-scope-smart-protocol-only-or-full-server.md)
(Git Adapter Scope — Smart Protocol Only or Full Server?). This is **not
actionable work** — it tracks when the alknet-git crate is being specified,
at which point OQ-10 must be resolved (not deferred past it).
## Trigger condition
The alknet-git crate is being specified. The OQ's Resolution text already
states: "Resolve this when speccing alknet-git, not deferred past it." The
two sub-questions:
1. **Git adapter scope** — start with git smart protocol over QUIC streams;
ERC721 integration and full server capabilities are additive.
2. **Composability fork** — whether git operations are registered in the
`OperationRegistry` and callable via `env.invoke()`, or only available as
raw smart protocol on `alknet/git`. The path of least resistance (raw
smart protocol only) forecloses agent composition of git operations; to
make git composable, a call-protocol projection (a set of
`HandlerRegistration` bundles wrapping git operations behind the
registry) must be built alongside or instead of the raw handler.
## What unblocking looks like
When alknet-git is specced:
1. Mark this task `status: completed`.
2. Move [OQ-10](../docs/architecture/questions/010-git-adapter-scope-smart-protocol-only-or-full-server.md)
from `deferred` to `open`, then resolve it as part of the alknet-git spec
pass (the Resolution text is explicit: do not defer past the spec).
## Why this is a task, not just an OQ field
OQ-10 predates the formalized `deferred(scope)` + blocking-condition pattern
and lacked a structured `Blocked on:` field (it used the legacy `deferred`
status with the deferral reason in the Resolution prose). This task
formalizes the blocking condition and gives the OQ a machine-readable
presence in the task graph.
## Verification
This task is "completed" when alknet-git is being specced and OQ-10 has been
moved to `open` (then resolved as part of that spec pass).
@@ -0,0 +1,53 @@
---
id: architecture/oq-32-multihop-use-case
name: External trigger — a concrete multi-hop federation use case
status: pending
depends_on: []
scope: single
risk: trivial
impact: component
level: research
tags: [external-trigger, deferred-oq]
---
## Description
External-trigger tracker for [OQ-32](../docs/architecture/questions/032-multi-hop-federation.md)
(Multi-Hop Federation). This is **not actionable work** — it tracks whether a
concrete use case for multi-hop federation has arrived. When it does, mark this
task `completed` and the OQ moves from `deferred(scope)` to `open`.
## Trigger condition
A concrete deployment or use case that requires transitive op discovery across
more than one hop — i.e., worker A needs to reach worker B's ops *through* the
head, where the head is not explicitly re-exporting them. The one-hop model
(head→worker, runner→hub) covers all current use cases.
## What unblocking looks like
When a use case arrives:
1. Mark this task `status: completed`.
2. Move [OQ-32](../docs/architecture/questions/032-multi-hop-federation.md)
from `deferred(scope)` to `open` (update the Status field + the
`open-questions.md` index tables + Deferred/Blocked section).
3. Create an architecture task to write the multi-hop federation ADR — the
peer-keyed overlay model extends to multi-hop without redesign (ADR-029 §3.7),
but path-finding (which peer reaches which op transitively) is where the
design work lives. A graph library (petgraph) may pay off for multi-hop; for
one-hop, a nested `HashMap<PeerId, HashMap<String, ...>>` suffices.
## Why this is a task, not just an OQ field
The OQ's `Blocked on:` field in `open-questions.md` is the human-readable
visibility surface ("what's parked and why"). This task is the machine-readable
half: it lives in the task graph so `taskgraph` tools can reason about it, and
so downstream work that depends on multi-hop being resolved can declare
`depends_on: [architecture/oq-32-multihop-use-case]`.
## Verification
This task is "completed" when a concrete multi-hop use case is documented
(e.g., in a research finding or deployment note) and OQ-32 has been moved to
`open`.
@@ -0,0 +1,66 @@
---
id: architecture/oq-41-stream-operators-use-case
name: External trigger — a handler that needs stream operators beyond existing combinators
status: pending
depends_on: []
scope: single
risk: trivial
impact: component
level: research
tags: [external-trigger, deferred-oq]
---
## Description
External-trigger tracker for [OQ-41](../docs/architecture/questions/041-stream-operators-library.md)
(Stream Operators Library). This is **not actionable work** — it tracks
whether a handler has emerged that needs stream operators (filter, map, batch,
dedupe, window, etc. on `BoxStream<T>`) and finds the existing combinators
insufficient. The operators library is a convenience, not a prerequisite for
any handler.
## Trigger condition
A handler that transforms subscription streams (`BoxStream<ResponseEnvelope>`)
and finds `Box::pin(stream::iter(...))`, `async_stream::stream!`, and
`futures::stream` combinators insufficient — i.e., the handler code is
demonstrably boilerplate-heavy for stream manipulation that the operators
library would collapse. The architectural decision (stream composition is
handler-level, not protocol-level) is already made in ADR-049; this tracks the
*implementation* of the utility library.
## What unblocking looks like
When a handler needs the operators:
1. Mark this task `status: completed`.
2. Move [OQ-41](../docs/architecture/questions/041-stream-operators-library.md)
from `deferred(scope)` to `resolved` (the architectural decision is already
made — ADR-049; what remains is the implementation, which is scheduling
work). The OQ may transition directly to `resolved` rather than `open`,
since no architecture decision remains.
3. Implement the operators library (no ADR needed — internal utility code
that doesn't cross crate boundaries as a contract; an ADR would be
warranted only if the operators become part of a public API surface, e.g.,
a handler-registration DSL that references operator names).
## Why this is a task, not just an OQ field
The OQ's `Blocked on:` field in `open-questions.md` is the human-readable
visibility surface. This task is the machine-readable half: it lives in the
task graph so `taskgraph` tools can reason about it, and so a handler that
needs the operators can declare `depends_on:
[architecture/oq-41-stream-operators-use-case]`.
## Prior art
`@alkdev/pubsub/src/operators.ts` — 13 operators (`filter`, `map`, `take`,
`batch`, `dedupe`, `window`, `chain`, `join`, `reduce`, `groupBy`, `flat`,
`pipe`, `toArray`) on `AsyncIterable<T>`, forked from graphql-yoga's
subscription implementation. The Rust analogue would provide the same set on
`BoxStream<T>` / `impl Stream<Item = T>`.
## Verification
This task is "completed" when a handler is identified that needs the operators
and OQ-41 has been moved to `resolved`.
@@ -0,0 +1,51 @@
---
id: architecture/oq-44-tty-modes-use-case
name: External trigger — a concrete TTY mode-control use case
status: pending
depends_on: []
scope: single
risk: trivial
impact: component
level: research
tags: [external-trigger, deferred-oq]
---
## Description
External-trigger tracker for [OQ-44](../docs/architecture/questions/044-terminal-modes-tty-modes.md)
(Terminal Modes / TTY modes). This is **not actionable work** — it tracks
whether a concrete deployment has emerged that needs to set TTY modes (echo,
raw, canonical, etc.) on a PTY beyond the backend's defaults.
## Trigger condition
A concrete deployment that needs to control TTY modes beyond the defaults the
backends already provide (`portable_pty`, docker `tty: true`, russh
`pty_request` all have defaults that work for the common terminal case). The
`modes` field in `TerminalParams` is `serde_json::Value` (reserved as `{}` in
v1) for when this arrives.
## What unblocking looks like
When a mode-control use case arrives:
1. Mark this task `status: completed`.
2. Move [OQ-44](../docs/architecture/questions/044-terminal-modes-tty-modes.md)
from `deferred(scope)` to `open`.
3. Specify the `modes` JSON shape (SSH's `pty_request` carries TTY modes as a
packed bitmask; the Rust analogue extends the `modes` field). Adding mode
control is additive (extend the `modes` JSON shape) and does not break
downstream — the architectural commitment is two-way-door.
## Why this is a task, not just an OQ field
The OQ's `Blocked on:` field in `open-questions.md` is the human-readable
visibility surface. This task is the machine-readable half: it lives in the
task graph so `taskgraph` tools can reason about it, and so downstream work
that depends on TTY mode control can declare `depends_on:
[architecture/oq-44-tty-modes-use-case]`.
## Verification
This task is "completed" when a concrete mode-control use case is documented
and OQ-44 has been moved to `open`.
@@ -0,0 +1,52 @@
---
id: architecture/oq-46-runner-policy-use-case
name: External trigger — a concrete runner-policy use case that forces the API surface
status: pending
depends_on: []
scope: single
risk: trivial
impact: component
level: research
tags: [external-trigger, deferred-oq]
---
## Description
External-trigger tracker for [OQ-46](../docs/architecture/questions/046-runner-api-surface.md)
(Runner API Surface). This is **not actionable work** — it tracks whether a
concrete runner-policy use case has emerged that forces the API surface (job
management, log persistence, task graph integration). The runner *mechanism*
(pipe mode) is already in alknet-tty (ADR-054); the runner *policy* is a
downstream crate's job.
## Trigger condition
A concrete deployment that needs runner *policy* — job management, log
persistence, task graph integration — on top of the pipe-mode mechanism
(`TtyParams.terminal = None` → `std::process::Command` with piped stdio →
framed byte stream + exit code) that alknet-tty already provides.
## What unblocking looks like
When a runner-policy use case arrives:
1. Mark this task `status: completed`.
2. Move [OQ-46](../docs/architecture/questions/046-runner-api-surface.md)
from `deferred(scope)` to `open`.
3. Decide whether a runner-policy crate (e.g., an `alknet-runner` crate that
builds on the pipe mode + the wire format to provide job management) is
needed, and what its API surface would be. The mechanism is preserved
regardless of the policy decision.
## Why this is a task, not just an OQ field
The OQ's `Blocked on:` field in `open-questions.md` is the human-readable
visibility surface. This task is the machine-readable half: it lives in the
task graph so `taskgraph` tools can reason about it, and so downstream work
that depends on runner policy can declare `depends_on:
[architecture/oq-46-runner-policy-use-case]`.
## Verification
This task is "completed" when a concrete runner-policy use case is documented
and OQ-46 has been moved to `open`.
@@ -1,12 +1,13 @@
---
id: architecture/safe-exit-blocker-task-mechanism
name: Establish the tasks/architecture/ blocker-task half of the Safe Exit protocol
status: pending
status: completed
depends_on: []
scope: moderate
risk: low
impact: project
level: planning
tags: [convention]
---
## Description
@@ -58,6 +59,30 @@ it").
role spec) so future deferrals create the blocker task as part of the Safe
Exit step.
## Summary
Completed in the July 2026 architecture-task pass. Decisions:
- **Format**: adopted the existing taskgraph frontmatter verbatim — no new
`blocks:` field. One edge type (`depends_on`) keeps the graph simple; the
reverse lookup uses `taskgraph dependents`. The OQ's `Blocked on:` text is
the human-readable pointer; the task `depends_on` is the machine-readable
edge. They serve different audiences (architect vs. planner/agent).
- **External-trigger tasks**: the four `deferred(scope)` OQs (32, 41, 44, 46)
plus the two legacy `deferred` OQs (09, 10) each got an external-trigger
tracker task under `tasks/architecture/` tagged `[external-trigger,
deferred-oq]`. These represent the external condition (a use case arriving,
a crate being specced) that would unblock the OQ — they are not actionable
work, so `risk: trivial` and `level: research`.
- **OQ-09/10 backfill**: added a structured `Blocked on:` field to both
(previously they used legacy `deferred` status with the reason in the
Resolution prose). The `open-questions.md` index now surfaces all six
deferred OQs with concrete blocking conditions in the Deferred/Blocked
section — no more "_(no explicit blocking condition recorded)_" placeholders.
- **Convention doc**: the `docs/sdd_process.md` Task File Format section is
updated with the architecture-task level mapping and the Safe Exit
blocker-task pattern (separate edit).
## Out of scope
- The DB-backed backend (no manual links, vector/text search) — that's a future
@@ -67,7 +92,9 @@ it").
## Verification
- `tasks/architecture/` contains one blocker task per `deferred(scope)` OQ
- Each blocker task's `depends_on` names the concrete unblocking condition (or
a task that represents it)
- `docs/sdd_process.md` (or equivalent) references the convention
- `tasks/architecture/` contains one external-trigger task per deferred OQ
(six total: OQ-09, 10, 32, 41, 44, 46).
- Each OQ's `Blocked on:` field names its tracker task ID, and the
`open-questions.md` Deferred/Blocked section surfaces the condition inline.
- `taskgraph validate` passes for `tasks/architecture/`.
- `docs/sdd_process.md` references the convention.
@@ -1,7 +1,7 @@
---
id: call/registry/access-control-ownership-check
name: Update AccessControl::check to consult OwnershipProvider for dynamic resource ownership (ADR-050 §2)
status: done
status: completed
depends_on: [core/ownership-store-trait]
scope: moderate
risk: medium
@@ -1,7 +1,7 @@
---
id: call/registry/dispatch-resource-id-extraction
name: Wire dispatch path to extract resource_id from input and thread OwnershipProvider to AccessControl::check (ADR-050 §2a, §4a)
status: done
status: completed
depends_on: [call/registry/operation-spec-resource-id-path, call/registry/access-control-ownership-check]
scope: moderate
risk: medium
@@ -1,7 +1,7 @@
---
id: call/registry/operation-spec-resource-id-path
name: Add resource_id_path field to OperationSpec (ADR-050 §2a)
status: done
status: completed
depends_on: []
scope: single
risk: low
+1 -1
View File
@@ -1,7 +1,7 @@
---
id: core/ownership-store-trait
name: Add OwnershipProvider (sync read) + OwnershipStore (async write) traits and InMemoryOwnershipStore (ADR-050)
status: done
status: completed
depends_on: []
scope: moderate
risk: low