149 std::ostringstream out;
155 out <<
"{\"version\":7,\"revision\":" <<
quote(std::to_string(revision_.
value()))
156 <<
",\"nextEnqueueSequence\":" <<
quote(std::to_string(nextEnqueueSequence_))
157 <<
",\"nextEventSequence\":" <<
quote(std::to_string(nextEventSequence_))
158 <<
",\"nextTaskId\":" <<
quote(std::to_string(nextTaskId_))
159 <<
",\"tick\":" <<
quote(std::to_string(tick_.
value())) <<
",\"slots\":[";
160 for (
size_t i = 0; i < slots_.size(); ++i) {
162 out <<
"{\"owner\":" <<
quote(slots_[i].
first) <<
",\"value\":" << slots_[i].second <<
'}';
164 out <<
"],\"resources\":[";
165 for (
size_t i = 0; i < resources_.size(); ++i) {
167 out <<
"{\"capacity\":" << resources_[i].second <<
",\"owner\":" <<
quote(resources_[i].
first.first)
168 <<
",\"resource\":" <<
quote(resources_[i].
first.second) <<
'}';
170 out <<
"],\"availableDefinitions\":[";
171 for (
size_t i = 0; i < availableDefinitions_.size(); ++i) {
173 out <<
"{\"definition\":" <<
quote(std::get<1>(availableDefinitions_[i]))
174 <<
",\"generation\":" <<
quote(std::to_string(std::get<2>(availableDefinitions_[i])))
175 <<
",\"owner\":" <<
quote(std::get<0>(availableDefinitions_[i])) <<
'}';
177 out <<
"],\"availableTags\":[";
178 for (
size_t i = 0; i < availableTags_.size(); ++i) {
180 out <<
"{\"owner\":" <<
quote(availableTags_[i].
first)
181 <<
",\"tag\":" <<
quote(availableTags_[i].
second) <<
'}';
183 out <<
"],\"events\":[";
184 for (
size_t i = 0; i < events_.size(); ++i) {
186 const auto&
event = events_[i];
187 out <<
"{\"correlationId\":" <<
quote(event.correlationId)
189 <<
",\"product\":" <<
quote(event.product) <<
",\"reason\":" <<
quote(event.reason)
190 <<
",\"sequence\":" <<
quote(std::to_string(event.sequence)) <<
",\"taskId\":" <<
quote(event.taskId)
191 <<
",\"taskKind\":" <<
quote(event.taskKind) <<
",\"tick\":" <<
quote(std::to_string(event.tick.value()))
194 out <<
"],\"schedulers\":[";
195 for (
size_t i = 0; i < schedulerStrategies_.size(); ++i) {
197 const auto strategy = schedulerStrategies_[i].second;
200 out <<
"{\"owner\":" <<
quote(schedulerStrategies_[i].
first)
201 <<
",\"strategy\":" <<
quote(
name) <<
'}';
203 out <<
"],\"tasks\":[";
204 const auto writeIds = [&out](
const std::vector<std::string>&
ids) {
207 if (
index) out <<
',';
212 for (
size_t i = 0; i < tasks_.size(); ++i) {
214 const auto&
t = *tasks_[i];
217 auto reservation =
t.reservation.toJson();
219 auto releasePayload =
t.reservationRelease.reservation.toJson();
221 auto settlementPayload =
t.settlement.payload.toJson();
223 out <<
"{\"batchSize\":" <<
t.batchSize <<
",\"blocks\":";
224 writeIds(
t.dependencies.blocks);
225 out <<
",\"completedCycles\":" <<
t.completedCycles
226 <<
",\"continuous\":" << (
t.repeat.continuous ?
"true" :
"false")
227 <<
",\"context\":" << std::move(
context).takeValue()
228 <<
",\"correlationId\":" <<
quote(
t.correlationId)
229 <<
",\"definition\":" <<
quote(
t.definition.isValid() ?
t.definition.reference.id().format() :
"")
230 <<
",\"definitionGeneration\":" <<
quote(std::to_string(
t.definition.generation.value()))
231 <<
",\"dependencyMode\":"
233 <<
",\"durationNs\":" <<
quote(std::to_string(
t.duration.nanoseconds()))
234 <<
",\"efficiencyPermille\":" <<
t.efficiencyPermille
235 <<
",\"enqueueSequence\":" <<
quote(std::to_string(
t.enqueueSequence)) <<
",\"id\":" <<
quote(
t.id)
236 <<
",\"kind\":" <<
quote(
t.kind) <<
",\"owner\":" <<
quote(
t.owner) <<
",\"priority\":" <<
t.priority
237 <<
",\"maintainStockTarget\":" <<
t.repeat.maintainStockTarget
238 <<
",\"lastSettlementId\":" <<
quote(
t.lastSettlementId)
239 <<
",\"observedStock\":" <<
t.repeat.observedStock
240 <<
",\"product\":" <<
quote(
t.product) <<
",\"prerequisites\":";
241 writeIds(
t.dependencies.prerequisites);
242 out <<
",\"randomDrawCount\":" <<
t.random.drawCount
243 <<
",\"randomDrawStart\":" <<
quote(std::to_string(
t.random.drawStart))
244 <<
",\"randomSeed\":" <<
quote(std::to_string(
t.random.seed))
245 <<
",\"randomStream\":" <<
quote(
t.random.stream)
246 <<
",\"refundCancellation\":" <<
quote(refundName(
t.termination.cancellation))
247 <<
",\"refundFailure\":" <<
quote(refundName(
t.termination.failure))
248 <<
",\"refundPermille\":" <<
t.refundPermille <<
",\"requiredDefinitions\":[";
249 for (
size_t requirementIndex = 0; requirementIndex <
t.dependencies.requiredDefinitions.size();
250 ++requirementIndex) {
251 if (requirementIndex) out <<
',';
252 const auto& definition =
t.dependencies.requiredDefinitions[requirementIndex];
253 out <<
"{\"definition\":" <<
quote(definition.reference.id().format())
254 <<
",\"generation\":" <<
quote(std::to_string(definition.generation.value())) <<
'}';
256 out <<
"],\"requiredTags\":";
257 writeIds(
t.dependencies.requiredTags);
258 out <<
",\"requirements\":[";
259 for (
size_t requirementIndex = 0; requirementIndex <
t.resources.size(); ++requirementIndex) {
260 if (requirementIndex) out <<
',';
261 out <<
"{\"resource\":" <<
quote(
t.resources[requirementIndex].resource)
262 <<
",\"units\":" <<
t.resources[requirementIndex].units <<
'}';
266 <<
",\"progressNs\":" <<
quote(std::to_string(
t.progress.nanoseconds()))
267 <<
",\"workRemainderPermille\":" <<
t.workRemainderPermille
268 <<
",\"reason\":" <<
quote(
t.reason) <<
",\"reservation\":"
269 << std::move(reservation).takeValue()
270 <<
",\"reservationReleaseId\":" <<
quote(
t.reservationRelease.releaseId)
271 <<
",\"reservationReleasePayload\":" << std::move(releasePayload).takeValue()
272 <<
",\"reservationReleaseRefundPermille\":" <<
t.reservationRelease.refundPermille
273 <<
",\"reservationState\":" <<
quote(
278 <<
",\"settlementId\":" <<
quote(
t.settlement.settlementId)
279 <<
",\"settlementPayload\":" << std::move(settlementPayload).takeValue()
280 <<
",\"settlementRequired\":" << (
t.settlementRequired ?
"true" :
"false")
282 <<
",\"totalCycles\":" <<
t.repeat.totalCycles <<
'}';
290 if (!doc.valid() || !doc.root().isObject()) {
292 error.empty() ?
"snapshot must be an object" :
error);
295 const auto root = doc.root();
296 const int version =
root.getInt(
"version");
297 if ((version != 1 && version != 2 && version != 3 && version != 4 && version != 5 && version != 6 &&
299 !parseU64(
root.get(
"nextTaskId"), candidate.nextTaskId_) ||
300 !parseU64(
root.get(
"nextEnqueueSequence"), candidate.nextEnqueueSequence_) ||
301 !parseU64(
root.get(
"nextEventSequence"), candidate.nextEventSequence_) || candidate.nextTaskId_ == 0 ||
302 candidate.nextEnqueueSequence_ == 0 || candidate.nextEventSequence_ == 0 || !
root.get(
"slots").isArray() ||
303 !
root.get(
"events").isArray() || !
root.get(
"tasks").isArray() ||
304 (version >= 4 && !
root.get(
"resources").isArray()) ||
305 (version >= 5 && !
root.get(
"schedulers").isArray()) ||
306 (version >= 6 && (!
root.get(
"availableDefinitions").isArray() || !
root.get(
"availableTags").isArray()))) {
309 if (!
root.get(
"tick").isNull()) {
311 if (!parseU64(
root.get(
"tick"),
tick)) {
316 if (!
root.get(
"revision").isNull()) {
324 candidate.revision_ =
eve::Revision(candidate.nextEventSequence_ - 1);
326 for (
size_t i = 0; i <
root.get(
"slots").
size(); ++i) {
328 if (!
value.isObject() || !
value.get(
"owner").isString() || !
value.get(
"value").isNumber()) {
336 for (
size_t i = 0; i <
root.get(
"resources").
size(); ++i) {
337 const auto value =
root.get(
"resources").at(i);
338 if (!
value.isObject() || !
value.get(
"owner").isString() || !
value.get(
"resource").isString() ||
339 !
value.get(
"capacity").isNumber())
341 "invalid resource capacity",
"resources");
343 value.get(
"resource").asString(),
344 value.get(
"capacity").asInt());
349 for (
size_t i = 0; i <
root.get(
"schedulers").
size(); ++i) {
350 const auto value =
root.get(
"schedulers").at(i);
351 if (!
value.isObject() || !
value.get(
"owner").isString() || !
value.get(
"strategy").isString())
353 "invalid scheduler strategy",
"schedulers");
355 const auto name =
value.get(
"strategy").asString();
360 "unknown scheduler strategy",
"schedulers.strategy");
366 for (
size_t i = 0; i <
root.get(
"availableDefinitions").
size(); ++i) {
367 const auto value =
root.get(
"availableDefinitions").at(i);
369 if (!
value.isObject() || !
value.get(
"owner").isString() ||
372 "invalid available definition",
"availableDefinitions");
376 "invalid available definition handle",
"availableDefinitions");
382 for (
size_t i = 0; i <
root.get(
"availableTags").
size(); ++i) {
383 const auto value =
root.get(
"availableTags").at(i);
384 if (!
value.isObject() || !
value.get(
"owner").isString() || !
value.get(
"tag").isString())
386 "invalid available tag",
"availableTags");
391 std::set<std::string>
ids;
392 for (
size_t i = 0; i <
root.get(
"tasks").
size(); ++i) {
394 auto task = std::make_unique<ProductionTask>();
397 if (!
value.isObject() || !
value.get(
"id").isString() || !
value.get(
"owner").isString() ||
398 !
value.get(
"kind").isString() || !
value.get(
"product").isString() ||
399 (!
value.get(
"progressNs").isString() && !
value.get(
"progress").isNumber()) ||
400 (!
value.get(
"durationNs").isString() && !
value.get(
"duration").isNumber()) ||
401 !
value.get(
"priority").isNumber() || !
value.get(
"state").isString() || !
value.get(
"reason").isString() ||
403 value.get(
"id").asString().empty() ||
value.get(
"owner").asString().empty() ||
404 value.get(
"kind").asString().empty() ||
value.get(
"product").asString().empty() ||
405 !
ids.insert(
value.get(
"id").asString()).second) {
408 task->id =
value.get(
"id").asString();
409 task->owner =
value.get(
"owner").asString();
410 task->kind =
value.get(
"kind").asString();
411 task->product =
value.get(
"product").asString();
414 if (!
context.value().isObject())
416 "work task context must be an object",
"tasks.context");
417 task->context = std::move(
context).takeValue();
419 if (!
value.get(
"definition").isString() || !
value.get(
"definitionGeneration").isString() ||
420 !
value.get(
"settlementId").isString() || !
value.get(
"settlementRequired").isBool())
422 "invalid task production metadata",
"tasks");
426 "invalid definition generation",
"tasks.definitionGeneration");
427 const std::string definition =
value.get(
"definition").asString();
428 if (!definition.empty()) {
432 "invalid pinned definition",
"tasks.definition");
436 "definition generation has no definition",
"tasks.definition");
440 if (!reservation || !settlementPayload || !reservation.value().isObject() ||
441 !settlementPayload.value().isObject())
443 "invalid reservation or settlement payload",
"tasks");
444 task->reservation = std::move(reservation).takeValue();
445 task->settlement.settlementId =
value.get(
"settlementId").asString();
446 task->settlement.payload = std::move(settlementPayload).takeValue();
447 task->settlementRequired =
value.get(
"settlementRequired").asBool();
450 "settled task has no receipt",
"tasks.settlementId");
452 task->settlement.settlementId =
"legacy:" + task->id;
453 task->settlementRequired =
false;
456 if (!
value.get(
"dependencyMode").isString() || !
value.get(
"prerequisites").isArray() ||
457 !
value.get(
"blocks").isArray())
459 "invalid production dependency metadata",
"tasks");
460 const std::string mode =
value.get(
"dependencyMode").asString();
461 if (mode ==
"all_of")
463 else if (mode ==
"any_of")
467 "invalid production dependency mode",
"tasks.dependencyMode");
469 std::vector<std::string>&
target) ->
bool {
470 std::set<std::string> unique;
473 if (!entry.isString() || entry.asString().empty() ||
474 !unique.insert(entry.asString()).second)
476 target.push_back(entry.asString());
480 if (!parseIds(
value.get(
"prerequisites"), task->dependencies.prerequisites) ||
481 !parseIds(
value.get(
"blocks"), task->dependencies.blocks))
483 "invalid production dependency ids",
"tasks");
486 if (!
value.get(
"batchSize").isNumber() || !
value.get(
"efficiencyPermille").isNumber() ||
487 !
value.get(
"refundCancellation").isString() || !
value.get(
"refundFailure").isString() ||
488 !
value.get(
"refundPermille").isNumber() || !
value.get(
"requirements").isArray())
490 "invalid production execution metadata",
"tasks");
498 const int batchSize =
value.get(
"batchSize").asInt();
499 const int efficiency =
value.get(
"efficiencyPermille").asInt();
500 const int refund =
value.get(
"refundPermille").asInt();
501 if (batchSize <= 0 || efficiency <= 0 || refund < 0 || refund > 1000 ||
502 !parseRefund(
value.get(
"refundCancellation").asString(), task->termination.cancellation) ||
503 !parseRefund(
value.get(
"refundFailure").asString(), task->termination.failure))
505 "invalid production batch, efficiency or refund",
"tasks");
506 task->batchSize =
static_cast<std::uint32_t
>(batchSize);
507 task->efficiencyPermille =
static_cast<std::uint32_t
>(efficiency);
508 task->refundPermille =
static_cast<std::uint32_t
>(refund);
509 std::set<std::string> resourceNames;
511 const auto requirement =
value.get(
"requirements").at(
index);
512 if (!requirement.isObject() || !requirement.get(
"resource").isString() ||
513 !requirement.get(
"units").isNumber() || requirement.get(
"resource").asString().empty() ||
514 requirement.get(
"units").asInt() <= 0 ||
515 !resourceNames.insert(requirement.get(
"resource").asString()).
second)
517 "invalid task resource requirement",
"tasks.requirements");
518 task->resources.push_back(
519 {requirement.get(
"resource").asString(), requirement.get(
"units").asInt()});
523 if (!
value.get(
"completedCycles").isNumber() || !
value.get(
"continuous").isBool() ||
524 !
value.get(
"correlationId").isString() || !
value.get(
"lastSettlementId").isString() ||
525 !
value.get(
"maintainStockTarget").isNumber() ||
526 !
value.get(
"observedStock").isNumber() || !
value.get(
"randomDrawCount").isNumber() ||
527 !
value.get(
"randomDrawStart").isString() || !
value.get(
"randomSeed").isString() ||
528 !
value.get(
"randomStream").isString() || !
value.get(
"totalCycles").isNumber())
530 "invalid advanced production metadata",
"tasks");
531 uint64_t randomSeed = 0;
532 uint64_t randomDrawStart = 0;
533 const int completedCycles =
value.get(
"completedCycles").asInt();
534 const int drawCount =
value.get(
"randomDrawCount").asInt();
535 const int totalCycles =
value.get(
"totalCycles").asInt();
536 const int stockTarget =
value.get(
"maintainStockTarget").asInt();
537 const int observedStock =
value.get(
"observedStock").asInt();
538 if (completedCycles < 0 || drawCount < 0 || totalCycles <= 0 || stockTarget < -1 || observedStock < 0 ||
539 !parseU64(
value.get(
"randomSeed"), randomSeed) ||
540 !parseU64(
value.get(
"randomDrawStart"), randomDrawStart) ||
541 (drawCount > 0 &&
value.get(
"randomStream").asString().empty()))
543 "invalid advanced production values",
"tasks");
544 task->completedCycles =
static_cast<std::uint32_t
>(completedCycles);
545 task->repeat.continuous =
value.get(
"continuous").asBool();
546 task->repeat.totalCycles =
static_cast<std::uint32_t
>(totalCycles);
547 task->repeat.maintainStockTarget = stockTarget;
548 task->repeat.observedStock = observedStock;
549 task->correlationId =
value.get(
"correlationId").asString();
550 task->lastSettlementId =
value.get(
"lastSettlementId").asString();
551 task->random = {
value.get(
"randomStream").asString(), randomSeed, randomDrawStart,
552 static_cast<std::uint32_t
>(drawCount)};
554 task->correlationId = task->id;
557 if (!
value.get(
"requiredDefinitions").isArray() || !
value.get(
"requiredTags").isArray() ||
558 !
value.get(
"reservationReleaseId").isString() ||
559 !
value.get(
"reservationReleaseRefundPermille").isNumber() ||
560 !
value.get(
"reservationState").isString())
562 "invalid definition or tag requirements",
"tasks");
563 std::set<std::pair<std::string, std::uint64_t>>
definitions;
565 const auto requirement =
value.get(
"requiredDefinitions").at(
index);
567 if (!requirement.isObject() || !requirement.get(
"definition").isString() ||
568 !parseU64(requirement.get(
"generation"),
generation))
570 "invalid required definition",
"tasks.requiredDefinitions");
571 const auto name = requirement.get(
"definition").asString();
575 "invalid required definition handle",
"tasks.requiredDefinitions");
576 task->dependencies.requiredDefinitions.push_back(
579 std::set<std::string> tags;
581 const auto requirement =
value.get(
"requiredTags").at(
index);
582 if (!requirement.isString() || requirement.asString().empty() ||
583 !tags.insert(requirement.asString()).second)
585 "invalid required tag",
"tasks.requiredTags");
586 task->dependencies.requiredTags.push_back(requirement.asString());
588 const auto reservationState =
value.get(
"reservationState").asString();
595 "invalid reservation state",
"tasks.reservationState");
597 const int releaseRefund =
value.get(
"reservationReleaseRefundPermille").asInt();
598 if (!releasePayload || !releasePayload.value().isObject() || releaseRefund < 0 || releaseRefund > 1000)
600 "invalid reservation release",
"tasks.reservationRelease");
601 task->reservationRelease = {
value.get(
"reservationReleaseId").asString(),
602 std::move(releasePayload).takeValue(),
603 static_cast<std::uint32_t
>(releaseRefund)};
605 !task->reservationRelease.releaseId.empty())
607 "reservation release state and receipt disagree",
608 "tasks.reservationReleaseId");
611 if (!
value.get(
"workRemainderPermille").isNumber())
613 "invalid production work remainder",
"tasks.workRemainderPermille");
614 const int remainder =
value.get(
"workRemainderPermille").asInt();
615 if (remainder < 0 || remainder >= 1000)
617 "production work remainder must be between 0 and 999",
618 "tasks.workRemainderPermille");
619 task->workRemainderPermille =
static_cast<std::uint32_t
>(remainder);
621 if (!parseDuration(
value.get(
"durationNs"),
value.get(
"duration"), task->duration) ||
622 !parseDuration(
value.get(
"progressNs"),
value.get(
"progress"), task->progress)) {
626 task->priority =
value.get(
"priority").asInt();
629 task->reason =
value.get(
"reason").asString();
630 if (task->duration.nanoseconds() <= 0 || task->progress.nanoseconds() < 0 || task->progress > task->duration) {
634 candidate.tasks_.push_back(std::move(task));
636 for (
const auto& task : candidate.tasks_) {
637 for (
const auto& dependencyId : task->dependencies.prerequisites) {
638 if (dependencyId == task->id || !
ids.contains(dependencyId))
640 "invalid prerequisite reference",
"tasks.prerequisites");
642 for (
const auto& blockerId : task->dependencies.blocks) {
643 if (blockerId == task->id || !
ids.contains(blockerId))
645 "invalid blocker reference",
"tasks.blocks");
649 std::set<std::string> visited;
651 if (visited.contains(task.id))
return true;
652 if (!
visiting.insert(task.id).second)
return false;
653 for (
const auto& dependencyId : task.dependencies.prerequisites) {
654 const auto dependency = candidate.
find(dependencyId);
655 if (!dependency || !acyclic(dependency->get()))
return false;
658 visited.insert(task.id);
661 for (
const auto& task : candidate.tasks_)
664 "production dependency graph contains a cycle",
665 "tasks.prerequisites");
666 uint64_t previousEventSequence = 0;
667 for (
size_t i = 0; i <
root.get(
"events").size(); ++i) {
670 if (!
value.isObject() || !
value.get(
"kind").isString() || !
value.get(
"owner").isString() ||
671 !
value.get(
"product").isString() || !
value.get(
"reason").isString() || !
value.get(
"taskId").isString() ||
672 !
value.get(
"taskKind").isString() || !parseU64(
value.get(
"sequence"), event.
sequence) ||
673 !parseEventKind(
value.get(
"kind").asString(), event.
kind) ||
event.sequence <= previousEventSequence ||
674 event.sequence >= candidate.nextEventSequence_) {
677 previousEventSequence =
event.sequence;
678 event.owner =
value.get(
"owner").asString();
679 event.product =
value.get(
"product").asString();
680 event.reason =
value.get(
"reason").asString();
681 event.taskId =
value.get(
"taskId").asString();
682 event.taskKind =
value.get(
"taskKind").asString();
683 event.correlationId = version >= 5 &&
value.get(
"correlationId").isString()
684 ?
value.get(
"correlationId").asString() :
event.taskId;
685 if (!
value.get(
"tick").isNull()) {
687 if (!parseU64(
value.get(
"tick"),
tick)) {
693 candidate.events_.push_back(std::move(event));
695 *
this = std::move(candidate);