KB-5895

05-c1-endpoint-no-mutation-deploy-path-proof-2026-06-22.md

5 min read Revision 1
c1-legoprewrite-gate

05 — G3b: C1 no-mutation endpoint — actual source located, minimal additive patch

Biggest hardening win: the executor source was "not in this checkout" in the prior package. This turn I located and read it live (read-only) inside the running container, so the C1 endpoint is now a precise minimal patch against real code, not a hand-wave.

1. Actual source & deploy mechanism (live, read-only)

SSOT note (see 00a): the executor source SSOT is /opt/incomex/deploy/agent-api-executor/ on the VPS — host main.py sha 09cdd867… == container /app/main.py sha 09cdd867… (identical), proving the deploy dir is authoritative. The C1 patch in this package is LOCAL_STAGING_NOT_SSOT, to be operator-applied there + rebuilt.

  • Container incomex-agent-api-executor, image agent-api-executor-local:v1, Up 2 weeks (healthy), 8090->8090.
  • Source/deploy SSOT dir: /opt/incomex/deploy/agent-api-executor/ (Dockerfile, main.py, verifier.py, llm_client.py, requirements.txt, fixtures/). Edit here → rebuild → redeploy.
  • docker inspect: WORKDIR=/app, CMD=[uvicorn main:app --host 0.0.0.0 --port 8090], no bind mounts (source baked into the image).
  • /app: main.py (167 lines), verifier.py, llm_client.py, requirements.txt, fixtures/dot_kg_explain_fixture_v1.json. Read in full via docker exec … cat (read-only).
  • Deploy path = rebuild image + redeploy container (operator/deploy-pipeline; DOT-approved deploy, not a dot-* CLI). No new container/port.

2. It is already generic & fail-closed (reused unchanged)

main.py header, verbatim: "generic: keyed by dot_code; not hardwired to dot:kg. Add a fixture + contract row to support another agent_api DOT." The full fail-closed envelope is reused unmodified:

  • _assert_runtime_dry_run_only() → 409 unless execute=false/real=false/dry_only=true (live gates already satisfy this).
  • mode==REAL_RUN403 "REAL_RUN not permitted"; mode∉{PLAN_ONLY,DRY_RUN} → 400; empty correlation_id → 400.
  • output_namespace must start DRYRUN-NS: → else 400.
  • writes_db:false on every path; /healthz reports gates + writes_db:false.

3. Minimal additive patch (design staged: patches/executor-main-py-c1.additive-design.md)

Only 3 additive changes; nothing existing is modified except one dict entry:

  1. FIXTURE_MAP += 1 line "FIXTURE:dot:c1:vocab:v1": "dot_c1_vocab_fixture_v1.json"; ship the fixture (payloads/c1_vocab_fixture_v1.json) into /app/fixtures/.
  2. _produce() C1 branch keyed on expected_producer_output.kind=="canonical_vocab_manifest"_produce_c1_vocab(): read apr_action_types(status=active) read-only, build C-sorted manifest, compute cser-v1 sha256, return {manifest, manifest_hash, operation_count:14, writes_db:false}. KG path byte-for-byte unchanged.
  3. verifier.py check_c1_vocab_output(): set-equality vs the 14 authority codes + count==14 + hash present + writes_db is False. Pure (no DB/network/writes).

4. Contract surface (per macro §3.4)

  • route name: reuse POST /dispatch (distinguished by dot_code=DOT_C1_VOCAB_BUILD + fixture_ref=FIXTURE:dot:c1:vocab:v1).
  • input contract: existing DispatchRequest{dot_code, fixture_ref, correlation_id, mode}.
  • output contract (DRY_RUN): {produced:{manifest, manifest_hash, operation_count:14, writes_db:false}, verifier:{pass}, writes_db:false}.
  • REAL_RUN rejection: inherited 403 (unchanged).
  • DRY_RUN readiness: PLAN_ONLY returns {validated:true, writes_db:false} with only change #1 (fixture present) — producer not invoked; full manifest needs #2/#3.
  • health check: GET /healthz (existing) used as post-deploy gate.

5. Rollback / revert (per macro §3.4)

Additive only → rollback = redeploy agent-api-executor-local:v1 (KG path unchanged ⇒ clean revert). Post-deploy regression check: re-run a KG PLAN_ONLY dispatch, assert identical to pre-deploy.

6. STOP conditions

  • C1 producer able to open a writable transaction in DRY_RUN → STOP (must be read-only role).
  • KG dispatch output changes post-patch → STOP (non-additive regression).
  • MOCK_PRODUCER=true on a bound endpoint → STOP.

7. Verdict

G3b = path PROVEN. Real source located; minimal no-mutation handler designed against it; route/input/output/REAL_RUN-refusal/DRY_RUN-readiness/health/rollback all specified. No broad rewrite, no mega endpoint, C1-only. Deploy remains operator/deploy-scope (the one irreducible non-CLI step) — disclosed, not hidden.

Back to Knowledge Hub knowledge/dev/laws-new/reports/c1-lego-dryrun-plan-hardening-no-prod-write/05-c1-endpoint-no-mutation-deploy-path-proof-2026-06-22.md