载入中...
搜索中...
未找到
FactStore.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <set>
6#include <utility>
7
8namespace eve::emergence {
9namespace {
10
11const char* domainPrefix(FactDomain domain) {
12 switch (domain) {
13 case FactDomain::Value: return "value:";
14 case FactDomain::Tag: return "tag:";
15 case FactDomain::Attribute: return "attr:";
16 case FactDomain::Resource: return "resource:";
17 case FactDomain::State: return "state:";
18 case FactDomain::Authority: return "authority:";
19 case FactDomain::Policy: return "policy:";
20 }
21 return "value:";
22}
23
24const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
25 const auto it = object.find(std::string(name));
26 return it == object.end() ? nullptr : &it->second;
27}
28
29bool exactFields(const eve::Value::Object& object, std::initializer_list<std::string_view> allowed) {
30 const std::set<std::string_view> fields(allowed.begin(), allowed.end());
31 for (const auto& [name, unused] : object) {
32 (void)unused;
33 if (!fields.contains(name)) return false;
34 }
35 return true;
36}
37
38eve::Result<decision::ConditionReasonCode> parseReasonCode(std::string_view text) {
40 static const std::pair<std::string_view, R> names[] = {
41 {"passed", R::Passed},
42 {"child_failed", R::ChildFailed},
43 {"no_child_passed", R::NoChildPassed},
44 {"negated", R::Negated},
45 {"missing_value", R::MissingValue},
46 {"value_mismatch", R::ValueMismatch},
47 {"tag_missing", R::TagMissing},
48 {"tag_unavailable", R::TagUnavailable},
49 {"attribute_missing", R::AttributeMissing},
50 {"resource_missing", R::ResourceMissing},
51 {"state_missing", R::StateMissing},
52 {"state_mismatch", R::StateMismatch},
53 {"authority_denied", R::AuthorityDenied},
54 {"authority_unavailable", R::AuthorityUnavailable},
55 {"policy_rejected", R::PolicyRejected},
56 {"policy_unavailable", R::PolicyUnavailable},
57 {"invalid_condition", R::InvalidCondition},
58 };
59 for (const auto& [name, code] : names) {
61 }
63 eve::DiagnosticCode::InvalidArgument, "unknown condition reason code", "reason", {}, "emergence.fact"));
64}
65
67 const auto* object = value.getIf<eve::Value::Object>();
68 if (object == nullptr)
70 eve::DiagnosticCode::InvalidArgument, "policy result must be an object", {}, {}, "emergence.fact"));
71 if (!exactFields(*object, {"passed", "reason", "evidence", "details"}))
73 eve::DiagnosticCode::InvalidArgument, "policy result contains an unknown field", {}, {}, "emergence.fact"));
74 const auto* passed = field(*object, "passed");
75 const auto* reason = field(*object, "reason");
76 if (passed == nullptr || !passed->isBool())
78 eve::DiagnosticCode::InvalidArgument, "policy passed must be a boolean", "passed", {}, "emergence.fact"));
79 if (reason == nullptr || !reason->isString())
81 eve::DiagnosticCode::InvalidArgument, "policy reason must be a string", "reason", {}, "emergence.fact"));
82 auto code = parseReasonCode(reason->asString());
84 eve::Value evidence;
86 if (const auto* evidenceValue = field(*object, "evidence")) evidence = *evidenceValue;
87 if (const auto* detailsValue = field(*object, "details")) details = *detailsValue;
88 if (passed->asBool())
90 decision::ConditionResult::success(std::move(evidence), std::move(details)));
92 decision::ConditionResult::failed(code.value(), std::move(evidence), std::move(details)));
93}
94
95eve::Value encodePolicyResult(const decision::ConditionResult& result) {
97 object.emplace("passed", eve::Value(result.passed()));
98 object.emplace("reason", eve::Value(decision::conditionReasonCodeName(result.reasonCode())));
99 object.emplace("evidence", result.evidence());
100 object.emplace("details", result.details());
101 return eve::Value(std::move(object));
102}
103
104} // namespace
105
106std::string makeFactKey(FactDomain domain, std::string_view name) {
107 if (name.empty()) return {};
108 std::string key(domainPrefix(domain));
109 key.append(name);
110 return key;
111}
112
114 const auto fail = [](const char* message) {
117 };
118 if (key.empty()) return fail("fact key must be non-empty");
119 const auto slash = key.find(':');
120 if (slash == std::string_view::npos || slash == 0 || slash + 1 >= key.size())
121 return fail("fact key must use domain:name form");
122 const auto domainText = key.substr(0, slash);
123 const auto name = key.substr(slash + 1);
125 if (domainText == "value")
126 domain = FactDomain::Value;
127 else if (domainText == "tag")
128 domain = FactDomain::Tag;
129 else if (domainText == "attr")
130 domain = FactDomain::Attribute;
131 else if (domainText == "resource")
132 domain = FactDomain::Resource;
133 else if (domainText == "state")
134 domain = FactDomain::State;
135 else if (domainText == "authority")
136 domain = FactDomain::Authority;
137 else if (domainText == "policy")
138 domain = FactDomain::Policy;
139 else
140 return fail("unknown fact domain prefix");
141 return eve::Result<std::pair<FactDomain, std::string>>::success(std::make_pair(domain, std::string(name)));
142}
143
144template <class Map, class T>
145FactChange FactStore::assign(Map& map, std::string key, T value) {
146 auto it = map.find(key);
147 if (it != map.end()) {
148 if (it->second == value) return FactChange::Unchanged;
149 it->second = std::move(value);
150 return FactChange::Changed;
151 }
152 map.emplace(std::move(key), std::move(value));
153 return FactChange::Changed;
154}
155
157 values_.clear();
158 tags_.clear();
159 attributes_.clear();
160 resources_.clear();
161 states_.clear();
162 authorities_.clear();
163 policies_.clear();
164}
165
167 if (key.empty()) return FactChange::Unchanged;
168 return assign(values_, std::move(key), std::move(value));
169}
170
171FactChange FactStore::setTag(std::string tag, bool present) {
172 if (tag.empty()) return FactChange::Unchanged;
173 return assign(tags_, std::move(tag), present);
174}
175
177 if (key.empty()) return FactChange::Unchanged;
178 return assign(attributes_, std::move(key), std::move(value));
179}
180
182 if (key.empty()) return FactChange::Unchanged;
183 return assign(resources_, std::move(key), std::move(value));
184}
185
187 if (key.empty()) return FactChange::Unchanged;
188 return assign(states_, std::move(key), std::move(value));
189}
190
191FactChange FactStore::setAuthority(std::string scope, bool granted) {
192 if (scope.empty()) return FactChange::Unchanged;
193 return assign(authorities_, std::move(scope), granted);
194}
195
197 if (name.empty()) return FactChange::Unchanged;
198 auto it = policies_.find(name);
199 if (it != policies_.end()) {
200 if (it->second.passed() == result.passed() && it->second.reasonCode() == result.reasonCode() &&
201 it->second.evidence() == result.evidence())
203 it->second = std::move(result);
204 return FactChange::Changed;
205 }
206 policies_.emplace(std::move(name), std::move(result));
207 return FactChange::Changed;
208}
209
211 return values_.erase(std::string(key)) > 0 ? FactChange::Changed : FactChange::Unchanged;
212}
213
215 return tags_.erase(std::string(tag)) > 0 ? FactChange::Changed : FactChange::Unchanged;
216}
217
218std::optional<eve::Value> FactStore::value(std::string_view key) const {
219 auto it = values_.find(std::string(key));
220 return it == values_.end() ? std::nullopt : std::optional<eve::Value>(it->second);
221}
222
223std::optional<bool> FactStore::hasTag(std::string_view tag) const {
224 auto it = tags_.find(std::string(tag));
225 return it == tags_.end() ? std::nullopt : std::optional<bool>(it->second);
226}
227
228std::optional<eve::Value> FactStore::attribute(std::string_view key) const {
229 auto it = attributes_.find(std::string(key));
230 return it == attributes_.end() ? std::nullopt : std::optional<eve::Value>(it->second);
231}
232
233std::optional<eve::Value> FactStore::resource(std::string_view key) const {
234 auto it = resources_.find(std::string(key));
235 return it == resources_.end() ? std::nullopt : std::optional<eve::Value>(it->second);
236}
237
238std::optional<eve::Value> FactStore::state(std::string_view key) const {
239 auto it = states_.find(std::string(key));
240 return it == states_.end() ? std::nullopt : std::optional<eve::Value>(it->second);
241}
242
243std::optional<bool> FactStore::authority(std::string_view scope) const {
244 auto it = authorities_.find(std::string(scope));
245 return it == authorities_.end() ? std::nullopt : std::optional<bool>(it->second);
246}
247
248std::optional<decision::ConditionResult> FactStore::policy(std::string_view name, const eve::Value&) const {
249 auto it = policies_.find(std::string(name));
250 return it == policies_.end() ? std::nullopt : std::optional<decision::ConditionResult>(it->second);
251}
252
253std::string FactStore::snapshotJson() const {
256 for (const auto& [key, value] : values_) values.emplace(key, value);
258 for (const auto& [key, value] : tags_) tags.emplace(key, eve::Value(value));
259 eve::Value::Object attributes;
260 for (const auto& [key, value] : attributes_) attributes.emplace(key, value);
262 for (const auto& [key, value] : resources_) resources.emplace(key, value);
263 eve::Value::Object states;
264 for (const auto& [key, value] : states_) states.emplace(key, value);
265 eve::Value::Object authorities;
266 for (const auto& [key, value] : authorities_) authorities.emplace(key, eve::Value(value));
267 eve::Value::Object policies;
268 for (const auto& [key, value] : policies_) policies.emplace(key, encodePolicyResult(value));
269 root.emplace("schema", eve::Value("eve.emergence.facts"));
270 root.emplace("version", eve::Value(std::int64_t{1}));
271 root.emplace("values", eve::Value(std::move(values)));
272 root.emplace("tags", eve::Value(std::move(tags)));
273 root.emplace("attributes", eve::Value(std::move(attributes)));
274 root.emplace("resources", eve::Value(std::move(resources)));
275 root.emplace("states", eve::Value(std::move(states)));
276 root.emplace("authorities", eve::Value(std::move(authorities)));
277 root.emplace("policies", eve::Value(std::move(policies)));
278 auto encoded = eve::Value(std::move(root)).toJson();
279 return encoded ? std::move(encoded).takeValue() : std::string("{}");
280}
281
283 auto parsed = eve::Value::fromJson(json);
284 if (!parsed) return eve::Result<void>::failure(parsed.status());
285 const auto* object = parsed.value().getIf<eve::Value::Object>();
286 if (object == nullptr)
288 eve::DiagnosticCode::InvalidArgument, "fact snapshot must be an object", {}, {}, "emergence.fact"));
289 if (!exactFields(*object, {"schema", "version", "values", "tags", "attributes", "resources", "states",
290 "authorities", "policies"}))
292 eve::DiagnosticCode::InvalidArgument, "fact snapshot contains an unknown field", {}, {}, "emergence.fact"));
293 const auto* schema = field(*object, "schema");
294 const auto* version = field(*object, "version");
295 if (schema == nullptr || !schema->isString() || schema->asString() != "eve.emergence.facts")
297 eve::DiagnosticCode::InvalidArgument, "unexpected fact snapshot schema", "schema", {}, "emergence.fact"));
298 if (version == nullptr || !version->isInt64() || version->asInt() != 1)
300 eve::DiagnosticCode::UnknownVersion, "unsupported fact snapshot version", "version", {}, "emergence.fact"));
301
302 FactStore next;
303 auto loadObject = [&](const char* fieldName, auto&& setter) -> eve::Result<void> {
304 const auto it = object->find(fieldName);
305 if (it == object->end()) return eve::Result<void>::success();
306 const auto* map = it->second.getIf<eve::Value::Object>();
307 if (map == nullptr)
309 eve::DiagnosticCode::InvalidArgument, "fact field must be an object", fieldName, {}, "emergence.fact"));
310 for (const auto& [key, value] : *map) {
311 if (!setter(next, key, value))
313 eve::DiagnosticCode::InvalidArgument, "invalid fact entry", fieldName, {}, "emergence.fact"));
314 }
316 };
317
318 if (auto r = loadObject("values",
319 [](FactStore& store, const std::string& key, const eve::Value& value) {
320 (void)store.setValue(key, value);
321 return true;
322 });
323 !r)
324 return r;
325 if (auto r = loadObject("tags",
326 [](FactStore& store, const std::string& key, const eve::Value& value) {
327 if (!value.isBool()) return false;
328 (void)store.setTag(key, value.asBool());
329 return true;
330 });
331 !r)
332 return r;
333 if (auto r = loadObject("attributes",
334 [](FactStore& store, const std::string& key, const eve::Value& value) {
335 (void)store.setAttribute(key, value);
336 return true;
337 });
338 !r)
339 return r;
340 if (auto r = loadObject("resources",
341 [](FactStore& store, const std::string& key, const eve::Value& value) {
342 (void)store.setResource(key, value);
343 return true;
344 });
345 !r)
346 return r;
347 if (auto r = loadObject("states",
348 [](FactStore& store, const std::string& key, const eve::Value& value) {
349 (void)store.setState(key, value);
350 return true;
351 });
352 !r)
353 return r;
354 if (auto r = loadObject("authorities",
355 [](FactStore& store, const std::string& key, const eve::Value& value) {
356 if (!value.isBool()) return false;
357 (void)store.setAuthority(key, value.asBool());
358 return true;
359 });
360 !r)
361 return r;
362 if (auto r = loadObject("policies",
363 [](FactStore& store, const std::string& key, const eve::Value& value) {
364 auto decoded = decodePolicyResult(value);
365 if (!decoded) return false;
366 (void)store.setPolicy(key, std::move(decoded).takeValue());
367 return true;
368 });
369 !r)
370 return r;
371
372 *this = std::move(next);
374}
375
376} // namespace eve::emergence
double value
int root
Definition AnimSmr.cpp:119
Stable, structured diagnostics shared by engine modules.
std::map< std::string, Var > values
Authoritative, rule-visible world facts implementing EvaluationContext.
std::string message
DiagnosticCode code
std::uint32_t key
double r
JobScope scope
std::string text
std::string name
const std::string * tag
std::vector< std::weak_ptr< DeviceBytes > > resources
Definition OnnxGpgpu.cpp:55
std::vector< std::string > fields
Definition PlayHost.cpp:111
Json object
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
Explain one condition evaluation in a UI- and log-friendly form.
Definition Condition.h:129
bool passed() const noexcept
Whether the condition passed.
Definition Condition.h:137
ConditionReasonCode reasonCode() const noexcept
Stable machine-readable reason code.
Definition Condition.h:139
static ConditionResult failed(ConditionReasonCode reason, Value evidence={}, Value details=Value::Object{})
Construct a rejected result with a stable reason and explanation.
static ConditionResult success(Value evidence={}, Value details=Value::Object{})
Construct a passed result with optional evidence and details.
const Value & evidence() const noexcept
Observed value or child evidence owned by this result.
Definition Condition.h:141
Mutable fact bag that doubles as a read-only Condition evaluation context.
Definition FactStore.h:60
std::string snapshotJson() const
Export deterministic compact JSON for runtime snapshots.
FactChange setPolicy(std::string name, decision::ConditionResult result)
Register or replace a read-only policy result.
eve::Result< void > restoreJson(std::string_view json)
Transactionally restore facts from a snapshot produced by snapshotJson().
FactChange setValue(std::string key, eve::Value value)
Set a compare/value fact.
FactChange setTag(std::string tag, bool present)
Set tag membership; present=true means the tag exists.
std::optional< eve::Value > attribute(std::string_view key) const override
Query an attribute value; nullopt means the attribute is absent.
std::optional< bool > authority(std::string_view scope) const override
Query authority without changing grants, leases, or state.
FactChange setState(std::string key, eve::Value value)
Set a state value.
FactChange setAttribute(std::string key, eve::Value value)
Set an attribute value.
void clear()
Clear every fact without invoking callbacks.
std::optional< bool > hasTag(std::string_view tag) const override
Query tag membership; nullopt means the tag source is unavailable.
std::optional< decision::ConditionResult > policy(std::string_view name, const eve::Value &arguments) const override
Evaluate a named read-only policy with owned arguments.
std::optional< eve::Value > resource(std::string_view key) const override
Query a resource amount or resource marker.
FactChange clearTag(std::string_view tag)
Erase a tag fact.
FactChange setAuthority(std::string scope, bool granted)
Set authority for a scope.
std::optional< eve::Value > state(std::string_view key) const override
Query a state value for StateEquals nodes.
FactChange setResource(std::string key, eve::Value value)
Set a resource value.
std::optional< eve::Value > value(std::string_view key) const override
Query a named scalar or structured value for Compare nodes.
FactChange clearValue(std::string_view key)
Erase a value fact.
const char * conditionReasonCodeName(ConditionReasonCode code) noexcept
Return the stable lowercase spelling of a condition reason code. @ownership Borrowed process-static t...
Definition Condition.cpp:82
ConditionReasonCode
Stable machine-readable explanation code for a condition outcome.
Definition Condition.h:74
FactChange
Whether a FactStore mutation changed stored state.
Definition FactStore.h:34
FactDomain
Domain partition of a fact key used by the watch index.
Definition FactStore.h:23
eve::Result< std::pair< FactDomain, std::string > > parseFactKey(std::string_view key)
Parse a canonical watch key into domain and leaf name.
std::string makeFactKey(FactDomain domain, std::string_view name)
Build the canonical watch key for one fact domain.
const EditorValue * field(const EditorValue &value, const char *name)