anvilkit-export-worker

module
v0.0.0-...-f02db28 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: MIT

README

anvilkit-export-worker

Stateless, queue-driven Go worker that owns the AnvilKit export stage: consume deployment.export.requested, load the authoritative deployment record, acquire a per-deploymentId lock, CAS EXPORT_QUEUED → EXPORTING, fetch version-pinned HTML from anvilkit-render-origin over internal HTTP, harvest dependencies deterministically, upload hashed artifacts plus artifact-manifest.json to S3-compatible storage, submit the manifest pointer, CAS EXPORTING → ARTIFACT_READY | EXPORT_FAILED, and emit deployment.artifact.ready / deployment.export.failed.

Canonical naming (ADR-015): this service is anvilkit-export-worker on every surface — repo, services/export-worker submodule path, export-worker image/Deployment/consumer group, anvilkit_export_worker_* metrics. The PRDs' anvilkit-render-worker and the old anvilkit-static-publisher repo name refer to this same service.

Status

M5 hardening (PLAN-0002), on top of the executed M1–M5 pipeline (full export pipeline, FR-015 redelivery re-emit, five-mechanism queue model, complete observability baseline, failure-injection/storm/SIGTERM-drain proofs, §15.4 alert rules, K8s slice, CD immutable images):

  • Same-origin output bounds: page, per-asset, and whole-bundle byte limits with bounded reads; oversize is non-retryable VALIDATION_FAILED (broken render contract), never truncated, never leaking body bytes or credentials.
  • Stream retention (ADR-011): the worker trims its four streams to configured floors with XTRIM MINID; the main stream never trims delivered-but-unacked or undelivered entries (the ack rule outranks retention). Trim activity: anvilkit_export_worker_stream_trimmed_total{stream}.
  • Trace correctness: the update_status_ready span records the actual CAS outcome, including recovered conflicts.
  • Schema-subset guard: embedded contract schemas are audited against the validator's keyword subset, so contract evolution can never silently bypass validation (internal/jsonschema.Audit).

Remaining before broad rollout is tracked in PRD 0011 (site cap, staging drills, DLQ replay tooling, BD-004/005/007 closure, ops sign-offs). Operations live in anvilkit-platform/docs/runbooks/export-worker.md.

M5 hardening configuration (extends PRD 0010 §14; all optional)
Variable Default Meaning
MAX_RENDER_HTML_BYTES 10 MiB Max bytes for one rendered page
MAX_RENDER_ASSET_BYTES 25 MiB Max bytes for one same-origin dependency
MAX_TOTAL_ARTIFACT_BYTES 512 MiB Max bytes for the whole artifact bundle
STREAM_MAIN_RETENTION_MS 72 h deployment.export.requested retention (0 disables)
STREAM_DLQ_RETENTION_MS 7 d DLQ retention (0 disables)
STREAM_READY_RETENTION_MS 7 d deployment.artifact.ready retention (0 disables)
STREAM_FAILED_RETENTION_MS 7 d deployment.export.failed retention (0 disables)

ASSET_SERVICE_URL is now optional (validated when set): no worker code path calls asset-service yet — the generated client is contract groundwork; the variable becomes required again in the change that introduces a real call.

Layout

cmd/export-worker/        entrypoint: config, wiring, consumer pool, dispatcher,
                          reclaim loop, SIGTERM drain skeleton
contracts/                GENERATED from anvilkit-platform contracts/ — do not edit.
                          events, deployment-service + asset-service bindings, and
                          the artifact-manifest bindings. Public on purpose: the
                          platform mocks (and future Go consumers) import them.
internal/buildinfo/       service identity + version stamp (ADR-015)
internal/config/          §14 env config, fail-fast validation, demo guard (FR-019)
internal/errclass/        §13 error registry classification (FR-014)
internal/jsonschema/      contract-subset JSON Schema validator (shared)
internal/obs/             logging + redaction, metrics, health/readyz, lifecycle
internal/queue/           driver seam + Redis Streams driver, validation, DLQ,
                          delayed retry, dispatcher, outcome streams (ADR-003)
