Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
55554f1
Add isolated commerce bridge with verified sandbox payment delivery
techize Sep 7, 2026
32393f6
Enrich missing manufacturing labels from validated variants
techize Sep 7, 2026
8c15243
Add isolated atomic stock reservation service and live contention tests
techize Sep 7, 2026
f9115e4
Consume reserved stock with verified payments and completed refunds
techize Sep 7, 2026
a1852a6
Add isolated Batchivo dispatch and delivery event workflow
techize Sep 7, 2026
97d14c9
Reconcile verified refunds independently of manufacturing state
techize Sep 7, 2026
5238bf8
Fence native order effects and share durable commerce dispatch
techize Sep 8, 2026
e5a422f
Expose authenticated native fulfilment commands and verify browser or…
techize Sep 8, 2026
5aad3f0
Add guarded commerce runtime profiles and additive ledger migration
techize Sep 8, 2026
25ae017
Sync commerce runtime issue export
techize Sep 8, 2026
1e0b18b
Fence legacy Square callbacks from WooCommerce-owned order state
techize Sep 8, 2026
6a8a2d6
Guard commerce manufacturing against refunded or unverified payments
techize Sep 9, 2026
547a6f9
Record manufacturing acceptance and remaining delivery gates
techize Sep 9, 2026
7cfaa50
Isolate commerce database privileges and scope storefront order lookup
techize Sep 9, 2026
438fbae
Add audited physical returns and idempotent inspected stock recovery
techize Sep 9, 2026
c347a70
Allow verified physical returns before the financial refund
techize Sep 9, 2026
0d6938f
Allow delivery facts after refund without reopening financial state
techize Sep 9, 2026
48c4c49
Preserve tenant RLS context in delivery rollback acceptance
techize Sep 9, 2026
24b0d5d
Add audited partial-refund quantity disposition and release guards
techize Sep 9, 2026
52c4bfc
Verify partial refund quantities, manufacturing release and replay sa…
techize Sep 9, 2026
35d5178
Show a fresh review hold when another partial refund arrives
techize Sep 9, 2026
8ca5ba2
Deploy reviewed commerce-aware native image with zero-unavailable rol…
techize Sep 9, 2026
83f7624
Keep production configuration release scoped to deployment manifest
techize Sep 9, 2026
22b71b7
Drain legacy shop writers safely and preserve retryable Square callbacks
techize Sep 9, 2026
4d63f8f
Prepare audited legacy write handover without changing storefront own…
techize Sep 9, 2026
2c58823
Merge commit '22b71b72e664bb2eda6bb0e01f99e8b406219854' into codex/co…
techize Sep 11, 2026
b059e02
ci: validate pull requests and separate image builds from promotion
techize Sep 11, 2026
f31d358
ci: provide PostgreSQL for unit fixtures and pin available Node image
techize Sep 11, 2026
460569d
fix: repair release validation, refresh dependencies and frontend typ…
techize Sep 11, 2026
3e4bc0e
ci: initialize route fixtures and avoid scratch database fsync overhead
techize Sep 11, 2026
de2b02a
docs: record release validation scope and CI log recovery
techize Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .beads/interactions.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
{"id":"int-3d831c89","kind":"field_change","created_at":"2026-05-01T14:43:10.775569Z","actor":"Jonathan Gill","issue_id":"batchivo-sc7p","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
{"id":"int-790f7156","kind":"field_change","created_at":"2026-05-01T14:57:59.843399Z","actor":"Jonathan Gill","issue_id":"batchivo-sc7p","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented manual admin order creation: backend POST /api/v1/orders requires admin auth and tenant-scoped product/channel validation; frontend /orders/new creates manual orders with recorded payment status/reference and navigates to detail; Playwright covers creation and existing order lifecycle."}}
{"id":"int-b5c1d92a","kind":"field_change","created_at":"2026-05-05T08:29:14.461784Z","actor":"Jonathan Gill","issue_id":"batchivo-qe8p","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed: Gmail send-as for hello@mystmereforge.co.uk configured via Resend SMTP and verified by received Gmail alias test."}}
{"id":"int-46bb38a0","kind":"field_change","created_at":"2026-09-07T11:35:32.215146Z","actor":"Jonathan Gill","issue_id":"batchivo-z85l","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
1 change: 1 addition & 0 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
{"id":"nozzly.app-2wp","title":"CRITICAL: Missing tenant_id in product_categories association table","description":"File: backend/app/models/category.py (lines 17-22)\n\nThe many-to-many association table product_categories has NO tenant_id. This is a severe multi-tenant security issue.\n\nImpact: A tenant's categories could be linked to another tenant's products via direct database queries.\n\nFix: Add tenant_id column and composite primary key.","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-31T17:32:35Z","created_by":"jonathan","updated_at":"2026-01-01T17:02:59Z","closed_at":"2026-01-01T17:02:59Z","close_reason":"Added tenant_id to both tables with migration and test fixes","dependency_count":0,"dependent_count":0,"comment_count":0}
{"id":"batchivo-im3","title":"CRITICAL: Hardcoded database password in K8s manifests","description":"File: infrastructure/k8s/postgres/statefulset.yaml (line 9) and backend ConfigMap (line 7)\n\nIssue: Postgres uses hardcoded password 'changeme-in-production' in plaintext ConfigMap.\nDATABASE_URL with password visible in repo: postgresql+psycopg://nozzly:changeme-in-production@postgres:5432/nozzly\n\nFix: Move to Kubernetes Secret, rotate immediately.","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-31T17:32:34Z","created_by":"jonathan","updated_at":"2025-12-31T17:52:12Z","closed_at":"2025-12-31T17:52:12Z","close_reason":"Removed hardcoded password from repo, now uses kubectl secret creation","dependency_count":0,"dependent_count":0,"comment_count":0}
{"id":"nozzly.app-im3","title":"CRITICAL: Hardcoded database password in K8s manifests","description":"File: infrastructure/k8s/postgres/statefulset.yaml (line 9) and backend ConfigMap (line 7)\n\nIssue: Postgres uses hardcoded password 'changeme-in-production' in plaintext ConfigMap.\nDATABASE_URL with password visible in repo: postgresql+psycopg://nozzly:changeme-in-production@postgres:5432/nozzly\n\nFix: Move to Kubernetes Secret, rotate immediately.","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-31T17:32:34Z","created_by":"jonathan","updated_at":"2025-12-31T17:52:12Z","closed_at":"2025-12-31T17:52:12Z","close_reason":"Removed hardcoded password from repo, now uses kubectl secret creation","dependency_count":0,"dependent_count":0,"comment_count":0}
{"id":"batchivo-z85l","title":"Finish production-capable commerce adapter and lifecycle acceptance","description":"Current app/commerce_bridge.py is an isolated acceptance bridge only. Complete reviewed production adapter, migrations, stock reservations before payment, partial refunds and fulfilment callbacks, replay/recovery and no duplicate manufacturing. Never remove isolation guard as a shortcut. Coordinate with shop commerce/docs/DELIVERY.md.","notes":"2026-09-09: Added operator-only audited ownership fence with session advisory locks spanning full legacy requests, preserving callbacks and read routes. Disposable actual PostgreSQL plus native ASGI route acceptance passes 34 checks including callback DB outage recovery. Checkout/webhook/order-lookup regressions 105 passed; prior broader run 142 passed with one stale unsigned-event expectation now corrected. New migration and guards not yet deployed; production ownership remains legacy. Build/canary and combined promotion rehearsal remain.","status":"in_progress","priority":1,"issue_type":"task","owner":"jonathan@techize.co.uk","created_at":"2026-09-07T11:26:10Z","created_by":"Jonathan Gill","updated_at":"2026-09-09T16:38:56Z","started_at":"2026-09-07T11:35:32Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"id":"batchivo-gtx9","title":"Audit API authentication and tenant authorization coverage","description":"Review existing API routes for public exposure, authentication requirements, role requirements, tenant isolation checks, and webhook/payment-specific signature or idempotency protections. User explicitly flagged that both new and existing APIs must be considered for auth/security exposure while adding admin order creation.","status":"open","priority":1,"issue_type":"task","owner":"jonathan@techize.co.uk","created_at":"2026-05-01T14:57:54Z","created_by":"Jonathan Gill","updated_at":"2026-05-01T14:57:54Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"id":"batchivo-7vgj","title":"Validate spool material type foreign keys","description":"Post/put spool API accepts a nonexistent material_type_id, causing backend regression tests to fail after pulling latest main. Return 400 before writing when the supplied material type is not found for the current tenant.","acceptance_criteria":"tests/api/test_spools.py::TestFKConstraintErrors passes and full backend pytest passes.","status":"closed","priority":1,"issue_type":"bug","owner":"jonathan@techize.co.uk","created_at":"2026-05-01T12:45:32Z","created_by":"Jonathan Gill","updated_at":"2026-05-01T12:52:12Z","started_at":"2026-05-01T12:45:37Z","closed_at":"2026-05-01T12:52:12Z","close_reason":"Added explicit material_type_id existence validation for spool create/update; targeted FK regression tests and full backend pytest pass.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"id":"batchivo-3c5h","title":"Patch frontend production dependency vulnerabilities","description":"npm audit --omit=dev reports 6 production dependency vulnerabilities: protobufjs critical, axios high, picomatch high, dompurify moderate, follow-redirects moderate, postcss moderate. Update lockfile/dependencies safely and verify frontend gates.","acceptance_criteria":"npm audit --omit=dev has no high/critical production vulnerabilities; frontend typecheck/lint/test/build pass.","status":"closed","priority":1,"issue_type":"bug","owner":"jonathan@techize.co.uk","created_at":"2026-05-01T12:22:07Z","created_by":"Jonathan Gill","updated_at":"2026-05-01T12:35:29Z","started_at":"2026-05-01T12:34:28Z","closed_at":"2026-05-01T12:35:29Z","close_reason":"Applied npm audit fixes; npm audit --omit=dev now reports 0 vulnerabilities and frontend typecheck/lint/test/build pass.","dependency_count":0,"dependent_count":0,"comment_count":0}
Expand Down
126 changes: 24 additions & 102 deletions .woodpecker/build.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
# Woodpecker CI — Build + Deploy Pipeline
#
# Purpose: Get code live as fast as possible.
# This pipeline does ONE thing: build images and update manifests.
# Tests, dep scans, and image scans all run concurrently in test.yml.
#
# Architecture:
# build-backend ──┐
# build-frontend ──┴──> deploy-staging ──> deploy-production
#
# Expected time: ~4-7 min on warm Kaniko cache (code-only change)
# ~8-12 min on cold cache (dep change)
#
# Triggered by: push to main (excluding k8s-only changes — ArgoCD handles those directly)
# Owner-triggered image publishing, after all validation succeeds.
# Promotion uses a separate manifest PR; this workflow never edits live manifests.

when:
- event: push
- event: deployment
branch: main
path:
exclude:
- 'infrastructure/k8s/**'
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "build-release"'

depends_on:
- test-fast
- test-integration

steps:
# ============================================
Expand Down Expand Up @@ -70,92 +60,24 @@ steps:
--export-cache type=registry,ref=registry.techize.co.uk/batchivo/batchivo-frontend-cache,mode=max \
--output type=image,name=registry.techize.co.uk/batchivo/batchivo-frontend:${CI_COMMIT_SHA},push=true

# ============================================
# Deploy to Staging
# Updates k8s manifest → ArgoCD auto-syncs the rest
# ============================================
deploy-staging:
image: registry.techize.co.uk/dockerhub-cache/alpine/git:latest
depends_on:
- build-backend
- build-frontend
scan-image-backend:
image: registry.techize.co.uk/dockerhub-cache/aquasec/trivy:0.55.0
depends_on: [build-backend]
environment:
GIT_AUTHOR_NAME: woodpecker-ci[bot]
GIT_AUTHOR_EMAIL: woodpecker-ci[bot]@batchivo
GIT_COMMITTER_NAME: woodpecker-ci[bot]
GIT_COMMITTER_EMAIL: woodpecker-ci[bot]@batchivo
GITHUB_TOKEN:
from_secret: github_token
TRIVY_USERNAME:
from_secret: docker_username
TRIVY_PASSWORD:
from_secret: docker_password
commands:
- apk add --no-cache sed curl
- cd /woodpecker/src/github.com/techize/batchivo
- echo "=== Deploying to STAGING ==="
- 'sed -i "s|image: registry.techize.co.uk/batchivo/batchivo-backend:.*|image: registry.techize.co.uk/batchivo/batchivo-backend:$CI_COMMIT_SHA|g" infrastructure/k8s/staging/deployment.yaml'
- git config --global --add safe.directory /woodpecker/src/github.com/techize/batchivo
- git remote set-url origin https://x-access-token:$GITHUB_TOKEN@github.com/techize/batchivo.git
- git add infrastructure/k8s/staging/deployment.yaml
- |
for i in 1 2 3; do
git pull --rebase origin main || true
git diff --staged --quiet && echo "No staging changes to commit" && exit 0
if git commit -m "cd(staging): update backend image to $CI_COMMIT_SHA" && git push origin main; then
echo "Successfully pushed staging manifest update"
exit 0
fi
echo "Attempt $i failed, retrying in 5s..."
git reset HEAD~1 2>/dev/null || true
sleep 5
done
echo "Failed after 3 attempts"
exit 1
- trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.techize.co.uk/batchivo/batchivo-backend:${CI_COMMIT_SHA}

# ============================================
# Deploy to Production
# Updates both backend + frontend manifests → ArgoCD syncs
# ============================================
deploy-production:
image: registry.techize.co.uk/dockerhub-cache/alpine/git:latest
depends_on:
- deploy-staging
scan-image-frontend:
image: registry.techize.co.uk/dockerhub-cache/aquasec/trivy:0.55.0
depends_on: [build-frontend]
environment:
GIT_AUTHOR_NAME: woodpecker-ci[bot]
GIT_AUTHOR_EMAIL: woodpecker-ci[bot]@batchivo
GIT_COMMITTER_NAME: woodpecker-ci[bot]
GIT_COMMITTER_EMAIL: woodpecker-ci[bot]@batchivo
GITHUB_TOKEN:
from_secret: github_token
TRIVY_USERNAME:
from_secret: docker_username
TRIVY_PASSWORD:
from_secret: docker_password
commands:
- apk add --no-cache sed
- cd /woodpecker/src/github.com/techize/batchivo
- echo "=== Deploying to PRODUCTION ==="
- BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- 'sed -i "s|image: registry.techize.co.uk/batchivo/batchivo-backend:.*|image: registry.techize.co.uk/batchivo/batchivo-backend:$CI_COMMIT_SHA|g" infrastructure/k8s/backend/deployment.yaml'
- 'sed -i "s|image: registry.techize.co.uk/batchivo/batchivo-frontend:.*|image: registry.techize.co.uk/batchivo/batchivo-frontend:$CI_COMMIT_SHA|g" infrastructure/k8s/frontend/deployment.yaml'
- 'sed -i "/name: BUILD_SHA/{n;s|value:.*|value: \"$CI_COMMIT_SHA\"|}" infrastructure/k8s/backend/deployment.yaml'
- 'sed -i "/name: BUILD_DATE/{n;s|value:.*|value: \"$BUILD_DATE\"|}" infrastructure/k8s/backend/deployment.yaml'
- 'sed -i "/name: VITE_BUILD_SHA/{n;s|value:.*|value: \"$CI_COMMIT_SHA\"|}" infrastructure/k8s/frontend/deployment.yaml'
- 'sed -i "/name: VITE_BUILD_DATE/{n;s|value:.*|value: \"$BUILD_DATE\"|}" infrastructure/k8s/frontend/deployment.yaml'
- git config --global --add safe.directory /woodpecker/src/github.com/techize/batchivo
- git remote set-url origin https://x-access-token:$GITHUB_TOKEN@github.com/techize/batchivo.git
- git add infrastructure/k8s/backend/deployment.yaml infrastructure/k8s/frontend/deployment.yaml
- |
for i in 1 2 3; do
git pull --rebase origin main || true
git diff --staged --quiet && echo "No production changes to commit" && exit 0
if git commit -m "cd(prod): update image tags to $CI_COMMIT_SHA" && git push origin main; then
echo "Successfully pushed production manifest update"
exit 0
fi
echo "Attempt $i failed, retrying in 5s..."
git reset HEAD~1 2>/dev/null || true
sleep 5
done
echo "Failed after 3 attempts"
exit 1

# Notes:
# - Staging health check removed — staging.batchivo.com DNS not yet configured, was wasting 60s per run.
# Re-add once staging is live: poll https://staging.batchivo.com/health between the two deploy steps.
# - Image security scans (Trivy) run in test.yml after tests complete — not a deploy gate here.
# - volumes disabled 2026-03-17 — Woodpecker agent next-bef0abaa4a DNS name bug
# - 2026-03-22 — if pipelines fail with "cannot ack workflow", restart woodpecker-server pod (SQLite queue corruption after 5+ days uptime)
- trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.techize.co.uk/batchivo/batchivo-frontend:${CI_COMMIT_SHA}
43 changes: 24 additions & 19 deletions .woodpecker/test-fast.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
# Woodpecker CI — Fast Test Pipeline (no database)
#
# Purpose: Fast signal on correctness — lint, unit tests, frontend checks.
# No postgres service needed. Completes in ~3-5 min.
#
# Architecture:
# scan-deps-backend ──> backend-unit ────┐
# scan-deps-frontend ──> frontend ────────┘
#
# Triggered by: same conditions as build.yml
# PR and main validation. No publishing, deployment credentials or production services.

when:
- event: pull_request
branch: main
- event: push
branch: main
path:
exclude:
- 'infrastructure/k8s/**'
- event: deployment
branch: main
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "build-release"'

variables:
- &ci_python_image registry.techize.co.uk/batchivo/ci-python:3.12
- &node_image registry.techize.co.uk/dockerhub-cache/node:20-slim
- &node_image registry.techize.co.uk/dockerhub-cache/node@sha256:ab07539e0988b63558ff621f5fbe1077054c39d9809112974fb79993949d41cd
- &trivy_image registry.techize.co.uk/dockerhub-cache/aquasec/trivy:0.55.0

services:
postgres:
image: registry.techize.co.uk/dockerhub-cache/postgres@sha256:20edbde7749f822887a1a022ad526fde0a47d6b2be9a8364433605cf65099416
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test_batchivo

steps:
scan-deps-backend:
image: *trivy_image
Expand All @@ -36,13 +37,14 @@ steps:

# ============================================
# Backend: Lint + Unit Tests (~3-5 min)
# Pure logic tests — no database required.
# Includes database-backed unit fixtures in an ephemeral PostgreSQL service.
# ============================================
backend-unit:
image: *ci_python_image
depends_on:
- scan-deps-backend
depends_on: []
environment:
PGPASSWORD: test
DATABASE_URL: postgresql+psycopg://test:test@postgres:5432/test_batchivo
SECRET_KEY: test-secret-key-for-ci-testing-minimum-32-chars
commands:
- cd backend
Expand All @@ -52,18 +54,21 @@ steps:
- poetry run ruff check .
- poetry run ruff format --check .
- echo "=== Unit tests ==="
- timeout 60 sh -c 'until pg_isready -h postgres -U test -q; do sleep 1; done'
# Scratch databases only: avoid synchronous disk flushes for each test DDL.
- psql -h postgres -U test -d test_batchivo -c "ALTER SYSTEM SET fsync = 'off'" -c "ALTER SYSTEM SET synchronous_commit = 'off'" -c "SELECT pg_reload_conf()"
- poetry run pytest tests/unit/ --tb=short -q

# ============================================
# Frontend: Lint + Type Check + Unit Tests (~3-5 min)
# ============================================
frontend:
image: *node_image
depends_on:
- scan-deps-frontend
depends_on: []
commands:
- cd frontend
- npm ci --silent
- npm run lint
- npm run typecheck
- npm run test:ci
- npm run build:check
Loading