Embedding $response as Python triple-quoted string caused json.loads()
to fail with 'Invalid control character' when Vault's JSON contained
\n sequences in the PEM public key (bash \n → Python newline → invalid
JSON control char).
Fix: write response to mktemp, pass path as argv, read with open().
Instead of skipping the frontend test when vitest is not installed,
security-gate.sh now runs `npm ci -w frontend` automatically.
Also add `cache: "npm"` to security-gate's actions/setup-node so the
npm cache from the frontend-tests job is reused — prevents OOM on
the 1GB VPS (cache restore is disk-only, not 300MB download).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
security-gate.sh checked only `command -v node` before running
`npm test -w frontend`. In the CI security-gate job, node is in PATH
(from actions/setup-node) but root node_modules are NOT installed
(npm ci was removed to prevent OOM). This caused the frontend test to
fail with "Cannot find module vitest".
Fix: also require node_modules/.bin/vitest to exist. Without it the
step SKIPs gracefully — frontend tests are already covered by the
dedicated frontend-tests CI job which runs first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Prisma schema: sqlite → mysql provider
- Migration SQL rewritten as MySQL DDL (utf8mb4, DATETIME(3), AUTO_INCREMENT)
- Add migration_lock.toml for mysql provider
- Dockerfile.backend: drop node:22/sqlite deps, use node:20-slim
- entrypoint.sh: replace SQLite first-run logic with prisma migrate deploy + db seed
- docker-compose.prod.yml: production compose for /opt/webapps/okr on web VPS
- reads DB creds from /opt/webapps/webapp-mysql.env
- reads app secrets from /opt/webapps/okr/.env.app (written by CI)
- port 80 (frontend), no conflict with Gitea 3000/Vault 8200
- ci.yml deploy-okr: moves from ubuntu-latest (web VPS) to ci-runner (161.33.149.243)
- builds images on CI runner VPS (no heavy build on web/Gitea VPS)
- transfers images via docker save | gzip | ssh | docker load
- deploys via SSH + docker compose up on web VPS
- scripts/setup-ci-runner.sh: one-time setup script for CI runner VPS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
act-runner restart cancelled orphaned security-gate task from run #5.
Concurrency cancel-in-progress will clear run #5 and start fresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The checkout action clones from http://gitea:3000/admin/casan5 — hostname 'gitea'
only resolves on the docker-compose network (gitea_default), not on the default
Docker bridge. Changing container.network: bridge → gitea_default fixes DNS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sequential jobs (security-gate needs frontend-tests) mean only one container
runs at a time. 768MB + Gitea/Vault/OS ~300MB fits within 1GB+swap headroom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both jobs running concurrently (two 512MB containers + gitea + vault) exhausted
the 1GB RAM. Make security-gate sequential with needs: [frontend-tests].
Also fix health-check to use docker ps instead of curl localhost (DooD mode).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
act_runner automatically passes /var/run/docker.sock from its own mounts
to job containers. Explicitly adding it in container.options caused
"Duplicate mount point" error, preventing all job containers from starting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gitea Actions requires the workflow file at repo root (.gitea/workflows/ci.yml)
not in the app subdirectory. Uses defaults.run.working-directory: AINative_OKR_CASAN5
so all run steps execute in the correct app context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
phase3-final-rescore.md: independent re-score of all 7 harnesses with
per-harness evidence, verification commands, and residual gap table.
H1=85 H2=84(+2) H3=78(+2) H4=86(+1) H5=83(+1) H6=84(+2) H7=87(+3)
Average ~84, all harnesses >80 (CASAN Level 4 genuine).
Limiting factors documented: no CI gate (H3), cloud recall gap (H4),
local signing key (H5), pipeline re-run not executed (H6).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
T1 (H7): casan-step.mjs now calls rollback-manager.sh checkpoint before
overwriting plan.md at attempt-2, writes tx-id to plan.checkpoint.txid
sidecar, and executes rollback on REJECTED verdict. rollback-transactions.jsonl
records a real cp restore command. Adversarial test: checkpoint exists,
real cp command recorded, plan hash matches pre-overwrite content.
T4 (H6): casan-harness.sh exports CASAN_STEP_NAME=$ACTION_NAME before
agent-metrics.sh so nested model calls (model-call.py) and the provider-
cost-lookup.py query share the same step label. metrics.jsonl now writes
cost_source=provider_telemetry instead of word_count_estimate when a real
Ollama call is made within the same step. Adversarial test: verified with
CASAN_STEP_NAME=t4-telemetry-test end-to-end.
adversarial-harness-tests.sh: 40 → 44 PASS / 0 FAIL (+3 T1, +1 T4)
security-gate.sh: PASS=10 FAIL=0 SKIP=0 (verified, local ornith:9b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- frontend/package.json: add @testing-library/dom ^10.0.0 (missing peer
dep of @testing-library/react that caused test failure on macOS)
- docs: update security gate result to PASS=10 FAIL=0 SKIP=0 (macOS
with local ornith:9b) vs PASS=7 SKIP=1 on Windows (no Ollama)
- audit logs: real evidence from running all 10 gates (adversarial suite,
model router, red-team 30-sample, judge gate, frontend Vitest)
- remove 10 timestamp-named trace stubs (not referenced by
pipeline-context.yaml; UUID stubs in place and validated)
Verified: security-gate.sh PASS=10 FAIL=0 SKIP=0
adversarial-harness-tests.sh PASS=40 FAIL=0
npm test -w frontend: 16 PASS / 0 FAIL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>