Ctrl K
ComunidadGuías…

CONTRIBUTING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Contributing to Brokoli

Welcome — this is the canonical contributing guide for the Brokoli OSS
core. `brokoli-sdk` and `brokoli-typescript` have their own short guides
that link back here for anything shared.

## Why contribute here

Brokoli is a data platform that starts as a single ~30MB binary: a
visual editor, parallel DAG execution, data quality checks, real-time
monitoring and a Python SDK, with no infrastructure required beyond the
binary itself. That constraint is the interesting part. Most tools in
this space assume a cluster, a scheduler and a warehouse before they do
anything at all; Brokoli has to earn each of those, and only when the
user actually needs it.

Some concrete reasons this is a good codebase to spend time in:

- **The problems are real ones, not glue.** Recent work here has
  included a spill-capable streaming data path, a polyglot task runtime
  that runs Python, Node and JVM code under one protocol, capability-
  based access control for a distributed data plane, and a pushdown
  compiler that decides when work belongs in the database instead of the
  engine. If you want to write systems code with observable
  consequences, there is a lot of it.
- **You can run the whole thing on a laptop.** No cluster, no cloud
  account, no seat license, no mock environment that behaves differently
  from production. `go build`, then `./brokoli serve`, and you have the
  same binary a user runs.
- **Decisions are written down.** [`docs/adr/`](./docs/adr/) holds the
  architecture decision records — not just what the design is, but what
  was rejected and why. When you disagree with something, there is a
  documented position to argue against, and a documented process for
  changing it. Reviews here argue about the design, not about style.
