docs(runbook §6): renumber to 6.1 → 6.2 → 6.3 → 6.4 (REQ-INB-36, v1.1 P1-1 closure)

The runbook §6 section ordering was 6.1 → 6.4 → 6.2 → 6.3
because the v1.1 EXECUTE stage inserted §6.4 "Secret
rotation" (REQ-INB-21, commit 6067008) between §6.1
"Secret inventory" and §6.2 "Why we never commit secrets".
The non-monotonic ordering was filed as v1.1 backlog P1-1
(CI-LOG.md:217-218) and v1.1 explicitly deferred the fix
to a follow-up milestone. v1.2 was a 0/1 disposition so
the carry-forward is v1.3.

This commit restores the monotonic ordering:
- §6.1 Secret inventory (unchanged)
- §6.2 Secret rotation (renumbered from §6.4)
- §6.3 Why we never commit secrets (renumbered from §6.2)
- §6.4 Data-sovereignty posture (renumbered from §6.3)

Plus 4 cross-reference updates in the runbook body:
- line 509: "see §6.4 for the procedure" → "see §6.2 for the procedure"
- line 625: "§6.4 (new in v1.1)" → "§6.2 (new in v1.1, renumbered in v1.3 from §6.4)"
- line 767: "§6.4 documents" → "§6.2 documents"
- line 790: "§5.7 + §6.4" → "§5.7 + §6.2"

Verified: grep -n '^### 6\.' docs/operations-runbook.md
returns 4 sections in 6.1 → 6.2 → 6.3 → 6.4 order.
grep -n '§6\.' docs/operations-runbook.md returns no
stale §6.4 cross-references pointing at the old
"Secret rotation" position.

This commit closes v1.1 backlog item P1-1 (runbook §6
ordering, per CI-LOG.md:217-218).

---ci---
project: inboxos
phase: 1
milestone: v1.3
status: execute
tasks_completed: [T2.5]
candidates_closed: [REQ-INB-36]
backlog_closures:
  v1_1_P1_1: REQ-INB-36
files_touched:
  - docs/operations-runbook.md (4 heading renumbers + 4 cross-ref updates, lines 509, 511, 561, 568, 625, 767, 790)