internal/lock/            per-deployment lock: SET NX PX, heartbeat, owner-checked
                          release (FR-005)
internal/deployment/      record load + reconciliation, CAS + pointer submission
internal/render/          render-origin client: pinning headers, classification,
                          timeout budget, preview path (FR-007/FR-024)
internal/harvest/         output guards, HTML/CSS parsers, allowlists, path
                          normalization + traversal corpus (FR-008/009/010)
internal/storage/         S3/MinIO adapter, hash idempotency, multipart, manifest
                          builder + cache-control classes (FR-011/012)
internal/emit/            schema-validated outcome-event emission (FR-013)
internal/export/          the Exporter pipeline tying it all together
internal/worker/          job processor: every ack decision + failure branches
internal/testsupport/     integration-test infrastructure (never in the binary)
scripts/dependency-audit.sh   forbidden-dependency gate (AC-002, AC-018)

Commands

make build          # go build ./...
make test           # go test -race ./...   (integration tests skip without the env below)
make vet            # go vet ./...
make lint           # golangci-lint run
make audit          # dependency audit

# Integration tests against disposable containers:
docker run -d --name test-redis -p 16379:6379 redis:7-alpine
docker run -d --name test-minio -p 19000:9000 \
  -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \
  minio/minio:latest server /data
REDIS_TEST_URL=redis://localhost:16379 S3_TEST_ENDPOINT=http://localhost:19000 \
  go test -race -count=1 ./...

# Container image (EW-K8S-001):
docker build -t anvilkit-export-worker:dev .

The full local stack (Redis, MinIO, all three contract mocks, worker) lives in the platform repo: anvilkit-platform/infra/docker-compose.yml — see infra/README.md there for the happy-path, negative, and multipart scenarios plus artifact verification.

