1#[cfg(test)]
14use std::any::Any;
15use std::collections::HashMap;
16use std::sync::Arc;
17
18#[cfg(test)]
19use crate::operations::LinkSpec;
20#[cfg(test)]
21use crate::runtime::NamespaceToken;
22#[cfg(test)]
23use async_trait::async_trait;
24#[cfg(test)]
25use khive_gate::{AllowAllGate, GateRef};
26use khive_gate::{AuditEvent, GateDecision, GateRequest};
27#[cfg(test)]
28use khive_storage::EventView;
29use khive_storage::{Event, EventStore, SubstrateKind};
30use khive_types::{EventKind, EventOutcome, Namespace};
31use serde_json::Value;
32
33pub use khive_types::{
34 json_type_name, EdgeEndpointRule, EndpointKind, EntityTypeDef, HandlerDef, IdResolutionMode,
35 NoteEmbeddingPolicy, NoteEmbeddingPolicySpec, NoteKindSpec, NoteLifecycleSpec,
36 PackColumnAddition, PackColumnAffinity, PackSchemaPlan, ParamDef, VerbCategory,
37 VerbPresentationPolicy, Visibility, RESERVED_ENVELOPE_ARGS,
38};
39#[allow(deprecated)]
41pub use khive_types::VerbDef;
42
43pub const GENERIC_CRUD_PACK: &str = "kg";
51
52pub const AUDIT_PERSISTENCE_SKIPPED_READ_ONLY: &str = "audit_persistence_skipped_read_only";
55
56const FULL_UUID_IDENTIFIER_HELP: &str = "A complete UUID spelling accepted by the consuming \
57 parameter directly names one globally unique record; direct UUID lookup is not a namespace \
58 search. Strict identifier responses use canonical lowercase dashed UUIDs.";
59const SHORT_PREFIX_IDENTIFIER_HELP: &str = "A short UUID prefix is at least 8 hexadecimal \
60 characters without dashes that do not parse as a complete UUID. It is a resolution, not a \
61 direct identifier; a 32-character compact UUID is complete input instead. Its lookup scope \
62 belongs to the consuming parameter — see `identifier_resolution.resolution_modes` for the \
63 exhaustive per-mode rule, and each `uuid`/`array of uuid` parameter's own description for \
64 which mode it uses. A prefix can be missing or ambiguous.";
65const IDENTIFIER_PARAMETER_HELP: &str = "A parameter that requires a full UUID rejects prefixes \
66 and explains the resolution consequence. Its corresponding response field remains a \
67 canonical full UUID so the value can be submitted again.";
68
69fn resolution_mode_contract(mode: IdResolutionMode) -> Option<&'static str> {
84 match mode {
85 IdResolutionMode::NotApplicable => None,
86 IdResolutionMode::UnscopedById => Some(
87 "ID contract (unscoped by-ID, ADR-007 Rev 6): a full UUID and a short hex prefix \
88 (8+ hex chars) both resolve with no namespace filter — the caller already knows \
89 the specific record, and authorization is the Gate's seam, not resolution's. A \
90 prefix matching nothing or matching more than one record is rejected. Used by \
91 get/update/delete/merge/link (link's source_id/target_id resolve through the same \
92 unfiltered path as the four record-level by-ID verbs), GTD's lifecycle id \
93 parameters, and brain's feedback target_id.",
94 ),
95 IdResolutionMode::PrefixScopedToPrimary => Some(
96 "ID contract (prefix scoped to primary namespace): a full UUID resolves as given, \
97 with no namespace check performed by this resolver. A short hex prefix (8+ hex \
98 chars) is resolved by searching only the caller's primary namespace, and is \
99 rejected if it matches nothing or matches more than one record there.",
100 ),
101 IdResolutionMode::FullAndPrefixScopedToPrimary => Some(
102 "ID contract (full UUID and prefix both scoped to primary namespace): both a full \
103 UUID and a short hex prefix (8+ hex chars) are validated against the caller's \
104 primary namespace — a record that exists but belongs to a different namespace \
105 resolves as not found. A prefix matching more than one record in that namespace \
106 is rejected as ambiguous.",
107 ),
108 IdResolutionMode::FullUuidOnlyScopedToPrimary => Some(
109 "ID contract (full UUID only, scoped to primary namespace): only a complete UUID \
110 is accepted — a short hex prefix is rejected outright because this field stores \
111 an explicit stable reference — and the UUID is validated against the caller's own \
112 (primary) namespace; a record that exists in a different namespace resolves as \
113 not found.",
114 ),
115 IdResolutionMode::UnscopedFullUuidOnly => Some(
116 "ID contract (full UUID only, unscoped): only a complete UUID is accepted — a \
117 short hex prefix is rejected outright — and no namespace check is performed on \
118 this parameter itself; any namespace scoping comes from the enclosing operation, \
119 not from this identifier.",
120 ),
121 IdResolutionMode::EdgeOrEventTarget => Some(
122 "ID contract (list target by kind): kind=event accepts only a full subject UUID; \
123 prefixes and names are rejected without graph resolution. Event rows remain \
124 scoped to the authorized event namespace. For kind=edge, a full UUID resolves as \
125 given; a unique 8+ hex prefix or entity name resolves in the primary namespace.",
126 ),
127 }
128}
129
130fn resolution_mode_key(mode: IdResolutionMode) -> &'static str {
133 match mode {
134 IdResolutionMode::NotApplicable => "not_applicable",
135 IdResolutionMode::UnscopedById => "unscoped_by_id",
136 IdResolutionMode::PrefixScopedToPrimary => "prefix_scoped_to_primary",
137 IdResolutionMode::FullAndPrefixScopedToPrimary => "full_and_prefix_scoped_to_primary",
138 IdResolutionMode::FullUuidOnlyScopedToPrimary => "full_uuid_only_scoped_to_primary",
139 IdResolutionMode::UnscopedFullUuidOnly => "unscoped_full_uuid_only",
140 IdResolutionMode::EdgeOrEventTarget => "edge_or_event_target",
141 }
142}
143
144pub fn identifier_resolution_help() -> Value {
146 let modes: serde_json::Map<String, Value> = [
147 IdResolutionMode::UnscopedById,
148 IdResolutionMode::PrefixScopedToPrimary,
149 IdResolutionMode::FullAndPrefixScopedToPrimary,
150 IdResolutionMode::FullUuidOnlyScopedToPrimary,
151 IdResolutionMode::UnscopedFullUuidOnly,
152 IdResolutionMode::EdgeOrEventTarget,
153 ]
154 .into_iter()
155 .map(|mode| {
156 (
157 resolution_mode_key(mode).to_string(),
158 Value::String(
159 resolution_mode_contract(mode)
160 .expect("every non-NotApplicable mode has contract text")
161 .to_string(),
162 ),
163 )
164 })
165 .collect();
166
167 serde_json::json!({
168 "full_uuid": FULL_UUID_IDENTIFIER_HELP,
169 "short_prefix": SHORT_PREFIX_IDENTIFIER_HELP,
170 "parameter_rule": IDENTIFIER_PARAMETER_HELP,
171 "resolution_modes": modes,
172 })
173}
174
175mod traits;
176pub use traits::{
177 DispatchHook, KindHook, NoteUpdateEffect, PackByIdResolver, PackRuntime, SchemaPlan,
178};
179
180#[cfg(test)]
181use crate::error::DispatchError;
182use crate::error::{AuditObligationFailure, RuntimeError};
183use crate::KhiveRuntime;
184
185mod builder;
186pub use builder::{PackMetadataRegistry, VerbRegistryBuilder};
187
188mod catalog;
189mod dispatch;
190mod registry_access;
191mod request_identity;
192pub(crate) use request_identity::is_special_relation;
193pub use request_identity::{
194 InterceptedDispatchResult, PackSchemaCollisionError, RequestIdentity, VerbRegistry,
195 VerifiedActor,
196};
197
198pub(crate) const SPECIAL_RELATIONS: &[khive_types::EdgeRelation] = &[
206 khive_types::EdgeRelation::Supersedes,
207 khive_types::EdgeRelation::Supports,
208 khive_types::EdgeRelation::Refutes,
209];
210
211mod loading;
212pub use loading::{
213 ChannelIngestCapability, IngestAuditStore, PackFactory, PackInstall, PackLoadError,
214 PackRegistration, PackRegistry,
215};
216
217pub(crate) const CHANNEL_INGEST_CAPABLE_PACKS: &[&str] = &["comm"];
219
220mod audit;
221use audit::{
222 append_audit_event_best_effort, build_audit_storage_event, fold_audit_obligation,
223 link_audit_success_from_result, masked_audit_event, persist_git_digest_receipt,
224 GitDigestReceiptOutcome,
225};
226pub use audit::{
227 audit_admission_refused_obligation_count, audit_admission_refused_obligation_last_at_ms,
228 audit_admission_unresolved_obligation_count, audit_admission_unresolved_obligation_last_at_ms,
229 resolve_explicit_namespace,
230};
231pub(crate) use audit::{audit_append_failure_count, audit_obligation_append_failure_count};
232
233#[cfg(test)]
239#[path = "pack_tests.rs"]
240pub(crate) mod tests;
241
242#[cfg(test)]
245#[path = "pack/dep_tests.rs"]
246mod dep_tests;
247
248#[cfg(test)]
257#[path = "pack/note_update_sequencing_tests.rs"]
258mod note_update_sequencing_tests;
259
260#[cfg(test)]
263#[path = "pack/hook_tests.rs"]
264mod hook_tests;
265
266#[cfg(test)]
269#[path = "pack/help_tests.rs"]
270mod help_tests;
271
272#[cfg(test)]
275#[path = "pack/admission_allowlist_adr_tests.rs"]
276mod admission_allowlist_adr_tests;
277
278#[cfg(test)]
279#[path = "gate_argument_contract_tests.rs"]
280mod gate_argument_contract_tests;