---/ci---
This commit is contained in:
2026-06-08 23:27:53 +00:00
parent 3746f03c58
commit fc2ae831bc
+7 -7
View File
@@ -506,9 +506,9 @@ All Gitea repo secrets live at **Gitea → repo → Settings → Actions → Sec
That's 22 distinct secret values (the v1.0 ship counted 21 because the `GITEA_USER` / `GITEA_PASS` row was treated as one credential; v1.1 counts both values to match the row count in the table above). The issue description rounds to 15 because the Papra trio is optional; the table is exhaustive.
**Rotation schedule:** **None — all static.** This is an explicit, accepted trade-off for a single-operator, self-hosted system. The S3 access keys and NocoDB API token are long-lived. The Gmail App Password can be revoked from the Google account at any time without breaking anything else; the IMAP user must re-issue a new App Password and update the Gitea secret. The helper landed in v1.1 (REQ-INB-21); see §6.4 for the procedure.
**Rotation schedule:** **None — all static.** This is an explicit, accepted trade-off for a single-operator, self-hosted system. The S3 access keys and NocoDB API token are long-lived. The Gmail App Password can be revoked from the Google account at any time without breaking anything else; the IMAP user must re-issue a new App Password and update the Gitea secret. The helper landed in v1.1 (REQ-INB-21); see §6.2 for the procedure.
### 6.4 Secret rotation (REQ-INB-21)
### 6.2 Secret rotation (REQ-INB-21)
The `scripts/rotate_secrets.sh` helper rotates a Gitea repo secret in
place via `pipelines.common.gitea_request`. The new value is read
@@ -558,14 +558,14 @@ and 3 on Gitea API failure. See
`pipelines/_tests/test_rotate_secrets_offline.py` for the test
contract and the helper's exit-code table in its header comment.
### 6.2 Why we never commit secrets
### 6.3 Why we never commit secrets
- `.gitignore` excludes `.env` and `.env.*` except `.env.example`. The example file contains placeholder values only.
- The CTO host deliberately does **not** have write-capable S3 credentials (only the read-only S3 keys used for verification). A leaked CTO host key cannot corrupt the `.eml` archive.
- The Gitea service account used for the REST API (`GITEA_USER=cto` in `.env.example`) is scoped to the `jc-capital/inboxos` repo. It can read/write workflow secrets, but it cannot create new repos or admin the Gitea instance.
- All secret reads happen via `${{ secrets.X }}` in the workflow YAMLs; the values are injected as environment variables into the ephemeral runner container and never appear in the build log.
### 6.3 Data-sovereignty posture
### 6.4 Data-sovereignty posture
- **No external API calls for PII-bearing data.** The only outbound network calls from the runner are to the in-network services (`http://gitea:3000`, `http://nocodb:8080`, `http://seaweedfs-filer:8888`, `http://ollama:11434`, `http://papra:1221`). The Ollama URL is FRP-tunneled to the local Ollama container — the FRP tunnel terminates inside the operator's network; no third-party LLM provider sees the request body.
- **Local LLM inference.** Default model is `gemma4:e4b` (4 B effective parameters). All `inboxos:classify` and `inboxos:extract-*` calls hit the local Ollama. There is no fallback to a cloud LLM. If Ollama is down, the pipeline dead-letters and the next run retries.
@@ -622,7 +622,7 @@ contract and the helper's exit-code table in its header comment.
- **Pipelines:** `pipelines/README.md` — pipeline flow diagrams, idempotency model, rollback procedure.
- **Papra write policy:** `docs/papra-write-policy.md` — when the pipeline writes to Papra (attachments, body for crm/finance/legal modules), the dry-run default, the live-write gate (REQ-INB-20), and the `papra_index` audit table (REQ-INB-19).
- **Phase 1.1 verification:** `docs/phase-1.1-verification/inb-28-report.md` — the schema-bootstrap deliverable that locked in the four table IDs and column counts cited in §4.3.
- **Secret rotation:** `docs/operations-runbook.md §6.4` (new in v1.1) — the rotation helper, the ordering, and the test-or-die gate (REQ-INB-21).
- **Secret rotation:** `docs/operations-runbook.md §6.2` (new in v1.1, renumbered in v1.3 from §6.4) — the rotation helper, the ordering, and the test-or-die gate (REQ-INB-21).
- **Metrics surface:** `docs/operations-runbook.md §4.5` (new in v1.1) — the per-run `metrics.jsonl` shape and the tail-and-summarize procedure (REQ-INB-23).
- **Backup and restore:** `docs/operations-runbook.md §5.7 + §10` (new in v1.1) — the `backup.sh` / `restore.sh` helpers, the daily cron, and the RTO < 4h / RPO < 24h targets (REQ-INB-22).
@@ -764,7 +764,7 @@ gets a review pass on this section.
| 4 | `inb-28-report.md` "Per-table row counts at freeze" is filled with one row per module table | `docs/phase-1.1-verification/inb-28-report.md:67-77` | data-engineer | REQ-INB-18 |
| 5 | `papra_index` NocoDB table exists with the 7 documented columns; `docs/schemas.md` and `scripts/apply_schemas.py` reflect the table; the v1.0 schema freeze is preserved for the 4 module tables | `docs/schemas.md` (new `papra_index` section, T1.4a), `scripts/apply_schemas.py` (new `AUDIT_TABLES` entry, T1.4b), `docs/phase-1.1-verification/inb-28-report.md` (the freeze amendment, T1.4a) | data-engineer | REQ-INB-19 |
| 6 | `PAPRA_DRY_RUN_OVERRIDE` env var exists; the new `papra_write_gate` event is emitted by both pipelines (via the T1.5 helper + T2.2 call-sites); the canary procedure is in `docs/papra-write-policy.md`; new test cases exercise the override and the helper | `pipelines/common.py` `PapraConfig` + `emit_papra_write_gate_event` (T1.5), `pipelines/live_pipeline.py` + `pipelines/historical_pipeline.py` (T2.2 call-sites), `pipelines/_tests/test_papra_decide_offline.py` (T1.5 + T2.2 cases), `docs/papra-write-policy.md` "Live-write gate" (T1.5) | security-engineer | REQ-INB-20 |
| 7 | `scripts/rotate_secrets.sh` exists with `--dry-run` and real-run modes; `pipelines/_tests/test_rotate_secrets_offline.py` passes; `docs/operations-runbook.md §6.4` documents the rotation ordering | `scripts/rotate_secrets.sh` (new), `pipelines/_tests/test_rotate_secrets_offline.py` (new, 3/3 tests pass), `docs/operations-runbook.md §6.4` (new) | security-engineer | REQ-INB-21 |
| 7 | `scripts/rotate_secrets.sh` exists with `--dry-run` and real-run modes; `pipelines/_tests/test_rotate_secrets_offline.py` passes; `docs/operations-runbook.md §6.2` documents the rotation ordering | `scripts/rotate_secrets.sh` (new), `pipelines/_tests/test_rotate_secrets_offline.py` (new, 3/3 tests pass), `docs/operations-runbook.md §6.2` (new) | security-engineer | REQ-INB-21 |
| 8 | `scripts/backup.sh` and `scripts/restore.sh` exist; `pipelines/_tests/test_restore_offline.py` round-trips a known fixture; `docs/operations-runbook.md §5.7 + §10` state RTO < 4h and RPO < 24h | `scripts/backup.sh` (new), `scripts/restore.sh` (new), `pipelines/_tests/test_restore_offline.py` (new, 7/7 tests pass), `docs/operations-runbook.md §5.7 + §10` (new) | devops-engineer | REQ-INB-22 |
| 9 | `pipelines/_metrics/emit.py` writes one JSONL line per run with the documented counter and gauge vocabulary; `docs/operations-runbook.md §4.5` documents the tail-and-summarize procedure; a new test exercises the emitter | `pipelines/_metrics/emit.py` (new), `pipelines/live_pipeline.py` + `pipelines/historical_pipeline.py` (T2.2 call-sites), `pipelines/_tests/test_metrics_offline.py` (new, 2/2 tests pass), `docs/operations-runbook.md §4.5` (new) | data-engineer | REQ-INB-23 |
| 10 | `plans/05-PLAN.md` parallelization table has the explicit three-file split footnote (P1-1 closure); runbook §9.2 row 19 has the backend-engineer persona annotation (P2-4 closure); CI-LOG P1-1 + P2-4 entries are marked closed | `.ciagent/inboxos/plans/05-PLAN.md:318-330` (footnote), `docs/operations-runbook.md:674` (row 19 owner annotation), `.ciagent/inboxos/CI-LOG.md:96-97,112-113,115` (closed_in pointers) | lead-developer | REQ-INB-24 |
@@ -787,7 +787,7 @@ section is the **commitment**.
- **RTO (Recovery Time Objective):** **< 4 hours**, single-
operator restore. The breakdown:
- 30 min — re-read §5.7 + §6.4 and decide which services
- 30 min — re-read §5.7 + §6.2 and decide which services
to restore (typically: all three, in order: Gitea first
so the workflows and secrets are back, then NocoDB so
the module-table IDs are available, then SeaweedFS).