Adds the v1 data shape for peer-scoped default-deny registry filtering, the one-way-door piece of the call-completion batch (ADR-028): - HandlerRegistration gains pub remote_safe: bool, defaulting false across all provenance (Local, Session, FromOpenAPI, FromMCP, FromCall, FromJsonSchema) per ADR-028 §4. HandlerRegistration::new() keeps its existing 6-arg signature (defaults remote_safe: false), so all current call sites compile unchanged. - Chainable HandlerRegistration::remote_safe(bool) setter + a OperationRegistryBuilder::remote_safe() helper that marks the most-recently-registered op (tracked via last_name, not HashMap iteration order which is unspecified). - Field is data-only here — the filtering behavior (dispatch path + services/list hide) is wired in call/client/call-client, not this task. services/list is unchanged. - Tests: default false, setter flips field, all six provenance variants default false, builder setter marks last op, existing call sites unchanged. 178 tests pass, clippy clean. Refs: tasks/call/registry/remote-safe-marking.md Refs: docs/architecture/decisions/028-callclient-peer-scoped-registry-filtering.md
5.8 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level |
|---|---|---|---|---|---|---|---|
| call/registry/remote-safe-marking | Add remote_safe field to HandlerRegistration for CallClient peer-scoped filtering (ADR-028) | completed | narrow | medium | isolated | implementation |
Description
Add the remote_safe: bool field to HandlerRegistration (and its builder) so
that a CallClient can default-deny which operations it exposes to a remote
peer. This is the v1 shape of the peer-scoped filtering mechanism locked by
ADR-028. It is the prerequisite for call/client/call-client (the
CallClient's dispatch path reads this field) and is the only one-way-door
piece of the call-completion work, so it goes first.
Field
pub struct HandlerRegistration {
pub spec: OperationSpec,
pub handler: Handler,
pub provenance: OperationProvenance,
pub composition_authority: Option<CompositionAuthority>, // None for leaves
pub scoped_env: Option<ScopedOperationEnv>, // None for leaves
pub capabilities: Capabilities,
pub remote_safe: bool, // default false; ADR-028 — exposes this op to
// CallClient peers (trusted-peer mode bypasses)
}
remote_safe defaults to false across all provenance (Local, Session,
and the leaf provenances — see ADR-028 §4). The operator flips specific
operations to true when they want a peer to reach them. This mirrors the
default-deny posture of ADR-015 (visibility Internal by default) and
ADR-022 (composition authority None for leaves by default).
Builder
OperationRegistryBuilder needs a way to set remote_safe:
.with_local(...)/.with_leaf(...)/.with(...)should defaultremote_safe: false(current call sites stay valid unchanged).- Add a chainable setter, e.g.
.remote_safe(true)on the builder or awith_local_remote(...)/ explicit-arg variant. The exact builder API shape is a two-way door — pick the least invasive (an optional trailing arg or a builder setter method); do not over-engineer per-peer allowlists (that's OQ-25's two-way-door remainder, explicitly out of scope here).
services/list interaction (ADR-028 Assumption 2)
services/list already filters by Visibility::External (ADR-015). Per
ADR-028 Assumption 2, when served to a CallClient peer, services/list must
additionally hide non-remote-safe ops — a peer should not see ops it
cannot call, so discovery and dispatch filters agree. The
services_list_handler in registry/discovery.rs currently filters only on
visibility.
Scoping note: the services/list handler doesn't know whether the caller
is a CallClient peer or a local process. The v1 implementation: the filter
applied by services/list is the registry's filter, and the peer-scoped
view a CallClient exposes is built atop this. The cleanest v1 split is:
services/listkeeps filtering byVisibility::External(unchanged).- The
CallClient's peer-scoped view (taskcall/client/call-client) is a dispatch-time read that additionally filters byremote_safe, and theCallClient's ownservices/listserving (when it receivesservices/listfrom the remote peer) hides non-remote-safe ops.
So this task adds the field + builder setter + provenance defaults, and
the filtering behavior that consumes the field is wired in
call/client/call-client (the dispatch path) and — for the
services/list-hides-non-remote-safe behavior — in the CallClient's
serving path. This task only adds the data and defaults, plus a unit
test that the field defaults to false and that the setter flips it. Keep
this task tightly scoped: adding the field must not change any existing
dispatch behavior (the field is read-only by the CallClient layer added
later).
Acceptance Criteria
HandlerRegistrationhaspub remote_safe: boolfield- All existing
with_local/with_leaf/withbuilder call sites compile unchanged (defaultremote_safe: false) - A builder setter exists to set
remote_safe: true(e.g..remote_safe(true)or an explicit-arg variant) - Provenance-aware defaults:
Local,Session,FromOpenAPI,FromMCP,FromCall,FromJsonSchemaall default tofalse(ADR-028 §4) - No existing dispatch path behavior changes (field is data-only here; the CallClient filter that reads it is a later task)
services/listhandler is unchanged in this task (filtering wired later)- Unit test:
HandlerRegistrationdefault hasremote_safe == false - Unit test: builder setter produces
remote_safe == true - Unit test: all six provenance variants default
remote_safe == false cargo test -p alknet-callsucceedscargo clippy -p alknet-call --all-targetssucceeds with no warnings
References
- docs/architecture/decisions/028-callclient-peer-scoped-registry-filtering.md — ADR-028 (the one-way door; §2 field location, §4 provenance defaults, Assumption 2 services/list hide)
- docs/architecture/crates/call/operation-registry.md — HandlerRegistration struct sketch (now shows
remote_safe) - docs/architecture/crates/call/client-and-adapters.md — CallClient § (consumes the field; trusted-peer bypass)
- docs/research/alknet-call-completion/gap-analysis.md — DC-1
Notes
This is the one-way-door piece of the call-completion work: the existence of default-deny filtering is locked by ADR-028; this task adds only the v1 data shape (
remote_safe: bool) and defaults. The richer per-peer shape (allowlist, capability-class tag) is explicitly out of scope — it's the two-way-door remainder tracked as OQ-25. Do not implement per-peer logic here. The filtering behavior (dispatch path + services/list hide) is wired incall/client/call-client, not here — this task is data + defaults only so it can land first and unblock the CallClient task.