The devcontainer has two databases: seed (postgres) and prod-like (prodlike), controlled by SIPPY_DATA_MODE (default: seed).
To migrate both databases:
go run ./cmd/sippy migrate --database-dsn "$SIPPY_SEED_DATABASE_DSN"
go run ./cmd/sippy migrate --database-dsn "$SIPPY_PRODLIKE_DATABASE_DSN"
If no env vars are set, the dev default is: postgresql://postgres:password@localhost:5432/postgres
Restore prod-like from a backup: scripts/restore_prodlike_db.sh or MCP restore_prodlike_db. Instructions and slash-command text are defined in .apm/prompts/sippy-dev-restore-prodlike.prompt.md; run make apm after changing that file so apm install redeploys generated command files. Stop sippy serve first.
Run lint: make lint
Run unit tests: make test
This runs Go tests via gotestsum and sippy-ng Vitest tests.
Run integration tests: make integration
This runs SQL-level tests against a real PostgreSQL instance using testcontainers-go. Requires Docker or Podman.
- When your change alters setup steps, usage instructions, or architecture,
update the relevant README (root
README.md,sippy-ng/README.md,mcp/README.md, etc.) in the same PR. - When API endpoints are added, removed, or modified, update
pkg/api/README.md(or create it if it doesn't exist). - When configuration options, environment variables, or CLI flags change,
update
config/README.mdor the relevant section of the rootREADME.md. - When new conventions, workflows, or tooling are introduced, consider
adding or updating an
.apm/instructions/file so that AI coding assistants stay aligned with the project's practices. - When your change affects a feature documented in
docs/features/, update the relevant feature doc in the same PR. Feature docs describe purpose, data flow, and key code locations; keep them accurate as the implementation evolves. If you add a new major feature, create a new doc indocs/features/. - Documentation and code belong in the same PR; never treat a docs update as a follow-up task.
- Do not use em dashes when writing docs. Use commas, parentheses, or periods instead.
- Files under
pkg/**/jobrunscan**/,pkg/**/jobrunannotator**/,pkg/api/jobartifacts/**, andsippy-ng/src/component_readiness/JobArtifactQuery.jsare part of the symptoms feature documented indocs/features/job-analysis-symptoms.md. Update this document when there are changes to data models, API surface, or data flow in the symptoms feature.
Sources under .apm/ (instructions, prompts, apm.yml, etc.) drive generated agent context. After editing those files, run make apm to regenerate AGENTS.md, CLAUDE.md, GEMINI.md, and the integrated copies under .claude/, .cursor/, .gemini/, and .opencode/. CI enforces freshness with make verify-apm.
Slash / agent commands: content under .apm/prompts/*.prompt.md is the single source of truth. apm install (part of make apm) copies each prompt into editor command targets (e.g. .claude/commands/, .opencode/commands/, .gemini/commands/). Do not add those generated paths by hand or installs will skip them as unmanaged duplicates.
Sippy (CIPI - Continuous Integration Private Investigator) is a tool used within the OpenShift engineering organization to analyze CI job results. Its primary goals are to:
- Provide insights into job and test statistics.
- Monitor release health and detect regressions.
- Support release management decisions through statistical analysis (e.g., Component Readiness).
The system consists of:
- A Go-based API backend.
- A React/Material-UI frontend (located in
sippy-ng). - Data sources including PostgreSQL, and BigQuery
- A headless daemon for asynchronous processing
Favor clarity and maintainability over cleverness. Comments should be minimal, helpful, and explain the "why" not the "what".
- Follow idiomatic Go practices.
- Choose names carefully using concise but appropriately descriptive words; in the scope of a package, a name need only describe its function relative to that package. Provide docstring for every package-level name explaining why it exists and describing any parameter whose purpose is not completely obvious from name and context.
- Keep packages, structs, and methods focused on a single clear conceptual "chunk": a package should represent one cohesive concept, a struct should represent a single entity in the scope of its package, and a method should operate on a single level of abstraction. Prefer descriptive names that evoke a specific concept (e.g., "TestResultAggregator" not "Manager"). Avoid generic names like "Manager", "Handler", "Util" without specific context, and method names that don't indicate what they do (e.g., "Process", "Handle", "Do"). Structs with more than about 7 top-level fields should be refactored into focused sub-types.
- When adding or updating APIs, use HATEOAS in responses to support discoverability and consistent client interaction.
- Use
k8s.io/apimachinery/pkg/util/sets(e.g.sets.New[string]()) to deduplicate or collect unique strings. Do not usemap[string]boolas a hand-rolled set. - Prefer structured logging where it makes sense; particularly for names and IDs, and often for counts, a log.WithField() call is preferred over formatting values into a string.
- When modifying any data provider (BigQuery or PostgreSQL), ensure parity between both implementations. Changes to query logic, filtering, or returned data in one provider must be reflected in the other.
- Timestamps and dates: Use proper types, never epoch integers.
- PostgreSQL columns: use
TIMESTAMP WITH TIME ZONEfor timestamps andDATEfor date-only values. All timestamps are UTC. - Go structs: use
time.Timefor timestamps. Usecivil.Date(cloud.google.com/go/civil) for date-only values (e.g., GA dates, development start dates). Do not usestringfor date or timestamp fields; let JSON marshaling produce the correct format. - API responses: timestamps serialize as RFC 3339 strings, dates as
YYYY-MM-DDstrings. Never return epoch millisecond integers. Never manually format withtime.Format()into a string field. - Materialized views: project timestamp columns directly from the source table. Do not convert to epoch with
EXTRACT(epoch FROM ...). - GORM model tags: include
gorm:"type:date"on date-only columns so GORM and migrations use the correct PostgreSQL type.
- PostgreSQL columns: use
- Check
pkg/util/for existing helper functions before adding inline utility logic. Avoid calling the same utility function multiple times with identical arguments in the same code path. - Never ignore returned errors as
_without clear justification. Errors should be wrapped with context usingfmt.Errorfwith%w. Avoidpanic()except ininit()or fatal conditions. Check for nil before dereferencing pointers. - Never concatenate or format SQL queries with values directly from user input. Always use
placeholders for parameters in queries, preferably named (
@Name). - Structs used with GORM that have fields not backed by database columns (such as computed or
API-only fields) must include the
gorm:"-"tag to explicitly exclude them from GORM operations. BigQuery struct fields must havebigquery:"column_name"tags that exactly match the BigQuery query result schema names, whether from table columns or aliases (give computed fields aliases). - After making changes, always run
gofmt -won modified files to ensure proper formatting.
- Use
go vetandgo test ./pkg/...to validate changes before resorting to a full e2e run. - Run
make e2e 2>&1 | tee e2e-test.logto verify your changes don't break end-to-end tests. You MUST read the log file (e2e-test.log) for results. Do not re-run e2e just to grep for different things. All output is already in the log file. - Do not mock storage clients (BigQuery, GCS, Postgres, etc.) when constructing golang unit tests.
Go does not make it easy to substitute mocks for concrete SDK clients, and mock-heavy tests tend
to verify the mock implementation rather than real behavior. Instead, structure code to separate
pure logic from client calls. Unit test the logic functions directly (validation, data
transformation, result aggregation and analysis). Enable testing against real storage systems with
functional tests that skip unless the user supplies connection credentials via environment
variables (see
releasesync_functional_test.gofor the pattern). - When a struct method needs a single, narrow query or RPC that would otherwise force a
database connection in tests, extract that call behind a function type field on the struct
(e.g.
type counterFunc func(id uint) (int, error)). This is a thin adapter seam, not a general-purpose mock — the function type replaces one specific call, not an entire storage client. The production constructor wires the real implementation; tests supply a closure. Seeregressiontracker.go(failureCounterFunc) for the pattern. This does not override the rule above: do not mock or stub BigQuery, GCS, Postgres clients, or any broad storage interface. - Prefer table-driven tests with descriptive case names. Search the same package for existing test patterns before writing new ones.
- New or modified functionality must include test coverage: new Go functions and methods should have corresponding unit tests, bug fixes should include a regression test that fails without the fix, and pure functions (no DB/external dependencies) should always be tested. If a function is hard to test, consider refactoring it into simpler functions (disentangle separate conceptual chunks). Exceptions: trivial changes (renaming, formatting, comments), generated code or configuration-only changes, and refactors already covered by existing tests.
This file was generated by APM CLI. Do not edit manually.
To regenerate: apm compile