Files
Dmitry Ng 242b79e97a feat: rework update/support contracts, fix SDK retry body replay, and refresh docs for v1.0.0
- Split reported components into images vs. files, add update strategies and per-stack resolution, and a shared action/reason vocabulary for update answers
- Add models.ParseEnvelope[T] and MsgLogTypeWait to match the server's response contract; fix SDK retries silently resending an exhausted request body
- Update examples/report-errors to continue issues via -issue-id and render streamed answers live; refresh README/API.md/doc.go for the license key flow
2026-08-12 20:09:30 +03:00

172 lines
5.7 KiB
Go

package models
import "testing"
// The component vocabulary, pinned as a literal.
//
// It is a COPY of the same list in the other repository, duplicated for the same
// reason the golden wire forms are: the update service and the public SDK are
// separate modules, a shared import would tie every service build to a
// published SDK version, and
// pinning the values on both sides catches drift just as well.
//
// Until now nothing related the two lists at all — they were two independent
// hand-written copies of the same vocabulary, and a component added to one was
// simply absent from the other. Absent quietly: the SDK's GetProductStack()
// answers `pentagi` for anything it does not know, so a client correlating its
// components against the update answer would put an observability component in
// the wrong stack and see nothing wrong.
//
// If you change one list, change the other.
var goldenComponentVocabulary = []string{
"pentagi",
"scraper",
"langfuse-worker",
"langfuse-web",
"grafana",
"otel",
"worker",
"installer",
"engine",
"minio",
"redis",
"postgres",
"pgvector",
"jaeger-clickhouse",
"graphiti",
"neo4j",
"victoriametrics",
"clickhouse",
"loki",
"jaeger",
"browseruse",
"engine-registry",
"engine-scenario",
"pgexporter",
"node-exporter",
"cadvisor",
"clickstore",
}
// The stack each component belongs to, pinned the same way. The vocabulary
// agreeing while the stacks disagree would be worse than an outright mismatch:
// both sides would name the same components and route them differently.
var goldenComponentStacks = map[string]string{
"pentagi": "pentagi",
"scraper": "pentagi",
"pgvector": "pentagi",
"pgexporter": "pentagi",
"langfuse-worker": "langfuse",
"langfuse-web": "langfuse",
"postgres": "langfuse",
"clickhouse": "langfuse",
"redis": "langfuse",
"minio": "langfuse",
"grafana": "observability",
"otel": "observability",
"victoriametrics": "observability",
"loki": "observability",
"jaeger": "observability",
"jaeger-clickhouse": "observability",
"clickstore": "observability",
"node-exporter": "observability",
"cadvisor": "observability",
"worker": "worker",
"installer": "installer",
"engine": "engine",
"engine-registry": "engine",
"engine-scenario": "engine",
"graphiti": "graphiti",
"neo4j": "graphiti",
"browseruse": "browser",
}
// goldenComponentKinds is how each component is delivered, pinned as a literal
// on both sides for the same reason the vocabulary is.
//
// Kind is not derivable from the name or the stack — jaeger-clickhouse is a
// FILE while clickstore, the ClickHouse server behind it, is an image — so a
// table that drifts between the two sides is a client reporting an artefact in
// a list the service will reject, or worse, in one it will answer wrongly.
var goldenComponentKinds = map[string]string{
"pentagi": "image",
"scraper": "image",
"langfuse-worker": "image",
"langfuse-web": "image",
"grafana": "image",
"otel": "image",
"worker": "image",
"installer": "file",
"engine": "file",
"minio": "image",
"redis": "image",
"postgres": "image",
"pgvector": "image",
"jaeger-clickhouse": "file",
"graphiti": "image",
"neo4j": "image",
"victoriametrics": "image",
"clickhouse": "image",
"loki": "image",
"jaeger": "image",
"browseruse": "image",
"engine-registry": "file",
"engine-scenario": "file",
"pgexporter": "image",
"node-exporter": "image",
"cadvisor": "image",
"clickstore": "image",
}
func TestComponentKindsMatchTheOtherSide(t *testing.T) {
for _, name := range goldenComponentVocabulary {
want, pinned := goldenComponentKinds[name]
if !pinned {
t.Errorf("%s has no pinned artefact kind", name)
continue
}
if got := ComponentType(name).ArtifactKind(); string(got) != want {
t.Errorf("%s is delivered as %q here and %q in the pinned list", name, got, want)
}
}
if len(goldenComponentKinds) != len(goldenComponentVocabulary) {
t.Errorf("the pinned kinds cover %d components, the pinned vocabulary has %d",
len(goldenComponentKinds), len(goldenComponentVocabulary))
}
}
func TestComponentVocabularyMatchesTheOtherSide(t *testing.T) {
got := make([]string, 0, len(AllComponentTypes))
for _, ct := range AllComponentTypes {
got = append(got, string(ct))
}
if len(got) != len(goldenComponentVocabulary) {
t.Errorf("this side has %d components, the pinned list has %d",
len(got), len(goldenComponentVocabulary))
}
// Order matters as well as membership: it is the order of the PostgreSQL
// enum and the ClickHouse Enum8, and a reader comparing the three by eye
// depends on it.
for i := range min(len(got), len(goldenComponentVocabulary)) {
if got[i] != goldenComponentVocabulary[i] {
t.Errorf("component %d is %q here and %q in the pinned list", i, got[i], goldenComponentVocabulary[i])
}
}
for _, name := range goldenComponentVocabulary {
stack, ok := ComponentToStackMapping[ComponentType(name)]
if !ok {
t.Errorf("%s has no stack on this side", name)
continue
}
if want := goldenComponentStacks[name]; string(stack) != want {
t.Errorf("%s is in stack %q here and %q in the pinned list", name, stack, want)
}
}
if len(ComponentToStackMapping) != len(goldenComponentStacks) {
t.Errorf("this side maps %d components to stacks, the pinned list maps %d",
len(ComponentToStackMapping), len(goldenComponentStacks))
}
}