Agent Onboarding — Coding Rules for the Build Loop (Prompt 3)
Audience: coding subagents implementing
agentic-manifest.json. Source of truth:specs/target-architecture-wasm-iac.md,specs/black-box-functional-spec.md,specs/production-viability-gap-analysis.md,specs/read-only-graph-view-contract.md. Purpose: remove every guess. If a rule is not stated here, do not invent one — open a question.
1. Language & Module (non-negotiable)
| Item | Value |
|---|---|
| Language | Go (>=1.22) |
| Module path | github.com/weavster-dev/weavster |
| Binary name | weavster |
| Package manager | Go modules (go.mod) |
| Web framework | net/http + chi (github.com/go-chi/chi/v5) |
| WASM runtime | wazero (github.com/tetratelabs/wazero) |
| DSL codegen target | Go + TinyGo → WASI (pinned toolchain, external) |
| Databases | PostgreSQL (pgx/v5), SQLite (modernc.org/sqlite — pure-Go, no CGo), in-memory |
Hard constraints:
- No CGo. The binary must cross-compile to linux/amd64, linux/arm64, darwin/arm64 as a single static binary. Any import "C" is a build failure.
- Zero heavy SDKs on the end-user host (no Rust/Java/.NET runtime). This is the Go binary only.
- Single binary / single OCI container (monolith-first). Internal boundaries exist only as Go interfaces.
2. Folder Layout (fixed)
weavster/ # repo root = module root
├── cmd/
│ └── weavster/ # main + CLI shell + server wiring (composition root)
├── internal/
│ ├── gateway/ # API Gateway (REST+OpenAPI, authN/Z, audit, TLS/CSRF/headers)
│ ├── auth/ # AuthProvider + Authorizer, password policy, lockout, MFA hook
│ ├── audit/ # AuditSink (PHI access logging)
│ ├── scheduler/ # cron/interval + durable job queue + reconciler + lease
│ ├── executor/ # wazero WASM runtime + resource limits + host functions
│ ├── state/ # State Manager (Store: postgres/sqlite/memory) + migrations + search/export
│ ├── adapters/ # Source/Sink ports (file/http/tcp-MLLP/db/smtp/webservice/interflow/document)
│ ├── codecs/ # Data-type codecs (HL7 v2, X12, NCPDP, JSON, XML, delimited, raw)
│ ├── compiler/ # YAML DSL → TinyGo → WASM codegen + schema validation
│ ├── registry/ # WASM module registry (version/sign/promote/rollback/GC)
│ ├── config/ # config-as-code: plan/apply/drift, JSON-Schema validation
│ ├── gitstore/ # native Git-backed config store (commit/push/pull/history/restore)
│ ├── migrate/ # legacy import ETL (extract/transform/load/dry-run)
│ ├── outbox/ # transactional outbox + idempotency keys + retry/dead-letter
│ ├── alerts/ # alert definitions + evaluation
│ ├── notify/ # Notifier port (SMTP/webhook)
│ ├── secrets/ # SecretProvider (local store + env//run/secrets)
│ ├── observability/ # metrics (Prometheus) + slog logging + OTel + events + stats
│ └── topology/ # read-only flow topology graph (MVP UI data contract)
└── pkg/ # only intentionally-exported shared libraries (empty at MVP is fine)
- Every package under
internal/is private to this module. Put nothing publicly-importable ininternal/. - One Go package per directory. Package name = directory name (
package gateway,package scheduler, …).cmd/weavsterispackage main.
3. Architectural Law (hexagonal / ports & adapters)
Every internal component depends only on a port (Go interface), never on another component's concrete type. Adapters implement ports. This is constraint #6 (modularity) and is the enabler for future K8s split.
The canonical ports (from the architecture doc §3.1) are:
| Port | Consumer | Implementations (MVP) |
|---|---|---|
Source / Sink |
Executor | file, http, tcp-MLLP, in-memory, db, smtp, web-service, document |
Store |
State Manager callers | Postgres, SQLite, in-memory |
JobQueue |
Scheduler | Postgres SKIP LOCKED, SQLite |
TransformEngine |
Executor | wazero |
AuthProvider |
Gateway | local user store |
Authorizer |
Gateway | built-in permission set |
AuditSink |
Gateway/State | local event store |
SecretProvider |
adapters/TLS | local credential store, env |
MetricsExporter |
all | Prometheus + OTel |
Notifier |
Alerts | SMTP, webhook |
Interface definitions live in the consuming package (Go idiom: "accept interfaces, return structs"). Adapters live in their own package and satisfy the interface implicitly.
4. Testing
- Test framework: Go standard
testingpackage. Files named*_test.goin the same package. - Unit test command (the review-loop gate):
bash go test -race ./... - Per-module gate (used in manifest acceptance criteria), e.g.:
bash go test -race ./internal/executor/... - Flow-level tests (end-user transform fixtures) run through the binary, never by hand-rolling fixtures in unit tests:
bash weavster test --format junit --output artifacts/ - Table-driven tests are the Go idiom — prefer them. WASM executors MUST have a
.wasmfixture or a build step that compiles a TinyGo sample under a pinned toolchain (checked in or produced bymake fixtures). - Any test that needs a database MUST use SQLite (in-memory
:memory:or temp file) — never require Postgres for the test suite (constraint #3).
5. Lint / Format / Vet (run before declaring a module done)
gofmt -l . # MUST print nothing
go vet ./... # MUST pass clean
golangci-lint run # MUST pass (use the repo .golangci.yml)
gofmt output is the formatting truth — do not hand-format. go vet catches real bugs. golangci-lint is the review-loop linter; fix all findings, do not //nolint without a written reason.
6. Build & Package
go build -o bin/weavster ./cmd/weavster # local build
go build -ldflags="-s -w" ./cmd/weavster # stripped release build
Cross-compile (must succeed, zero CGo):
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/weavster-linux-amd64 ./cmd/weavster
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bin/weavster-linux-arm64 ./cmd/weavster
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o bin/weavster-darwin-arm64 ./cmd/weavster
Container (distroless, non-root):
docker build -t weavster:latest .
The Dockerfile MUST be multi-stage (golang:1.22 → gcr.io/distroless/static-debian12:nonroot), run as non-root, and contain only the static binary + the agent-docs/ assets.
7. Version Control of Your Work
- Go module deps go in
go.mod/go.sumviago get/go mod tidy. Never hand-editgo.sum. - Do not commit build artifacts (
bin/, compiled.wasmcaches) — they are derived. YAML is the source of truth for transforms. - Follow the repo's existing commit style (lowercase imperative summary, no scope noise).
8. What You Are NOT Allowed to Do
- ❌ Add a second language (no Rust, no Python, no Node runtime). TinyGo/WASI is the only guest path.
- ❌ Introduce CGo or any c-shared dependency.
- ❌ Bypass a port and depend on a concrete adapter across a module boundary.
- ❌ Require Postgres for any unit test or for local
weavster testexecution. - ❌ Implement Enterprise-scoped items (SSO/OIDC-SAML, ABAC/OPA-Cedar, Redis/NATS queue, leader election, DICOM SCU/SCP, distributed tracing, object-store blobs, multi-tenancy). Leave the interface, stub/
NOT_IMPLEMENTEDthe rest. - ❌ Add features beyond the manifest's
acceptance_criteriafor a module.
9. Acceptance Loop
For each module in agentic-manifest.json:
1. Create the files_to_create in the given path with the given frameworks_to_use.
2. Implement against the acceptance_criteria (trace each criterion to a spec use-case).
3. Run gofmt -l . (empty), go vet ./... (clean), golangci-lint run (clean).
4. Run go test -race ./<module-path>/... — every acceptance criterion that names a test command must pass.
5. Report per-criterion pass/fail; do not mark a module complete until its gates are green.
The manifest is the contract. The specs are the semantics. This document is the how. When they conflict, surface the conflict — do not resolve it silently.