@ttsc/evidence 0.25.0 → 0.26.1

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/native/graph.go CHANGED
@@ -70,14 +70,24 @@ func (graphRule) Check(ctx *rule.ProjectContext) {
70
70
  )
71
71
  problems = append(problems, markdownClaimProblems...)
72
72
  problems = append(problems, prismaClaimProblems...)
73
+ // Governance is judged against the configuration as declared, before
74
+ // activation drops a claim whose population materialized no unit of its
75
+ // symbol kind. The question is whether an author put this file in a
76
+ // population, and a claim that deactivated still declared one: judging after
77
+ // activation silenced a file whose every declaration the author had
78
+ // commented out, which is the shape the second repair clause exists for.
79
+ declared := config
73
80
  config = activeGraphConfig(config, markdownClaims, prismaClaims, typescript)
74
- extendTypeScriptInventories(root, ctx.Sources, config, typescript)
81
+ governed := map[string]bool{}
82
+ extendTypeScriptInventories(root, ctx.Sources, config, typescript, nil)
83
+ recordGovernedTypeScriptFiles(ctx.Sources, declared, governed)
75
84
  markdown, markdownProblems := loadMarkdownInventories(root, config)
76
85
  prisma, prismaProblems := loadPrismaInventories(root, config)
77
86
  swagger, swaggerProblems := loadSwaggerInventories(root, config)
78
87
  problems = append(problems, markdownProblems...)
79
88
  problems = append(problems, prismaProblems...)
80
89
  problems = append(problems, swaggerProblems...)
90
+ problems = append(problems, unreadableTypeScriptTags(typescript, governed)...)
81
91
  loader := newTypeScriptLoader(root, typescript)
