pub fn validate_link_pre_create(
conn: &Connection,
source_id: &str,
target_id: &str,
relation: &str,
agent_id: &str,
skip_governance: bool,
) -> Result<()>Expand description
v0.7.0 fix-campaign A3 (LINK-PARITY) — shared pre-create validator invoked by every link-write entry point.
Closes the S5-H2 HIGH finding (#690): before A3 the L1-2 cycle
check + K9 permission pipeline ran only in
src/mcp/tools/link.rs::handle_link, so the HTTP POST /api/v1/links
path and the federation-receive sync_push link loop could land
reflects_on edges that the MCP path would have refused. The fix
is defense-in-depth at the storage layer: every path — MCP, HTTP,
SAL, federation — calls this helper, so the gates enforce no
matter which entry point initiates the write.
Pipeline:
- Cycle check — invoked only when
relation == "reflects_on". Callscrate::kg::cycle_check::would_create_reflection_cyclewith the namespace-scopedeffective_max_reflection_depthcap; on awould_cyclehit, returns an error prefixed withLINK_CYCLE_ERR_PREFIXso HTTP can surface 409 CONFLICT and signed-event emit can record the refusal. The walk fails CLOSED on SQL errors and on depth-ceiling truncation. - K9 permission eval — runs the unified
crate::permissions::Permissions::evaluatepipeline against the source memory’s namespace. OnDeny, returns an error prefixed withLINK_PERMISSION_DENIED_ERR_PREFIXso HTTP surfaces 403.Askis treated asDenyhere because the storage-layer helper has no Ask-channel back to the operator; entry points that want interactive Ask handling (MCP) should invokePermissions::evaluatedirectly BEFORE calling create_link.
skip_governance lets federation-receive bypass the K9 gate when
the inbound link has already been cryptographically attested by an
enrolled peer (attest_level == “peer_attested”). The cycle check
always runs — even a trusted peer should not be able to extend a
reflection cycle on the receiver. See create_link_inbound for the
caller-side decision logic.
agent_id defaults to "system" when the caller cannot resolve a
concrete claimant (federation receive path with no claim, etc.) —
the permission rule matcher uses it for agent_pattern matching.