#[cfg(test)]
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
#[cfg(test)]
use crate::operations::LinkSpec;
#[cfg(test)]
use crate::runtime::NamespaceToken;
#[cfg(test)]
use async_trait::async_trait;
#[cfg(test)]
use khive_gate::{AllowAllGate, GateRef};
use khive_gate::{AuditEvent, GateDecision, GateRequest};
#[cfg(test)]
use khive_storage::EventView;
use khive_storage::{Event, EventStore, SubstrateKind};
use khive_types::{EventKind, EventOutcome, Namespace};
use serde_json::Value;
pub use khive_types::{
json_type_name, EdgeEndpointRule, EndpointKind, EntityTypeDef, HandlerDef, IdResolutionMode,
NoteEmbeddingPolicy, NoteEmbeddingPolicySpec, NoteKindSpec, NoteLifecycleSpec,
PackColumnAddition, PackColumnAffinity, PackSchemaPlan, ParamDef, VerbCategory,
VerbPresentationPolicy, Visibility, RESERVED_ENVELOPE_ARGS,
};
#[allow(deprecated)]
pub use khive_types::VerbDef;
pub const GENERIC_CRUD_PACK: &str = "kg";
pub const AUDIT_PERSISTENCE_SKIPPED_READ_ONLY: &str = "audit_persistence_skipped_read_only";
const FULL_UUID_IDENTIFIER_HELP: &str = "A complete UUID spelling accepted by the consuming \
parameter directly names one globally unique record; direct UUID lookup is not a namespace \
search. Strict identifier responses use canonical lowercase dashed UUIDs.";
const SHORT_PREFIX_IDENTIFIER_HELP: &str = "A short UUID prefix is at least 8 hexadecimal \
characters without dashes that do not parse as a complete UUID. It is a resolution, not a \
direct identifier; a 32-character compact UUID is complete input instead. Its lookup scope \
belongs to the consuming parameter — see `identifier_resolution.resolution_modes` for the \
exhaustive per-mode rule, and each `uuid`/`array of uuid` parameter's own description for \
which mode it uses. A prefix can be missing or ambiguous.";
const IDENTIFIER_PARAMETER_HELP: &str = "A parameter that requires a full UUID rejects prefixes \
and explains the resolution consequence. Its corresponding response field remains a \
canonical full UUID so the value can be submitted again.";
fn resolution_mode_contract(mode: IdResolutionMode) -> Option<&'static str> {
match mode {
IdResolutionMode::NotApplicable => None,
IdResolutionMode::UnscopedById => Some(
"ID contract (unscoped by-ID, ADR-007 Rev 6): a full UUID and a short hex prefix \
(8+ hex chars) both resolve with no namespace filter — the caller already knows \
the specific record, and authorization is the Gate's seam, not resolution's. A \
prefix matching nothing or matching more than one record is rejected. Used by \
get/update/delete/merge/link (link's source_id/target_id resolve through the same \
unfiltered path as the four record-level by-ID verbs), GTD's lifecycle id \
parameters, and brain's feedback target_id.",
),
IdResolutionMode::PrefixScopedToPrimary => Some(
"ID contract (prefix scoped to primary namespace): a full UUID resolves as given, \
with no namespace check performed by this resolver. A short hex prefix (8+ hex \
chars) is resolved by searching only the caller's primary namespace, and is \
rejected if it matches nothing or matches more than one record there.",
),
IdResolutionMode::FullAndPrefixScopedToPrimary => Some(
"ID contract (full UUID and prefix both scoped to primary namespace): both a full \
UUID and a short hex prefix (8+ hex chars) are validated against the caller's \
primary namespace — a record that exists but belongs to a different namespace \
resolves as not found. A prefix matching more than one record in that namespace \
is rejected as ambiguous.",
),
IdResolutionMode::FullUuidOnlyScopedToPrimary => Some(
"ID contract (full UUID only, scoped to primary namespace): only a complete UUID \
is accepted — a short hex prefix is rejected outright because this field stores \
an explicit stable reference — and the UUID is validated against the caller's own \
(primary) namespace; a record that exists in a different namespace resolves as \
not found.",
),
IdResolutionMode::UnscopedFullUuidOnly => Some(
"ID contract (full UUID only, unscoped): only a complete UUID is accepted — a \
short hex prefix is rejected outright — and no namespace check is performed on \
this parameter itself; any namespace scoping comes from the enclosing operation, \
not from this identifier.",
),
IdResolutionMode::EdgeOrEventTarget => Some(
"ID contract (list target by kind): kind=event accepts only a full subject UUID; \
prefixes and names are rejected without graph resolution. Event rows remain \
scoped to the authorized event namespace. For kind=edge, a full UUID resolves as \
given; a unique 8+ hex prefix or entity name resolves in the primary namespace.",
),
}
}
fn resolution_mode_key(mode: IdResolutionMode) -> &'static str {
match mode {
IdResolutionMode::NotApplicable => "not_applicable",
IdResolutionMode::UnscopedById => "unscoped_by_id",
IdResolutionMode::PrefixScopedToPrimary => "prefix_scoped_to_primary",
IdResolutionMode::FullAndPrefixScopedToPrimary => "full_and_prefix_scoped_to_primary",
IdResolutionMode::FullUuidOnlyScopedToPrimary => "full_uuid_only_scoped_to_primary",
IdResolutionMode::UnscopedFullUuidOnly => "unscoped_full_uuid_only",
IdResolutionMode::EdgeOrEventTarget => "edge_or_event_target",
}
}
pub fn identifier_resolution_help() -> Value {
let modes: serde_json::Map<String, Value> = [
IdResolutionMode::UnscopedById,
IdResolutionMode::PrefixScopedToPrimary,
IdResolutionMode::FullAndPrefixScopedToPrimary,
IdResolutionMode::FullUuidOnlyScopedToPrimary,
IdResolutionMode::UnscopedFullUuidOnly,
IdResolutionMode::EdgeOrEventTarget,
]
.into_iter()
.map(|mode| {
(
resolution_mode_key(mode).to_string(),
Value::String(
resolution_mode_contract(mode)
.expect("every non-NotApplicable mode has contract text")
.to_string(),
),
)
})
.collect();
serde_json::json!({
"full_uuid": FULL_UUID_IDENTIFIER_HELP,
"short_prefix": SHORT_PREFIX_IDENTIFIER_HELP,
"parameter_rule": IDENTIFIER_PARAMETER_HELP,
"resolution_modes": modes,
})
}
mod traits;
pub use traits::{
DispatchHook, KindHook, NoteUpdateEffect, PackByIdResolver, PackRuntime, SchemaPlan,
};
#[cfg(test)]
use crate::error::DispatchError;
use crate::error::{AuditObligationFailure, RuntimeError};
use crate::KhiveRuntime;
mod builder;
pub use builder::{PackMetadataRegistry, VerbRegistryBuilder};
mod catalog;
mod dispatch;
mod registry_access;
mod request_identity;
pub(crate) use request_identity::is_special_relation;
pub use request_identity::{
InterceptedDispatchResult, PackSchemaCollisionError, RequestIdentity, VerbRegistry,
VerifiedActor,
};
pub(crate) const SPECIAL_RELATIONS: &[khive_types::EdgeRelation] = &[
khive_types::EdgeRelation::Supersedes,
khive_types::EdgeRelation::Supports,
khive_types::EdgeRelation::Refutes,
];
mod loading;
pub use loading::{
ChannelIngestCapability, IngestAuditStore, PackFactory, PackInstall, PackLoadError,
PackRegistration, PackRegistry,
};
pub(crate) const CHANNEL_INGEST_CAPABLE_PACKS: &[&str] = &["comm"];
mod audit;
use audit::{
append_audit_event_best_effort, build_audit_storage_event, fold_audit_obligation,
link_audit_success_from_result, masked_audit_event, persist_git_digest_receipt,
GitDigestReceiptOutcome,
};
pub use audit::{
audit_admission_refused_obligation_count, audit_admission_refused_obligation_last_at_ms,
audit_admission_unresolved_obligation_count, audit_admission_unresolved_obligation_last_at_ms,
resolve_explicit_namespace,
};
pub(crate) use audit::{audit_append_failure_count, audit_obligation_append_failure_count};
#[cfg(test)]
#[path = "pack_tests.rs"]
pub(crate) mod tests;
#[cfg(test)]
#[path = "pack/dep_tests.rs"]
mod dep_tests;
#[cfg(test)]
#[path = "pack/note_update_sequencing_tests.rs"]
mod note_update_sequencing_tests;
#[cfg(test)]
#[path = "pack/hook_tests.rs"]
mod hook_tests;
#[cfg(test)]
#[path = "pack/help_tests.rs"]
mod help_tests;
#[cfg(test)]
#[path = "pack/admission_allowlist_adr_tests.rs"]
mod admission_allowlist_adr_tests;
#[cfg(test)]
#[path = "gate_argument_contract_tests.rs"]
mod gate_argument_contract_tests;