@ttsc/evidence 0.27.0 → 0.28.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/config.go CHANGED
@@ -92,6 +92,7 @@ func decodeClaim(raw json.RawMessage, index int) (claimSpec, []string) {
92
92
  problems = append(problems, symbolProblems...)
93
93
  references, referenceProblems := decodeReferences(kind, object["reference"], path+".reference")
94
94
  problems = append(problems, referenceProblems...)
95
+ problems = append(problems, rejectChecklistCarriers(carriers, references, path)...)
95
96
  if len(problems) != 0 {
96
97
  return claimSpec{}, problems
97
98
  }
@@ -186,6 +187,7 @@ func decodeReference(
186
187
  "uniqueEvidence",
187
188
  "singleEvidencePerSymbol",
188
189
  "requireReview",
190
+ "checklist",
189
191
  "package",
190
192
  "root",
191
193
  "file",
@@ -204,7 +206,7 @@ func decodeReference(
204
206
  }
205
207
  root, rootProblems := decodeRoot(object["root"], kind, path+".root")
206
208
  problems = append(problems, rootProblems...)
207
- policy, policyProblems := decodeReferencePolicy(object, path)
209
+ policy, policyProblems := decodeReferencePolicy(object, kind, path)
208
210
  problems = append(problems, policyProblems...)
209
211
  files := globSet{}
210
212
  source := ""
@@ -278,8 +280,13 @@ func decodeReference(
278
280
  // before a disabled claim is filtered. Each option is a flat boolean whose
279
281
  // false value means "not configured", so an omitted option and an explicit
280
282
  // false preserve the original behavior identically.
283
+ //
284
+ // The reference kind is read only by `checklist`, which the other four do not
285
+ // need: they tighten a count that every artifact kind has, while a per-host
286
+ // obligation is a statement about a document that is read item by item.
281
287
  func decodeReferencePolicy(
282
288
  object map[string]json.RawMessage,
289
+ kind artifactKind,
283
290
  path string,
284
291
  ) (referencePolicy, []string) {
285
292
  policy := referencePolicy{}
@@ -308,9 +315,106 @@ func decodeReferencePolicy(
308
315
  decodeFlag("uniqueEvidence", &policy.UniqueEvidence)
309
316
  decodeFlag("singleEvidencePerSymbol", &policy.SingleEvidencePerSymbol)
310
317
  decodeFlag("requireReview", &policy.RequireReview)
318
+ decodeFlag("checklist", &policy.Checklist)
319
+ problems = append(problems, rejectChecklistConflicts(policy, kind, path)...)
311
320
  return policy, problems
312
321
  }
313
322
 
323
+ // rejectChecklistCarriers refuses a checklist beside gathered exclusion
324
+ // carriers, which is the third combination no author can satisfy.
325
+ //
326
+ // The two state opposite intents. Carriers gather every exclusion a claim owns
327
+ // into one file so a reviewer reads them by opening it; a checklist makes every
328
+ // acknowledgement one host's own answer. Together they leave every host outside
329
+ // the carrier globs with no way to say an item does not apply: the tag written
330
+ // beside the host is refused as misplaced, and the tag written in the carrier
331
+ // answers for no host. Measured, the two diagnostics name each other's file, so
332
+ // an author following either repair is sent back to the other.
333
+ //
334
+ // Refused here rather than as coverage for the same reason the cardinality
335
+ // pairs are: it is a property of the configuration, and a claim whose every
336
+ // host happens to sit in a carrier file satisfies it by accident.
337
+ func rejectChecklistCarriers(
338
+ carriers globSet,
339
+ references []referenceSpec,
340
+ path string,
341
+ ) []string {
342
+ if len(carriers.Patterns) == 0 {
343
+ return nil
344
+ }
345
+ for _, reference := range references {
346
+ // A reference refusing exclusions outright has nothing for the carriers to
347
+ // confine, so the impossibility this refuses does not arise there and the
348
+ // carriers are governing some other reference's exclusions, which is the
349
+ // ledger the property exists for. Refusing that pair would contradict the
350
+ // published guidance that `checklist` and `noEvidenceExclude` are the
351
+ // intended pairing.
352
+ if !reference.Policy.Checklist || reference.Policy.NoExclude {
353
+ continue
354
+ }
355
+ where := path + ".reference"
356
+ if len(references) > 1 {
357
+ where += "[" + decimal(reference.Index) + "]"
358
+ }
359
+ return []string{configurationProblem(
360
+ graphRuleName,
361
+ path+".evidenceExcludeCarriers",
362
+ "a checklist reference cannot be gathered into exclusion carriers: "+where+
363
+ " makes every acknowledgement one host's own answer, while these globs confine exclusions to other files, so no host outside them can record that an item does not apply. Drop the carriers, drop `checklist` from that reference, or give it `noEvidenceExclude` so it accepts no exclusion to gather.",
364
+ )}
365
+ }
366
+ return nil
367
+ }
368
+
369
+ // rejectChecklistConflicts refuses the two combinations no author can satisfy
370
+ // and the artifact kinds a checklist is not a statement about.
371
+ //
372
+ // The kind refusal is silent when the kind itself failed to decode, the way
373
+ // rejectForeignTypeScriptReference is: naming an artifact the author did not
374
+ // successfully spell would send them to the wrong line.
375
+ //
376
+ // The two cardinality refusals are unsatisfiability, not preference. A
377
+ // checklist makes every selected host cite every unit, so uniqueEvidence's
378
+ // one-host-per-unit ceiling breaks the moment a claim has two hosts, and
379
+ // singleEvidencePerSymbol's exactly-one-unit-per-host breaks the moment the
380
+ // population has two units. Both are reported at decode rather than as
381
+ // coverage failures, because a population of one unit and one host satisfies
382
+ // them by accident and would let a contradictory configuration ship until the
383
+ // day a second file arrives.
384
+ func rejectChecklistConflicts(
385
+ policy referencePolicy,
386
+ kind artifactKind,
387
+ path string,
388
+ ) []string {
389
+ if !policy.Checklist {
390
+ return nil
391
+ }
392
+ if kind != artifactMarkdown && kind != "" {
393
+ return []string{configurationProblem(
394
+ graphRuleName,
395
+ path+".checklist",
396
+ "only a Markdown reference can be a checklist; a "+string(kind)+
397
+ " population has no reading order in which a host answers one item at a time. Drop the option, or move the items into a Markdown document this reference selects.",
398
+ )}
399
+ }
400
+ problems := []string{}
401
+ if policy.UniqueEvidence {
402
+ problems = append(problems, configurationProblem(
403
+ graphRuleName,
404
+ path+".checklist",
405
+ "checklist and uniqueEvidence cannot both hold: a checklist requires every selected host to cite every unit, while uniqueEvidence allows each unit at most one host, so any claim with two hosts fails both ways. Keep the one that states this obligation.",
406
+ ))
407
+ }
408
+ if policy.SingleEvidencePerSymbol {
409
+ problems = append(problems, configurationProblem(
410
+ graphRuleName,
411
+ path+".checklist",
412
+ "checklist and singleEvidencePerSymbol cannot both hold: a checklist requires every selected host to cite every unit, while singleEvidencePerSymbol allows each host exactly one, so any population with two units fails both ways. Keep the one that states this obligation.",
413
+ ))
414
+ }
415
+ return problems
416
+ }
417
+
314
418
  // rejectForeignTypeScriptReference refuses a code population to a claim that
315
419
  // cannot address one.
316
420
  //
@@ -563,7 +667,7 @@ func normalizeSwaggerSource(value string) (string, string) {
563
667
  }
564
668
  parsed, err := url.Parse(value)
565
669
  if err != nil {
566
- return "", "invalid Swagger source '" + value + "': " + err.Error() + "."
670
+ return "", "invalid Swagger source '" + value + "': " + causeText(err) + "."
567
671
  }
568
672
  if !drive && parsed.Scheme != "" {
569
673
  if parsed.Scheme != "http" && parsed.Scheme != "https" {
@@ -617,7 +721,7 @@ func decodeFiles(raw json.RawMessage, path string) (globSet, []string) {
617
721
  }
618
722
  globs, err := newGlobSet(patterns)
619
723
  if err != nil {
620
- return globSet{}, []string{"Invalid evidence/graph configuration at " + path + ": " + err.Error()}
724
+ return globSet{}, []string{"Invalid evidence/graph configuration at " + path + ": " + causeText(err) + "."}
621
725
  }
622
726
  return globs, nil
623
727
  }
@@ -779,12 +883,17 @@ func describePatterns(globs globSet) string {
779
883
  }
780
884
 
781
885
  // describePopulation names the patterns a population selects with, and the base
782
- // they were resolved against when that base is not the project root.
886
+ // they resolve against when that base is not the project root.
783
887
  //
784
888
  // The base is stated rather than assumed, because a citation that resolves
785
889
  // outside the project has to be repairable from the diagnostic alone: patterns
786
890
  // that look correct against a root the reader is imagining are the failure this
787
- // property introduces, and naming the resolved base is what removes it.
891
+ // property introduces, and naming the base is what removes it.
892
+ //
893
+ // It is named by its declared spelling rather than its resolved one, because
894
+ // the repair is an edit to a configuration property and the reader has to find
895
+ // that property first. `populationRootLabel` owns that choice and the reasoning
896
+ // behind it.
788
897
  func describePopulation(base populationBase, globs globSet) string {
789
898
  if base.Default {
790
899
  return describePatterns(globs)