Boundary rules (hard)

  • No cross-repo source imports with anvilkit-studio, in either direction — integration is contract-only (JSON Schema events, OpenAPI internal APIs, versioned in anvilkit-platform/contracts/). Render output is consumed over HTTP, never via render code.
  • Never depend on React, Next.js, Puck, @anvilkit/render-runtime, or any @anvilkit/* frontend package (CI-enforced).
  • External services (deployment-service, asset-service, cdn-service, …) are contracts and mocks only; the worker is stateless and never touches their databases.
  • No CDN upload/purge/verify/activation code paths — delivery beyond artifact storage is cdn-service's stage (AC-017). The manifest is internal-only, never public.
  • apps/demo is never a render target outside local development (startup guard, ADR-010).
  • The token and storage credentials never appear in logs (redaction-enforced, §11.1).
  • S3 ETag is never a content hash — idempotency compares x-amz-meta-content-sha256 only (AC-006).

Generated contracts

contracts/ is generated by the platform repo's codegen pipeline (bun packages/contracts-codegen/generate.ts in anvilkit-platform) and committed here so this repo builds standalone. Platform CI regenerates and fails on drift; edit the contract files in anvilkit-platform/contracts/, never the generated Go.

Directories

Path Synopsis
cmd
export-worker command
Command export-worker is anvilkit-export-worker: the stateless, queue-driven Go worker that owns the AnvilKit export stage.
Command export-worker is anvilkit-export-worker: the stateless, queue-driven Go worker that owns the AnvilKit export stage.
contracts
artifact
Package artifact contains Go bindings for artifact-manifest.json — the canonical description of one deployment's static artifact bundle (PRD 0008 §9.2; FR-012).
Package artifact contains Go bindings for artifact-manifest.json — the canonical description of one deployment's static artifact bundle (PRD 0008 §9.2; FR-012).
assetservice
Package assetservice contains Go bindings for the asset-service internal API (consumed contract).
Package assetservice contains Go bindings for the asset-service internal API (consumed contract).
deploymentservice
Package deploymentservice contains Go bindings for the deployment-service internal API (consumed contract).
Package deploymentservice contains Go bindings for the deployment-service internal API (consumed contract).
events
Package events contains Go bindings for the versioned AnvilKit platform event contracts (contracts/events/v1).
Package events contains Go bindings for the versioned AnvilKit platform event contracts (contracts/events/v1).
internal
buildinfo
Package buildinfo carries the service identity (ADR-015 canonical naming) and the build-time version stamp.
Package buildinfo carries the service identity (ADR-015 canonical naming) and the build-time version stamp.
config
Package config owns configuration loading for all PRD 0010 §14 environment variables with startup fail-fast validation (FR-019, EW-CONFIG-001): a misconfigured worker refuses to boot (CONFIG_MISSING class) and never starts jobs.
Package config owns configuration loading for all PRD 0010 §14 environment variables with startup fail-fast validation (FR-019, EW-CONFIG-001): a misconfigured worker refuses to boot (CONFIG_MISSING class) and never starts jobs.
deployment
Package deployment owns the worker-side wrapper around the generated deploymentservice bindings (FR-004, FR-006; EW-DEPLOY-001..003/005): authoritative record load with event-hint reconciliation, CAS status transitions restricted to the worker-owned targets, DEPLOYMENT_SERVICE_* error classification, and the terminal / non-actionable state decisions that gate acks.
Package deployment owns the worker-side wrapper around the generated deploymentservice bindings (FR-004, FR-006; EW-DEPLOY-001..003/005): authoritative record load with event-hint reconciliation, CAS status transitions restricted to the worker-owned targets, DEPLOYMENT_SERVICE_* error classification, and the terminal / non-actionable state decisions that gate acks.
emit
Package emit owns outcome-event construction and emission (FR-013, EW-EVENT-001..003): deployment.artifact.ready after the CAS to ARTIFACT_READY succeeds, deployment.export.failed after a terminal failure.
Package emit owns outcome-event construction and emission (FR-013, EW-EVENT-001..003): deployment.artifact.ready after the CAS to ARTIFACT_READY succeeds, deployment.export.failed after a terminal failure.
errclass
Package errclass owns job-level error classification (FR-014; PRD 0010 §13, PRD 0008 §12.2): every failure carries a registry error code, the pipeline stage where it occurred (trace-span vocabulary), and a retryable / non-retryable classification that drives the retry-vs-fail branch.
Package errclass owns job-level error classification (FR-014; PRD 0010 §13, PRD 0008 §12.2): every failure carries a registry error code, the pipeline stage where it occurred (trace-span vocabulary), and a retryable / non-retryable classification that drives the retry-vs-fail branch.
export
Package export implements the Exporter seam with the full M3 pipeline (PRD 0010 §5.1 stages render_html → harvest_dependencies → upload_artifacts → write_manifest → submit_artifact → update_status_ready → emit_ready): fetch version-pinned HTML, harvest dependencies deterministically, upload hashed artifacts, write the internal-only manifest, submit the pointer (BD-004 interim semantics), CAS EXPORTING → ARTIFACT_READY, and emit deployment.artifact.ready (CAS-then-emit, FR-013).
Package export implements the Exporter seam with the full M3 pipeline (PRD 0010 §5.1 stages render_html → harvest_dependencies → upload_artifacts → write_manifest → submit_artifact → update_status_ready → emit_ready): fetch version-pinned HTML, harvest dependencies deterministically, upload hashed artifacts, write the internal-only manifest, submit the pointer (BD-004 interim semantics), CAS EXPORTING → ARTIFACT_READY, and emit deployment.artifact.ready (CAS-then-emit, FR-013).
harvest
Package harvest owns render output guards, deterministic dependency harvesting, allowlist enforcement, and path safety (FR-008, FR-009, FR-010; EW-ARTIFACT-001..006).
Package harvest owns render output guards, deterministic dependency harvesting, allowlist enforcement, and path safety (FR-008, FR-009, FR-010; EW-ARTIFACT-001..006).
jsonschema
Package jsonschema is a minimal JSON Schema (draft 2020-12 subset) validator covering exactly the keywords used by the frozen contracts: type, properties, required, additionalProperties, enum, const, pattern, minLength, minimum, maximum, items.
Package jsonschema is a minimal JSON Schema (draft 2020-12 subset) validator covering exactly the keywords used by the frozen contracts: type, properties, required, additionalProperties, enum, const, pattern, minLength, minimum, maximum, items.
lock
Package lock owns the per-deploymentId distributed lock (FR-005, EW-LOCK-001..004): SET lock:deployment:{deploymentId} {workerId} NX PX with TTL = renderTimeout + uploadTimeout + 60s (min 90s), heartbeat renewal while the job runs, and owner-checked release.
Package lock owns the per-deploymentId distributed lock (FR-005, EW-LOCK-001..004): SET lock:deployment:{deploymentId} {workerId} NX PX with TTL = renderTimeout + uploadTimeout + 60s (min 90s), heartbeat renewal while the job runs, and owner-checked release.
obs
Package obs owns observability and lifecycle: structured JSON logging with the required job-scoped fields (PRD 0010 §15.1) and secret redaction (EW-CONFIG-005), the full Prometheus metric baseline in the anvilkit_export_worker_* namespace (ADR-015, EW-OBS-002) including the §15.4 alert feeds, OpenTelemetry per-job spans (§15.3 vocabulary) with trace context forwarded to render-origin (EW-OBS-003), the internal-only health/readiness/metrics endpoints on ports 8081/9091 (FR-018), and the worker lifecycle state reflected by the readiness probe — including the DRAINING state the SIGTERM graceful drain flips through (FR-017).
Package obs owns observability and lifecycle: structured JSON logging with the required job-scoped fields (PRD 0010 §15.1) and secret redaction (EW-CONFIG-005), the full Prometheus metric baseline in the anvilkit_export_worker_* namespace (ADR-015, EW-OBS-002) including the §15.4 alert feeds, OpenTelemetry per-job spans (§15.3 vocabulary) with trace context forwarded to render-origin (EW-OBS-003), the internal-only health/readiness/metrics endpoints on ports 8081/9091 (FR-018), and the worker lifecycle state reflected by the readiness probe — including the DRAINING state the SIGTERM graceful drain flips through (FR-017).
queue
Package queue owns the queue driver abstraction and its Redis Streams MVP implementation (FR-003, FR-014, FR-016; ADR-003 five-mechanism model):
Package queue owns the queue driver abstraction and its Redis Streams MVP implementation (FR-003, FR-014, FR-016; ADR-003 five-mechanism model):
render
Package render owns the render-origin HTTP client (FR-007, EW-RENDER-001..004): version-pinned page fetch with the bearer token and all seven X-AnvilKit-* pinning headers, same-origin dependency fetch for the harvester, response classification per PRD 0010 §8.3, and the RENDER_TIMEOUT_MS budget.
Package render owns the render-origin HTTP client (FR-007, EW-RENDER-001..004): version-pinned page fetch with the bearer token and all seven X-AnvilKit-* pinning headers, same-origin dependency fetch for the harvester, response classification per PRD 0010 §8.3, and the RENDER_TIMEOUT_MS budget.
storage
Package storage owns the S3-compatible artifact adapter (FR-011, EW-STORAGE-001..004: MinIO locally; S3/R2/OSS/COS in production) and the artifact-manifest builder (FR-012, EW-STORAGE-005/006).
Package storage owns the S3-compatible artifact adapter (FR-011, EW-STORAGE-001..004: MinIO locally; S3/R2/OSS/COS in production) and the artifact-manifest builder (FR-012, EW-STORAGE-005/006).
testsupport
Package testsupport provides integration-test infrastructure.
Package testsupport provides integration-test infrastructure.
worker
Package worker wires the job pipeline (PRD 0010 §5.1): consume → validate → load record → reconcile → lock → CAS EXPORT_QUEUED→EXPORTING → Exporter seam (render/harvest/upload/manifest/emit live in internal/export).
Package worker wires the job pipeline (PRD 0010 §5.1): consume → validate → load record → reconcile → lock → CAS EXPORT_QUEUED→EXPORTING → Exporter seam (render/harvest/upload/manifest/emit live in internal/export).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL