kibi-core 0.28.0 → 0.30.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.
- package/package.json +1 -1
- package/src/discovery.pl +9 -2
- package/src/model_check.pl +239 -6
- package/src/requirement_proof.pl +23 -15
- package/src/rule_registry.pl +4 -0
package/package.json
CHANGED
package/src/discovery.pl
CHANGED
|
@@ -417,7 +417,7 @@ evidence_floor_json(BindingMode, TestBindings, VerificationSnapshot, CheckedAt,
|
|
|
417
417
|
( member(Id-Proof, Evaluated),
|
|
418
418
|
Floor = Proof.evidenceFloor,
|
|
419
419
|
Floor.status \== met,
|
|
420
|
-
|
|
420
|
+
Row0 = _{
|
|
421
421
|
id: Id,
|
|
422
422
|
evidenceLevel: Proof.evidenceLevel,
|
|
423
423
|
requiredEvidence: Floor.required,
|
|
@@ -427,7 +427,14 @@ evidence_floor_json(BindingMode, TestBindings, VerificationSnapshot, CheckedAt,
|
|
|
427
427
|
proofGaps: Proof.proofGaps,
|
|
428
428
|
gapDetails: Details
|
|
429
429
|
},
|
|
430
|
-
evidence_floor_gap_details(Proof, Details)
|
|
430
|
+
evidence_floor_gap_details(Proof, Details),
|
|
431
|
+
% implements REQ-kibi-claim-bound-proofs
|
|
432
|
+
% A claim its proof refuted: the test and what it read.
|
|
433
|
+
( get_dict(claimBindingRejections, Proof, Refutations),
|
|
434
|
+
Refutations \== []
|
|
435
|
+
-> Row = Row0.put(refutations, Refutations)
|
|
436
|
+
; Row = Row0
|
|
437
|
+
)
|
|
431
438
|
),
|
|
432
439
|
Rows0),
|
|
433
440
|
sort(id, @<, Rows0, Rows),
|
package/src/model_check.pl
CHANGED
|
@@ -117,8 +117,9 @@ certificate_model(state_machine, Certificate, model(any(States), Table, machine(
|
|
|
117
117
|
; Initial = none
|
|
118
118
|
),
|
|
119
119
|
machine_table(States, Edges, Initial, Table).
|
|
120
|
-
certificate_model(source, Certificate, model(none, none, source(Members, Entities, Routes, Sequence))) :-
|
|
120
|
+
certificate_model(source, Certificate, model(none, none, source(Members, Entities, Routes, Sequence, Matching))) :-
|
|
121
121
|
!,
|
|
122
|
+
source_matching(Certificate, Matching),
|
|
122
123
|
required(Certificate, model, Model),
|
|
123
124
|
required(Model, members, RawMembers),
|
|
124
125
|
maplist(text_string, RawMembers, Members0),
|
|
@@ -306,6 +307,234 @@ relation_problem("excludes", Actual, Expected, What, Reason) :-
|
|
|
306
307
|
atomic_list_concat(Shared, ', ', Text),
|
|
307
308
|
format(string(Reason), "~w include ~w, which the fact excludes", [What, Text]).
|
|
308
309
|
|
|
310
|
+
% ---------------------------------------------------------------------------
|
|
311
|
+
% Reference members
|
|
312
|
+
%
|
|
313
|
+
% The references reader's members are dotted, slashed or ::-joined names
|
|
314
|
+
% (java.sql.Connection, github.com/labstack/echo/v4/middleware,
|
|
315
|
+
% std::env::var), a wildcard import (java.sql.*), or *.m for a call of m on a
|
|
316
|
+
% receiver the reader cannot name. For these members an expected item E
|
|
317
|
+
% covers more than its own spelling, and each operator reads it in the
|
|
318
|
+
% direction that can never prove a claim the code does not hold:
|
|
319
|
+
%
|
|
320
|
+
% excludes E fails on any member that is or may be E: E itself, a member
|
|
321
|
+
% under E at a segment boundary (java.sql covers
|
|
322
|
+
% java.sql.Connection), a member under the prefix of E* (java.sql.*
|
|
323
|
+
% covers java.sql.Connection), a wildcard import that may bring E
|
|
324
|
+
% in, a shorter name E ends with (File.delete may be
|
|
325
|
+
% java.io.File.delete), a longer name ending in E at a segment
|
|
326
|
+
% boundary (checkNotNull may be named by
|
|
327
|
+
% com.google.common.base.Preconditions.checkNotNull), and *.m
|
|
328
|
+
% when E ends in .m (it may be E).
|
|
329
|
+
% includes E holds only on a member that certainly is E: E itself, a
|
|
330
|
+
% member under the prefix of E*, or a member ending in .m for *.m.
|
|
331
|
+
% *.m never satisfies X.m.
|
|
332
|
+
% subset_of A member is allowed only when it certainly is an allowed item:
|
|
333
|
+
% the item itself, a member under the prefix of an item E*, or a
|
|
334
|
+
% member ending in .m for *.m. *.m is allowed only when *.m is.
|
|
335
|
+
% eq Compares the spellings exactly, as for every other reader.
|
|
336
|
+
|
|
337
|
+
source_matching(Certificate, references(Files, Case)) :-
|
|
338
|
+
get_dict(source, Certificate, Source),
|
|
339
|
+
is_dict(Source),
|
|
340
|
+
get_dict(reader, Source, Reader),
|
|
341
|
+
text_string(Reader, "references"),
|
|
342
|
+
!,
|
|
343
|
+
( get_dict(files, Source, RawFiles)
|
|
344
|
+
-> ( is_list(RawFiles) -> maplist(text_string, RawFiles, Files) ; text_string(RawFiles, File), Files = [File] )
|
|
345
|
+
; Files = []
|
|
346
|
+
),
|
|
347
|
+
% Some languages ignore letter case in the names they call, throw and
|
|
348
|
+
% import (PHP classes, methods and functions): excludes then also fails
|
|
349
|
+
% on a member that differs from an item only in case.
|
|
350
|
+
( get_dict(extract, Source, RawExtract),
|
|
351
|
+
text_string(RawExtract, Extract),
|
|
352
|
+
memberchk(Extract, ["calls", "thrown", "imports"])
|
|
353
|
+
-> Case = fold
|
|
354
|
+
% mutalog-disable-next-line goal_true: Case is only compared with ==, so an unbound Case reads as exact
|
|
355
|
+
; Case = exact
|
|
356
|
+
).
|
|
357
|
+
source_matching(_, literal).
|
|
358
|
+
|
|
359
|
+
member_separator(".").
|
|
360
|
+
member_separator("/").
|
|
361
|
+
member_separator("::").
|
|
362
|
+
member_separator("\\").
|
|
363
|
+
|
|
364
|
+
% extends_member(+Member, +Stem): Member is Stem or lies under it at a
|
|
365
|
+
% segment boundary. The empty stem (from the item "*") covers everything.
|
|
366
|
+
extends_member(_, "") :- !.
|
|
367
|
+
extends_member(Member, Stem) :- Member == Stem, !.
|
|
368
|
+
extends_member(Member, Stem) :-
|
|
369
|
+
member_separator(Separator),
|
|
370
|
+
string_concat(Stem, Separator, Prefix),
|
|
371
|
+
string_concat(Prefix, _, Member),
|
|
372
|
+
!.
|
|
373
|
+
|
|
374
|
+
% pattern_stem(+Item, -Stem): Item ends in *; Stem drops it and the separator
|
|
375
|
+
% before it (java.sql.* -> java.sql, * -> "").
|
|
376
|
+
pattern_stem(Item, Stem) :-
|
|
377
|
+
\+ unknown_receiver(Item, _),
|
|
378
|
+
string_concat(Base, "*", Item),
|
|
379
|
+
( member_separator(Separator), string_concat(Stem0, Separator, Base)
|
|
380
|
+
-> Stem = Stem0
|
|
381
|
+
; Stem = Base
|
|
382
|
+
).
|
|
383
|
+
|
|
384
|
+
% unknown_receiver(+Member, -Method): Member is *.Method.
|
|
385
|
+
unknown_receiver(Member, Method) :-
|
|
386
|
+
string_concat("*.", Method, Member),
|
|
387
|
+
Method \== "".
|
|
388
|
+
|
|
389
|
+
% ends_with_method(+Member, +Method): Member's last segment is Method.
|
|
390
|
+
ends_with_method(Member, Method) :-
|
|
391
|
+
member_separator(Separator),
|
|
392
|
+
string_concat(Separator, Method, Suffix),
|
|
393
|
+
string_concat(_, Suffix, Member),
|
|
394
|
+
!.
|
|
395
|
+
|
|
396
|
+
% exclude_match(+Actual, +Expected, -How): Actual is (certain) or may be
|
|
397
|
+
% (possible) what Expected names.
|
|
398
|
+
exclude_match(Actual, Expected, certain) :- Actual == Expected, !.
|
|
399
|
+
exclude_match(Actual, Expected, certain) :-
|
|
400
|
+
unknown_receiver(Expected, Method),
|
|
401
|
+
\+ unknown_receiver(Actual, _),
|
|
402
|
+
ends_with_method(Actual, Method),
|
|
403
|
+
!.
|
|
404
|
+
exclude_match(Actual, Expected, possible) :-
|
|
405
|
+
unknown_receiver(Actual, Method),
|
|
406
|
+
!,
|
|
407
|
+
( pattern_stem(Expected, _)
|
|
408
|
+
; ends_with_method(Expected, Method)
|
|
409
|
+
),
|
|
410
|
+
!.
|
|
411
|
+
exclude_match(Actual, Expected, How) :-
|
|
412
|
+
pattern_stem(Actual, ActualStem),
|
|
413
|
+
!,
|
|
414
|
+
( pattern_stem(Expected, Target) -> true ; Target = Expected ),
|
|
415
|
+
( extends_member(ActualStem, Target)
|
|
416
|
+
-> How = certain
|
|
417
|
+
; extends_member(Target, ActualStem)
|
|
418
|
+
-> How = possible
|
|
419
|
+
).
|
|
420
|
+
exclude_match(Actual, Expected, How) :-
|
|
421
|
+
( pattern_stem(Expected, Stem)
|
|
422
|
+
-> extends_member(Actual, Stem),
|
|
423
|
+
How = certain
|
|
424
|
+
; extends_member(Actual, Expected)
|
|
425
|
+
-> How = certain
|
|
426
|
+
; ends_with_method(Expected, Actual)
|
|
427
|
+
-> How = possible
|
|
428
|
+
; ends_with_method(Actual, Expected)
|
|
429
|
+
-> How = possible
|
|
430
|
+
).
|
|
431
|
+
|
|
432
|
+
% folded_match(+Actual, +Expected, -How): Actual may be what Expected names
|
|
433
|
+
% once letter case is ignored. Called only for a pair exclude_match/3
|
|
434
|
+
% rejected, so a match here is one that only letter case makes.
|
|
435
|
+
folded_match(Actual, Expected, possible) :-
|
|
436
|
+
string_lower(Actual, LowerActual),
|
|
437
|
+
string_lower(Expected, LowerExpected),
|
|
438
|
+
exclude_match(LowerActual, LowerExpected, _),
|
|
439
|
+
% mutalog-disable-next-line cut: the caller sorts the pairs it collects, so a second answer adds nothing
|
|
440
|
+
!.
|
|
441
|
+
|
|
442
|
+
% certain_match(+Actual, +Expected): Actual certainly is what Expected names
|
|
443
|
+
% (includes, subset_of).
|
|
444
|
+
certain_match(Actual, Expected) :- Actual == Expected, !.
|
|
445
|
+
certain_match(_, "*") :- !.
|
|
446
|
+
certain_match(Actual, Expected) :-
|
|
447
|
+
\+ unknown_receiver(Actual, _),
|
|
448
|
+
( pattern_stem(Expected, Stem)
|
|
449
|
+
-> ( pattern_stem(Actual, ActualStem)
|
|
450
|
+
-> extends_member(ActualStem, Stem)
|
|
451
|
+
; extends_member(Actual, Stem)
|
|
452
|
+
)
|
|
453
|
+
; unknown_receiver(Expected, Method)
|
|
454
|
+
-> \+ pattern_stem(Actual, _),
|
|
455
|
+
ends_with_method(Actual, Method)
|
|
456
|
+
),
|
|
457
|
+
!.
|
|
458
|
+
|
|
459
|
+
files_text([], "the files read") :- !.
|
|
460
|
+
files_text(Files, Text) :-
|
|
461
|
+
length(Files, N),
|
|
462
|
+
( N > 3
|
|
463
|
+
-> length(Shown, 3), append(Shown, _, Files),
|
|
464
|
+
atomic_list_concat(Shown, ', ', Listed),
|
|
465
|
+
Extra is N - 3,
|
|
466
|
+
format(string(Text), "~w and ~w more", [Listed, Extra])
|
|
467
|
+
; atomic_list_concat(Files, ', ', Text)
|
|
468
|
+
).
|
|
469
|
+
|
|
470
|
+
excluded_text(Files, Actual-Expected-How, Text) :-
|
|
471
|
+
( \+ exclude_match(Actual, Expected, _)
|
|
472
|
+
-> format(string(Text), "~w (it may be ~w: the names differ only in letter case, which some languages ignore)", [Actual, Expected])
|
|
473
|
+
; Actual == Expected
|
|
474
|
+
-> Text = Actual
|
|
475
|
+
; How == certain
|
|
476
|
+
-> format(string(Text), "~w (within ~w)", [Actual, Expected])
|
|
477
|
+
; unknown_receiver(Actual, Method)
|
|
478
|
+
-> files_text(Files, Where),
|
|
479
|
+
format(string(Text), "~w (a call of ~w on a receiver Kibi cannot name, in ~w; it may be ~w)", [Actual, Method, Where, Expected])
|
|
480
|
+
; pattern_stem(Actual, _)
|
|
481
|
+
-> format(string(Text), "~w (a wildcard that may cover ~w)", [Actual, Expected])
|
|
482
|
+
; ends_with_method(Actual, Expected)
|
|
483
|
+
-> format(string(Text), "~w (it may be the ~w the fact excludes)", [Actual, Expected])
|
|
484
|
+
; format(string(Text), "~w (a shorter name that may be ~w)", [Actual, Expected])
|
|
485
|
+
).
|
|
486
|
+
|
|
487
|
+
% reference_relation_problem(+Operator, +Actual, +Expected, +Matching, +What,
|
|
488
|
+
% -Reason) fails when the relation holds under the reference matching above.
|
|
489
|
+
reference_relation_problem("excludes", Actual, Expected, references(Files, Case), What, Reason) :-
|
|
490
|
+
!,
|
|
491
|
+
findall(A-E-How,
|
|
492
|
+
( member(A, Actual), member(E, Expected),
|
|
493
|
+
( exclude_match(A, E, How)
|
|
494
|
+
-> true
|
|
495
|
+
; Case == fold, folded_match(A, E, How)
|
|
496
|
+
)
|
|
497
|
+
),
|
|
498
|
+
Pairs0),
|
|
499
|
+
sort(Pairs0, Pairs),
|
|
500
|
+
Pairs \== [],
|
|
501
|
+
include(certain_pair, Pairs, Certain),
|
|
502
|
+
maplist(excluded_text(Files), Pairs, Texts),
|
|
503
|
+
atomic_list_concat(Texts, ', ', Text),
|
|
504
|
+
( Certain \== []
|
|
505
|
+
-> format(string(Reason), "~w include ~w, which the fact excludes", [What, Text])
|
|
506
|
+
; format(string(Reason), "~w include ~w, which the fact excludes; Kibi cannot decide the claim for those members", [What, Text])
|
|
507
|
+
).
|
|
508
|
+
reference_relation_problem("includes", Actual, Expected, _, What, Reason) :-
|
|
509
|
+
!,
|
|
510
|
+
exclude(matched_by_some(Actual), Expected, Missing),
|
|
511
|
+
Missing \== [],
|
|
512
|
+
atomic_list_concat(Missing, ', ', Text),
|
|
513
|
+
format(string(Reason), "~w do not include ~w", [What, Text]).
|
|
514
|
+
reference_relation_problem("subset_of", Actual, Expected, _, What, Reason) :-
|
|
515
|
+
!,
|
|
516
|
+
exclude(allowed_by_some(Expected), Actual, Extra),
|
|
517
|
+
Extra \== [],
|
|
518
|
+
atomic_list_concat(Extra, ', ', Text),
|
|
519
|
+
( member(Member, Extra), unknown_receiver(Member, _)
|
|
520
|
+
-> format(string(Reason), "~w include ~w, which the fact's set does not allow (a call on a receiver Kibi cannot name is allowed only when the set lists it as *.m)", [What, Text])
|
|
521
|
+
; format(string(Reason), "~w include ~w, which the fact's set does not allow", [What, Text])
|
|
522
|
+
).
|
|
523
|
+
reference_relation_problem(Operator, Actual, Expected, _, What, Reason) :-
|
|
524
|
+
relation_problem(Operator, Actual, Expected, What, Reason).
|
|
525
|
+
|
|
526
|
+
certain_pair(_-_-certain).
|
|
527
|
+
|
|
528
|
+
matched_by_some(Actual, Expected) :-
|
|
529
|
+
member(Member, Actual),
|
|
530
|
+
certain_match(Member, Expected),
|
|
531
|
+
!.
|
|
532
|
+
|
|
533
|
+
allowed_by_some(Expected, Member) :-
|
|
534
|
+
member(Item, Expected),
|
|
535
|
+
certain_match(Member, Item),
|
|
536
|
+
!.
|
|
537
|
+
|
|
309
538
|
% ---------------------------------------------------------------------------
|
|
310
539
|
% Obligations
|
|
311
540
|
|
|
@@ -397,14 +626,18 @@ obligation_result("machine_relation", Obligation, model(_, _, Machine), Result)
|
|
|
397
626
|
; Result = ok
|
|
398
627
|
)
|
|
399
628
|
).
|
|
400
|
-
obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _, _)), Result) :- !,
|
|
629
|
+
obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _, _, Matching)), Result) :- !,
|
|
401
630
|
dict_text(Obligation, operator, Operator),
|
|
402
631
|
obligation_strings(Obligation, Expected),
|
|
403
|
-
|
|
632
|
+
What = "the members Kibi read from the source",
|
|
633
|
+
( ( Matching = references(_, _)
|
|
634
|
+
-> reference_relation_problem(Operator, Actual, Expected, Matching, What, Reason)
|
|
635
|
+
; relation_problem(Operator, Actual, Expected, What, Reason)
|
|
636
|
+
)
|
|
404
637
|
-> Result = problem(Reason)
|
|
405
638
|
; Result = ok
|
|
406
639
|
).
|
|
407
|
-
obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _)), Result) :-
|
|
640
|
+
obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _, _)), Result) :-
|
|
408
641
|
memberchk(FieldKind, ["required_fields", "nullable_fields"]),
|
|
409
642
|
!,
|
|
410
643
|
( is_list(Entities), Entities \== []
|
|
@@ -417,7 +650,7 @@ obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _)),
|
|
|
417
650
|
-> Result = problem(Reason)
|
|
418
651
|
; Result = ok
|
|
419
652
|
).
|
|
420
|
-
obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes, _)), Result) :- !,
|
|
653
|
+
obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes, _, _)), Result) :- !,
|
|
421
654
|
( is_list(Routes), Routes \== []
|
|
422
655
|
-> true
|
|
423
656
|
; throw(model_error("the source has no routes; read a route table for a middleware claim"))
|
|
@@ -436,7 +669,7 @@ obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Route
|
|
|
436
669
|
; Result = ok
|
|
437
670
|
).
|
|
438
671
|
% implements REQ-kibi-source-proof-kinds
|
|
439
|
-
obligation_result("sequence_precedes", Obligation, model(_, _, source(_, _, _, Sequence)), Result) :- !,
|
|
672
|
+
obligation_result("sequence_precedes", Obligation, model(_, _, source(_, _, _, Sequence, _)), Result) :- !,
|
|
440
673
|
( is_list(Sequence)
|
|
441
674
|
-> true
|
|
442
675
|
; throw(model_error("the source has no order; read a list for a precedes claim"))
|
package/src/requirement_proof.pl
CHANGED
|
@@ -2402,15 +2402,15 @@ receipt_claim_fields(Receipt, Evidence0, Evidence) :-
|
|
|
2402
2402
|
claim_evidence(ReqId, Props, Inventory, ProofStatus, ScenarioTestStage, Context,
|
|
2403
2403
|
ClaimEvidence, ProvedTests, Rejections) :-
|
|
2404
2404
|
( get_dict(tests, ScenarioTestStage, Tests) -> true ; Tests = [] ),
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2405
|
+
% The bindings are read whatever the proof status, so a claim that is not
|
|
2406
|
+
% proved because the requirement is not tested end to end still shows the
|
|
2407
|
+
% binding each receipt holds, and a rejected binding its reason.
|
|
2408
|
+
findall(TestId-Evidence,
|
|
2409
|
+
(member(TestId, Tests),
|
|
2410
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
2411
|
+
get_dict(state, Evidence, passed),
|
|
2412
|
+
get_dict(claimBindings, Evidence, _)),
|
|
2413
|
+
BoundTests),
|
|
2414
2414
|
intent_claim_level(Props, IntentLevel),
|
|
2415
2415
|
maplist(claim_entry(ReqId, ProofStatus, IntentLevel, BoundTests), Inventory, Entries0),
|
|
2416
2416
|
exclude(==(none), Entries0, ClaimEvidence),
|
|
@@ -2458,17 +2458,25 @@ claim_entry(ReqId, ProofStatus, IntentLevel, BoundTests, InventoryEntry, Entry)
|
|
|
2458
2458
|
; Status \== modeled
|
|
2459
2459
|
-> Entry = Base.put(_{applicable: true, level: IntentLevel, bindings: [],
|
|
2460
2460
|
blocker: unresolved_semantic_proposition})
|
|
2461
|
-
; ProofStatus \== tested
|
|
2462
|
-
-> Entry = Base.put(_{applicable: true, level: IntentLevel, bindings: []})
|
|
2463
2461
|
; findall(Binding, test_claim_binding(ReqId, Key, BoundTests, Binding), Bindings0),
|
|
2464
2462
|
sort(Bindings0, Bindings),
|
|
2465
2463
|
include(binding_is(rejected), Bindings, RejectedBindings),
|
|
2466
2464
|
maplist(binding_rejection(Key), RejectedBindings, Rejections),
|
|
2467
|
-
(
|
|
2468
|
-
->
|
|
2469
|
-
|
|
2465
|
+
( ProofStatus \== tested
|
|
2466
|
+
-> % A derived binding proves nothing until the requirement is
|
|
2467
|
+
% tested end to end; the claim keeps the intent level and
|
|
2468
|
+
% says why.
|
|
2469
|
+
Entry1 = Base.put(_{applicable: true, level: IntentLevel, bindings: Bindings}),
|
|
2470
|
+
( member(B, Bindings), get_dict(binding, B, derived)
|
|
2471
|
+
-> Entry0 = Entry1.put(notProved, requirement_not_tested)
|
|
2472
|
+
; Entry0 = Entry1
|
|
2473
|
+
)
|
|
2474
|
+
; ( member(B, Bindings), get_dict(binding, B, derived), \+ get_dict(scope, B, intent)
|
|
2475
|
+
-> Level = proved
|
|
2476
|
+
; Level = tested
|
|
2477
|
+
),
|
|
2478
|
+
Entry0 = Base.put(_{applicable: true, level: Level, bindings: Bindings})
|
|
2470
2479
|
),
|
|
2471
|
-
Entry0 = Base.put(_{applicable: true, level: Level, bindings: Bindings}),
|
|
2472
2480
|
( Rejections == []
|
|
2473
2481
|
-> Entry = Entry0
|
|
2474
2482
|
; Entry = Entry0.put(rejections, Rejections)
|
package/src/rule_registry.pl
CHANGED
|
@@ -18,6 +18,7 @@ known_rule('must-priority-coverage').
|
|
|
18
18
|
known_rule('symbol-coverage').
|
|
19
19
|
known_rule('symbol-traceability').
|
|
20
20
|
known_rule('symbol-granularity').
|
|
21
|
+
known_rule('symbol-coordinates-missing').
|
|
21
22
|
known_rule('no-dangling-refs').
|
|
22
23
|
known_rule('source-relationship-parity').
|
|
23
24
|
known_rule('branch-store-not-compiled').
|
|
@@ -77,6 +78,7 @@ rule_enforcement_class('must-priority-coverage', canonical).
|
|
|
77
78
|
rule_enforcement_class('symbol-coverage', canonical).
|
|
78
79
|
rule_enforcement_class('symbol-traceability', canonical).
|
|
79
80
|
rule_enforcement_class('symbol-granularity', canonical).
|
|
81
|
+
rule_enforcement_class('symbol-coordinates-missing', advisory).
|
|
80
82
|
rule_enforcement_class('no-dangling-refs', canonical).
|
|
81
83
|
rule_enforcement_class('source-relationship-parity', canonical).
|
|
82
84
|
rule_enforcement_class('branch-store-not-compiled', canonical).
|
|
@@ -136,6 +138,7 @@ rule_implementation('must-priority-coverage', prolog).
|
|
|
136
138
|
rule_implementation('symbol-coverage', prolog).
|
|
137
139
|
rule_implementation('symbol-traceability', prolog).
|
|
138
140
|
rule_implementation('symbol-granularity', typescript).
|
|
141
|
+
rule_implementation('symbol-coordinates-missing', typescript).
|
|
139
142
|
rule_implementation('no-dangling-refs', prolog).
|
|
140
143
|
rule_implementation('source-relationship-parity', typescript).
|
|
141
144
|
rule_implementation('branch-store-not-compiled', typescript).
|
|
@@ -271,6 +274,7 @@ rule_description('must-priority-coverage', 'Every current must-priority requirem
|
|
|
271
274
|
rule_description('symbol-coverage', 'Production symbols need qualifying coverage via covered_by plus a canonical requirement/scenario test path').
|
|
272
275
|
rule_description('symbol-traceability', 'Production symbols must directly implement requirements for ownership; covered_by is coverage only and executable_for is test identity only').
|
|
273
276
|
rule_description('symbol-granularity', 'A symbol with traceability relationships must not link its source file coarsely while narrower behavioral symbols are available, unless it carries an allowed granularity_reason; the staged gate\'s symbol_granularity_violation evaluated KB-wide').
|
|
277
|
+
rule_description('symbol-coordinates-missing', 'Advisory: an active symbol names a source file that exists, but Kibi could not place it there (the declaration was not found, the file is unreadable to the analyzer, or a declared plugin or grammar pack is missing), so its link is checked against no code').
|
|
274
278
|
rule_description('no-dangling-refs', 'All relationship targets must exist in the KB').
|
|
275
279
|
rule_description('source-relationship-parity', 'Authored Markdown and relationship-shard edges must exactly match the compiled KB').
|
|
276
280
|
rule_description('branch-store-not-compiled', 'The current branch\'s KB store must be compiled from the authored .kb/ sources; a missing or empty store is reported once, naming kibi sync, instead of every store-backed rule reading an empty KB').
|