[gold] Add type expectations.LabelStr and use it everywhere.

This change allows us to generate a LabelStr type union in TypeScript will all three possible values.

It also makes the structs that generate our JSON RPC responses more self-documenting by using LabelStr rather than raw strings.

Recommended review order:
- labels.go
- labels_test.go
- types.go
- search.go
- web.go
- generate_typescript_rpc_types/main.go
- rpc_types.ts
- Everything else.

Bug: skia:9525
Change-Id: I2842397df1af57eabc912e49118f5c4be017156a
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/303347
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
diff --git a/gold-client/go/goldclient/goldclient.go b/gold-client/go/goldclient/goldclient.go
index 41407be..4f2f21e 100644
--- a/gold-client/go/goldclient/goldclient.go
+++ b/gold-client/go/goldclient/goldclient.go
@@ -809,7 +809,7 @@
 func (c *CloudClient) TriageAsPositive(testName types.TestName, digest types.Digest, algorithmName string) error {
 	// Build TriageRequest struct and encode it into JSON.
 	triageRequest := &frontend.TriageRequest{
-		TestDigestStatus:       map[types.TestName]map[types.Digest]string{testName: {digest: expectations.Positive.String()}},
+		TestDigestStatus:       map[types.TestName]map[types.Digest]expectations.LabelStr{testName: {digest: expectations.PositiveStr}},
 		ChangeListID:           c.resultState.SharedConfig.ChangeListID,
 		ImageMatchingAlgorithm: algorithmName,
 	}
diff --git a/golden/go/expectations/fs_expectationstore/fs_expstore.go b/golden/go/expectations/fs_expectationstore/fs_expstore.go
index faec801..f75862f 100644
--- a/golden/go/expectations/fs_expectationstore/fs_expstore.go
+++ b/golden/go/expectations/fs_expectationstore/fs_expstore.go
@@ -790,7 +790,7 @@
 	var toGC []*firestore.DocumentRef
 	// Use IterDocs instead of q.Documents(ctx).GetAll because this might be a very large query
 	// and we want to use the retry/restart logic of IterDocs to get them all.
-	err := s.client.IterDocs(ctx, "mark_expectations_for_GC", label.String(), q, 3, 10*time.Minute, func(doc *firestore.DocumentSnapshot) error {
+	err := s.client.IterDocs(ctx, "mark_expectations_for_GC", string(label.String()), q, 3, 10*time.Minute, func(doc *firestore.DocumentSnapshot) error {
 		if doc == nil {
 			return nil
 		}
diff --git a/golden/go/expectations/labels.go b/golden/go/expectations/labels.go
index eaff751..a376229 100644
--- a/golden/go/expectations/labels.go
+++ b/golden/go/expectations/labels.go
@@ -4,42 +4,53 @@
 type Label int
 
 const (
-	// Untriaged represents a previously unseen digest
+	// Untriaged represents a previously unseen digest.
 	Untriaged Label = iota // == 0
-	// Positive represents a known good digest
+	// Positive represents a known good digest.
 	Positive
-	// Negative represents a known bad digest
+	// Negative represents a known bad digest.
 	Negative
 )
 
-// String representation for Labels. The order must match order above.
-var labelStringRepresentation = []string{
-	"untriaged",
-	"positive",
-	"negative",
+// LabelStr is the string version of Label. Used e.g. to represent digest classifications in JSON.
+type LabelStr string
+
+const (
+	// UntriagedStr represents a previously unseen digest. String version of Untriaged.
+	UntriagedStr = LabelStr("untriaged")
+
+	// PositiveStr represents a known good digest. String version of Positive.
+	PositiveStr = LabelStr("positive")
+
+	// NegativeStr represents a known bad digest. String version of Negative.
+	NegativeStr = LabelStr("negative")
+)
+
+// AllLabelStr is a list of all possible LabelStr values. The index of each element in this list
+// must match its Label value (Untriaged = 0, etc.).
+var AllLabelStr = []LabelStr{UntriagedStr, PositiveStr, NegativeStr}
+
+func (l Label) String() LabelStr {
+	return AllLabelStr[l]
 }
 
-func (l Label) String() string {
-	return labelStringRepresentation[l]
+var labels = map[LabelStr]Label{
+	UntriagedStr: Untriaged,
+	PositiveStr:  Positive,
+	NegativeStr:  Negative,
 }
 
-var labels = map[string]Label{
-	"untriaged": Untriaged,
-	"positive":  Positive,
-	"negative":  Negative,
-}
-
-// LabelFromString returns the Label corresponding to the serialized string or Untriaged
-// if there is no match.
-func LabelFromString(s string) Label {
+// LabelFromString returns the Label corresponding to the given LabelStr, or Untriaged if there is
+// no match.
+func LabelFromString(s LabelStr) Label {
 	if l, ok := labels[s]; ok {
 		return l
 	}
 	return Untriaged
 }
 
-// ValidLabel returns true if the given label is a valid label string.
-func ValidLabel(s string) bool {
+// ValidLabelStr returns true if the given LabelStr is valid.
+func ValidLabelStr(s LabelStr) bool {
 	_, ok := labels[s]
 	return ok
 }
diff --git a/golden/go/expectations/labels_test.go b/golden/go/expectations/labels_test.go
new file mode 100644
index 0000000..e4f9ff1
--- /dev/null
+++ b/golden/go/expectations/labels_test.go
@@ -0,0 +1,41 @@
+package expectations
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"go.skia.org/infra/go/testutils/unittest"
+)
+
+func TestLabel_String_Success(t *testing.T) {
+	unittest.SmallTest(t)
+	assert.Equal(t, UntriagedStr, Untriaged.String())
+	assert.Equal(t, PositiveStr, Positive.String())
+	assert.Equal(t, NegativeStr, Negative.String())
+}
+
+func TestLabelFromString_KnownLabelStr_ReturnsCorrespondingLabel(t *testing.T) {
+	unittest.SmallTest(t)
+	assert.Equal(t, Untriaged, LabelFromString(UntriagedStr))
+	assert.Equal(t, Positive, LabelFromString(PositiveStr))
+	assert.Equal(t, Negative, LabelFromString(NegativeStr))
+}
+
+func TestLabelFromString_UnknownLabelStr_ReturnsUntriaged(t *testing.T) {
+	unittest.SmallTest(t)
+	assert.Equal(t, Untriaged, LabelFromString("unknown label"))
+}
+
+func TestValidLabel_KnownLabelStr_ReturnsTrue(t *testing.T) {
+	unittest.SmallTest(t)
+	assert.True(t, ValidLabelStr(UntriagedStr))
+	assert.True(t, ValidLabelStr(PositiveStr))
+	assert.True(t, ValidLabelStr(NegativeStr))
+
+}
+
+func TestValidLabel_UnknownLabelStr_ReturnsFalse(t *testing.T) {
+	unittest.SmallTest(t)
+	assert.False(t, ValidLabelStr("unknown label"))
+}
diff --git a/golden/go/search/frontend/types.go b/golden/go/search/frontend/types.go
index 425b082..118b20d 100644
--- a/golden/go/search/frontend/types.go
+++ b/golden/go/search/frontend/types.go
@@ -8,6 +8,7 @@
 	"go.skia.org/infra/go/paramtools"
 	"go.skia.org/infra/golden/go/comment/trace"
 	"go.skia.org/infra/golden/go/diff"
+	"go.skia.org/infra/golden/go/expectations"
 	"go.skia.org/infra/golden/go/search/common"
 	"go.skia.org/infra/golden/go/tiling"
 	"go.skia.org/infra/golden/go/types"
@@ -25,10 +26,10 @@
 	Commits       []frontend.Commit `json:"commits"`
 	TraceComments []TraceComment    `json:"trace_comments"`
 	// BulkTriageData contains *all* digests that match the query as keys. The value for each key is
-	// an expectations.Label.String() value giving the label of the closest triaged digest to the
-	// key digest or empty string if there is no "closest digest". Note the similarity to the
+	// an expectations.LabelStr value giving the label of the closest triaged digest to the key digest
+	// or empty string if there is no "closest digest". Note the similarity to the
 	// frontend.TriageRequest type.
-	BulkTriageData map[types.TestName]map[types.Digest]string `json:"bulk_triage_data"`
+	BulkTriageData map[types.TestName]map[types.Digest]expectations.LabelStr `json:"bulk_triage_data"`
 }
 
 // TriageHistory represents who last triaged a certain digest for a certain test.
@@ -47,7 +48,7 @@
 	Test types.TestName `json:"test"`
 	// Status is positive, negative, or untriaged. This is also known as the expectation for the
 	// primary digest (for Test).
-	Status string `json:"status"`
+	Status expectations.LabelStr `json:"status"`
 	// TriageHistory is a history of all the times the primary digest has been retriaged for the
 	// given Test.
 	TriageHistory []TriageHistory `json:"triage_history"`
@@ -76,7 +77,7 @@
 	// is the image on the right side of the comparison.
 	Digest types.Digest `json:"digest"`
 	// Status represents the expectation.Label for this digest.
-	Status string `json:"status"`
+	Status expectations.LabelStr `json:"status"`
 	// ParamSet is all of the params of all traces that produce this digest (the digest on the right).
 	// It is for frontend UI presentation only; essentially a word cloud of what drew the primary
 	// digest.
@@ -163,8 +164,8 @@
 
 // DigestStatus is a digest and its status, used in TraceGroup.
 type DigestStatus struct {
-	Digest types.Digest `json:"digest"`
-	Status string       `json:"status"`
+	Digest types.Digest          `json:"digest"`
+	Status expectations.LabelStr `json:"status"`
 }
 
 // DigestComparison contains the result of comparing two digests.
diff --git a/golden/go/search/search.go b/golden/go/search/search.go
index f219b16..3037355 100644
--- a/golden/go/search/search.go
+++ b/golden/go/search/search.go
@@ -201,21 +201,21 @@
 	return searchRet, nil
 }
 
-func collectDigestsForBulkTriage(results []*frontend.SearchResult) map[types.TestName]map[types.Digest]string {
-	testNameToPrimaryDigest := map[types.TestName]map[types.Digest]string{}
+func collectDigestsForBulkTriage(results []*frontend.SearchResult) map[types.TestName]map[types.Digest]expectations.LabelStr {
+	testNameToPrimaryDigest := map[types.TestName]map[types.Digest]expectations.LabelStr{}
 	for _, r := range results {
 		test := r.Test
 		digestToLabel, ok := testNameToPrimaryDigest[test]
 		if !ok {
-			digestToLabel = map[types.Digest]string{}
+			digestToLabel = map[types.Digest]expectations.LabelStr{}
 			testNameToPrimaryDigest[test] = digestToLabel
 		}
 		primary := r.Digest
 		switch r.ClosestRef {
 		case common.PositiveRef:
-			digestToLabel[primary] = expectations.Positive.String()
+			digestToLabel[primary] = expectations.PositiveStr
 		case common.NegativeRef:
-			digestToLabel[primary] = expectations.Negative.String()
+			digestToLabel[primary] = expectations.NegativeStr
 		case common.NoRef:
 			digestToLabel[primary] = ""
 		}
diff --git a/golden/go/search/search_test.go b/golden/go/search/search_test.go
index cfe5a6f..145c8fd 100644
--- a/golden/go/search/search_test.go
+++ b/golden/go/search/search_test.go
@@ -197,12 +197,12 @@
 				},
 			},
 		},
-		BulkTriageData: map[types.TestName]map[types.Digest]string{
+		BulkTriageData: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			data.AlphaTest: {
-				data.AlphaUntriagedDigest: expectations.Positive.String(),
+				data.AlphaUntriagedDigest: expectations.PositiveStr,
 			},
 			data.BetaTest: {
-				data.BetaUntriagedDigest: expectations.Positive.String(),
+				data.BetaUntriagedDigest: expectations.PositiveStr,
 			},
 		},
 	}, resp)
@@ -245,12 +245,12 @@
 	// This checks that the returned result is the first one of the results we expect.
 	assert.Equal(t, data.AlphaUntriagedDigest, resp.Results[0].Digest)
 	// BulkTriageData should still be fully filled out for all digests in the full results.
-	assert.Equal(t, map[types.TestName]map[types.Digest]string{
+	assert.Equal(t, map[types.TestName]map[types.Digest]expectations.LabelStr{
 		data.AlphaTest: {
-			data.AlphaUntriagedDigest: expectations.Positive.String(),
+			data.AlphaUntriagedDigest: expectations.PositiveStr,
 		},
 		data.BetaTest: {
-			data.BetaUntriagedDigest: expectations.Positive.String(),
+			data.BetaUntriagedDigest: expectations.PositiveStr,
 		},
 	}, resp.BulkTriageData)
 
@@ -266,12 +266,12 @@
 	// This checks that the returned result is the second one of the results we expect.
 	assert.Equal(t, data.BetaUntriagedDigest, resp.Results[0].Digest)
 	// BulkTriageData should still be fully filled out for all digests in the full results.
-	assert.Equal(t, map[types.TestName]map[types.Digest]string{
+	assert.Equal(t, map[types.TestName]map[types.Digest]expectations.LabelStr{
 		data.AlphaTest: {
-			data.AlphaUntriagedDigest: expectations.Positive.String(),
+			data.AlphaUntriagedDigest: expectations.PositiveStr,
 		},
 		data.BetaTest: {
-			data.BetaUntriagedDigest: expectations.Positive.String(),
+			data.BetaUntriagedDigest: expectations.PositiveStr,
 		},
 	}, resp.BulkTriageData)
 }
@@ -294,7 +294,7 @@
 	type spotCheck struct {
 		test            types.TestName
 		digest          types.Digest
-		labelStr        string
+		labelStr        expectations.LabelStr
 		closestPositive types.Digest
 		closestNegative types.Digest
 	}
@@ -895,9 +895,9 @@
 				},
 			},
 		},
-		BulkTriageData: map[types.TestName]map[types.Digest]string{
+		BulkTriageData: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			data.BetaTest: {
-				BetaBrandNewDigest: expectations.Positive.String(),
+				BetaBrandNewDigest: expectations.PositiveStr,
 			},
 		},
 	}, resp)
@@ -1135,7 +1135,7 @@
 
 	details, err := s.GetDigestDetails(context.Background(), testWeWantDetailsAbout, digestWeWantDetailsAbout, testCLID, testCRS)
 	require.NoError(t, err)
-	assert.Equal(t, details.Result.Status, expectations.Negative.String())
+	assert.Equal(t, details.Result.Status, expectations.NegativeStr)
 	assert.Equal(t, []frontend.TriageHistory{
 		{
 			User: clUser,
@@ -1429,7 +1429,7 @@
 		common.NegativeRef: {
 			DiffMetrics: makeBigDiffMetric(),
 			Digest:      data.AlphaNegativeDigest,
-			Status:      expectations.Negative.String(),
+			Status:      expectations.NegativeStr,
 			ParamSet: paramtools.ParamSet{
 				"device":              []string{data.AnglerDevice, data.BullheadDevice, data.CrosshatchDevice},
 				types.PrimaryKeyField: []string{string(data.AlphaTest)},
@@ -1459,7 +1459,7 @@
 		Left: frontend.SearchResult{
 			Test:   testWeWantDetailsAbout,
 			Digest: leftDigest,
-			Status: expectations.Untriaged.String(),
+			Status: expectations.UntriagedStr,
 			ParamSet: paramtools.ParamSet{
 				"device":              []string{data.BullheadDevice},
 				types.PrimaryKeyField: []string{string(data.AlphaTest)},
@@ -1468,7 +1468,7 @@
 		},
 		Right: &frontend.SRDiffDigest{
 			Digest:      rightDigest,
-			Status:      expectations.Positive.String(),
+			Status:      expectations.PositiveStr,
 			DiffMetrics: makeSmallDiffMetric(),
 			ParamSet: paramtools.ParamSet{
 				"device":              []string{data.AnglerDevice, data.CrosshatchDevice},
@@ -1501,7 +1501,7 @@
 
 	cd, err := s.DiffDigests(context.Background(), testWeWantDetailsAbout, leftDigest, rightDigest, clID, crs)
 	require.NoError(t, err)
-	assert.Equal(t, cd.Left.Status, expectations.Negative.String())
+	assert.Equal(t, cd.Left.Status, expectations.NegativeStr)
 }
 
 // TestUntriagedUnignoredTryJobExclusiveDigests_NoIndexBuilt_Success models the case where a set of
@@ -2093,22 +2093,22 @@
 		{
 			Test:   data.AlphaTest,
 			Digest: data.AlphaPositiveDigest,
-			Status: expectations.Positive.String(),
+			Status: expectations.PositiveStr,
 		},
 		{
 			Test:   data.AlphaTest,
 			Digest: data.AlphaNegativeDigest,
-			Status: expectations.Negative.String(),
+			Status: expectations.NegativeStr,
 		},
 		{
 			Test:   data.BetaTest,
 			Digest: data.BetaPositiveDigest,
-			Status: expectations.Positive.String(),
+			Status: expectations.PositiveStr,
 		},
 		{
 			Test:   data.BetaTest,
 			Digest: data.BetaUntriagedDigest,
-			Status: expectations.Untriaged.String(),
+			Status: expectations.UntriagedStr,
 		},
 	}, results)
 }
@@ -2305,7 +2305,7 @@
 	}
 
 	bulkTriageData := collectDigestsForBulkTriage(results)
-	assert.Equal(t, map[types.TestName]map[types.Digest]string{
+	assert.Equal(t, map[types.TestName]map[types.Digest]expectations.LabelStr{
 		"apple": {
 			"grannysmith": "positive",
 			"honeycrisp":  "",
diff --git a/golden/go/status/status.go b/golden/go/status/status.go
index 649854b..c04e955 100644
--- a/golden/go/status/status.go
+++ b/golden/go/status/status.go
@@ -93,9 +93,9 @@
 func New(ctx context.Context, swc StatusWatcherConfig) (*StatusWatcher, error) {
 	ret := &StatusWatcher{
 		StatusWatcherConfig: swc,
-		allUntriagedGauge:   metrics2.GetInt64Metric(allMetric, map[string]string{"type": expectations.Untriaged.String()}),
-		allPositiveGauge:    metrics2.GetInt64Metric(allMetric, map[string]string{"type": expectations.Positive.String()}),
-		allNegativeGauge:    metrics2.GetInt64Metric(allMetric, map[string]string{"type": expectations.Negative.String()}),
+		allUntriagedGauge:   metrics2.GetInt64Metric(allMetric, map[string]string{"type": string(expectations.UntriagedStr)}),
+		allPositiveGauge:    metrics2.GetInt64Metric(allMetric, map[string]string{"type": string(expectations.PositiveStr)}),
+		allNegativeGauge:    metrics2.GetInt64Metric(allMetric, map[string]string{"type": string(expectations.NegativeStr)}),
 		totalGauge:          metrics2.GetInt64Metric(totalDigestsMetric, nil),
 		corpusGauges:        map[string]map[expectations.Label]metrics2.Int64Metric{},
 	}
@@ -250,9 +250,9 @@
 
 			if _, ok := s.corpusGauges[corpus]; !ok {
 				s.corpusGauges[corpus] = map[expectations.Label]metrics2.Int64Metric{
-					expectations.Untriaged: metrics2.GetInt64Metric(corpusMetric, map[string]string{"type": expectations.Untriaged.String(), "corpus": corpus}),
-					expectations.Positive:  metrics2.GetInt64Metric(corpusMetric, map[string]string{"type": expectations.Positive.String(), "corpus": corpus}),
-					expectations.Negative:  metrics2.GetInt64Metric(corpusMetric, map[string]string{"type": expectations.Negative.String(), "corpus": corpus}),
+					expectations.Untriaged: metrics2.GetInt64Metric(corpusMetric, map[string]string{"type": string(expectations.UntriagedStr), "corpus": corpus}),
+					expectations.Positive:  metrics2.GetInt64Metric(corpusMetric, map[string]string{"type": string(expectations.PositiveStr), "corpus": corpus}),
+					expectations.Negative:  metrics2.GetInt64Metric(corpusMetric, map[string]string{"type": string(expectations.NegativeStr), "corpus": corpus}),
 				}
 			}
 		}
diff --git a/golden/go/web/frontend/generate_typescript_rpc_types/main.go b/golden/go/web/frontend/generate_typescript_rpc_types/main.go
index 97f27bb..92a8213 100644
--- a/golden/go/web/frontend/generate_typescript_rpc_types/main.go
+++ b/golden/go/web/frontend/generate_typescript_rpc_types/main.go
@@ -8,6 +8,7 @@
 	"go.skia.org/infra/go/skerr"
 	"go.skia.org/infra/go/sklog"
 	"go.skia.org/infra/go/util"
+	"go.skia.org/infra/golden/go/expectations"
 	"go.skia.org/infra/golden/go/search/common"
 	search_frontend "go.skia.org/infra/golden/go/search/frontend"
 	"go.skia.org/infra/golden/go/status"
@@ -48,6 +49,10 @@
 		return skerr.Wrap(err)
 	}
 
+	if err := generator.AddUnionWithName(expectations.AllLabelStr, "LabelStr"); err != nil {
+		return skerr.Wrap(err)
+	}
+
 	if err := generator.AddUnionWithName(common.AllRefClosest, "RefClosest"); err != nil {
 		return skerr.Wrap(err)
 	}
diff --git a/golden/go/web/frontend/types.go b/golden/go/web/frontend/types.go
index b1b217c..27a2e35 100644
--- a/golden/go/web/frontend/types.go
+++ b/golden/go/web/frontend/types.go
@@ -93,7 +93,7 @@
 type TriageRequest struct {
 	// TestDigestStatus maps status to test name and digests. The strings are
 	// expectation.Label.String() values
-	TestDigestStatus map[types.TestName]map[types.Digest]string `json:"testDigestStatus"`
+	TestDigestStatus map[types.TestName]map[types.Digest]expectations.LabelStr `json:"testDigestStatus"`
 
 	// ChangeListID is the id of the ChangeList for which we want to change the expectations.
 	// "issue" is the JSON field for backwards compatibility.
@@ -112,9 +112,9 @@
 // TriageDelta represents one changed digest and the label that was
 // assigned as part of the triage operation.
 type TriageDelta struct {
-	TestName types.TestName `json:"test_name"`
-	Digest   types.Digest   `json:"digest"`
-	Label    string         `json:"label"`
+	TestName types.TestName        `json:"test_name"`
+	Digest   types.Digest          `json:"digest"`
+	Label    expectations.LabelStr `json:"label"`
 }
 
 // TriageLogEntry represents a set of changes by a single person.
diff --git a/golden/go/web/web.go b/golden/go/web/web.go
index cbfac26..2e03edc 100644
--- a/golden/go/web/web.go
+++ b/golden/go/web/web.go
@@ -923,7 +923,7 @@
 				// side than make the JS check for empty string and mutate the POST body.
 				continue
 			}
-			if !expectations.ValidLabel(label) {
+			if !expectations.ValidLabelStr(label) {
 				return skerr.Fmt("invalid label %q in triage request", label)
 			}
 			tc = append(tc, expectations.Delta{
@@ -1047,8 +1047,8 @@
 
 // Node represents a single node in a d3 diagram. Used in ClusterDiffResult.
 type Node struct {
-	Name   types.Digest `json:"name"`
-	Status string       `json:"status"`
+	Name   types.Digest          `json:"name"`
+	Status expectations.LabelStr `json:"status"`
 }
 
 // Link represents a link between d3 nodes, used in ClusterDiffResult.
diff --git a/golden/go/web/web_test.go b/golden/go/web/web_test.go
index fcd6255..eb44fcf 100644
--- a/golden/go/web/web_test.go
+++ b/golden/go/web/web_test.go
@@ -578,9 +578,9 @@
 
 	tr := frontend.TriageRequest{
 		ChangeListID: "",
-		TestDigestStatus: map[types.TestName]map[types.Digest]string{
+		TestDigestStatus: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			bug_revert.TestOne: {
-				bug_revert.BravoUntriagedDigest: expectations.Negative.String(),
+				bug_revert.BravoUntriagedDigest: expectations.NegativeStr,
 			},
 		},
 	}
@@ -614,9 +614,9 @@
 
 	tr := frontend.TriageRequest{
 		ChangeListID: "",
-		TestDigestStatus: map[types.TestName]map[types.Digest]string{
+		TestDigestStatus: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			bug_revert.TestOne: {
-				bug_revert.BravoUntriagedDigest: expectations.Negative.String(),
+				bug_revert.BravoUntriagedDigest: expectations.NegativeStr,
 			},
 		},
 		ImageMatchingAlgorithm: algorithmName,
@@ -663,9 +663,9 @@
 
 	tr := frontend.TriageRequest{
 		ChangeListID: clID,
-		TestDigestStatus: map[types.TestName]map[types.Digest]string{
+		TestDigestStatus: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			bug_revert.TestOne: {
-				bug_revert.BravoUntriagedDigest: expectations.Negative.String(),
+				bug_revert.BravoUntriagedDigest: expectations.NegativeStr,
 			},
 		},
 	}
@@ -710,9 +710,9 @@
 
 	tr := frontend.TriageRequest{
 		ChangeListID: clID,
-		TestDigestStatus: map[types.TestName]map[types.Digest]string{
+		TestDigestStatus: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			bug_revert.TestOne: {
-				bug_revert.BravoUntriagedDigest: expectations.Negative.String(),
+				bug_revert.BravoUntriagedDigest: expectations.NegativeStr,
 			},
 		},
 		ImageMatchingAlgorithm: algorithmName,
@@ -766,14 +766,14 @@
 
 	tr := frontend.TriageRequest{
 		ChangeListID: "",
-		TestDigestStatus: map[types.TestName]map[types.Digest]string{
+		TestDigestStatus: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			bug_revert.TestOne: {
-				bug_revert.AlfaPositiveDigest:   expectations.Untriaged.String(),
-				bug_revert.BravoUntriagedDigest: expectations.Negative.String(),
+				bug_revert.AlfaPositiveDigest:   expectations.UntriagedStr,
+				bug_revert.BravoUntriagedDigest: expectations.NegativeStr,
 			},
 			bug_revert.TestTwo: {
-				bug_revert.CharliePositiveDigest: expectations.Positive.String(),
-				bug_revert.DeltaUntriagedDigest:  expectations.Negative.String(),
+				bug_revert.CharliePositiveDigest: expectations.PositiveStr,
+				bug_revert.DeltaUntriagedDigest:  expectations.NegativeStr,
 				"digestWithNoClosestPositive":    "",
 			},
 		},
@@ -809,9 +809,9 @@
 
 	tr := frontend.TriageRequest{
 		ChangeListID: "0",
-		TestDigestStatus: map[types.TestName]map[types.Digest]string{
+		TestDigestStatus: map[types.TestName]map[types.Digest]expectations.LabelStr{
 			bug_revert.TestOne: {
-				bug_revert.BravoUntriagedDigest: expectations.Negative.String(),
+				bug_revert.BravoUntriagedDigest: expectations.NegativeStr,
 			},
 		},
 	}
@@ -889,7 +889,7 @@
 			TS:          ts1.Unix() * 1000,
 			Details: []frontend.TriageDelta{
 				{
-					Label:    expectations.Positive.String(),
+					Label:    expectations.PositiveStr,
 					Digest:   bug_revert.DeltaUntriagedDigest,
 					TestName: bug_revert.TestOne,
 				},
@@ -902,12 +902,12 @@
 			TS:          ts2.Unix() * 1000,
 			Details: []frontend.TriageDelta{
 				{
-					Label:    expectations.Positive.String(),
+					Label:    expectations.PositiveStr,
 					Digest:   bug_revert.BravoUntriagedDigest,
 					TestName: bug_revert.TestOne,
 				},
 				{
-					Label:    expectations.Negative.String(),
+					Label:    expectations.NegativeStr,
 					Digest:   bug_revert.CharliePositiveDigest,
 					TestName: bug_revert.TestOne,
 				},
diff --git a/golden/modules/rpc_types.ts b/golden/modules/rpc_types.ts
index 8eba6d0..a9df6c2 100644
--- a/golden/modules/rpc_types.ts
+++ b/golden/modules/rpc_types.ts
@@ -44,7 +44,7 @@
 
 export interface DigestStatus {
 	digest: Digest;
-	status: string;
+	status: LabelStr;
 }
 
 export interface TraceGroup {
@@ -61,7 +61,7 @@
 	dimDiffer?: boolean;
 	diffs?: { [key: string]: number };
 	digest: Digest;
-	status: string;
+	status: LabelStr;
 	paramset: ParamSet;
 	n: number;
 }
@@ -69,7 +69,7 @@
 export interface SearchResult {
 	digest: Digest;
 	test: TestName;
-	status: string;
+	status: LabelStr;
 	triage_history: TriageHistory[] | null;
 	paramset: ParamSet;
 	traces: TraceGroup;
@@ -101,7 +101,7 @@
 	size: number;
 	commits: Commit[] | null;
 	trace_comments: TraceComment[] | null;
-	bulk_triage_data: { [key: string]: { [key: string]: string } };
+	bulk_triage_data: { [key: string]: { [key: string]: LabelStr } };
 }
 
 export interface GUICorpusStatus {
@@ -127,6 +127,8 @@
 
 export type TestName = string;
 
+export type LabelStr = "untriaged" | "positive" | "negative";
+
 export type ParamSet = { [key: string]: string[] | null };
 
 export type TraceID = string;