diff --git a/docs/reference/dev-runtime-boundary.md b/docs/reference/dev-runtime-boundary.md index 5953e40a..054c98b9 100644 --- a/docs/reference/dev-runtime-boundary.md +++ b/docs/reference/dev-runtime-boundary.md @@ -127,6 +127,40 @@ contracts, docs, and tests so future rollouts reproduce the same env and safety boundaries. The runner must not re-execute the live hotfix, restart services, read Secrets, mutate PROD, or claim live evidence from source-only changes. +## Durable Runtime Readiness Contract + +DB live readiness and durable runtime readiness are separate gates. A +`/health/live` payload with `db.connected=true` and `db.liveDbEvidence=true` +only proves that the Cloud API reached the DEV DB endpoint without exposing +secret values. It does not prove that runtime writes are persisted through a +durable store. + +When health reports: + +- `runtime.adapter: "memory"` +- `runtime.durable: false` +- `runtime.status: "degraded"` + +the Cloud API is using the process-local runtime store in +`internal/db/runtime-store.mjs`. Gateway sessions, box resources, operations, +audit events, and evidence records accepted through the runtime can be lost on +pod restart, redeploy, or scale-out, and cannot be treated as durable M3/M4/M5 +evidence. Users may see accepted operations or evidence disappear after a +runtime replacement, and agent-loop/MVP acceptance must remain degraded or +blocked even if the DB connection layer is green. + +Readiness reports must keep these dimensions distinct: + +| Dimension | Green condition | Degraded/blocking condition | +| --- | --- | --- | +| DB live | `db.ready=true`, `db.connected=true`, `db.liveDbEvidence=true` | Missing env, failed connection, disabled probe, or no `liveDbEvidence` | +| Runtime durability | `runtime.durable=true` with a durable adapter | `runtime.adapter="memory"` or `runtime.durable=false` | + +Do not declare DEV-LIVE complete while `runtime.adapter="memory"` or +`runtime.durable=false`, even when DB live readiness is connected. The next +fix boundary is a durable runtime store and migration path; documenting or +testing this contract must not mutate runtime state or PROD. + ## Runtime Substitution Ban UniDesk services, provider-gateway, backend-core, microservice proxies, local diff --git a/internal/cloud/server.test.mjs b/internal/cloud/server.test.mjs index 5b009757..34aa98ad 100644 --- a/internal/cloud/server.test.mjs +++ b/internal/cloud/server.test.mjs @@ -119,6 +119,10 @@ test("cloud api health reports DB env presence and live connection classificatio assert.equal(payload.db.evidence, "live_db_tcp_connection_ready"); assert.equal(payload.db.safety.liveDbEvidence, true); assert.equal(payload.db.safety.liveDbConnectedEvidence, true); + assert.equal(payload.runtime.adapter, "memory"); + assert.equal(payload.runtime.durable, false); + assert.equal(payload.runtime.status, "degraded"); + assert.match(payload.runtime.reason, /process-local/u); assert.equal(payload.db.redaction.valuesRedacted, true); assert.equal(payload.db.redaction.endpointRedacted, true); assert.equal(payload.db.redaction.secretMaterialRead, false); diff --git a/scripts/cloud-api-runtime-smoke.mjs b/scripts/cloud-api-runtime-smoke.mjs index 2c2c3091..855eb1f0 100644 --- a/scripts/cloud-api-runtime-smoke.mjs +++ b/scripts/cloud-api-runtime-smoke.mjs @@ -92,8 +92,10 @@ async function run() { assert.equal(health.body.db.connectionResult, "not_attempted_missing_env"); assert.equal(health.body.db.ready, false); assert.deepEqual(health.body.db.missingEnv, ["HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE"]); + assert.equal(health.body.runtime.adapter, "memory"); assert.equal(health.body.runtime.durable, false); assert.equal(health.body.runtime.status, "degraded"); + assert.match(health.body.runtime.reason, /process-local/u); logOk("health degraded DB contract"); const v1 = await requestJson(`${baseUrl}/v1`);