载入中...
搜索中...
未找到
PolicyRegistry.cpp
浏览该文件的文档.
2
5
6#include <simplesquirrel/simplesquirrel.hpp>
7
8#include <charconv>
9#include <cstdint>
10#include <exception>
11#include <limits>
12#include <utility>
13
15namespace {
16
17const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
18 const auto it = object.find(std::string(name));
19 return it == object.end() ? nullptr : &it->second;
20}
21
22bool readString(const eve::Value::Object& object, std::string_view name, std::string& output) {
23 const auto* value = field(object, name);
24 const auto* text = value ? value->getIf<std::string>() : nullptr;
25 if (!text) return false;
26 output = *text;
27 return true;
28}
29
30bool readPositiveSchemaVersion(const eve::Value& value, eve::SchemaVersion& output) {
31 const auto* number = value.getIf<std::int64_t>();
32 if (!number || *number <= 0) return false;
33 output = eve::SchemaVersion(static_cast<std::uint64_t>(*number));
34 return true;
35}
36
37bool readInt(const eve::Value& value, int& output) {
38 const auto* number = value.getIf<std::int64_t>();
39 if (!number || *number < std::numeric_limits<int>::min() || *number > std::numeric_limits<int>::max()) return false;
40 output = static_cast<int>(*number);
41 return true;
42}
43
44bool readUint64String(const eve::Value& value, std::uint64_t& output) {
45 const auto* text = value.getIf<std::string>();
46 if (!text || text->empty()) return false;
47 const auto [end, error] = std::from_chars(text->data(), text->data() + text->size(), output);
48 return error == std::errc{} && end == text->data() + text->size();
49}
50
51eve::Value parseStoredJson(std::string_view text) {
52 auto parsed = eve::Value::fromJson(text);
53 if (!parsed.ok()) std::terminate();
54 return std::move(parsed).takeValue();
55}
56
57eve::LogicalId policySchema() {
58 const auto schema = eve::LogicalId::parse("policyregistry:registry");
59 if (!schema) std::terminate();
60 return *schema;
61}
62
63const eve::SnapshotMigrationChain& policyMigrations() {
64 static const eve::SnapshotMigrationChain chain = [] {
66 const auto registration =
67 result.add(policySchema(), eve::SchemaVersion(0), eve::SchemaVersion(1),
69 const auto* object = payload.getIf<eve::Value::Object>();
70 if (!object)
72 eve::DiagnosticCode::ParseError, "policy snapshot payload must be an object"));
73 eve::Value::Object migrated = *object;
74 migrated["version"] = eve::Value(std::int64_t(1));
75 return eve::Result<eve::Value>::success(eve::Value(std::move(migrated)));
76 });
77 if (!registration.ok()) std::terminate();
78 return result;
79 }();
80 return chain;
81}
82
83eve::Result<PolicyDescriptor> makeDescriptor(const std::string& domain, const std::string& name, int version,
84 int priority, bool enabled, const std::string& kindName,
85 const std::string& schemaId, const std::string& metadataJson) {
86 if (domain.empty() || name.empty())
88 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "policy domain and name must not be empty"));
89 if (version <= 0)
91 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "policy schema version must be positive"));
92
94 if (!parseImplementationKind(kindName, kind))
96 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "invalid implementation kind"));
97
98 auto metadata = eve::Value::fromJson(metadataJson);
99 if (!metadata.ok()) return eve::Result<PolicyDescriptor>::failure(metadata.status());
100 auto metadataValue = std::move(metadata).takeValue();
101 if (!metadataValue.isObject())
103 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "policy metadata must be a JSON object"));
104 auto canonical = metadataValue.toJson();
105 if (!canonical.ok()) return eve::Result<PolicyDescriptor>::failure(canonical.status());
106
107 if (!schemaId.empty() && eve::schema::SchemaRegistry::resolve(schemaId, version)) {
108 const auto errors = eve::schema::SchemaRegistry::validate(schemaId, version, metadataJson);
109 if (!errors.empty()) {
110 const auto& error = errors.front();
114 {"schemaId", schemaId}, {"schemaVersion", std::to_string(version)}, {"validationCode", error.code}},
115 "policyregistry.schema"));
116 }
117 }
118
120 descriptor.domain = domain;
121 descriptor.name = name;
122 descriptor.version = eve::SchemaVersion(static_cast<std::uint64_t>(version));
123 descriptor.priority = priority;
124 descriptor.enabled = enabled;
125 descriptor.kind = kind;
126 descriptor.schemaId = schemaId;
127 descriptor.metadataJson = std::move(canonical).takeValue();
129}
130
131eve::Value policyHandleValue(const PolicyHandle& handle) {
133 {"domain", handle.domain},
134 {"name", handle.name},
135 {"generation", eve::Value(static_cast<std::int64_t>(handle.generation.value()))},
136 });
137}
138
139eve::Value policyDescriptorValue(const PolicyDescriptor& descriptor) {
141 {"domain", descriptor.domain},
142 {"name", descriptor.name},
143 {"version", eve::Value(static_cast<std::int64_t>(descriptor.version.value()))},
144 {"priority", eve::Value(descriptor.priority)},
145 {"enabled", eve::Value(descriptor.enabled)},
147 {"schemaId", eve::Value(descriptor.schemaId)},
148 {"metadataJson", eve::Value(descriptor.metadataJson)},
149 {"generation", eve::Value(static_cast<std::int64_t>(descriptor.generation.value()))},
150 });
151}
152
153} // namespace
154
156 switch (kind) {
157 case ImplementationKind::Builtin: return "builtin";
158 case ImplementationKind::Script: return "script";
159 case ImplementationKind::Batch: return "batch";
160 }
161 return {};
162}
163
165 if (name == "builtin")
166 kind = ImplementationKind::Builtin;
167 else if (name == "script")
168 kind = ImplementationKind::Script;
169 else if (name == "batch")
170 kind = ImplementationKind::Batch;
171 else
172 return false;
173 return true;
174}
175
176PolicyRegistry::PolicyRegistry(eve::PersistentId instanceId)
177 : storage_([](PolicyDescriptor& value, eve::Generation generation) { value.generation = generation; }),
178 instanceId_(instanceId) {}
179
180PolicyEvent PolicyRegistry::projectEvent(const Storage::Event& event) {
181 PolicyEvent result;
182 result.sequence = event.sequence;
183 if (event.label == "enabled")
184 result.name = "policy_enabled";
185 else if (event.operation == eve::RegistryOperation::Insert)
186 result.name = "policy_registered";
187 else if (event.operation == eve::RegistryOperation::Replace)
188 result.name = "policy_replaced";
189 else
190 result.name = "policy_removed";
191 result.domain = event.key.first;
192 result.policyName = event.key.second;
193 result.version = event.data.version;
194 result.generation = event.generation;
195 result.enabled = event.data.enabled;
196 return result;
197}
198
199PolicyHandle PolicyRegistry::projectHandle(const Storage::Handle& handle) {
200 return PolicyHandle{handle.key.first, handle.key.second, handle.generation};
201}
202
203PolicyRegistry::Storage::Handle PolicyRegistry::storageHandle(const PolicyHandle& handle) {
204 return Storage::Handle{Key{handle.domain, handle.name}, handle.generation};
205}
206
207void PolicyRegistry::clearEventProjection() const { eventProjection_.clear(); }
208
209eve::Result<PolicyHandle> PolicyRegistry::insert(const std::string& domain, const std::string& name, int version,
210 int priority, bool enabled, const std::string& kind,
211 const std::string& schemaId, const std::string& metadataJson) {
212 auto descriptor = makeDescriptor(domain, name, version, priority, enabled, kind, schemaId, metadataJson);
213 if (!descriptor.ok()) {
215 }
216 PolicyDescriptor value = std::move(descriptor).takeValue();
217 const EventData data{value.version, value.enabled};
218 auto result = storage_.insert(Key{domain, name}, std::move(value), data);
219 if (!result.ok()) {
220 return eve::Result<PolicyHandle>::failure(result.status());
221 }
222 const eve::Status mutationStatus = result.status();
223 const auto handle = std::move(result).takeValue();
224 clearEventProjection();
225 if (const auto next = revision_.incremented()) revision_ = *next;
226 return eve::Result<PolicyHandle>::success(projectHandle(handle), mutationStatus);
227}
228
229eve::Result<PolicyHandle> PolicyRegistry::replace(const std::string& domain, const std::string& name, int version,
230 int priority, bool enabled, const std::string& kind,
231 const std::string& schemaId, const std::string& metadataJson) {
232 auto descriptor = makeDescriptor(domain, name, version, priority, enabled, kind, schemaId, metadataJson);
233 if (!descriptor.ok()) {
235 }
236 PolicyDescriptor value = std::move(descriptor).takeValue();
237 const EventData data{value.version, value.enabled};
238 auto result = storage_.replace(Key{domain, name}, std::move(value), data);
239 if (!result.ok()) {
240 return eve::Result<PolicyHandle>::failure(result.status());
241 }
242 const eve::Status mutationStatus = result.status();
243 const auto handle = std::move(result).takeValue();
244 clearEventProjection();
245 if (const auto next = revision_.incremented()) revision_ = *next;
246 return eve::Result<PolicyHandle>::success(projectHandle(handle), mutationStatus);
247}
248
249eve::Result<PolicyHandle> PolicyRegistry::remove(const std::string& domain, const std::string& name) {
250 const Key key{domain, name};
251 auto current = storage_.resolve(key);
252 if (!current.ok()) {
254 }
255 const auto& descriptor = current.value().get();
256 const EventData data{descriptor.version, descriptor.enabled};
257 auto result = storage_.remove(key, data);
258 if (!result.ok()) {
259 return eve::Result<PolicyHandle>::failure(result.status());
260 }
261 const eve::Status mutationStatus = result.status();
262 const auto handle = std::move(result).takeValue();
263 clearEventProjection();
264 if (const auto next = revision_.incremented()) revision_ = *next;
265 return eve::Result<PolicyHandle>::success(projectHandle(handle), mutationStatus);
266}
267
268eve::Result<PolicyHandle> PolicyRegistry::enable(const std::string& domain, const std::string& name, bool enabled) {
269 const Key key{domain, name};
270 auto current = storage_.resolve(key);
271 if (!current.ok()) {
273 }
274 PolicyDescriptor value = current.value().get();
275 if (value.enabled == enabled) {
276 auto handle = storage_.handle(key);
277 if (!handle.ok()) {
279 }
280 return eve::Result<PolicyHandle>::success(projectHandle(handle.value()),
282 }
283 value.enabled = enabled;
284 const EventData data{value.version, value.enabled};
285 auto result = storage_.replace(key, std::move(value), data, "enabled");
286 if (!result.ok()) {
287 return eve::Result<PolicyHandle>::failure(result.status());
288 }
289 const eve::Status mutationStatus = result.status();
290 const auto handle = std::move(result).takeValue();
291 clearEventProjection();
292 if (const auto next = revision_.incremented()) revision_ = *next;
293 return eve::Result<PolicyHandle>::success(projectHandle(handle), mutationStatus);
294}
295
297 const std::string& name) const {
298 auto result = storage_.resolve(Key{domain, name});
299 if (!result.ok()) return eve::ResultRef<const PolicyDescriptor>::failure(result.status());
300 return eve::ResultRef<const PolicyDescriptor>::success(std::cref(result.value().get()));
301}
302
304 auto result = storage_.resolve(storageHandle(handle));
305 if (!result.ok()) return eve::ResultRef<const PolicyDescriptor>::failure(result.status());
306 return eve::ResultRef<const PolicyDescriptor>::success(std::cref(result.value().get()));
307}
308
309eve::Result<PolicyHandle> PolicyRegistry::handle(const std::string& domain, const std::string& name) const {
310 auto result = storage_.handle(Key{domain, name});
311 if (!result.ok()) return eve::Result<PolicyHandle>::failure(result.status());
312 return eve::Result<PolicyHandle>::success(projectHandle(result.value()));
313}
314
315eve::Result<eve::Generation> PolicyRegistry::generationOf(const std::string& domain, const std::string& name) const {
316 return storage_.generationOf(Key{domain, name});
317}
318
319bool PolicyRegistry::isTombstone(const std::string& domain, const std::string& name) const noexcept {
320 return storage_.isTombstone(Key{domain, name});
321}
322
323bool PolicyRegistry::isStale(const PolicyHandle& handle) const noexcept {
324 return storage_.isStale(storageHandle(handle));
325}
326
327const PolicyDescriptor* PolicyRegistry::select(const std::string& domain) const {
328 const PolicyDescriptor* selected = nullptr;
329 for (std::size_t index = 0;; ++index) {
330 const auto* key = storage_.keyAt(index);
331 if (!key) break;
332 if (key->first != domain) continue;
333 const auto* candidate = storage_.at(index);
334 if (candidate && candidate->enabled &&
335 (!selected || candidate->priority > selected->priority ||
336 (candidate->priority == selected->priority && candidate->name < selected->name)))
337 selected = candidate;
338 }
339 return selected;
340}
341
342int PolicyRegistry::size() const { return static_cast<int>(storage_.size()); }
343
344int PolicyRegistry::countDomain(const std::string& domain) const {
345 int count = 0;
346 for (std::size_t index = 0;; ++index) {
347 const auto* key = storage_.keyAt(index);
348 if (!key) break;
349 if (key->first == domain) ++count;
350 }
351 return count;
352}
353
355 if (index < 0) return nullptr;
356 return storage_.at(static_cast<std::size_t>(index));
357}
358
359const PolicyDescriptor* PolicyRegistry::atDomain(const std::string& domain, int index) const {
360 if (index < 0) return nullptr;
361 int matching = 0;
362 for (std::size_t position = 0;; ++position) {
363 const auto* key = storage_.keyAt(position);
364 if (!key) break;
365 if (key->first != domain) continue;
366 if (matching++ == index) return storage_.at(position);
367 }
368 return nullptr;
369}
370
371int PolicyRegistry::eventCount() const { return static_cast<int>(storage_.eventCount()); }
372
374 if (index < 0 || static_cast<std::size_t>(index) >= storage_.eventCount()) return nullptr;
375 if (eventProjection_.size() != storage_.eventCount()) {
376 eventProjection_.clear();
377 eventProjection_.reserve(storage_.eventCount());
378 for (std::size_t i = 0; i < storage_.eventCount(); ++i)
379 eventProjection_.push_back(projectEvent(*storage_.eventAt(i)));
380 }
381 return &eventProjection_[static_cast<std::size_t>(index)];
382}
383
385 storage_.clearEvents();
386 clearEventProjection();
387 if (const auto next = revision_.incremented()) revision_ = *next;
388}
389
390std::string PolicyRegistry::snapshotJson() const {
391 const auto state = storage_.snapshotState();
392 eve::Value::Array descriptors;
395 descriptors.reserve(storage_.size());
396 events.reserve(storage_.eventCount());
397 generations.reserve(state.entries.size());
398
399 for (const auto& [key, entry] : state.entries) {
401 generation.emplace("domain", eve::Value(key.first));
402 generation.emplace("generation", eve::Value(std::to_string(entry.generation.value())));
403 generation.emplace("name", eve::Value(key.second));
404 generations.emplace_back(std::move(generation));
405
406 if (!entry.value.has_value()) continue;
407 const auto& descriptor = *entry.value;
409 item.emplace("domain", eve::Value(descriptor.domain));
410 item.emplace("enabled", eve::Value(descriptor.enabled));
411 item.emplace("generation", eve::Value(std::to_string(entry.generation.value())));
412 item.emplace("kind", eve::Value(implementationKindName(descriptor.kind)));
413 item.emplace("metadata", parseStoredJson(descriptor.metadataJson));
414 item.emplace("name", eve::Value(descriptor.name));
415 item.emplace("priority", eve::Value(static_cast<std::int64_t>(descriptor.priority)));
416 item.emplace("schemaId", eve::Value(descriptor.schemaId));
417 item.emplace("version", eve::Value(static_cast<std::int64_t>(descriptor.version.value())));
418 descriptors.emplace_back(std::move(item));
419 }
420
421 for (const auto& raw : state.events) {
422 const auto event = projectEvent(raw);
424 item.emplace("domain", eve::Value(event.domain));
425 item.emplace("enabled", eve::Value(event.enabled));
426 item.emplace("generation", eve::Value(std::to_string(event.generation.value())));
427 item.emplace("name", eve::Value(event.name));
428 item.emplace("policyName", eve::Value(event.policyName));
429 item.emplace("sequence", eve::Value(std::to_string(event.sequence.value())));
430 item.emplace("version", eve::Value(static_cast<std::int64_t>(event.version.value())));
431 events.emplace_back(std::move(item));
432 }
433
435 root.emplace("descriptors", eve::Value(std::move(descriptors)));
436 root.emplace("events", eve::Value(std::move(events)));
437 root.emplace("generations", eve::Value(std::move(generations)));
438 root.emplace("nextEventSequence", eve::Value(std::to_string(state.nextEventSequence.value())));
439 root.emplace("version", eve::Value(std::int64_t(1)));
440 auto encoded = eve::Value(std::move(root)).toJson();
441 if (!encoded.ok()) std::terminate();
442 return std::move(encoded).takeValue();
443}
444
446 auto parsed = eve::Value::fromJson(json);
447 if (!parsed.ok()) {
448 return eve::Result<void>::failure(parsed.status());
449 }
450 const eve::Value value = std::move(parsed).takeValue();
451 const auto* root = value.getIf<eve::Value::Object>();
452 if (!root) {
454 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "policy snapshot must be an object"));
455 }
456 const auto* snapshotVersion = field(*root, "version");
457 const auto* versionNumber = snapshotVersion ? snapshotVersion->getIf<std::int64_t>() : nullptr;
458 const auto* descriptorValue = field(*root, "descriptors");
459 const auto* generationValue = field(*root, "generations");
460 const auto* eventValue = field(*root, "events");
461 const auto* nextSequenceValue = field(*root, "nextEventSequence");
462 const auto* descriptorArray = descriptorValue ? descriptorValue->getIf<eve::Value::Array>() : nullptr;
463 const auto* generationArray = generationValue ? generationValue->getIf<eve::Value::Array>() : nullptr;
464 const auto* eventArray = eventValue ? eventValue->getIf<eve::Value::Array>() : nullptr;
465 std::uint64_t nextSequence = 0;
466 if (!versionNumber || *versionNumber != 1 || !descriptorArray || !generationArray || !eventArray ||
467 !nextSequenceValue || !readUint64String(*nextSequenceValue, nextSequence) || nextSequence == 0) {
469 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "invalid policy snapshot fields"));
470 }
471
472 Storage::State candidate;
473 for (std::size_t index = 0; index < generationArray->size(); ++index) {
474 const auto* item = (*generationArray)[index].getIf<eve::Value::Object>();
475 std::string domain;
476 std::string name;
477 std::uint64_t generation = 0;
478 const auto* generationField = item ? field(*item, "generation") : nullptr;
479 if (!item || !readString(*item, "domain", domain) || !readString(*item, "name", name) || domain.empty() ||
480 name.empty() || !generationField || !readUint64String(*generationField, generation) || generation == 0 ||
481 !candidate.entries.emplace(Key{domain, name}, Storage::Entry{eve::Generation(generation), std::nullopt})
482 .second) {
484 eve::DiagnosticCode::ParseError, "invalid policy generation at index " + std::to_string(index)));
485 }
486 }
487
488 for (std::size_t index = 0; index < descriptorArray->size(); ++index) {
489 const auto* item = (*descriptorArray)[index].getIf<eve::Value::Object>();
490 std::string domain;
491 std::string name;
492 std::string kindName;
493 std::string schemaId;
494 eve::SchemaVersion version;
495 int priority = 0;
496 std::uint64_t generation = 0;
497 bool enabled = false;
498 const auto* versionField = item ? field(*item, "version") : nullptr;
499 const auto* priorityField = item ? field(*item, "priority") : nullptr;
500 const auto* enabledField = item ? field(*item, "enabled") : nullptr;
501 const auto* generationField = item ? field(*item, "generation") : nullptr;
502 const auto* metadata = item ? field(*item, "metadata") : nullptr;
504 if (!item || !readString(*item, "domain", domain) || !readString(*item, "name", name) || domain.empty() ||
505 name.empty() || !versionField || !readPositiveSchemaVersion(*versionField, version) || !priorityField ||
506 !readInt(*priorityField, priority) || !enabledField || !enabledField->isBool() || !generationField ||
507 !readUint64String(*generationField, generation) || generation == 0 ||
508 !readString(*item, "kind", kindName) || !parseImplementationKind(kindName, kind) || !metadata ||
509 !metadata->isObject() || !readString(*item, "schemaId", schemaId)) {
511 eve::DiagnosticCode::ParseError, "invalid policy descriptor at index " + std::to_string(index)));
512 }
513 enabled = *enabledField->getIf<bool>();
514 auto entry = candidate.entries.find(Key{domain, name});
515 if (entry == candidate.entries.end() || entry->second.generation != eve::Generation(generation) ||
516 entry->second.value.has_value()) {
519 "inconsistent policy generation at index " + std::to_string(index)));
520 }
521 auto metadataJson = metadata->toJson();
522 if (!metadataJson.ok()) {
523 return eve::Result<void>::failure(metadataJson.status());
524 }
526 descriptor.domain = domain;
527 descriptor.name = name;
528 descriptor.version = version;
529 descriptor.priority = priority;
530 descriptor.enabled = enabled;
531 descriptor.kind = kind;
532 descriptor.schemaId = schemaId;
534 descriptor.metadataJson = std::move(metadataJson).takeValue();
535 entry->second.value = std::move(descriptor);
536 }
537
538 eve::EventSequence previousSequence;
539 for (std::size_t index = 0; index < eventArray->size(); ++index) {
540 const auto* item = (*eventArray)[index].getIf<eve::Value::Object>();
541 std::string name;
542 std::string domain;
543 std::string policyName;
544 std::uint64_t sequence = 0;
545 std::uint64_t generation = 0;
546 const auto* sequenceField = item ? field(*item, "sequence") : nullptr;
547 const auto* generationField = item ? field(*item, "generation") : nullptr;
548 const auto* enabledField = item ? field(*item, "enabled") : nullptr;
549 const auto* versionField = item ? field(*item, "version") : nullptr;
550 eve::SchemaVersion version{1};
551 if (!item || !readString(*item, "name", name) || !readString(*item, "domain", domain) ||
552 !readString(*item, "policyName", policyName) || domain.empty() || policyName.empty() ||
553 (name != "policy_registered" && name != "policy_replaced" && name != "policy_removed" &&
554 name != "policy_enabled") ||
555 !sequenceField || !readUint64String(*sequenceField, sequence) || sequence == 0 ||
556 (!previousSequence.isZero() && eve::EventSequence(sequence) <= previousSequence) || !generationField ||
557 !readUint64String(*generationField, generation) || generation == 0 || !enabledField ||
558 !enabledField->isBool()) {
560 eve::DiagnosticCode::ParseError, "invalid policy event at index " + std::to_string(index)));
561 }
562 if (versionField && !readPositiveSchemaVersion(*versionField, version)) {
564 eve::DiagnosticCode::ParseError, "invalid policy event version at index " + std::to_string(index)));
565 }
566 Storage::Event event;
567 event.sequence = eve::EventSequence(sequence);
568 event.operation = name == "policy_removed"
571 event.key = Key{domain, policyName};
572 event.generation = eve::Generation(generation);
573 event.tombstone = name == "policy_removed";
574 event.label = name == "policy_enabled" ? "enabled" : std::string{};
575 event.data.version = version;
576 event.data.enabled = *enabledField->getIf<bool>();
577 candidate.events.push_back(std::move(event));
578 previousSequence = eve::EventSequence(sequence);
579 }
580 if (!previousSequence.isZero() && eve::EventSequence(nextSequence) <= previousSequence) {
582 "next event sequence must exceed retained events"));
583 }
584 candidate.nextEventSequence = eve::EventSequence(nextSequence);
585
586 auto restored = storage_.restoreState(std::move(candidate));
587 if (!restored.ok()) {
588 return eve::Result<void>::failure(restored.status());
589 }
590 clearEventProjection();
591 revision_ = eve::Revision(nextSequence - 1);
592 tick_ = eve::SimulationTick{};
594}
595
599 return eve::makeSnapshotEnvelope("policyregistry.registry", policySchema(), eve::SchemaVersion(1), instanceId_,
600 revision_, tick_, std::move(payload).takeValue(), hashProvider);
601}
602
604 const eve::SnapshotHashProvider& hashProvider) {
605 if (source.type != "policyregistry.registry" || source.schema != policySchema())
607 eve::DiagnosticCode::InvalidArgument, "snapshot does not belong to policyregistry::PolicyRegistry"));
608 if (!instanceId_.isNil() && source.instanceId != instanceId_)
610 eve::DiagnosticCode::Conflict, "snapshot instanceId does not match policyregistry::PolicyRegistry"));
611 auto migrated = policyMigrations().migrate(source, eve::SchemaVersion(1), hashProvider);
612 if (!migrated.ok()) return eve::Result<void>::failure(migrated.status());
613 auto payload = migrated.value().payload.toJson();
614 if (!payload.ok()) return eve::Result<void>::failure(payload.status());
615 auto restored = restoreJson(std::move(payload).takeValue());
616 if (!restored.ok()) return eve::Result<void>::failure(restored.status());
617 const auto& candidate = migrated.value();
618 instanceId_ = candidate.instanceId;
619 revision_ = candidate.revision;
620 tick_ = candidate.tick;
622}
623
625 auto value = snapshot(hashProvider);
626 if (!value.ok()) return eve::Result<std::string>::failure(value.status());
627 return std::move(value).andThen(
628 [](eve::SnapshotEnvelope&& envelope) { return eve::serializeSnapshotEnvelope(envelope); });
629}
630
632 const eve::SnapshotHashProvider& hashProvider) {
633 auto source = eve::parseSnapshotEnvelope(json, hashProvider);
634 if (!source.ok()) return eve::Result<void>::failure(source.status());
635 return restoreSnapshot(std::move(source).takeValue(), hashProvider);
636}
637
639 return storage_.subscribe([callback = std::move(callback)](const Storage::Event& event) {
640 if (!callback) return;
641 const PolicyEvent projection = PolicyRegistry::projectEvent(event);
642 callback(projection);
643 });
644}
645
647 registries_.push_back(std::make_unique<PolicyRegistry>());
648 return registries_.back().get();
649}
650
652
653void PolicyRegistryModule::expose(ssq::Table& table) {
654 const HSQUIRRELVM vm = table.getHandle();
655 auto descriptor = table.addClass<PolicyDescriptor>(
656 "PolicyDescriptor", std::function<PolicyDescriptor*()>([]() -> PolicyDescriptor* { return nullptr; }), false);
657 descriptor.addFunc("getDomain", [](PolicyDescriptor* v) { return v ? v->domain : std::string{}; });
658 descriptor.addFunc("getName", [](PolicyDescriptor* v) { return v ? v->name : std::string{}; });
659 descriptor.addFunc("getVersion", [](PolicyDescriptor* v) {
660 return v ? static_cast<std::int64_t>(v->version.value()) : std::int64_t{0};
661 });
662 descriptor.addFunc("getPriority", [](PolicyDescriptor* v) { return v ? v->priority : 0; });
663 descriptor.addFunc("isEnabled", [](PolicyDescriptor* v) { return v && v->enabled; });
664 descriptor.addFunc("getKind",
665 [](PolicyDescriptor* v) { return v ? implementationKindName(v->kind) : std::string{}; });
666 descriptor.addFunc("getSchemaId", [](PolicyDescriptor* v) { return v ? v->schemaId : std::string{}; });
667 descriptor.addFunc("getMetadataJson", [](PolicyDescriptor* v) { return v ? v->metadataJson : std::string{}; });
668 descriptor.addFunc("getGeneration", [](PolicyDescriptor* v) {
669 return v ? static_cast<std::int64_t>(v->generation.value()) : std::int64_t{0};
670 });
671
672 auto event = table.addClass<PolicyEvent>(
673 "PolicyEvent", std::function<PolicyEvent*()>([]() -> PolicyEvent* { return nullptr; }), false);
674 event.addFunc("getSequence",
675 [](PolicyEvent* v) { return v ? static_cast<std::int64_t>(v->sequence.value()) : std::int64_t{0}; });
676 event.addFunc("getName", [](PolicyEvent* v) { return v ? v->name : std::string{}; });
677 event.addFunc("getDomain", [](PolicyEvent* v) { return v ? v->domain : std::string{}; });
678 event.addFunc("getPolicyName", [](PolicyEvent* v) { return v ? v->policyName : std::string{}; });
679 event.addFunc("getVersion",
680 [](PolicyEvent* v) { return v ? static_cast<std::int64_t>(v->version.value()) : std::int64_t{0}; });
681 event.addFunc("getGeneration", [](PolicyEvent* v) {
682 return v ? static_cast<std::int64_t>(v->generation.value()) : std::int64_t{0};
683 });
684 event.addFunc("isEnabled", [](PolicyEvent* v) { return v && v->enabled; });
685
686 auto registry = table.addClass<PolicyRegistry>(
687 "PolicyRegistry", std::function<PolicyRegistry*()>([]() -> PolicyRegistry* { return nullptr; }), false);
688 registry.addFunc("insert", [vm](PolicyRegistry* value, const std::string& domain, const std::string& policyName,
689 int version, int priority, bool enabled, const std::string& kind,
690 const std::string& schemaId, const std::string& metadataJson) {
691 if (!value)
693 vm,
695 "policy registry must not be null",
696 "registry", {}, "policyregistry")),
697 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
699 vm, value->insert(domain, policyName, version, priority, enabled, kind, schemaId, metadataJson),
700 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
701 });
702 registry.addFunc("replace", [vm](PolicyRegistry* value, const std::string& domain, const std::string& policyName,
703 int version, int priority, bool enabled, const std::string& kind,
704 const std::string& schemaId, const std::string& metadataJson) {
705 if (!value)
707 vm,
709 "policy registry must not be null",
710 "registry", {}, "policyregistry")),
711 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
713 vm, value->replace(domain, policyName, version, priority, enabled, kind, schemaId, metadataJson),
714 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
715 });
716 registry.addFunc("remove", [vm](PolicyRegistry* value, const std::string& domain, const std::string& policyName) {
717 if (!value)
719 vm,
721 "policy registry must not be null",
722 "registry", {}, "policyregistry")),
723 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
724 return eve::script::projectResult(vm, value->remove(domain, policyName),
725 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
726 });
727 registry.addFunc(
728 "enable", [vm](PolicyRegistry* value, const std::string& domain, const std::string& policyName, bool enabled) {
729 if (!value)
731 vm,
733 "policy registry must not be null",
734 "registry", {}, "policyregistry")),
735 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
736 return eve::script::projectResult(vm, value->enable(domain, policyName, enabled),
737 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
738 });
739 registry.addFunc("resolve", [vm](PolicyRegistry* value, const std::string& domain, const std::string& policyName) {
740 if (!value)
742 vm,
743 eve::Result<std::reference_wrapper<const PolicyDescriptor>>::failure(
744 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "policy registry must not be null",
745 "registry", {}, "policyregistry")),
746 [](std::reference_wrapper<const PolicyDescriptor>&& descriptor) {
747 return policyDescriptorValue(descriptor.get());
748 });
749 return eve::script::projectResult(vm, value->resolve(domain, policyName),
750 [](std::reference_wrapper<const PolicyDescriptor>&& descriptor) {
751 return policyDescriptorValue(descriptor.get());
752 });
753 });
754 registry.addFunc("resolveHandle", [vm](PolicyRegistry* value, const std::string& domain,
755 const std::string& policyName, std::int64_t generation) {
756 using DescriptorRef = std::reference_wrapper<const PolicyDescriptor>;
757 if (!value)
759 vm,
761 "policy registry must not be null",
762 "registry", {}, "policyregistry")),
763 [](DescriptorRef&& descriptor) { return policyDescriptorValue(descriptor.get()); });
764 if (generation <= 0)
766 vm,
768 "policy generation must be positive",
769 "generation", {}, "policyregistry")),
770 [](DescriptorRef&& descriptor) { return policyDescriptorValue(descriptor.get()); });
772 vm,
773 value->resolveHandle(
774 PolicyHandle{domain, policyName, eve::Generation(static_cast<std::uint64_t>(generation))}),
775 [](DescriptorRef&& descriptor) { return policyDescriptorValue(descriptor.get()); });
776 });
777 registry.addFunc("handle", [vm](PolicyRegistry* value, const std::string& domain, const std::string& policyName) {
778 if (!value)
780 vm,
782 "policy registry must not be null",
783 "registry", {}, "policyregistry")),
784 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
785 return eve::script::projectResult(vm, value->handle(domain, policyName),
786 [](PolicyHandle&& handle) { return policyHandleValue(handle); });
787 });
788 registry.addFunc("select", [](PolicyRegistry* v, const std::string& d) -> PolicyDescriptor* {
789 return v ? const_cast<PolicyDescriptor*>(v->select(d)) : nullptr;
790 });
791 registry.addFunc("size", &PolicyRegistry::size);
792 registry.addFunc("countDomain", &PolicyRegistry::countDomain);
793 registry.addFunc("at", [](PolicyRegistry* v, int i) -> PolicyDescriptor* {
794 return v ? const_cast<PolicyDescriptor*>(v->at(i)) : nullptr;
795 });
796 registry.addFunc("atDomain", [](PolicyRegistry* v, const std::string& d, int i) -> PolicyDescriptor* {
797 return v ? const_cast<PolicyDescriptor*>(v->atDomain(d, i)) : nullptr;
798 });
799 registry.addFunc("eventCount", &PolicyRegistry::eventCount);
800 registry.addFunc("eventAt", [](PolicyRegistry* v, int i) -> PolicyEvent* {
801 return v ? const_cast<PolicyEvent*>(v->eventAt(i)) : nullptr;
802 });
803 registry.addFunc("clearEvents", &PolicyRegistry::clearEvents);
804 registry.addFunc("snapshotJson", &PolicyRegistry::snapshotJson);
805 registry.addFunc("restoreJson", [vm](PolicyRegistry* value, const std::string& json) {
806 if (!value)
809 "policy registry must not be null", "registry",
810 {}, "policyregistry")));
811 return eve::script::projectResult(vm, value->restoreJson(json));
812 });
813 registry.addFunc("generationOf", [vm](PolicyRegistry* value, const std::string& domain,
814 const std::string& policyName) {
815 if (!value)
817 vm,
819 "policy registry must not be null",
820 "registry", {}, "policyregistry")),
821 [](eve::Generation generation) { return eve::Value(static_cast<std::int64_t>(generation.value())); });
822 return eve::script::projectResult(vm, value->generationOf(domain, policyName), [](eve::Generation generation) {
823 return eve::Value(static_cast<std::int64_t>(generation.value()));
824 });
825 });
826
827 auto cls = table.addClass(name, PolicyRegistryModule::create, false);
828 expose(cls);
829}
830
831void PolicyRegistryModule::expose(ssq::Class& cls) {
832 cls.addFunc("getName", &PolicyRegistryModule::getName);
833 cls.addFunc("newRegistry", &PolicyRegistryModule::newRegistry);
834}
835
836} // namespace eve::policyregistry
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
std::string output
std::string descriptor
std::unordered_map< std::string, std::unordered_map< std::string, std::uint64_t > > generations
int priority
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::uint32_t key
float v
std::string text
TokenKind kind
std::array< float, 3 > position
std::string name
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::string error
Definition Package.cpp:60
std::uint32_t generation
PrimitiveHandle handle
float d
double number
double current
std::string string
std::uint32_t count
The single Squirrel projection for common Result, Status and Value.
Battle::Events events
Json object
uint32_t index
const UnitySourceAsset & source
ViewPreparation callback
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
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
virtual std::string getName() const =0
Returns the name.
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
One directed payload migration step.
Definition Snapshot.h:145
Result< void > add(LogicalId schema, SchemaVersion from, SchemaVersion to, Migration migration)
Register one schema-local migration edge.
Definition Snapshot.cpp:196
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Move-only RAII token that owns one observer registration.
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
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
const Key * keyAt(std::size_t index) const noexcept
Returns the key for a live enumeration index, or nullptr.
ResultRef< const Value > resolve(const Key &key) const
Resolves a live key to a borrowed value.
std::size_t size() const noexcept
Number of live values, excluding tombstones.
Result< Handle > replace(Key key, Value value, EventData data={}, std::string label={})
Replaces an existing live value and invalidates its old handle.
void clearEvents() noexcept
Clears retained events without changing entries or sequence monotonicity.
Result< Handle > handle(const Key &key) const
Returns the current live handle for a key.
Result< void > restoreState(State candidate)
Transactionally replaces all registry state from an owning image.
const Value * at(std::size_t index) const noexcept
Returns a live value by deterministic key order.
Subscription subscribe(ChangeCallback callback)
Subscribes to successful insert, replace and remove events.
Result< Generation > generationOf(const Key &key) const
Returns the latest generation for a live key or tombstone.
Result< Handle > insert(Key key, Value value, EventData data={}, std::string label={})
Inserts a new live value, or revives a tombstoned key.
Result< Handle > remove(Key key, EventData data={}, std::string label={})
Removes a live value while retaining its generation tombstone.
const Event * eventAt(std::size_t index) const noexcept
Returns a retained event by insertion sequence order, or nullptr.
State snapshotState() const
Takes an owning copy of live entries, tombstones and event history.
std::size_t eventCount() const noexcept
Number of retained mutation events.
Id128 public API.
Definition Identity.h:112
constexpr bool isNil() const noexcept
Returns whether this value is the all-zero nil ID.
Definition Identity.h:176
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
constexpr bool isZero() const noexcept
Returns whether this value is zero.
Script module factory for independent policy registries.
PolicyRegistry * newRegistry()
Allocates a module-owned policy registry.
Deterministic registry for discoverable, non-executable policy descriptions.
eve::Subscription subscribe(std::function< void(const PolicyEvent &)> callback)
Subscribes to successful registry mutations.
eve::ResultRef< const PolicyDescriptor > resolve(const std::string &domain, const std::string &name) const
Resolves a descriptor through the common checked-registry API.
int eventCount() const
Returns retained event count.
const PolicyDescriptor * atDomain(const std::string &domain, int index) const
Returns a live descriptor in lexical name order within a domain.
eve::Result< PolicyHandle > replace(const std::string &domain, const std::string &name, int version, int priority, bool enabled, const std::string &kind, const std::string &schemaId, const std::string &metadataJson)
Replaces a descriptor and invalidates the prior generation handle.
eve::Result< void > restoreSnapshotJson(std::string_view json, const eve::SnapshotHashProvider &hashProvider)
Parses and transactionally restores a common snapshot envelope.
int countDomain(const std::string &domain) const
Returns live descriptor count in a domain.
const PolicyDescriptor * select(const std::string &domain) const
Selects the enabled highest-priority policy, breaking ties by lexical name.
eve::Result< std::string > snapshotEnvelopeJson(const eve::SnapshotHashProvider &hashProvider) const
Serializes the common snapshot envelope as canonical JSON.
eve::Result< PolicyHandle > handle(const std::string &domain, const std::string &name) const
Returns the current generation-qualified handle through Result.
eve::Result< PolicyHandle > enable(const std::string &domain, const std::string &name, bool enabled)
Changes enabled state through the same replacement generation path.
eve::Result< void > restoreJson(const std::string &json)
Transactionally restores state from the registry snapshot.
bool isTombstone(const std::string &domain, const std::string &name) const noexcept
Returns whether a key is retained as a removal tombstone.
eve::Result< eve::SnapshotEnvelope > snapshot(const eve::SnapshotHashProvider &hashProvider) const
Captures this registry in the common snapshot envelope.
eve::Result< PolicyHandle > insert(const std::string &domain, const std::string &name, int version, int priority, bool enabled, const std::string &kind, const std::string &schemaId, const std::string &metadataJson)
Inserts a descriptor through the common checked registry.
eve::ResultRef< const PolicyDescriptor > resolveHandle(const PolicyHandle &handle) const
Resolves a generation-qualified policy handle through Result.
void clearEvents()
Clears events without resetting the sequence allocator.
const PolicyDescriptor * at(int index) const
Returns a live descriptor in globally sorted domain/name order.
eve::Result< eve::Generation > generationOf(const std::string &domain, const std::string &name) const
Returns the latest generation, including a retained tombstone.
bool isStale(const PolicyHandle &handle) const noexcept
Returns whether a generation-qualified policy handle is stale.
eve::Result< void > restoreSnapshot(const eve::SnapshotEnvelope &snapshot, const eve::SnapshotHashProvider &hashProvider)
Restores a verified snapshot transactionally without per-item events.
eve::Result< PolicyHandle > remove(const std::string &domain, const std::string &name)
Removes a descriptor and returns its retained tombstone handle.
const PolicyEvent * eventAt(int index) const
Returns a retained event by sequence order.
int size() const
Returns total live descriptor count.
std::string snapshotJson() const
Exports registry state as deterministic JSON.
static std::vector< ValidationError > validate(const std::string &schemaId, const std::string &json)
Validates JSON text against the highest registered schema version.
static const SchemaDefinition * resolve(const std::string &schemaId, int schemaVersion)
Resolves one exact (schemaId, schemaVersion) pair, or nullptr.
ImplementationKind
Implementation category advertised by a policy descriptor.
std::string implementationKindName(ImplementationKind kind)
Returns the stable lowercase implementation kind name.
bool parseImplementationKind(const std::string &name, ImplementationKind &kind)
Parses a stable lowercase implementation kind name.
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
std::vector< DiagnosticDetail > DiagnosticDetails
Owning collection of diagnostic details with stable insertion order.
Definition Diagnostic.h:88
detail::StrongUint64< detail::GenerationTag > Generation
Registry/object replacement generation used to reject stale handles.
Definition Generation.h:13
Result< SnapshotEnvelope > parseSnapshotEnvelope(std::string_view json, const SnapshotHashProvider &hashProvider)
Parse and verify an envelope from canonical or compatible JSON text.
Definition Snapshot.cpp:190
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Result< std::string > serializeSnapshotEnvelope(const SnapshotEnvelope &snapshot)
Serialize an envelope as deterministic compact JSON.
Definition Snapshot.cpp:184
Result< SnapshotEnvelope > makeSnapshotEnvelope(std::string type, LogicalId schema, SchemaVersion schemaVersion, PersistentId instanceId, Revision revision, SimulationTick tick, Value payload, const SnapshotHashProvider &hashProvider)
Construct and seal a snapshot envelope.
Definition Snapshot.cpp:103
detail::StrongUint64< detail::RevisionTag > Revision
Monotonic content/state revision used for optimistic-concurrency checks.
Definition Revision.h:13
detail::StrongUint64< detail::EventSequenceTag > EventSequence
Stream-local event ordering value; it is not a global event identity.
std::function< Result< ContentId >(std::string_view canonicalInput)> SnapshotHashProvider
Injected content-digest implementation used by snapshots.
Definition Snapshot.h:36
bool enabled
Stable outer format shared by persistence and cross-process snapshots.
Definition Snapshot.h:46
Discoverable policy description without an executable callback.
Deterministically sequenced policy registry mutation event.
Generation-qualified policy handle used to detect replacement or removal.