OGV-2C R2 — Write Gate + Test Teardown — Implementation Report (2026-05-07)
OGV-2C R2 — Write Gate + Test Teardown — Implementation Report
Date: 2026-05-07
Commit: a40b217 on /opt/incomex/docker/agent-data-repo (main)
Status: Deployed, verified against production API.
1. Discovery findings
| Item | Value |
|---|---|
| Container | incomex-agent-data |
| Framework | FastAPI |
| Write entrypoint (REST) | POST /documents → create_document (server.py:1278) |
| Write entrypoint (MCP) | upload_document tool (server.py:2678) builds DocumentCreate and calls create_document directly. Single chokepoint — gate placed once. |
| Update entrypoint | PUT /documents/{doc_id:path} (server.py:1389) — gate NOT applied (would require existing doc, doc_id is fixed; out of scope for write-gate-on-creation) |
| Payload schema | DocumentCreate{document_id: str, content: DocumentContent{mime_type, body}, metadata: DocumentMetadata, ...}. Field is document_id and content.body, NOT path/content. |
| Error helper | _error(status, code, message, **details) → FastAPI HTTPException with detail={code,message,details}. Used as-is. |
| Env config | APP_ENV=production. No prior test-mode flag → introduced new KB_TEST_MODE (default false; values 1/true/yes enable). |
| Existing tests | None inside container. |
| Top-level prefixes in production (live count) | knowledge(1801), registries(222), operations(105), reports(4 stale), tham-khao(2 stale), ''(3 stale). Allowlist = knowledge/, operations/, registries/. |
No assumption deviations triggered a STOP. Schema-field difference (document_id vs path) was adapted in code and tests per Addendum A1.
2. Code changes
File: agent_data/server.py (host: /opt/incomex/docker/agent-data-repo/, container: /app/)
- Added module-level constants
_OGV2C_VALID_PREFIXES,_OGV2C_BARE_PATH_REGEXand helper_ogv2c_validate_write(document_id, body)immediately after_error()(line 1078). - Modified
create_documentbody (server.py:1283) to invoke the gate immediately after_ensure_pg():
doc_id = payload.document_id
# OGV-2C write gate
_ogv2c_validate_write(doc_id, getattr(payload.content, "body", None))
84 lines added, 0 removed. No other functions touched. Read/delete/search/move/MCP-non-upload paths untouched.
3. Rules — implementation status
| Rule | Status | Notes |
|---|---|---|
R1 — block test/ unless KB_TEST_MODE=true |
Implemented | Default false; documented in code docstring + here. |
R2 — block inline- prefix |
Implemented | Single chokepoint catches REST + MCP. |
| R3 — top-level prefix allowlist | Implemented | Allowlist: knowledge/, operations/, registries/. reports/ deliberately not added per Addendum A4. |
| R4 — bare-path / file-URL content | Implemented | Whole-content match against ^file:///, ^/Users/[A-Za-z]…. Short-content (<500) match against `^(file:/// |
4. Test results (8 cases) — against live API
| Case | Path | HTTP | Verdict |
|---|---|---|---|
| POS-1 | knowledge/dev/test-gate-pos.md |
200 | PASS |
| POS-2 | operations/tasks/test-gate-pos.md |
200 | PASS |
| POS-3 | knowledge/test-gate-report.md (long body mentioning /tmp/foo, /Users/nmhuyen, file:///opt/data) |
200 | PASS |
| NEG-1 | test/should-block.md |
422 (R1) | PASS |
| NEG-2 | inline-should-block |
422 (R2) | PASS |
| NEG-3 | random-root-doc |
422 (R3) | PASS |
| NEG-4 | knowledge/test-gate-leak.md body=file:///Users/nmhuyen/.gemini/tmp/session.txt |
422 (R4) | PASS |
| NEG-5 | reports/drift.md |
422 (R3) | PASS |
HTTP codes captured via curl -w "%{http_code}" per Addendum A2.
5. Deploy method
scp server.py contabo:/opt/incomex/docker/agent-data-repo/agent_data/
docker cp .../server.py incomex-agent-data:/app/agent_data/server.py
docker restart incomex-agent-data
cd /opt/incomex/docker/agent-data-repo
git add agent_data/server.py # specific file, not -A
git commit -m "OGV-2C: write gate ..." # → a40b217
Container ready (/api/health returned healthy) before tests ran.
6. Post-deploy API evidence
Negative responses (excerpt, all returned detail={code, message, details} envelope):
- NEG-1:
{"code":"INVALID_ARGUMENT","message":"Path under 'test/' prefix is rejected in production. Set KB_TEST_MODE=true to allow.","details":{"document_id":"test/should-block.md","rule":"OGV-2C-R1"}} - NEG-2:
{"code":"INVALID_ARGUMENT","message":"Path with 'inline-' prefix is rejected.","details":{"document_id":"inline-should-block","rule":"OGV-2C-R2"}} - NEG-3 / NEG-5:
{"code":"INVALID_ARGUMENT","message":"Document path must start with one of: knowledge/, operations/, registries/","details":{"rule":"OGV-2C-R3"}} - NEG-4:
{"code":"INVALID_ARGUMENT","message":"Document body is a bare file path / URL, not a real document.","details":{"rule":"OGV-2C-R4"}}
Positive responses returned {"id":"…","status":"created","revision":1} with HTTP 200.
7. Rollback
Not triggered. Path documented in prompt: git revert a40b217 && docker cp + docker restart incomex-agent-data. Verified deploy is forward-compatible — no schema/DB changes, no other call sites.
8. Pre-existing KB documents
Did NOT cleanup/move/delete any pre-existing KB document in this session. The 4 stale reports/* docs and 2 tham-khao/* docs found during prefix histogram remain untouched. They will simply be blocked from future creation by R3 (gate enforces creation only; existing rows unaffected).
9. OGV-2C test docs cleanup
The 3 positive test docs (POS-1/POS-2/POS-3) were deleted via the existing DELETE /documents/{doc_id:path} endpoint:
DELETE knowledge/dev/test-gate-pos.md → 200
DELETE operations/tasks/test-gate-pos.md → 200
DELETE knowledge/test-gate-report.md → 200
GET knowledge/dev/test-gate-pos.md → 404
GET operations/tasks/test-gate-pos.md → 404
GET knowledge/test-gate-report.md → 404
Confirmed cleaned up. Delete API not modified.