kibi-core 0.28.0 → 0.29.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 +209 -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,204 @@ 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)) :-
|
|
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
|
+
source_matching(_, literal).
|
|
348
|
+
|
|
349
|
+
member_separator(".").
|
|
350
|
+
member_separator("/").
|
|
351
|
+
member_separator("::").
|
|
352
|
+
|
|
353
|
+
% extends_member(+Member, +Stem): Member is Stem or lies under it at a
|
|
354
|
+
% segment boundary. The empty stem (from the item "*") covers everything.
|
|
355
|
+
extends_member(_, "") :- !.
|
|
356
|
+
extends_member(Member, Stem) :- Member == Stem, !.
|
|
357
|
+
extends_member(Member, Stem) :-
|
|
358
|
+
member_separator(Separator),
|
|
359
|
+
string_concat(Stem, Separator, Prefix),
|
|
360
|
+
string_concat(Prefix, _, Member),
|
|
361
|
+
!.
|
|
362
|
+
|
|
363
|
+
% pattern_stem(+Item, -Stem): Item ends in *; Stem drops it and the separator
|
|
364
|
+
% before it (java.sql.* -> java.sql, * -> "").
|
|
365
|
+
pattern_stem(Item, Stem) :-
|
|
366
|
+
\+ unknown_receiver(Item, _),
|
|
367
|
+
string_concat(Base, "*", Item),
|
|
368
|
+
( member_separator(Separator), string_concat(Stem0, Separator, Base)
|
|
369
|
+
-> Stem = Stem0
|
|
370
|
+
; Stem = Base
|
|
371
|
+
).
|
|
372
|
+
|
|
373
|
+
% unknown_receiver(+Member, -Method): Member is *.Method.
|
|
374
|
+
unknown_receiver(Member, Method) :-
|
|
375
|
+
string_concat("*.", Method, Member),
|
|
376
|
+
Method \== "".
|
|
377
|
+
|
|
378
|
+
% ends_with_method(+Member, +Method): Member's last segment is Method.
|
|
379
|
+
ends_with_method(Member, Method) :-
|
|
380
|
+
member_separator(Separator),
|
|
381
|
+
string_concat(Separator, Method, Suffix),
|
|
382
|
+
string_concat(_, Suffix, Member),
|
|
383
|
+
!.
|
|
384
|
+
|
|
385
|
+
% exclude_match(+Actual, +Expected, -How): Actual is (certain) or may be
|
|
386
|
+
% (possible) what Expected names.
|
|
387
|
+
exclude_match(Actual, Expected, certain) :- Actual == Expected, !.
|
|
388
|
+
exclude_match(Actual, Expected, certain) :-
|
|
389
|
+
unknown_receiver(Expected, Method),
|
|
390
|
+
\+ unknown_receiver(Actual, _),
|
|
391
|
+
ends_with_method(Actual, Method),
|
|
392
|
+
!.
|
|
393
|
+
exclude_match(Actual, Expected, possible) :-
|
|
394
|
+
unknown_receiver(Actual, Method),
|
|
395
|
+
!,
|
|
396
|
+
( pattern_stem(Expected, _)
|
|
397
|
+
; ends_with_method(Expected, Method)
|
|
398
|
+
),
|
|
399
|
+
!.
|
|
400
|
+
exclude_match(Actual, Expected, How) :-
|
|
401
|
+
pattern_stem(Actual, ActualStem),
|
|
402
|
+
!,
|
|
403
|
+
( pattern_stem(Expected, Target) -> true ; Target = Expected ),
|
|
404
|
+
( extends_member(ActualStem, Target)
|
|
405
|
+
-> How = certain
|
|
406
|
+
; extends_member(Target, ActualStem)
|
|
407
|
+
-> How = possible
|
|
408
|
+
).
|
|
409
|
+
exclude_match(Actual, Expected, How) :-
|
|
410
|
+
( pattern_stem(Expected, Stem)
|
|
411
|
+
-> extends_member(Actual, Stem),
|
|
412
|
+
How = certain
|
|
413
|
+
; extends_member(Actual, Expected)
|
|
414
|
+
-> How = certain
|
|
415
|
+
; ends_with_method(Expected, Actual)
|
|
416
|
+
-> How = possible
|
|
417
|
+
; ends_with_method(Actual, Expected)
|
|
418
|
+
-> How = possible
|
|
419
|
+
).
|
|
420
|
+
|
|
421
|
+
% certain_match(+Actual, +Expected): Actual certainly is what Expected names
|
|
422
|
+
% (includes, subset_of).
|
|
423
|
+
certain_match(Actual, Expected) :- Actual == Expected, !.
|
|
424
|
+
certain_match(_, "*") :- !.
|
|
425
|
+
certain_match(Actual, Expected) :-
|
|
426
|
+
\+ unknown_receiver(Actual, _),
|
|
427
|
+
( pattern_stem(Expected, Stem)
|
|
428
|
+
-> ( pattern_stem(Actual, ActualStem)
|
|
429
|
+
-> extends_member(ActualStem, Stem)
|
|
430
|
+
; extends_member(Actual, Stem)
|
|
431
|
+
)
|
|
432
|
+
; unknown_receiver(Expected, Method)
|
|
433
|
+
-> \+ pattern_stem(Actual, _),
|
|
434
|
+
ends_with_method(Actual, Method)
|
|
435
|
+
),
|
|
436
|
+
!.
|
|
437
|
+
|
|
438
|
+
files_text([], "the files read") :- !.
|
|
439
|
+
files_text(Files, Text) :-
|
|
440
|
+
length(Files, N),
|
|
441
|
+
( N > 3
|
|
442
|
+
-> length(Shown, 3), append(Shown, _, Files),
|
|
443
|
+
atomic_list_concat(Shown, ', ', Listed),
|
|
444
|
+
Extra is N - 3,
|
|
445
|
+
format(string(Text), "~w and ~w more", [Listed, Extra])
|
|
446
|
+
; atomic_list_concat(Files, ', ', Text)
|
|
447
|
+
).
|
|
448
|
+
|
|
449
|
+
excluded_text(Files, Actual-Expected-How, Text) :-
|
|
450
|
+
( How == certain, Actual == Expected
|
|
451
|
+
-> Text = Actual
|
|
452
|
+
; How == certain
|
|
453
|
+
-> format(string(Text), "~w (within ~w)", [Actual, Expected])
|
|
454
|
+
; unknown_receiver(Actual, Method)
|
|
455
|
+
-> files_text(Files, Where),
|
|
456
|
+
format(string(Text), "~w (a call of ~w on a receiver Kibi cannot name, in ~w; it may be ~w)", [Actual, Method, Where, Expected])
|
|
457
|
+
; pattern_stem(Actual, _)
|
|
458
|
+
-> format(string(Text), "~w (a wildcard that may cover ~w)", [Actual, Expected])
|
|
459
|
+
; ends_with_method(Actual, Expected)
|
|
460
|
+
-> format(string(Text), "~w (it may be the ~w the fact excludes)", [Actual, Expected])
|
|
461
|
+
; format(string(Text), "~w (a shorter name that may be ~w)", [Actual, Expected])
|
|
462
|
+
).
|
|
463
|
+
|
|
464
|
+
% reference_relation_problem(+Operator, +Actual, +Expected, +Files, +What,
|
|
465
|
+
% -Reason) fails when the relation holds under the reference matching above.
|
|
466
|
+
reference_relation_problem("excludes", Actual, Expected, Files, What, Reason) :-
|
|
467
|
+
!,
|
|
468
|
+
findall(A-E-How, (member(A, Actual), member(E, Expected), exclude_match(A, E, How)), Pairs0),
|
|
469
|
+
sort(Pairs0, Pairs),
|
|
470
|
+
Pairs \== [],
|
|
471
|
+
include(certain_pair, Pairs, Certain),
|
|
472
|
+
maplist(excluded_text(Files), Pairs, Texts),
|
|
473
|
+
atomic_list_concat(Texts, ', ', Text),
|
|
474
|
+
( Certain \== []
|
|
475
|
+
-> format(string(Reason), "~w include ~w, which the fact excludes", [What, Text])
|
|
476
|
+
; format(string(Reason), "~w include ~w, which the fact excludes; Kibi cannot decide the claim for those members", [What, Text])
|
|
477
|
+
).
|
|
478
|
+
reference_relation_problem("includes", Actual, Expected, _, What, Reason) :-
|
|
479
|
+
!,
|
|
480
|
+
exclude(matched_by_some(Actual), Expected, Missing),
|
|
481
|
+
Missing \== [],
|
|
482
|
+
atomic_list_concat(Missing, ', ', Text),
|
|
483
|
+
format(string(Reason), "~w do not include ~w", [What, Text]).
|
|
484
|
+
reference_relation_problem("subset_of", Actual, Expected, _, What, Reason) :-
|
|
485
|
+
!,
|
|
486
|
+
exclude(allowed_by_some(Expected), Actual, Extra),
|
|
487
|
+
Extra \== [],
|
|
488
|
+
atomic_list_concat(Extra, ', ', Text),
|
|
489
|
+
( member(Member, Extra), unknown_receiver(Member, _)
|
|
490
|
+
-> 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])
|
|
491
|
+
; format(string(Reason), "~w include ~w, which the fact's set does not allow", [What, Text])
|
|
492
|
+
).
|
|
493
|
+
reference_relation_problem(Operator, Actual, Expected, _, What, Reason) :-
|
|
494
|
+
relation_problem(Operator, Actual, Expected, What, Reason).
|
|
495
|
+
|
|
496
|
+
certain_pair(_-_-certain).
|
|
497
|
+
|
|
498
|
+
matched_by_some(Actual, Expected) :-
|
|
499
|
+
member(Member, Actual),
|
|
500
|
+
certain_match(Member, Expected),
|
|
501
|
+
!.
|
|
502
|
+
|
|
503
|
+
allowed_by_some(Expected, Member) :-
|
|
504
|
+
member(Item, Expected),
|
|
505
|
+
certain_match(Member, Item),
|
|
506
|
+
!.
|
|
507
|
+
|
|
309
508
|
% ---------------------------------------------------------------------------
|
|
310
509
|
% Obligations
|
|
311
510
|
|
|
@@ -397,14 +596,18 @@ obligation_result("machine_relation", Obligation, model(_, _, Machine), Result)
|
|
|
397
596
|
; Result = ok
|
|
398
597
|
)
|
|
399
598
|
).
|
|
400
|
-
obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _, _)), Result) :- !,
|
|
599
|
+
obligation_result("set_relation", Obligation, model(_, _, source(Actual, _, _, _, Matching)), Result) :- !,
|
|
401
600
|
dict_text(Obligation, operator, Operator),
|
|
402
601
|
obligation_strings(Obligation, Expected),
|
|
403
|
-
|
|
602
|
+
What = "the members Kibi read from the source",
|
|
603
|
+
( ( Matching = references(Files)
|
|
604
|
+
-> reference_relation_problem(Operator, Actual, Expected, Files, What, Reason)
|
|
605
|
+
; relation_problem(Operator, Actual, Expected, What, Reason)
|
|
606
|
+
)
|
|
404
607
|
-> Result = problem(Reason)
|
|
405
608
|
; Result = ok
|
|
406
609
|
).
|
|
407
|
-
obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _)), Result) :-
|
|
610
|
+
obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _, _)), Result) :-
|
|
408
611
|
memberchk(FieldKind, ["required_fields", "nullable_fields"]),
|
|
409
612
|
!,
|
|
410
613
|
( is_list(Entities), Entities \== []
|
|
@@ -417,7 +620,7 @@ obligation_result(FieldKind, Obligation, model(_, _, source(_, Entities, _, _)),
|
|
|
417
620
|
-> Result = problem(Reason)
|
|
418
621
|
; Result = ok
|
|
419
622
|
).
|
|
420
|
-
obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes, _)), Result) :- !,
|
|
623
|
+
obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Routes, _, _)), Result) :- !,
|
|
421
624
|
( is_list(Routes), Routes \== []
|
|
422
625
|
-> true
|
|
423
626
|
; throw(model_error("the source has no routes; read a route table for a middleware claim"))
|
|
@@ -436,7 +639,7 @@ obligation_result("route_middleware", Obligation, model(_, _, source(_, _, Route
|
|
|
436
639
|
; Result = ok
|
|
437
640
|
).
|
|
438
641
|
% implements REQ-kibi-source-proof-kinds
|
|
439
|
-
obligation_result("sequence_precedes", Obligation, model(_, _, source(_, _, _, Sequence)), Result) :- !,
|
|
642
|
+
obligation_result("sequence_precedes", Obligation, model(_, _, source(_, _, _, Sequence, _)), Result) :- !,
|
|
440
643
|
( is_list(Sequence)
|
|
441
644
|
-> true
|
|
442
645
|
; 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').
|