- **The review culture is technical and specific.** See
  [Engineering values](#engineering-values) below. Nobody will
  rubber-stamp your PR, and nobody will bikeshed it either.
- **Apache 2.0**, so what you write stays available to you and everyone
  else.

You do not need distributed-systems experience to start. A good number
of the most useful contributions have been error messages that finally
said which thing failed, tests that closed a gap someone assumed was
covered, and documentation that stopped a user from guessing.

## Where to start

**New here? Start with the
[good first issues](https://github.com/Tnsor-Labs/brokoli/contribute).**
Those are deliberately kept to work that is self-contained, has the
relevant file and function named in the issue body, and does not require
reading an ADR first. If an issue carries that label and turns out not
to match that description, say so on the issue — that is a bug in our
labelling and we want to know.

Beyond that:

- Issues labelled [`help
  wanted`](https://github.com/Tnsor-Labs/brokoli/issues?q=is%3Aopen+label%3A%22help+wanted%22)
  are work we would genuinely like help with, at any experience level.
  Several are substantial.
- [`docs/adr/README.md`](./docs/adr/README.md) explains the architecture
  and, importantly, how to propose changing it. Read the ADR for the
  area you're touching before writing code.
- [`RELEASING.md`](./RELEASING.md) explains the release cadence and
  where a given issue sits in it.
- Issues carrying the current milestone are written with enough context
  to implement from, and usually reference the ADR that motivated them.

## Getting set up

**Prerequisites**

| Tool | Version | Why |
| --- | --- | --- |
| Go | 1.25 or newer (`go.mod` pins the toolchain to 1.26.11) | the engine, API and CLI |
| Node.js | v20 — the version CI pins | the Svelte UI |
| Docker | any recent version | Postgres and MySQL for the live tests |
| Python 3 | 3.9 or newer | code nodes, the Python task harness, and the SDK |

**Build and run**

The UI must be built before the Go binary. `web/embed.go` embeds
`web/dist` into the binary, and `ui/vite.config.ts` builds directly into
that directory — so building Go first gives you a binary with a stale or
empty UI, which looks like a broken frontend rather than a build-order
mistake.

```bash
git clone https://github.com/Tnsor-Labs/brokoli.git
cd brokoli

# 1. UI first -- vite writes into web/dist, which the binary embeds
cd ui && npm ci && npm run build && cd ..

# 2. then the binary -- the main package is the repo root, not cmd/
#    (minicore_disabled leaves the Snowflake driver's native library out,
#    as release builds do)
CGO_ENABLED=0 go build -tags minicore_disabled -o brokoli .

# 3. run it
./brokoli serve
```

Open `http://localhost:8080` and create the admin account. Data goes
into a local SQLite file by default, so there is nothing else to
configure.

**Working on the UI**

`npm run dev` in `ui/` gives you Vite's dev server with hot reload,
proxying API calls to a `brokoli serve` running separately on 8080. You
do not need to rebuild the Go binary for frontend work — only for a
build that ships.

**Finding your way around**

| Directory | What lives there |
| --- | --- |
| `engine/` | DAG execution, node logic, expansion, retries, the run lifecycle |
| `api/` | HTTP handlers, auth, the REST surface |
| `store/` | persistence; every backend implemented for both SQLite and Postgres |
| `models/` | the pipeline IR and its schema |
| `pkg/` | self-contained libraries: `netguard`, `codeexec`, `taskharness`, `artifact`, `datacap`, `plugins`, and more |
| `ui/` | the Svelte editor and monitoring UI |
| `docs/adr/` | architecture decision records — read before changing a design |
| `docs/schema/` | canonical JSON schemas and their fixtures |
| `cmd/` | the CLI |

## Claiming an issue

Comment on the issue to claim it. Substantial issues (the kind with a
companion ADR) are usually split into a few independently-landable
milestones inside the issue body — pick one at a time rather than trying
to land the whole thing in one PR. If you're not sure where to start on
an issue, ask on the issue itself; that's what it's there for.

Asking a question on an issue is always welcome, including "is this
still accurate?" — issues do drift, and we would rather correct one than
have you build against a stale description.

## Branches, commits, PRs

- Branch names: `feat/short-description` or `fix/short-description`.
- PR titles follow Conventional Commits (`feat: ...`, `fix: ...`,
  `docs: ...`) — the title becomes the squash-merge commit message, so
  write it as you'd want it to read in `git log`.
- No AI co-authorship on commits, issues, or PRs in this project —
  attribute authorship to yourself.
- Reference the issue the PR closes (`Closes #NN`) so it closes
  automatically on merge.

## What "done" means

- `gofmt` clean, `go vet ./...` clean.
- `go test -race ./...` passing.
- If you touched `ui/`, the Playwright e2e suite (`ui/`'s test script)
  passing too.
- CI green on the PR.

## Code review

There's no required-approval branch protection on this repo today —
that's a deliberate choice for now, not an oversight. In its place:

> Tag a maintainer for a second pair of eyes on anything non-trivial —
> a new node type, a protocol change, or anything touching `engine/`,
> `store/`, or the plugin protocol. Small, obvious fixes can self-merge.

If you're ever unsure whether something counts as non-trivial, tag
someone — that's cheaper than guessing wrong in either direction.

## Engineering values

These are the things review actually asks about. They are written down
because they are more specific than "write good code", and because
knowing them in advance makes a first PR go much more smoothly.

- **Fix the cause, not the symptom.** A workaround that makes a test
  pass while the underlying defect survives is worse than the failing
  test, because it removes the signal. If the real fix is too large for
  your PR, say so in the PR and open an issue for it.
- **A check that cannot fail is not a check.** This project has been
  bitten repeatedly by gates that passed because they inspected nothing
  — a license scan that checked zero dependencies, a security baseline
  that could not tell a clean scan from no scan at all, a CI job that
  skipped its own tests silently. When you add a guard, verify it fails
  when it should, not only that it passes.
- **Validate a guard in both directions.** Prove the good input is
  accepted *and* the bad input is rejected. One half alone is how a
  permanently-open gate gets merged.
- **Live tests catch what CI does not.** Some questions only a real
  server can answer: what a string literal means, how a driver converts
  a value, whether a collation orders the way Go does. See
  [below](#tests-that-need-a-real-database).
- **Measure, don't assume.** Benchmark against the real thing. Isolated
  microbenchmarks and careful code reading have both given confidently
  wrong answers here.
- **Errors should name the thing that failed.** "Deadline exceeded" that
  cannot say which of two deadlines fired is a bug worth fixing, not a
  cosmetic complaint.

## Keeping docs honest

If your change affects behavior an ADR describes, update that ADR in
the same PR (a Deferred item filled in, a dated `## Update` section
appended) rather than leaving it to drift. See
[`docs/adr/README.md`](./docs/adr/README.md) for the exact convention.

## Local-first workflow

CI exists to confirm what you already verified — not to discover failures.
Before every push:

1. `./preflight.sh` — replicates CI exactly (gofmt unfiltered, race tests,
   UI build, gosec against the baseline via the CI-pinned image,
   govulncheck, licenses, the netguard outbound-client check) plus two
   local-only strictness additions (`go vet`, `svelte-check`).
   `PREFLIGHT_FAST=1` skips `npm ci`; `PREFLIGHT_COVERAGE=1` adds the CI
   coverage pass.
2. For UI work, deploy to the evaluation server and review visually
   before pushing.
3. Push only on local green. CI failures after a green preflight are
   treated as preflight bugs — fix the script alongside the code.

Node: CI pins v20. `preflight.sh` switches via nvm when available and
warns otherwise — a build under a different major may pass locally and
fail in CI (or vice versa).

### Tests that need a real database

Some tests answer questions only a server can: what a string literal means,
how a driver converts a value, whether a collation orders the way Go does.
A fake would define exactly those away, so they run against Postgres and
MySQL and skip when neither is configured.

```bash
docker compose -f docker-compose.test.yml up -d
export BROKOLI_TEST_POSTGRES_URL='postgres://brokoli:brokoli@localhost:55532/brokoli_test?sslmode=disable'
export BROKOLI_TEST_MYSQL_URL='mysql://brokoli:br@k:li/pw#1@tcp(localhost:55533)/brokoli_test'
```

`preflight.sh` does this for you when the variables are unset and docker
compose is available, and says so loudly when it cannot.

A skipped test looks identical to a passing one in the summary line. If you
are changing anything that touches SQL generation, value encoding, or the
pushdown compiler, check the output actually says `PASS` for the live tests
rather than assuming — the differential tests are the only thing standing
between a plausible change and a silently wrong one.

### Changing the database schema

Schema changes are versioned migrations (`store/migrations.go`). A
database records each migration it has applied in `schema_migrations`, and
a start applies only the ones it lacks.

To change the schema, append a step to both stores' lists,
`(*SQLiteStore).migrations` and `(*PostgresStore).migrations`, each with its
list's next version number. The numbers can differ between the two: SQLite's
migration 2 rewrote its timestamps and has no Postgres counterpart.

```go
{version: 3, name: "runs priority", apply: func(tx *sql.Tx) error {
	_, err := tx.Exec(`ALTER TABLE runs ADD COLUMN priority INTEGER NOT NULL DEFAULT 0`)
	return err
}},
```

- A step runs once, inside a transaction with the row that records it. If
  it fails, the start fails and nothing is recorded, so return every error.
  Do not write `IF NOT EXISTS` guards to make a step safe to repeat; it
  runs once.
- Keep steps additive. An older binary keeps running against a newer
  schema during a rolling deploy or a rollback, and it only warns.
- Never edit the baseline (version 1, `migrateBaseline`) or a step that has
  shipped. A database that has recorded it never runs it again, so the edit
  would reach new installations only. `TestBaselineIsFrozen` fails if the
  baseline changes.
- Work that has to follow the code on every start, such as the system
  roles, belongs in the stores' `reconcile`, not in a migration.
- Test the upgrade too, not only a fresh database: open a store, write the
  data the step changes, close it, and open it again.
- A step that changes the schema also adds its undo to `forgetMigrations`
  (`store/migrations_test.go`). Tests use it to turn a database this build
  created into one an earlier release left, and without the undo they fail
  on "already exists".
- SQLite has no time type, so timestamps are text and are compared as
  text. Write them with `formatTime` and read them with `parseTime`
  (`store/timefmt.go`); never `Format(time.RFC3339Nano)`, and never hand a
  `time.Time` to the driver, which stores `time.Time.String()`. Values of
  different widths compare wrongly.

## Code of conduct and licence

Be straightforward and civil; argue about the work, not the person.
Contributions are made under the [Apache 2.0 licence](./LICENSE) that
covers the project.