82
92
  states, stateProblems := materializeClaimStates(
83
93
  config,
@@ -267,6 +277,10 @@ func materializeClaimStates(
267
277
  state.Declarations,
268
278
  inventories[path].Declarations...,
269
279
  )
280
+ // Reviews travel beside declarations and never among them. They carry no
281
+ // obligation of their own; they are looked up by host and target when a
282
+ // reference demands one.
283
+ state.Reviews = append(state.Reviews, inventories[path].Reviews...)
270
284
  if len(claim.ExclusionCarriers.Patterns) != 0 && !carrierPaths[path] {
271
285
  for _, declaration := range inventories[path].Declarations {
272
286
  state.OutsideCarrier[declaration.ID] = true
@@ -389,13 +403,26 @@ func materializeClaimStates(
389
403
  }
390
404
  }
391
405
  sortUnits(referenceState.Scopes)
406
+ // The complete materialized population is kept because a review
407
+ // fingerprint must not depend on this reference's `symbol` selector.
408
+ // Units and Scopes hold only what this reference selected plus the
409
+ // ancestors of those, so an unselected descendant appears in neither, and
410
+ // composing a scope digest from them made the value a function of the
411
+ // reference rather than of the cited address: a narrowed selector dropped
412
+ // the subtree from the digest, and two references over one scope demanded
413
+ // two different values from a tag that carries one token, which no author
414
+ // could satisfy.
415
+ for _, unit := range availableUnits {
416
+ referenceState.Population = append(referenceState.Population, unit)
417
+ }
418
+ sortUnits(referenceState.Population)
392
419
  if len(referencePaths) != 0 &&
393
420
  len(referenceState.Units) == 0 &&
394
421
  referenceState.Healthy &&
395
422
  !selectedInventoryProblem {
396
423
  problems = append(
397
424
  problems,
398
- claimLabel(claim)+" "+referenceLabel(reference)+" matched "+decimal(len(referencePaths))+" file(s) but materialized no selected evidence units ("+reference.Symbols.names()+"). Select symbol kinds present in those files or correct the reference globs.",
425
+ claimLabel(claim)+" "+referenceLabel(reference)+" matched "+decimal(len(referencePaths))+" file(s) but found no selected evidence units ("+reference.Symbols.names()+"). Select symbol kinds present in those files or correct the reference globs.",
399
426
  )
400
427
  }
401
428
  state.References = append(state.References, referenceState)
@@ -525,7 +552,7 @@ func evaluateEvidenceGraph(
525
552
  if !declaration.valid() {
526
553
  problems = append(
527
554
  problems,
528
- "Malformed @"+string(declaration.Tag)+" declaration at "+declaration.location()+" for "+context+": target and non-empty reason are mandatory. Write '@"+string(declaration.Tag)+" <target> <reason>'.",
555
+ "Malformed @"+string(declaration.Tag)+" declaration at "+declaration.location()+" for "+context+": target and non-empty reason are mandatory. Write '@"+string(declaration.Tag)+" <target> <reason>'.+untrueTagWarning",
529
556
  )
530
557
  continue
531
558
  }
@@ -591,7 +618,7 @@ func evaluateEvidenceGraph(
591
618
  }
592
619
  problems = append(
593
620
  problems,
594
- "Unresolved evidence target '"+declaration.Target+"' at "+declaration.location()+" for "+context+": no configured source materializes that evidence unit. Correct the target, or make one of the named references select the source unit this claim actually uses.",
621
+ "Unresolved evidence target '"+declaration.Target+"' at "+declaration.location()+" for "+context+": no configured source materializes that evidence unit. Correct the target, make one of the named references select the source unit this claim actually uses, or remove the tag when this host does not answer for that target."+untrueTagWarning,
595
622
  )
596
623
  case 1:
597
624
  for unitID := range candidates {
@@ -628,6 +655,10 @@ func evaluateEvidenceGraph(
628
655
  uncertain[declaration.ID] = true
629
656
  }
630
657
  }
658
+ // One ledger per claim, because reviews belong to the claim rather than to
659
+ // any one of its references, and built on first use so a claim with no
660
+ // reviewing reference pays nothing.
661
+ var reviewLedgerForClaim *reviewLedger
631
662
  for _, reference := range state.References {
632
663
  if !reference.Healthy {
633
664
  for _, declaration := range state.Declarations {
@@ -668,6 +699,11 @@ func evaluateEvidenceGraph(
668
699
  for _, scope := range reference.Scopes {
669
700
  scopesByID[scope.ID] = scope
670
701
  }
702
+ // Built on first use and shared by every citation of this reference. The
703
+ // index spans the whole materialized population, so building it per
704
+ // citation is quadratic, and nil until a citation actually needs it so a
705
+ // reference without the policy pays nothing.
706
+ var reviewScopes *scopeIndex
671
707
  for _, declaration := range state.Declarations {
672
708
  scopeID := resolved[declaration.ID]
673
709
  covered := reference.UnitsByScope[scopeID]
@@ -707,10 +743,34 @@ func evaluateEvidenceGraph(
707
743
  if declaration.Tag == tagExclude && reference.Spec.Policy.NoExclude {
708
744
  problems = append(
709
745
  problems,
710
- "Forbidden @evidenceExclude for '"+scopesByID[scopeID].Target+"' at "+declaration.location()+" in "+claimLabel(state.Spec)+" "+referenceLabel(reference.Spec)+": noEvidenceExclude requires positive @evidence for this reference. Remove the exclusion and cite the target from a selected "+string(state.Spec.Type)+" host.",
746
+ "Forbidden @evidenceExclude for '"+scopesByID[scopeID].Target+"' at "+declaration.location()+" in "+claimLabel(state.Spec)+" "+referenceLabel(reference.Spec)+": noEvidenceExclude requires positive @evidence for this reference. Remove the exclusion and cite the target from a selected "+string(state.Spec.Type)+" host that answers for it."+untrueTagWarning,
711
747
  )
712
748
  continue
713
749
  }
750
+ if reference.Spec.Policy.RequireReview {
751
+ if reviewScopes == nil {
752
+ // Withdrawn units are absent from Population by construction, and
753
+ // the scope composite still needs their identities so a member
754
+ // leaving the public surface moves the fingerprint.
755
+ reviewScopes = newScopeIndex(
756
+ append(
757
+ append([]*evidenceUnit{}, reference.Population...),
758
+ reference.Hidden...,
759
+ ),
760
+ )
761
+ }
762
+ if reviewLedgerForClaim == nil {
763
+ reviewLedgerForClaim = newReviewLedger(state.Reviews)
764
+ }
765
+ problems = append(problems, reviewProblems(
766
+ declaration,
767
+ scopesByID[scopeID],
768
+ reference,
769
+ state,
770
+ reviewScopes,
771
+ reviewLedgerForClaim,
772
+ )...)
773
+ }
714
774
  if declaration.Tag == tagEvidence && declaration.HostID != "" {
715
775
  byScope := evidenceByHostAndScope[declaration.HostID]
716
776
  if byScope == nil {
@@ -789,7 +849,7 @@ func evaluateEvidenceGraph(
789
849
  }
790
850
  problems = append(
791
851
  problems,
792
- "Conflicting acknowledgements for '"+conflictingUnit.Target+"' in "+claimLabel(state.Spec)+" "+referenceLabel(reference.Spec)+": @evidence at "+evidence.location()+" overlaps @evidenceExclude at "+exclusion.location()+".",
852
+ "Conflicting acknowledgements for '"+conflictingUnit.Target+"' in "+claimLabel(state.Spec)+" "+referenceLabel(reference.Spec)+": @evidence at "+evidence.location()+" overlaps @evidenceExclude at "+exclusion.location()+". Delete whichever is untrue of this host."+untrueTagWarning,
793
853
  )
794
854
  }
795
855
  if duplicateExclusionUnit != nil {
@@ -810,15 +870,18 @@ func evaluateEvidenceGraph(
810
870
  }
811
871
  problems = append(
812
872
  problems,
813
- "Evidence host "+host.Readable+" at "+host.location()+" in "+claimLabel(state.Spec)+" "+referenceLabel(reference.Spec)+" cites "+decimal(count)+" distinct selected evidence unit(s); singleEvidencePerSymbol requires exactly 1. Keep positive @evidence citations on this semantic host to exactly one distinct unit.",
873
+ "Evidence host "+host.Readable+" at "+host.location()+" in "+claimLabel(state.Spec)+" "+referenceLabel(reference.Spec)+" cites "+decimal(count)+" distinct selected evidence unit(s); singleEvidencePerSymbol requires exactly 1. Split this host so each unit has one that owns it, or keep positive @evidence citations on this semantic host to exactly one distinct unit.",
814
874
  )
815
875
  }
816
876
  }
817
877
  for _, unit := range reference.Units {
818
878
  if !acknowledged[unit.ID] {
819
- repair := "Use @evidence on a selected " + string(state.Spec.Type) + " host or @evidenceExclude on an eligible carrier."
879
+ // Building the host is named first because the other two hide it. A
880
+ // repair offering only the two tags frames the whole problem as which
881
+ // tag to write, and an unbuilt unit then leaves as an exclusion.
882
+ repair := "Cite the artifact that answers for this unit with @evidence on a selected " + string(state.Spec.Type) + " host, building that artifact first when none does, or write @evidenceExclude on an eligible carrier when nothing here owes it." + untrueTagWarning
820
883
  if reference.Spec.Policy.NoExclude {
821
- repair = "Use @evidence on a selected " + string(state.Spec.Type) + " host; this reference forbids @evidenceExclude."
884
+ repair = "Cite the artifact that answers for this unit with @evidence on a selected " + string(state.Spec.Type) + " host, building that artifact first when none does; this reference forbids @evidenceExclude." + untrueTagWarning
822
885
  }
823
886
  problems = append(
824
887
  problems,
@@ -845,7 +908,7 @@ func evaluateEvidenceGraph(
845
908
  if obligations := outsideCarrier[id]; len(obligations) != 0 {
846
909
  problems = append(
847
910
  problems,
848
- "Misplaced @evidenceExclude at "+declaration.location()+" for "+strings.Join(obligations, "; ")+", target '"+displayTarget(declaration.Target)+"': evidenceExcludeCarriers confines this claim's exclusions to "+outsideCarrierGlobs[id]+". Move the tag there, or delete it and implement the target this claim owes.",
911
+ "Misplaced @evidenceExclude at "+declaration.location()+" for "+strings.Join(obligations, "; ")+", target '"+displayTarget(declaration.Target)+"': evidenceExcludeCarriers confines this claim's exclusions to "+outsideCarrierGlobs[id]+". Move the tag there, or delete it and build what the target requires of this claim."+untrueTagWarning,
849
912
  )
850
913
  continue
851
914
  }
@@ -863,7 +926,7 @@ func evaluateEvidenceGraph(
863
926
  }
864
927
  problems = append(
865
928
  problems,
866
- "Out-of-scope @"+string(declaration.Tag)+" host at "+declaration.location()+" for "+strings.Join(obligations, "; ")+", target '"+displayTarget(declaration.Target)+"': host kind '"+host+"' is not selected ("+outOfScopeSelections[id].names()+") by any of these claim obligations. Move the declaration to a selected host, or widen only the claim symbol selector that genuinely owns this target.",
929
+ "Out-of-scope @"+string(declaration.Tag)+" host at "+declaration.location()+" for "+strings.Join(obligations, "; ")+", target '"+displayTarget(declaration.Target)+"': host kind '"+host+"' is not selected ("+outOfScopeSelections[id].names()+") by any of these claim obligations. Move the declaration to a selected host, or widen the claim symbol selector only when it genuinely owns this target."+untrueTagWarning,
867
930
  )
868
931
  continue
869
932
  }
@@ -875,12 +938,181 @@ func evaluateEvidenceGraph(
875
938
  }
876
939
  problems = append(
877
940
  problems,
878
- "Non-participating @"+string(declaration.Tag)+" target '"+displayTarget(declaration.Target)+"' at "+declaration.location()+" for "+context+": the target resolves, but none of this declaration's configured references selects it. Correct the target or reference, or move the tag to an eligible host or exclusion carrier in the claim that owes it; a resolving tag must discharge at least one obligation.",
941
+ "Non-participating @"+string(declaration.Tag)+" target '"+displayTarget(declaration.Target)+"' at "+declaration.location()+" for "+context+": the target resolves, but none of this declaration's configured references selects it. Correct the target or reference, or move the tag to an eligible host or exclusion carrier in the claim that owes it; a resolving tag must discharge at least one obligation."+untrueTagWarning,
879
942
  )
880
943
  }
881
944
  return problems
882
945
  }
883
946
 
947
+ // reviewProblems judges one acknowledgement against the review it owes.
948
+ //
949
+ // The review is found by host and target rather than resolved again. A citation
950
+ // and its review spell one address, so resolving the review separately would let
951
+ // it answer a scope its citation does not name, and would report an unresolved
952
+ // target for a tag whose only job is to annotate one that already resolved.
953
+ //
954
+ // Three states are reported and they are mutually exclusive, because each repair
955
+ // subsumes the one after it: without a review there is nothing to carry a
956
+ // fingerprint, and without a fingerprint there is nothing to compare. Every
957
+ // message states the expected value, because completions cannot: the host
958
+ // publishes a rule's corpus only on a cycle where the rule reports nothing, and
959
+ // a stale fingerprint is a report.
960
+ //
961
+ // An empty expected value means this population's loader reported no content
962
+ // for the cited scope. Every bridge digests what it parsed, so that is a loader
963
+ // gap rather than a configuration one, and reporting a mismatch against nothing
964
+ // would name a repair no author can perform. It is silence by design and the
965
+ // only silence here: a bridge that fails reports its own failure, and an
966
+ // unhealthy population is skipped before this runs.
967
+ func reviewProblems(
968
+ declaration *evidenceDeclaration,
969
+ scope *evidenceUnit,
970
+ reference referenceState,
971
+ state claimState,
972
+ scopes *scopeIndex,
973
+ reviews *reviewLedger,
974
+ ) []string {
975
+ if declaration == nil || scope == nil {
976
+ return nil
977
+ }
978
+ // The complete population is what the digest walks, never Units or Scopes.
979
+ // Both of those are narrowed by this reference's `symbol` selector, so an
980
+ // unselected descendant is absent from each: a Markdown reference selecting
981
+ // only `h2` would fingerprint a cited section without the H3 bodies inside it,
982
+ // and a review of it would never expire however much of that subtree was
983
+ // rewritten. Worse, two references over one scope under different selectors
984
+ // would then expect two different digests from a tag that carries exactly one
985
+ // fingerprint token, and no value an author could write would satisfy both.
986
+ expected := scopes.fingerprint(scope.ID)
987
+ if expected == "" {
988
+ return nil
989
+ }
990
+ where := " at " + declaration.location() + " in " +
991
+ claimLabel(state.Spec) + " " + referenceLabel(reference.Spec)
992
+ // Every message names the review tag that answers for *this* acknowledgement.
993
+ // A generic `@evidenceReview` in the repair would send the author of an
994
+ // exclusion to write the tag that does not answer it, which is the one mistake
995
+ // the split into two tags exists to prevent.
996
+ marker := reviewMarkerFor(declaration.Tag)
997
+ review := reviews.find(declaration)
998
+ if review == nil {
999
+ return []string{
1000
+ "Unreviewed @" + string(declaration.Tag) + " for '" + displayTarget(declaration.Target) + "'" + where +
1001
+ ": requireReview asks what was verified, which the reason does not answer. Add '" + marker + " " +
1002
+ displayTarget(declaration.Target) + " #" + expected + " <what you checked>' to the same documentation block, or correct this host when what you checked did not hold. " + reviewExample(declaration.Tag) + untrueReviewWarning,
1003
+ }
1004
+ }
1005
+ if review.Fingerprint == "" {
1006
+ return []string{
1007
+ "Unfingerprinted " + marker + " for '" + displayTarget(declaration.Target) + "'" + where +
1008
+ ": the review states what was checked and does not name the fingerprint of what it checked, so it can never expire. Write '" +
1009
+ marker + " " + displayTarget(declaration.Target) + " #" + expected + " " + firstReviewWords(review.Description) + "'." + untrueReviewWarning,
1010
+ }
1011
+ }
1012
+ if review.Fingerprint != expected {
1013
+ return []string{
1014
+ "Stale " + marker + " for '" + displayTarget(declaration.Target) + "'" + where +
1015
+ ": the review names '#" + review.Fingerprint + "' and that scope now digests to '#" + expected +
1016
+ "'. The cited content changed after this review was written. Read it again and replace the review, including the new fingerprint, or correct this host when the change means it no longer answers for that target." + untrueReviewWarning,
1017
+ }
1018
+ }
1019
+ return nil
1020
+ }
1021
+
1022
+ // reviewLedger indexes a claim's reviews by the identity they were written on.
1023
+ //
1024
+ // Two properties are load-bearing and neither is obvious.
1025
+ //
1026
+ // The key is a *semantic* host identity, never `HostID`. `HostID` is a source
1027
+ // position, so the two halves of a merged identity carry different ones: keying on
1028
+ // it refused a review written on `namespace I` for a citation on `interface I`,
1029
+ // which is placement the graph elsewhere calls not worth a diagnostic and which
1030
+ // `evidence/review` accepts. The two rules then disagreed about the same file.
1031
+ // `model.go` says as much where it defines the field.
1032
+ //
1033
+ // It is built once per claim rather than scanned per citation. A linear search
1034
+ // over every review, for every declaration, for every reference, is cubic in the
1035
+ // three things a large project has most of.
1036
+ type reviewLedger struct {
1037
+ byHostAndTarget map[string]*evidenceReview
1038
+ }
1039
+
1040
+ func newReviewLedger(reviews []*evidenceReview) *reviewLedger {
1041
+ ledger := &reviewLedger{
1042
+ byHostAndTarget: make(map[string]*evidenceReview, len(reviews)),
1043
+ }
1044
+ for _, review := range reviews {
1045
+ if review == nil {
1046
+ continue
1047
+ }
1048
+ for _, hostID := range reviewLedgerHostIDs(review) {
1049
+ key := reviewLedgerKey(hostID, review.Reviews, review.Target)
1050
+ if ledger.byHostAndTarget[key] == nil {
1051
+ ledger.byHostAndTarget[key] = review
1052
+ }
1053
+ }
1054
+ }
1055
+ return ledger
1056
+ }
1057
+
1058
+ // reviewLedgerHostIDs is every identity a review answers on.
1059
+ //
1060
+ // The position identity is kept as a fallback for a carrier that has no semantic
1061
+ // host at all, such as an unattached Prisma documentation run, whose declarations
1062
+ // are keyed the same way.
1063
+ func reviewLedgerHostIDs(review *evidenceReview) []string {
1064
+ if len(review.SemanticHostIDs) != 0 {
1065
+ return review.SemanticHostIDs
1066
+ }
1067
+ if review.HostID == "" {
1068
+ return nil
1069
+ }
1070
+ return []string{review.HostID}
1071
+ }
1072
+
1073
+ // find locates the review bound to one declaration's identity and target.
1074
+ func (ledger *reviewLedger) find(
1075
+ declaration *evidenceDeclaration,
1076
+ ) *evidenceReview {
1077
+ if ledger == nil {
1078
+ return nil
1079
+ }
1080
+ hostIDs := declaration.SemanticHostIDs
1081
+ if len(hostIDs) == 0 && declaration.HostID != "" {
1082
+ hostIDs = []string{declaration.HostID}
1083
+ }
1084
+ for _, hostID := range hostIDs {
1085
+ key := reviewLedgerKey(hostID, declaration.Tag, declaration.Target)
1086
+ if review := ledger.byHostAndTarget[key]; review != nil {
1087
+ return review
1088
+ }
1089
+ }
1090
+ return nil
1091
+ }
1092
+
1093
+ // reviewLedgerKey composes the one key both sides of the ledger use.
1094
+ //
1095
+ // Spelled once rather than at each site. The acknowledgement kind is part of it,
1096
+ // because verifying a citation and verifying an exclusion are opposite questions
1097
+ // and a review of one must never be found for the other; two independent
1098
+ // three-part spellings would agree only by inspection, and the day they stop
1099
+ // agreeing every review silently stops matching.
1100
+ func reviewLedgerKey(hostID string, tag tagKind, target string) string {
1101
+ return hostID + "\x00" + reviewKey(tag, target)
1102
+ }
1103
+
1104
+ // firstReviewWords echoes enough of a description to show where it goes.
1105
+ func firstReviewWords(description string) string {
1106
+ words := strings.Fields(description)
1107
+ if len(words) == 0 {
1108
+ return "<what you checked>"
1109
+ }
1110
+ if len(words) > 6 {
1111
+ words = append(words[:6], "...")
1112
+ }
1113
+ return strings.Join(words, " ")
1114
+ }
1115
+
884
1116
  // declarationEligibleForClaim keeps ownership evidence on the selected host
885
1117
  // while allowing an intentional exclusion to live on a claim-file carrier.
886
1118
  func declarationEligibleForClaim(
@@ -904,7 +1136,7 @@ func hiddenTargetProblem(
904
1136
  hidden *evidenceUnit,
905
1137
  context string,
906
1138
  ) string {
907
- return "Hidden evidence target '" + displayTarget(declaration.Target) + "' at " + declaration.location() + " for " + context + ": " + hidden.Readable + " at " + hidden.location() + " carries '" + hidden.Hidden + "' in its documentation comment, which withdraws it from the evidence population along with everything nested inside it. Remove the tag if the declaration is public contract, or drop this citation if it is not."
1139
+ return "Hidden evidence target '" + displayTarget(declaration.Target) + "' at " + declaration.location() + " for " + context + ": " + hidden.Readable + " at " + hidden.location() + " carries '" + hidden.Hidden + "' in its documentation comment, which removes it from the evidence population along with everything nested inside it. Remove the tag if the declaration is public contract, or drop this citation if it is not. Do not remove '@hidden' from the target to make this citation legal." + untrueTagWarning
908
1140
  }
909
1141
 
910
1142
  func declarationResolutionUncertain(owners []claimState) bool {
@@ -987,6 +1219,10 @@ func applyTraversedScopes(state *referenceState, reached []*evidenceUnit) {
987
1219
  }
988
1220
  }
989
1221
  sortUnits(state.Scopes)
1222
+ // Every reached declaration, selected or not, so a review fingerprint over a
1223
+ // cited scope walks the structure rather than this reference's selection.
1224
+ state.Population = append(state.Population, reached...)
1225
+ sortUnits(state.Population)
990
1226
  }
991
1227
 
992
1228
  // materializeLocalTypeScriptReference selects modules of the active project
@@ -1037,7 +1273,7 @@ func materializeLocalTypeScriptReference(
1037
1273
  }
1038
1274
  if len(state.Units) == 0 && !selectedInventoryProblem {
1039
1275
  return state, []string{
1040
- claimLabel(claim) + " " + referenceLabel(reference) + " matched " + decimal(len(paths)) + " file(s) but materialized no selected evidence units (" + reference.Symbols.names() + "). Select symbol kinds present in those files or correct the reference globs.",
1276
+ claimLabel(claim) + " " + referenceLabel(reference) + " matched " + decimal(len(paths)) + " file(s) but found no selected evidence units (" + reference.Symbols.names() + "). Select symbol kinds present in those files or correct the reference globs.",
1041
1277
  }
1042
1278
  }
1043
1279
  return state, nil
@@ -1093,7 +1329,7 @@ func materializePackageGlobReference(
1093
1329
  return state, problems
1094
1330
  }
1095
1331
  return state, []string{
1096
- claimLabel(claim) + " " + referenceLabel(reference) + " matched no files inside package '" + reference.Package + "' for " + describePatterns(reference.Files) + ". The globs resolve against the package root, not the project root. Check that the installed package actually contains those paths before changing the reference: an empty population demands nothing, so a claim that reaches this state reports full coverage while checking nothing.",
1332
+ claimLabel(claim) + " " + referenceLabel(reference) + " matched no files inside package '" + reference.Package + "' for " + describePatterns(reference.Files) + ". The globs resolve against the package root, not the project root. Check that the installed package actually contains those paths before changing the reference: an empty population contains no units, so this claim reports full coverage without checking anything.",
1097
1333
  }
1098
1334
  }
1099
1335
  // The glob decides membership, the entry decides addresses. A matched module
@@ -1122,7 +1358,7 @@ func materializePackageGlobReference(
1122
1358
  }
1123
1359
  if len(state.Units) == 0 {
1124
1360
  return state, []string{
1125
- claimLabel(claim) + " " + referenceLabel(reference) + " matched " + decimal(len(state.Paths)) + " file(s) inside package '" + reference.Package + "' but materialized no selected evidence units (" + reference.Symbols.names() + ") reachable from the package entry. Select symbol kinds present in those files, correct the globs, or narrow to modules the entry publishes.",
1361
+ claimLabel(claim) + " " + referenceLabel(reference) + " matched " + decimal(len(state.Paths)) + " file(s) inside package '" + reference.Package + "' but found no selected evidence units (" + reference.Symbols.names() + ") reachable from the package entry. Select symbol kinds present in those files, correct the globs, or narrow to modules the entry publishes.",
1126
1362
  }
1127
1363
  }
1128
1364
  return state, nil