kibi-core 0.26.1 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -47,7 +47,9 @@
47
47
  % routes. set_relation compares the members with the
48
48
  % fact's set; required_fields and nullable_fields check
49
49
  % each selected table or object; route_middleware checks
50
- % each selected route's middleware.
50
+ % each selected route's middleware; sequence_precedes
51
+ % checks each "A -> B" member against the list in the
52
+ % order read (every A before every B).
51
53
  %
52
54
  % Rules with explicit exists quantifiers, count or temporal expressions, or
53
55
  % durations, timestamps and intervals are outside what Kibi enumerates and
@@ -115,14 +117,18 @@ certificate_model(state_machine, Certificate, model(any(States), Table, machine(
115
117
  ; Initial = none
116
118
  ),
117
119
  machine_table(States, Edges, Initial, Table).
118
- certificate_model(source, Certificate, model(none, none, source(Members, Entities, Routes))) :-
120
+ certificate_model(source, Certificate, model(none, none, source(Members, Entities, Routes, Sequence))) :-
119
121
  !,
120
122
  required(Certificate, model, Model),
121
123
  required(Model, members, RawMembers),
122
124
  maplist(text_string, RawMembers, Members0),
123
125
  sort(Members0, Members),
124
126
  ( get_dict(entities, Model, Entities) -> true ; Entities = none ),
125
- ( get_dict(routes, Model, Routes) -> true ; Routes = none ).
127
+ ( get_dict(routes, Model, Routes) -> true ; Routes = none ),
128
+ ( get_dict(sequence, Model, RawSequence)
129
+ -> maplist(text_string, RawSequence, Sequence)
130
+ ; Sequence = none
131
+ ).
126
132
  certificate_model(Kind, _, _) :-
127
133
  format(string(Reason), "a ~w certificate has no finite model to check", [Kind]),
128
134
  throw(model_error(Reason)).
@@ -391,14 +397,14 @@ obligation_result("machine_relation", Obligation, model(_, _, Machine), Result)
391
397
  ; Result = ok
392
398
  )
393
399
  ).
394
- obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _)), Result) :- !,
400
+ obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _, _)), Result) :- !,
395
401
  dict_text(Obligation, operator, Operator),
396
402
  obligation_strings(Obligation, Expected),
397
403
  ( relation_problem(Operator, Actual, Expected, "the members Kibi read from the source", Reason)
398
404
  -> Result = problem(Reason)
399
405
  ; Result = ok
400
406
  ).
401
- obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _)), Result) :-
407
+ obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _)), Result) :-
402
408
  memberchk(FieldKind, ["required_fields", "nullable_fields"]),
403
409
  !,
404
410
  ( is_list(Entities), Entities \== []
@@ -411,7 +417,7 @@ obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _)), Re
411
417
  -> Result = problem(Reason)
412
418
  ; Result = ok
413
419
  ).
414
- obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes)), Result) :- !,
420
+ obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes, _)), Result) :- !,
415
421
  ( is_list(Routes), Routes \== []
416
422
  -> true
417
423
  ; throw(model_error("the source has no routes; read a route table for a middleware claim"))
@@ -429,9 +435,51 @@ obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Route
429
435
  -> Result = problem(Reason)
430
436
  ; Result = ok
431
437
  ).
438
+ % implements REQ-kibi-source-proof-kinds
439
+ obligation_result("sequence_precedes", Obligation, model(_, _, source(_, _, _, Sequence)), Result) :- !,
440
+ ( is_list(Sequence)
441
+ -> true
442
+ ; throw(model_error("the source has no order; read a list for a precedes claim"))
443
+ ),
444
+ obligation_strings(Obligation, Pairs),
445
+ ( member(Pair, Pairs),
446
+ precedes_problem(Pair, Sequence, Reason)
447
+ -> Result = problem(Reason)
448
+ ; Result = ok
449
+ ).
432
450
  obligation_result(Kind, _, _, problem(Reason)) :-
433
451
  format(string(Reason), "a ~w obligation cannot be checked against a finite model", [Kind]).
434
452
 
453
+ % A precedes member "A -> B" holds when A and B are both in the list and
454
+ % every A comes before every B.
455
+ precedes_problem(Pair, Sequence, Reason) :-
456
+ ( precedes_pair(Pair, First, Second)
457
+ -> findall(I, nth1(I, Sequence, First), FirstAt),
458
+ findall(I, nth1(I, Sequence, Second), SecondAt),
459
+ ( FirstAt == []
460
+ -> format(string(Reason), "~w is not in the list Kibi read", [First])
461
+ ; SecondAt == []
462
+ -> format(string(Reason), "~w is not in the list Kibi read", [Second])
463
+ ; max_list(FirstAt, LastFirst),
464
+ min_list(SecondAt, FirstSecond),
465
+ LastFirst > FirstSecond
466
+ -> format(string(Reason), "~w comes before ~w in the list Kibi read (items ~w and ~w)",
467
+ [Second, First, FirstSecond, LastFirst])
468
+ )
469
+ ; format(string(Reason), "~w is not spelled \"A -> B\"", [Pair])
470
+ ).
471
+
472
+ precedes_pair(Pair, First, Second) :-
473
+ sub_string(Pair, Before, 4, After, " -> "),
474
+ sub_string(Pair, 0, Before, _, First0),
475
+ sub_string(Pair, _, After, 0, Second0),
476
+ \+ sub_string(Second0, _, _, _, " -> "),
477
+ normalize_space(string(First), First0),
478
+ normalize_space(string(Second), Second0),
479
+ First \== "",
480
+ Second \== "",
481
+ !.
482
+
435
483
  obligation_strings(Obligation, Members) :-
436
484
  get_dict(members, Obligation, Raw),
437
485
  maplist(text_string, Raw, Members0),