kibi-core 0.14.0 → 0.15.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 +5 -2
- package/schema/entities.pl +22 -0
- package/schema/relationships.pl +14 -0
- package/schema/validation.pl +45 -1
- package/src/checks.pl +1850 -4
- package/src/discovery.pl +214 -3
- package/src/intervals.pl +175 -0
- package/src/kb.pl +82 -34
- package/src/logic_ir.pl +679 -65
- package/src/requirement_proof.pl +63 -9
- package/src/rule_registry.pl +77 -3
- package/src/semantic_quality.pl +151 -3
- package/src/status.pl +25 -2
package/src/requirement_proof.pl
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
:- use_module('kb.pl').
|
|
19
19
|
:- use_module('checks.pl', [
|
|
20
20
|
check_domain_contradictions_and_witnesses/2,
|
|
21
|
+
infeasible_scenario/5,
|
|
22
|
+
scenario_feasibility_outcome/2,
|
|
21
23
|
check_rule_safety/1,
|
|
22
24
|
check_rule_verifiability/1
|
|
23
25
|
]).
|
|
@@ -61,7 +63,7 @@ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
|
|
|
61
63
|
proofTestBindings: TestBindings
|
|
62
64
|
}.
|
|
63
65
|
|
|
64
|
-
% implements REQ-kibi-
|
|
66
|
+
% implements REQ-kibi-conservative-requirement-proof
|
|
65
67
|
% Exemption first: a current requirement explicitly marked proof_exempt with a
|
|
66
68
|
% reason is intentionally outside E2E-proof scope and must never fall through
|
|
67
69
|
% to the ladder (which would report misleading receipt gaps).
|
|
@@ -78,7 +80,7 @@ requirement_proof(ReqId, _ReqProps, _Context, Proof) :-
|
|
|
78
80
|
requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
79
81
|
semantic_inventory_stage(ReqProps, SemanticStage, Inventory),
|
|
80
82
|
logic_grounding_stage(ReqId, ReqProps, Inventory, Context, LogicStage),
|
|
81
|
-
contradiction_stage(ReqId, LogicStage.status, Context, ContradictionStage),
|
|
83
|
+
contradiction_stage(ReqId, LogicStage.status, SemanticStage.status, Context, ContradictionStage),
|
|
82
84
|
scenario_stage(ReqId, ScenarioStage, ScenarioIds),
|
|
83
85
|
scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
|
|
84
86
|
passing_e2e_stage(ScenarioTestStage, Context, PassingE2eStage, PassingE2eTests),
|
|
@@ -512,6 +514,21 @@ contradiction_stage(_ReqId, LogicStatus, _Context, Stage) :-
|
|
|
512
514
|
Stage = _{status: unresolved, outcome: incomplete_grounding, conflicts: []}.
|
|
513
515
|
contradiction_stage(_ReqId, passed, _Context, _{status: passed, outcome: no_conflict_found, conflicts: []}).
|
|
514
516
|
|
|
517
|
+
%% contradiction_stage(+ReqId, +LogicStatus, +InventoryStatus, +Context, -Stage)
|
|
518
|
+
% implements REQ-kibi-truthful-consistency
|
|
519
|
+
% Absence of a conflict is evidence only when every normative proposition was
|
|
520
|
+
% modeled. Propositions still marked ambiguous or ontology_gap were never
|
|
521
|
+
% encoded, so the checker cannot have looked at them: report the analysis as
|
|
522
|
+
% incomplete instead of no_conflict_found. Found conflicts still win.
|
|
523
|
+
contradiction_stage(ReqId, LogicStatus, InventoryStatus, Context, Stage) :-
|
|
524
|
+
contradiction_stage(ReqId, LogicStatus, Context, Stage0),
|
|
525
|
+
( Stage0.outcome == no_conflict_found,
|
|
526
|
+
InventoryStatus == unresolved
|
|
527
|
+
-> Stage = _{status: unresolved, outcome: analysis_incomplete, conflicts: [],
|
|
528
|
+
reason: "unresolved_propositions"}
|
|
529
|
+
; Stage = Stage0
|
|
530
|
+
).
|
|
531
|
+
|
|
515
532
|
requirement_contradictions(ReqId, Violations, Conflicts) :-
|
|
516
533
|
include(violation_mentions_requirement(ReqId), Violations, Conflicts0),
|
|
517
534
|
maplist(violation_description, Conflicts0, Conflicts).
|
|
@@ -542,16 +559,46 @@ scenario_stage(ReqId, Stage, ScenarioIds) :-
|
|
|
542
559
|
findall(ScenarioId,
|
|
543
560
|
(member(ScenarioId, ScenarioTargets), \+ existing_scenario(ScenarioId)),
|
|
544
561
|
InvalidScenarioTargets),
|
|
545
|
-
|
|
562
|
+
% implements REQ-kibi-scenario-feasibility-v2
|
|
563
|
+
% A scenario that expects success but whose assumptions cannot hold with
|
|
564
|
+
% what current requirements require (pairwise or jointly) can never pass,
|
|
565
|
+
% so the requirement cannot be proven through it.
|
|
566
|
+
findall(ScenarioId,
|
|
567
|
+
(member(ScenarioId, ScenarioIds), once(infeasible_scenario(ScenarioId, _, _, _, _))),
|
|
568
|
+
InfeasibleScenarios),
|
|
569
|
+
% A success scenario whose feasibility cannot be decided is reported as a
|
|
570
|
+
% distinct, non-blocking note instead of being counted as feasible.
|
|
571
|
+
findall(_{scenario: ScenarioId, reason: ReasonText},
|
|
572
|
+
( member(ScenarioId, ScenarioIds),
|
|
573
|
+
once(scenario_feasibility_outcome(ScenarioId, unknown(Reason))),
|
|
574
|
+
unknown_feasibility_reason(Reason, ReasonText)
|
|
575
|
+
),
|
|
576
|
+
UnknownFeasibility),
|
|
577
|
+
( (ScenarioIds = [] ; InvalidScenarioTargets \= [])
|
|
578
|
+
-> Status = missing
|
|
579
|
+
; InfeasibleScenarios \= []
|
|
580
|
+
-> Status = blocked
|
|
581
|
+
; Status = passed
|
|
582
|
+
),
|
|
546
583
|
maplist(entity_source_ref, ScenarioIds, Sources),
|
|
547
584
|
Stage = _{
|
|
548
585
|
status: Status,
|
|
549
586
|
scenarios: ScenarioIds,
|
|
550
587
|
scenarioTargets: ScenarioTargets,
|
|
551
588
|
invalidScenarioTargets: InvalidScenarioTargets,
|
|
589
|
+
infeasibleScenarios: InfeasibleScenarios,
|
|
590
|
+
unknownFeasibility: UnknownFeasibility,
|
|
552
591
|
sources: Sources
|
|
553
592
|
}.
|
|
554
593
|
|
|
594
|
+
unknown_feasibility_reason(no_assumptions, no_assumptions).
|
|
595
|
+
unknown_feasibility_reason(contradictory_assumptions(_), contradictory_assumptions).
|
|
596
|
+
unknown_feasibility_reason(unmatched_assumption(_), unmatched_assumption).
|
|
597
|
+
unknown_feasibility_reason(incomparable_assumption(_), incomparable_assumption).
|
|
598
|
+
unknown_feasibility_reason(conflicting_requirements(_), conflicting_requirements).
|
|
599
|
+
unknown_feasibility_reason(undecided_rule(_), undecided_rule).
|
|
600
|
+
unknown_feasibility_reason(undetermined_validity(_), undetermined_validity).
|
|
601
|
+
|
|
555
602
|
existing_scenario(ScenarioId) :-
|
|
556
603
|
kb_entity(ScenarioId, scenario, _).
|
|
557
604
|
|
|
@@ -939,10 +986,10 @@ receipt_for_current_mode(TestId, Context, Receipts, SnapshotReceipts) :-
|
|
|
939
986
|
( Context.proofBindingMode == per_contract,
|
|
940
987
|
get_dict(TestId, Context.proofTestBindings, CurrentBinding)
|
|
941
988
|
-> include(receipt_with_binding_hash, Receipts, BoundReceipts),
|
|
942
|
-
include(receipt_for_binding(CurrentBinding), BoundReceipts,
|
|
943
|
-
(
|
|
989
|
+
include(receipt_for_binding(CurrentBinding), BoundReceipts, BindingReceipts),
|
|
990
|
+
( BindingReceipts == []
|
|
944
991
|
-> include(receipt_for_snapshot(Context.proofSnapshot), Receipts, SnapshotReceipts)
|
|
945
|
-
;
|
|
992
|
+
; SnapshotReceipts = BindingReceipts
|
|
946
993
|
)
|
|
947
994
|
; include(receipt_for_snapshot(Context.proofSnapshot), Receipts, SnapshotReceipts)
|
|
948
995
|
).
|
|
@@ -1610,8 +1657,9 @@ receipt_completeness_issue(failed_proof_receipt).
|
|
|
1610
1657
|
receipt_completeness_issue(invalid_proof_receipt).
|
|
1611
1658
|
receipt_completeness_issue(proof_contract_mismatch).
|
|
1612
1659
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1660
|
+
% Unknown scenario feasibility never blocks proof: it is surfaced as an
|
|
1661
|
+
% advisory so it is not mistaken for a passed feasibility check.
|
|
1662
|
+
proof_issue_advisory(unknown_scenario_feasibility, _Stages).
|
|
1615
1663
|
|
|
1616
1664
|
proof_gap_present(missing_semantic_inventory, Stages) :- Stages.semanticInventory.propositionCount =:= 0.
|
|
1617
1665
|
proof_gap_present(incomplete_semantic_inventory, Stages) :- Stages.semanticInventory.missingCount > 0.
|
|
@@ -1626,7 +1674,11 @@ proof_gap_present(ambiguous_logic_grounding, Stages) :-
|
|
|
1626
1674
|
proof_gap_present(blocking_contradiction, Stages) :- Stages.contradictions.status == blocked.
|
|
1627
1675
|
proof_gap_present(contradiction_check_incomplete, Stages) :- Stages.contradictions.status == unresolved.
|
|
1628
1676
|
proof_gap_present(missing_scenario, Stages) :- Stages.scenarios.status == missing.
|
|
1629
|
-
proof_gap_present(
|
|
1677
|
+
proof_gap_present(infeasible_scenario, Stages) :- Stages.scenarios.infeasibleScenarios \= [].
|
|
1678
|
+
proof_gap_present(unknown_scenario_feasibility, Stages) :-
|
|
1679
|
+
get_dict(unknownFeasibility, Stages.scenarios, Unknown),
|
|
1680
|
+
Unknown \= [].
|
|
1681
|
+
proof_gap_present(missing_scenario_test, Stages) :- memberchk(Stages.scenarios.status, [passed, blocked]), Stages.scenarioTests.status == missing.
|
|
1630
1682
|
proof_gap_present(missing_passing_e2e, Stages) :- Stages.passingE2e.status == missing.
|
|
1631
1683
|
proof_gap_present(missing_proof_receipt, Stages) :- Stages.passingE2e.missingReceiptTests \= [].
|
|
1632
1684
|
proof_gap_present(stale_proof_receipt, Stages) :- Stages.passingE2e.staleReceiptTests \= [].
|
|
@@ -1652,7 +1704,9 @@ gap_definition(missing_logic_grounding, 32, logic_grounding, "Ground every model
|
|
|
1652
1704
|
gap_definition(ambiguous_logic_grounding, 33, logic_grounding, "Remove duplicate or invalid ground representations and validate linked rules.").
|
|
1653
1705
|
gap_definition(blocking_contradiction, 40, contradictions, "Supersede or reconcile the conflicting normative requirement.").
|
|
1654
1706
|
gap_definition(contradiction_check_incomplete, 41, contradictions, "Complete logical grounding before interpreting absence of a conflict as evidence.").
|
|
1707
|
+
gap_definition(unknown_scenario_feasibility, 49, scenarios, "Link the property values the success scenario relies on with assumes facts that a current requirement constrains, so its feasibility can be decided.").
|
|
1655
1708
|
gap_definition(missing_scenario, 50, scenarios, "Add a specified_by scenario for the requirement.").
|
|
1709
|
+
gap_definition(infeasible_scenario, 49, scenarios, "Set expects: rejection on the scenario, correct its assumes fact, or record a human-approved exception requirement that exempts the forbidding requirement.").
|
|
1656
1710
|
gap_definition(missing_scenario_test, 51, scenario_tests, "Link the scenario to a test with verified_by or validates.").
|
|
1657
1711
|
gap_definition(missing_passing_e2e, 52, passing_e2e, "Record fresh passing end-to-end evidence on a scenario-backed test.").
|
|
1658
1712
|
gap_definition(missing_proof_receipt, 53, passing_e2e, "Run the configured proof integration through kibi prove and append its kibi.proof-receipt.v1 result for the scenario-backed proof-bearing test.").
|
package/src/rule_registry.pl
CHANGED
|
@@ -19,9 +19,16 @@ known_rule('symbol-coverage').
|
|
|
19
19
|
known_rule('symbol-traceability').
|
|
20
20
|
known_rule('no-dangling-refs').
|
|
21
21
|
known_rule('source-relationship-parity').
|
|
22
|
+
known_rule('source-path-dangling').
|
|
22
23
|
known_rule('no-cycles').
|
|
23
24
|
known_rule('required-fields').
|
|
24
25
|
known_rule('deprecated-adr-no-successor').
|
|
26
|
+
known_rule('superseded-requirement-open').
|
|
27
|
+
known_rule('scenario-feasibility').
|
|
28
|
+
known_rule('scenario-feasibility-unknown').
|
|
29
|
+
known_rule('exception-claim-keys').
|
|
30
|
+
known_rule('numeric-string-value').
|
|
31
|
+
known_rule('rule-key-arguments-missing').
|
|
25
32
|
known_rule('domain-contradictions').
|
|
26
33
|
known_rule('logic-coverage').
|
|
27
34
|
known_rule('rule-safety').
|
|
@@ -40,6 +47,13 @@ known_rule('domain-implication').
|
|
|
40
47
|
known_rule('subject-key-identity').
|
|
41
48
|
known_rule('subject-key-shape').
|
|
42
49
|
known_rule('ontology-quality').
|
|
50
|
+
known_rule('exception-unapproved').
|
|
51
|
+
known_rule('exception-approval-self-attested').
|
|
52
|
+
known_rule('agent-requirement-unapproved').
|
|
53
|
+
known_rule('requirement-rationale-missing').
|
|
54
|
+
known_rule('symbol-owner-superseded').
|
|
55
|
+
known_rule('adr-unlinked').
|
|
56
|
+
known_rule('adr-proposed').
|
|
43
57
|
known_rule('predicate-schema-conformance').
|
|
44
58
|
|
|
45
59
|
rule_enforcement_class('must-priority-coverage', canonical).
|
|
@@ -47,16 +61,23 @@ rule_enforcement_class('symbol-coverage', canonical).
|
|
|
47
61
|
rule_enforcement_class('symbol-traceability', canonical).
|
|
48
62
|
rule_enforcement_class('no-dangling-refs', canonical).
|
|
49
63
|
rule_enforcement_class('source-relationship-parity', canonical).
|
|
64
|
+
rule_enforcement_class('source-path-dangling', canonical).
|
|
50
65
|
rule_enforcement_class('no-cycles', canonical).
|
|
51
66
|
rule_enforcement_class('required-fields', canonical).
|
|
52
67
|
rule_enforcement_class('deprecated-adr-no-successor', canonical).
|
|
68
|
+
rule_enforcement_class('superseded-requirement-open', canonical).
|
|
69
|
+
rule_enforcement_class('scenario-feasibility', canonical).
|
|
70
|
+
rule_enforcement_class('scenario-feasibility-unknown', advisory).
|
|
71
|
+
rule_enforcement_class('exception-claim-keys', canonical).
|
|
72
|
+
rule_enforcement_class('numeric-string-value', advisory).
|
|
73
|
+
rule_enforcement_class('rule-key-arguments-missing', advisory).
|
|
53
74
|
rule_enforcement_class('domain-contradictions', canonical).
|
|
54
75
|
rule_enforcement_class('logic-coverage', canonical).
|
|
55
76
|
rule_enforcement_class('rule-safety', canonical).
|
|
56
77
|
rule_enforcement_class('rule-verifiability', canonical).
|
|
57
78
|
rule_enforcement_class('query-plan-safety', canonical).
|
|
58
79
|
rule_enforcement_class('req-status-vocabulary', canonical).
|
|
59
|
-
rule_enforcement_class('strict-fact-shape',
|
|
80
|
+
rule_enforcement_class('strict-fact-shape', canonical).
|
|
60
81
|
rule_enforcement_class('strict-req-fact-pairing', advisory).
|
|
61
82
|
rule_enforcement_class('predicate-verifiability', advisory).
|
|
62
83
|
rule_enforcement_class('strict-readiness', migration).
|
|
@@ -68,6 +89,13 @@ rule_enforcement_class('domain-implication', advisory).
|
|
|
68
89
|
rule_enforcement_class('subject-key-identity', advisory).
|
|
69
90
|
rule_enforcement_class('subject-key-shape', advisory).
|
|
70
91
|
rule_enforcement_class('ontology-quality', advisory).
|
|
92
|
+
rule_enforcement_class('exception-unapproved', advisory).
|
|
93
|
+
rule_enforcement_class('exception-approval-self-attested', advisory).
|
|
94
|
+
rule_enforcement_class('agent-requirement-unapproved', advisory).
|
|
95
|
+
rule_enforcement_class('requirement-rationale-missing', advisory).
|
|
96
|
+
rule_enforcement_class('symbol-owner-superseded', advisory).
|
|
97
|
+
rule_enforcement_class('adr-unlinked', advisory).
|
|
98
|
+
rule_enforcement_class('adr-proposed', advisory).
|
|
71
99
|
rule_enforcement_class('predicate-schema-conformance', advisory).
|
|
72
100
|
|
|
73
101
|
rule_implementation('must-priority-coverage', prolog).
|
|
@@ -75,9 +103,16 @@ rule_implementation('symbol-coverage', prolog).
|
|
|
75
103
|
rule_implementation('symbol-traceability', prolog).
|
|
76
104
|
rule_implementation('no-dangling-refs', prolog).
|
|
77
105
|
rule_implementation('source-relationship-parity', typescript).
|
|
106
|
+
rule_implementation('source-path-dangling', typescript).
|
|
78
107
|
rule_implementation('no-cycles', prolog).
|
|
79
108
|
rule_implementation('required-fields', prolog).
|
|
80
109
|
rule_implementation('deprecated-adr-no-successor', prolog).
|
|
110
|
+
rule_implementation('superseded-requirement-open', prolog).
|
|
111
|
+
rule_implementation('scenario-feasibility', prolog).
|
|
112
|
+
rule_implementation('scenario-feasibility-unknown', prolog).
|
|
113
|
+
rule_implementation('exception-claim-keys', prolog).
|
|
114
|
+
rule_implementation('numeric-string-value', prolog).
|
|
115
|
+
rule_implementation('rule-key-arguments-missing', prolog).
|
|
81
116
|
rule_implementation('domain-contradictions', prolog).
|
|
82
117
|
rule_implementation('logic-coverage', prolog).
|
|
83
118
|
rule_implementation('rule-safety', prolog).
|
|
@@ -96,6 +131,13 @@ rule_implementation('domain-implication', prolog).
|
|
|
96
131
|
rule_implementation('subject-key-identity', prolog).
|
|
97
132
|
rule_implementation('subject-key-shape', prolog).
|
|
98
133
|
rule_implementation('ontology-quality', prolog).
|
|
134
|
+
rule_implementation('exception-unapproved', typescript).
|
|
135
|
+
rule_implementation('exception-approval-self-attested', typescript).
|
|
136
|
+
rule_implementation('agent-requirement-unapproved', typescript).
|
|
137
|
+
rule_implementation('requirement-rationale-missing', typescript).
|
|
138
|
+
rule_implementation('symbol-owner-superseded', prolog).
|
|
139
|
+
rule_implementation('adr-unlinked', prolog).
|
|
140
|
+
rule_implementation('adr-proposed', prolog).
|
|
99
141
|
rule_implementation('predicate-schema-conformance', typescript).
|
|
100
142
|
|
|
101
143
|
rule_predicate('must-priority-coverage', check_must_priority_coverage).
|
|
@@ -105,6 +147,12 @@ rule_predicate('no-dangling-refs', check_no_dangling_refs).
|
|
|
105
147
|
rule_predicate('no-cycles', check_no_cycles).
|
|
106
148
|
rule_predicate('required-fields', check_required_fields).
|
|
107
149
|
rule_predicate('deprecated-adr-no-successor', check_deprecated_adrs).
|
|
150
|
+
rule_predicate('superseded-requirement-open', check_superseded_requirement_open).
|
|
151
|
+
rule_predicate('scenario-feasibility', check_scenario_feasibility).
|
|
152
|
+
rule_predicate('scenario-feasibility-unknown', check_scenario_feasibility_unknown).
|
|
153
|
+
rule_predicate('exception-claim-keys', check_exception_claim_keys).
|
|
154
|
+
rule_predicate('numeric-string-value', check_numeric_string_values).
|
|
155
|
+
rule_predicate('rule-key-arguments-missing', check_rule_key_arguments_missing).
|
|
108
156
|
rule_predicate('domain-contradictions', check_domain_contradictions).
|
|
109
157
|
rule_predicate('logic-coverage', check_logic_coverage).
|
|
110
158
|
rule_predicate('rule-safety', check_rule_safety).
|
|
@@ -122,6 +170,9 @@ rule_predicate('domain-implication', check_domain_implication).
|
|
|
122
170
|
rule_predicate('subject-key-identity', check_subject_key_identity).
|
|
123
171
|
rule_predicate('subject-key-shape', check_subject_key_shape).
|
|
124
172
|
rule_predicate('ontology-quality', check_ontology_quality).
|
|
173
|
+
rule_predicate('symbol-owner-superseded', check_symbol_owner_superseded).
|
|
174
|
+
rule_predicate('adr-unlinked', check_adr_unlinked).
|
|
175
|
+
rule_predicate('adr-proposed', check_adr_proposed).
|
|
125
176
|
|
|
126
177
|
rule_predicate_arity('must-priority-coverage', 1).
|
|
127
178
|
rule_predicate_arity('symbol-coverage', 1).
|
|
@@ -130,6 +181,12 @@ rule_predicate_arity('no-dangling-refs', 1).
|
|
|
130
181
|
rule_predicate_arity('no-cycles', 1).
|
|
131
182
|
rule_predicate_arity('required-fields', 1).
|
|
132
183
|
rule_predicate_arity('deprecated-adr-no-successor', 1).
|
|
184
|
+
rule_predicate_arity('superseded-requirement-open', 1).
|
|
185
|
+
rule_predicate_arity('scenario-feasibility', 1).
|
|
186
|
+
rule_predicate_arity('scenario-feasibility-unknown', 1).
|
|
187
|
+
rule_predicate_arity('exception-claim-keys', 1).
|
|
188
|
+
rule_predicate_arity('numeric-string-value', 1).
|
|
189
|
+
rule_predicate_arity('rule-key-arguments-missing', 1).
|
|
133
190
|
rule_predicate_arity('domain-contradictions', 1).
|
|
134
191
|
rule_predicate_arity('logic-coverage', 1).
|
|
135
192
|
rule_predicate_arity('rule-safety', 1).
|
|
@@ -147,15 +204,25 @@ rule_predicate_arity('domain-implication', 1).
|
|
|
147
204
|
rule_predicate_arity('subject-key-identity', 1).
|
|
148
205
|
rule_predicate_arity('subject-key-shape', 1).
|
|
149
206
|
rule_predicate_arity('ontology-quality', 1).
|
|
207
|
+
rule_predicate_arity('symbol-owner-superseded', 1).
|
|
208
|
+
rule_predicate_arity('adr-unlinked', 1).
|
|
209
|
+
rule_predicate_arity('adr-proposed', 1).
|
|
150
210
|
|
|
151
211
|
rule_description('must-priority-coverage', 'Every must-priority requirement must have a scenario and a test').
|
|
152
212
|
rule_description('symbol-coverage', 'Production symbols need qualifying coverage via covered_by plus a canonical requirement/scenario test path').
|
|
153
213
|
rule_description('symbol-traceability', 'Production symbols must directly implement requirements for ownership; covered_by is coverage only and executable_for is test identity only').
|
|
154
214
|
rule_description('no-dangling-refs', 'All relationship targets must exist in the KB').
|
|
155
215
|
rule_description('source-relationship-parity', 'Authored Markdown and relationship-shard edges must exactly match the compiled KB').
|
|
216
|
+
rule_description('source-path-dangling', 'An authored entity\'s source field must name an existing workspace path (a #anchor suffix is ignored), an existing entity id, or an http(s) URL; a missing source is allowed').
|
|
156
217
|
rule_description('no-cycles', 'No circular dependency chains in requirements').
|
|
157
218
|
rule_description('required-fields', 'All entities must have required fields').
|
|
158
219
|
rule_description('deprecated-adr-no-successor', 'Deprecated ADRs must have a successor ADR that supersedes them').
|
|
220
|
+
rule_description('superseded-requirement-open', 'A requirement that another requirement supersedes must have status closed, and supersedes links between requirements must not form a cycle').
|
|
221
|
+
rule_description('scenario-feasibility', 'A scenario that expects success must not assume property values that cannot hold, alone or together, with what current requirements require through typed property values or rules, unless an approved exception requirement waives that requirement or the listed clauses').
|
|
222
|
+
rule_description('scenario-feasibility-unknown', 'Advisory: a scenario that expects success assumes nothing, assumes values that contradict each other, assumes a property value no governing requirement constrains comparably (type, unit or operator mismatch, or conflicting requirements), leaves a governing rule\'s conditions undecided, or conflicts only with constraints whose validity window may not cover it, so its feasibility cannot be decided').
|
|
223
|
+
rule_description('exception-claim-keys', 'An exception requirement\'s exempts_claims must name claim keys of a requirement it exempts, and requires an exempts link').
|
|
224
|
+
rule_description('numeric-string-value', 'Advisory: a property value stores a numeric-looking string where the comparison is numeric (ordering operator, or an int/number fact on the same property); the finding names the fact and the typed value, without converting it').
|
|
225
|
+
rule_description('rule-key-arguments-missing', 'Advisory: an opposing rule pair stays unresolved only because a body predicate declares no key_arguments; the finding names the predicate and the key positions that would decide the pair').
|
|
159
226
|
rule_description('domain-contradictions', 'Detect contradictions between requirements constraining the same fact').
|
|
160
227
|
rule_description('logic-coverage', 'Require every explicitly declared atomic requirement claim to be grounded by a linked strict-property or predicate fact').
|
|
161
228
|
rule_description('rule-safety', 'Stored Logic IR rules use only the typed, bounded, stratified vocabulary').
|
|
@@ -171,7 +238,14 @@ rule_description('proof-contract-symbols', 'Detect unresolved required_proofs.sy
|
|
|
171
238
|
rule_description('entity-id-style', 'Entity IDs name the governed behavior and match their filename stem; new purely numeric IDs are reported at creation boundaries').
|
|
172
239
|
rule_description('domain-redundancy', 'Two current requirements must not ground the identical logical term or share a ground fact unless linked by supersedes or restates').
|
|
173
240
|
rule_description('domain-implication', 'Informational: one requirement\'s numeric bound strictly implies another requirement\'s bound on the same subject and property').
|
|
174
|
-
rule_description('subject-key-identity', 'Subject keys must name a shared component, not be derived from a requirement ID').
|
|
175
|
-
rule_description('subject-key-shape', 'Subject keys follow dotted component.aspect[.sub] with lowercase snake segments').
|
|
241
|
+
rule_description('subject-key-identity', 'Subject keys must name a shared component, not be derived from a requirement ID, and one subject or claim must not be minted as several active subject facts or identical property_value facts').
|
|
242
|
+
rule_description('subject-key-shape', 'Subject keys follow dotted component.aspect[.sub] with lowercase snake segments, and property keys name a property rather than a numbered clause (clause_NN)').
|
|
176
243
|
rule_description('ontology-quality', 'Informational: predicate schemas whose argument values mostly occur in only one fact are carrying prose instead of a shared vocabulary').
|
|
244
|
+
rule_description('exception-unapproved', 'Advisory: a current exception requirement exempts another requirement but has no approved_by, so it exempts nothing until a human approves it').
|
|
245
|
+
rule_description('exception-approval-self-attested', 'Advisory: an agent-authored exception records approved_by but no human corroborated it (origin.approved_by or approval_ref is missing), so a human should confirm the approval').
|
|
246
|
+
rule_description('agent-requirement-unapproved', 'Informational: current requirements authored by an agent (origin.kind agent) that no human has approved (origin.approved_by), listed up to 25 per check').
|
|
247
|
+
rule_description('requirement-rationale-missing', 'Advisory: a current requirement authored by a human or an agent (origin.kind human or agent) states no rationale: no rationale field, no Rationale or Why section in its body, and no link to an ADR; listed up to 25 per check plus one summary finding').
|
|
248
|
+
rule_description('symbol-owner-superseded', 'Advisory: a symbol whose every implements target is superseded or deprecated has no current owning requirement; listed up to 25 per check plus one summary finding').
|
|
249
|
+
rule_description('adr-unlinked', 'Advisory: an accepted ADR that no requirement or other ADR is linked with (relates_to, supersedes or any typed edge, in either direction)').
|
|
250
|
+
rule_description('adr-proposed', 'Informational: an ADR that is still proposed and not superseded, so nobody accepted or withdrew the decision').
|
|
177
251
|
rule_description('predicate-schema-conformance', 'Predicate facts match a predicate_schema (project-local, or the built-in catalog in the default namespace) and use its declared argument constants').
|
package/src/semantic_quality.pl
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
% entity-id-style filename stem must equal the frontmatter id
|
|
9
9
|
% domain-redundancy two current requirements ground the same term
|
|
10
10
|
% domain-implication one numeric bound implies another (informational)
|
|
11
|
-
% subject-key-identity subject keys derived from requirement IDs
|
|
12
|
-
%
|
|
11
|
+
% subject-key-identity subject keys derived from requirement IDs, and one
|
|
12
|
+
% subject or claim minted as several facts
|
|
13
|
+
% subject-key-shape subject keys outside component.aspect[.sub], and
|
|
14
|
+
% property keys that number a clause
|
|
13
15
|
% ontology-quality predicate schemas whose arguments are mostly
|
|
14
16
|
% one-off atoms (prose smuggled into atoms)
|
|
15
17
|
%
|
|
@@ -356,6 +358,16 @@ implication_witness_violation(Witness, violation(
|
|
|
356
358
|
%
|
|
357
359
|
% Subject facts are reported; property_value facts are reported only when no
|
|
358
360
|
% subject fact carries the same key (otherwise fixing the subject fixes them).
|
|
361
|
+
%
|
|
362
|
+
% The rule also reports vocabulary fragmentation, where one subject or claim
|
|
363
|
+
% was minted as several facts so requirements about it never meet: more than
|
|
364
|
+
% one active subject fact for the same subject_key, and more than one active
|
|
365
|
+
% property_value fact stating the same claim: the same subject_key,
|
|
366
|
+
% property_key, operator, typed value and unit. Facts that differ in operator
|
|
367
|
+
% (the two bounds of a range) or in value (two requirements bounding the same
|
|
368
|
+
% property differently) are distinct claims, which domain-contradictions and
|
|
369
|
+
% domain-implication compare. Each group is reported once, on its first fact
|
|
370
|
+
% id.
|
|
359
371
|
% implements REQ-kibi-subject-vocabulary
|
|
360
372
|
check_subject_key_identity(Violations) :-
|
|
361
373
|
findall(Segment-ReqId, requirement_subject_segment(ReqId, Segment), SegmentPairs0),
|
|
@@ -368,7 +380,116 @@ check_subject_key_identity(Violations) :-
|
|
|
368
380
|
subject_key_identity_violation(Segments, SubjectKeys, Violation),
|
|
369
381
|
Violations0
|
|
370
382
|
),
|
|
371
|
-
|
|
383
|
+
duplicate_subject_fact_violations(SubjectDuplicates),
|
|
384
|
+
duplicate_property_fact_violations(PropertyDuplicates),
|
|
385
|
+
append([Violations0, SubjectDuplicates, PropertyDuplicates], Violations1),
|
|
386
|
+
sort(Violations1, Violations).
|
|
387
|
+
|
|
388
|
+
%% duplicate_subject_fact_violations(-Violations)
|
|
389
|
+
duplicate_subject_fact_violations(Violations) :-
|
|
390
|
+
findall(
|
|
391
|
+
SubjectKey-FactId,
|
|
392
|
+
( fact_subject_key_of_kind(FactId, subject, SubjectKey),
|
|
393
|
+
active_fact(FactId)
|
|
394
|
+
),
|
|
395
|
+
Pairs0
|
|
396
|
+
),
|
|
397
|
+
sort(Pairs0, Pairs),
|
|
398
|
+
group_pairs_by_key(Pairs, Groups),
|
|
399
|
+
findall(
|
|
400
|
+
violation(
|
|
401
|
+
'subject-key-identity',
|
|
402
|
+
FirstId,
|
|
403
|
+
Description,
|
|
404
|
+
Suggestion,
|
|
405
|
+
Source,
|
|
406
|
+
_{subjectKey: SubjectKey, facts: FactIds}
|
|
407
|
+
),
|
|
408
|
+
( member(SubjectKey-FactIds, Groups),
|
|
409
|
+
FactIds = [FirstId, _|_],
|
|
410
|
+
length(FactIds, Count),
|
|
411
|
+
atomic_list_concat(FactIds, ', ', FactText),
|
|
412
|
+
format(string(Description),
|
|
413
|
+
"~w active subject facts share subject_key ~w (~w), so requirements about this subject are split across facts that never meet",
|
|
414
|
+
[Count, SubjectKey, FactText]),
|
|
415
|
+
format(string(Suggestion),
|
|
416
|
+
"Keep one subject fact for ~w, point every requirement's constrains link at it, and remove the others (kb_delete) once nothing links them",
|
|
417
|
+
[SubjectKey]),
|
|
418
|
+
entity_text(FirstId, fact, source, Source)
|
|
419
|
+
),
|
|
420
|
+
Violations
|
|
421
|
+
).
|
|
422
|
+
|
|
423
|
+
%% duplicate_property_fact_violations(-Violations)
|
|
424
|
+
duplicate_property_fact_violations(Violations) :-
|
|
425
|
+
findall(
|
|
426
|
+
key(SubjectKey, PropertyKey, Operator, Value)-FactId,
|
|
427
|
+
( fact_subject_key_of_kind(FactId, property_value, SubjectKey),
|
|
428
|
+
kb_entity(FactId, fact, Props),
|
|
429
|
+
memberchk(property_key=RawProperty, Props),
|
|
430
|
+
normalize_term_atom(RawProperty, PropertyKey),
|
|
431
|
+
memberchk(operator=RawOperator, Props),
|
|
432
|
+
normalize_term_atom(RawOperator, Operator),
|
|
433
|
+
property_value_signature(Props, Value),
|
|
434
|
+
active_fact(FactId)
|
|
435
|
+
),
|
|
436
|
+
Pairs0
|
|
437
|
+
),
|
|
438
|
+
sort(Pairs0, Pairs),
|
|
439
|
+
group_pairs_by_key(Pairs, Groups),
|
|
440
|
+
findall(
|
|
441
|
+
violation(
|
|
442
|
+
'subject-key-identity',
|
|
443
|
+
FirstId,
|
|
444
|
+
Description,
|
|
445
|
+
"Link every requirement to one shared fact and remove the duplicates (kb_delete) once nothing links them",
|
|
446
|
+
Source,
|
|
447
|
+
_{subjectKey: SubjectKey, propertyKey: PropertyKey, operator: Operator, facts: FactIds}
|
|
448
|
+
),
|
|
449
|
+
( member(key(SubjectKey, PropertyKey, Operator, _)-FactIds, Groups),
|
|
450
|
+
FactIds = [FirstId, _|_],
|
|
451
|
+
length(FactIds, Count),
|
|
452
|
+
atomic_list_concat(FactIds, ', ', FactText),
|
|
453
|
+
format(string(Description),
|
|
454
|
+
"~w active property_value facts state the same claim on ~w ~w (operator ~w, same value) (~w), so one claim is minted as several facts",
|
|
455
|
+
[Count, SubjectKey, PropertyKey, Operator, FactText]),
|
|
456
|
+
entity_text(FirstId, fact, source, Source)
|
|
457
|
+
),
|
|
458
|
+
Violations
|
|
459
|
+
).
|
|
460
|
+
|
|
461
|
+
%% property_value_signature(+Props, -Signature)
|
|
462
|
+
% The typed value and unit of a property_value fact, as Field-Value pairs in a
|
|
463
|
+
% fixed field order, so two facts compare equal only when they store the same
|
|
464
|
+
% value of the same type in the same unit.
|
|
465
|
+
property_value_signature(Props, Signature) :-
|
|
466
|
+
findall(
|
|
467
|
+
Field-Value,
|
|
468
|
+
( member(Field, [value_type, value_string, value_int, value_number, value_bool, unit]),
|
|
469
|
+
memberchk(Field=Raw, Props),
|
|
470
|
+
signature_value(Raw, Value),
|
|
471
|
+
Value \== ''
|
|
472
|
+
),
|
|
473
|
+
Signature
|
|
474
|
+
).
|
|
475
|
+
|
|
476
|
+
signature_value('^^'(Raw, _), Value) :- !, signature_value(Raw, Value).
|
|
477
|
+
signature_value(literal(type(_, Raw)), Value) :- !, signature_value(Raw, Value).
|
|
478
|
+
signature_value(Raw, Value) :- string(Raw), !, atom_string(Value, Raw).
|
|
479
|
+
signature_value(Raw, Raw).
|
|
480
|
+
|
|
481
|
+
%% active_fact(+FactId)
|
|
482
|
+
% A fact whose status does not retire it.
|
|
483
|
+
active_fact(FactId) :-
|
|
484
|
+
entity_text(FactId, fact, status, Status),
|
|
485
|
+
retired_fact_status(Status, Retired),
|
|
486
|
+
Retired == false.
|
|
487
|
+
|
|
488
|
+
retired_fact_status(Status, Retired) :-
|
|
489
|
+
( memberchk(Status, ["superseded", "deprecated", "closed", "removed", "retired", "inactive", "archived"])
|
|
490
|
+
-> Retired = true
|
|
491
|
+
; Retired = false
|
|
492
|
+
).
|
|
372
493
|
|
|
373
494
|
subject_key_identity_violation(Segments, SubjectKeys, violation(
|
|
374
495
|
'subject-key-identity',
|
|
@@ -471,6 +592,33 @@ subject_key_shape_violation(violation(
|
|
|
471
592
|
\+ valid_subject_key(SubjectKey),
|
|
472
593
|
format(string(Description), "Subject key ~w does not follow the component.aspect[.sub] convention", [SubjectKey]),
|
|
473
594
|
entity_text(FactId, fact, source, Source).
|
|
595
|
+
% A property key that numbers a clause (`clause_03_must_refresh_...`,
|
|
596
|
+
% `contract_clause_2`) names a position in one requirement's prose, not a
|
|
597
|
+
% property other requirements can share, so equal claims never meet.
|
|
598
|
+
subject_key_shape_violation(violation(
|
|
599
|
+
'subject-key-shape',
|
|
600
|
+
FactId,
|
|
601
|
+
Description,
|
|
602
|
+
"Rename the property key to the property the clause constrains (for example refresh_before_serving), reusing an existing property_key for the same subject when one fits",
|
|
603
|
+
Source
|
|
604
|
+
)) :-
|
|
605
|
+
kb_entity(FactId, fact, Props),
|
|
606
|
+
memberchk(property_key=RawKey, Props),
|
|
607
|
+
normalize_term_atom(RawKey, PropertyKey),
|
|
608
|
+
clause_numbered_property_key(PropertyKey),
|
|
609
|
+
format(string(Description), "Property key ~w numbers a clause instead of naming a property", [PropertyKey]),
|
|
610
|
+
entity_text(FactId, fact, source, Source).
|
|
611
|
+
|
|
612
|
+
%% clause_numbered_property_key(+PropertyKey)
|
|
613
|
+
% True when the key contains the word `clause` followed by a number.
|
|
614
|
+
clause_numbered_property_key(PropertyKey) :-
|
|
615
|
+
atom(PropertyKey),
|
|
616
|
+
atomic_list_concat(Words, '_', PropertyKey),
|
|
617
|
+
append(_, [clause, Number|_], Words),
|
|
618
|
+
atom_codes(Number, Codes),
|
|
619
|
+
Codes \== [],
|
|
620
|
+
forall(member(Code, Codes), code_type(Code, digit)),
|
|
621
|
+
!.
|
|
474
622
|
|
|
475
623
|
%% valid_subject_key(+SubjectKey)
|
|
476
624
|
valid_subject_key(SubjectKey) :-
|
package/src/status.pl
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
:- module(status, [
|
|
5
5
|
kb_status_json/1,
|
|
6
|
-
status_meta_dict/1
|
|
6
|
+
status_meta_dict/1,
|
|
7
|
+
status_scope_dict/1
|
|
7
8
|
]).
|
|
8
9
|
|
|
9
10
|
:- use_module(library(http/json)).
|
|
@@ -57,6 +58,19 @@ status_meta_dict(StatusDict) :-
|
|
|
57
58
|
lastSyncSource: unknown
|
|
58
59
|
}.
|
|
59
60
|
|
|
61
|
+
%% status_scope_dict(-Scope)
|
|
62
|
+
% implements REQ-kibi-search-answer-layer-v2
|
|
63
|
+
% The snapshot an answer is computed from: the attached snapshot id and when
|
|
64
|
+
% it was synced. Unlike status_meta_dict/1 it skips the freshness scan, so a
|
|
65
|
+
% discovery answer can name its snapshot at no extra cost; kb_status remains
|
|
66
|
+
% the place to ask whether that snapshot is stale.
|
|
67
|
+
status_scope_dict(_{snapshotId: SnapshotId, syncedAt: SyncedAt}) :-
|
|
68
|
+
attached_kb_info(_Branch, _KbPath, DataFile),
|
|
69
|
+
!,
|
|
70
|
+
snapshot_id(SnapshotId),
|
|
71
|
+
synced_at(DataFile, SyncedAt).
|
|
72
|
+
status_scope_dict(_{snapshotId: unknown, syncedAt: null}).
|
|
73
|
+
|
|
60
74
|
attached_kb_info(Branch, KbPath, DataFile) :-
|
|
61
75
|
kb:kb_attached(KbPath),
|
|
62
76
|
branch_workspace_from_kb_path(KbPath, Branch, _WorkspaceRoot),
|
|
@@ -99,7 +113,8 @@ synced_at(DataFile, SyncedAt) :-
|
|
|
99
113
|
exists_file(DataFile),
|
|
100
114
|
!,
|
|
101
115
|
time_file(DataFile, Timestamp),
|
|
102
|
-
|
|
116
|
+
stamp_date_time(Timestamp, TimestampUTC, 'UTC'),
|
|
117
|
+
format_time(atom(SyncedAt), '%FT%TZ', TimestampUTC).
|
|
103
118
|
% Before the first successful sync there is no kb.rdf, so the public JSON contract must expose syncedAt: null.
|
|
104
119
|
synced_at(_, null).
|
|
105
120
|
|
|
@@ -263,6 +278,7 @@ directory_tree_newer(Path, SnapshotTime) :-
|
|
|
263
278
|
!.
|
|
264
279
|
|
|
265
280
|
directory_tree_newer(Path, SnapshotTime) :-
|
|
281
|
+
\+ ignored_documentation_file(Path),
|
|
266
282
|
exists_directory(Path),
|
|
267
283
|
directory_files(Path, Entries),
|
|
268
284
|
member(Entry, Entries),
|
|
@@ -275,6 +291,7 @@ directory_tree_newer(Path, SnapshotTime) :-
|
|
|
275
291
|
directory_tree_newer_path(Path, SnapshotTime, Path) :-
|
|
276
292
|
newer_entity_documentation_file(Path, SnapshotTime).
|
|
277
293
|
directory_tree_newer_path(Path, SnapshotTime, ChildPath) :-
|
|
294
|
+
\+ ignored_documentation_file(Path),
|
|
278
295
|
exists_directory(Path),
|
|
279
296
|
directory_files(Path, Entries),
|
|
280
297
|
member(Entry, Entries),
|
|
@@ -377,8 +394,12 @@ ignored_documentation_file(Path) :-
|
|
|
377
394
|
file_base_name(Path, 'README.md').
|
|
378
395
|
ignored_documentation_file(Path) :-
|
|
379
396
|
sub_atom(Path, _, _, _, '/tests/e2e/').
|
|
397
|
+
ignored_documentation_file(Path) :-
|
|
398
|
+
sub_atom(Path, _, _, 0, '/tests/e2e').
|
|
380
399
|
ignored_documentation_file(Path) :-
|
|
381
400
|
sub_atom(Path, _, _, _, '/tests/benchmarks/').
|
|
401
|
+
ignored_documentation_file(Path) :-
|
|
402
|
+
sub_atom(Path, _, _, 0, '/tests/benchmarks').
|
|
382
403
|
|
|
383
404
|
%% newer_entity_documentation_file(+Path, +SnapshotTime) is semidet.
|
|
384
405
|
% Cheap path and mtime tests run before the file is read, and the content
|
|
@@ -395,6 +416,8 @@ newer_entity_documentation_file(Path, SnapshotTime) :-
|
|
|
395
416
|
entity_documentation_file(Path) :-
|
|
396
417
|
read_file_to_string(Path, Content, []),
|
|
397
418
|
sub_string(Content, 0, 3, _, "---"),
|
|
419
|
+
% These are existence checks, not generators. Repeated metadata examples
|
|
420
|
+
% must not multiply freshness reasons or cause Cartesian backtracking.
|
|
398
421
|
once(sub_string(Content, _, _, _, "id:")),
|
|
399
422
|
once(sub_string(Content, _, _, _, "title:")),
|
|
400
423
|
once(sub_string(Content, _, _, _, "status:")).
|