Refuse metric metadata edits the plane cannot store
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Successful in 21m35s
Hanzo CI/CD / test-amd64 (push) Successful in 33m56s
Hanzo CI/CD / cicd (push) Successful in 3m6s
CI/CD / cicd (push) Successful in 37m4s

There is no event.descriptor to write; answer 501, not 500.

Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
This commit is contained in:
2026-09-29 20:05:57 -07:00
parent c66a1d6dd5
commit 8af36b1bb0
2 changed files with 34 additions and 38 deletions
@@ -913,45 +913,10 @@ func (m *module) checkForLabelInMetric(ctx context.Context, metricName string, l
return hasLabel, nil
}
// insertMetricsMetadata refuses: metric metadata is read from the series the
// collector writes, and the event plane has no table an edit could land in.
func (m *module) insertMetricsMetadata(ctx context.Context, orgID valuer.UUID, req *metricsexplorertypes.UpdateMetricMetadataRequest) error {
ctx = m.withMetricsExplorerContext(ctx, "insertMetricsMetadata")
createdAt := time.Now().UnixMilli()
ib := sqlbuilder.NewInsertBuilder()
ib.InsertInto(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.DescriptorTableName))
ib.Cols("metric_name", "temporality", "is_monotonic", "type", "description", "unit", "created_at")
ib.Values(
req.MetricName,
req.Temporality,
req.IsMonotonic,
req.Type,
req.Description,
req.Unit,
createdAt,
)
query, args := ib.BuildWithFlavor(datastoresql.Flavor)
valueCtx := ctxtypes.SetDatastoreMaxThreads(ctx, m.config.TelemetryStore.Threads)
db := m.telemetryStore.Datastore()
if err := db.Exec(valueCtx, query, args...); err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "failed to insert metrics metadata")
}
// Set in cache after successful DB insert
metricMetadata := &metricsexplorertypes.MetricMetadata{
Description: req.Description,
MetricType: req.Type,
MetricUnit: req.Unit,
Temporality: req.Temporality,
IsMonotonic: req.IsMonotonic,
}
cacheKey := generateMetricMetadataCacheKey(req.MetricName)
if err := m.cache.Set(ctx, orgID, cacheKey, metricMetadata, 0); err != nil {
m.logger.WarnContext(ctx, "failed to set metric metadata in cache after insert", slog.String("metric_name", req.MetricName), errors.Attr(err))
}
return nil
return errors.Newf(errors.TypeUnsupported, errors.CodeUnsupported, "metric %q metadata is read from its series and cannot be edited", req.MetricName)
}
func (m *module) buildFilterClause(ctx context.Context, filter *qbtypes.Filter, startMillis, endMillis int64) (*sqlbuilder.WhereClause, error) {
@@ -0,0 +1,31 @@
package datastorereader
import (
"context"
"strings"
"testing"
"github.com/hanzoai/o11y/pkg/query-service/constants"
"github.com/hanzoai/o11y/pkg/query-service/model"
"github.com/stretchr/testify/assert"
)
// Exceptions read the event fact table, scoped to the caller's org, and never
// the retired error index.
func TestListErrorsQueryReadsFactByOrg(t *testing.T) {
q := listErrorsQuery("event.fact", "", &model.ListErrorsParams{
OrderParam: "lastSeen", Order: constants.Descending, Limit: 10, Offset: 5,
})
assert.Contains(t, q, "FROM event.fact WHERE org = @org AND signal = 'error'")
assert.Contains(t, q, "GROUP BY issue")
assert.Contains(t, q, "ORDER BY lastSeen DESC LIMIT @limit OFFSET @offset")
assert.False(t, strings.Contains(q, "error_index"))
}
func TestErrorFactsNeedsTenant(t *testing.T) {
r := &DatastoreReader{TraceDB: "event", errorTable: "fact"}
_, _, apiErr := r.errorFacts(context.Background())
if assert.NotNil(t, apiErr) {
assert.Equal(t, model.ErrorForbidden, apiErr.Typ)
}
}