From 34792bdb5c38dc0b79ffa45391779e7667c51948 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Wed, 15 Jul 2026 21:23:15 +0900 Subject: [PATCH] docs(release): draft M5 manual ledger rollout --- docs/releases/index.md | 1 + ...anual-ledger-api-and-metadata-migration.md | 118 ++++++++++ ...004-m5-manual-ledger-deployment-runbook.md | 210 ++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 docs/releases/rel-004_20260715_manual-ledger-api-and-metadata-migration.md create mode 100644 docs/runbooks/rel-004-m5-manual-ledger-deployment-runbook.md diff --git a/docs/releases/index.md b/docs/releases/index.md index 21fd2cd..2f040a8 100644 --- a/docs/releases/index.md +++ b/docs/releases/index.md @@ -11,3 +11,4 @@ | REL-001 | app-service | released | BAT-001~016 (전체 통합) | 2026-05-21 | [rel-001](./rel-001_20260521_v1-api-full-surface.md) | | REL-002 | app-service | feedback-captured | BAT-017 (dcy-007 M1: 거래내역+실현PnL API) | 2026-06-19 | [rel-002](./rel-002_20260619_trade-history-and-pnl-api.md) | | REL-003 | app-service | released | BAT-018~022 (dcy-008/009/010/011: WAC조회+데몬목록+daemon-scoped 인증정보+관리API) | 2026-06-30 | [rel-003](./rel-003_20260628_daemon-credential-and-admin-api.md) | +| REL-004 | app-service | draft | BAT-024 (REQ-056), BAT-025 (REQ-057): owner-scoped 수동 장부 API + portfolio_ledger metadata Alembic migration | 2026-07-15 | [rel-004](./rel-004_20260715_manual-ledger-api-and-metadata-migration.md) | diff --git a/docs/releases/rel-004_20260715_manual-ledger-api-and-metadata-migration.md b/docs/releases/rel-004_20260715_manual-ledger-api-and-metadata-migration.md new file mode 100644 index 0000000..88a4676 --- /dev/null +++ b/docs/releases/rel-004_20260715_manual-ledger-api-and-metadata-migration.md @@ -0,0 +1,118 @@ +# REL-004: owner-scoped 수동 장부 API + portfolio_ledger metadata migration (M5) + +- Status: **draft** +- Profile: app-service +- Included Batch: BAT-024 (REQ-056), BAT-025 (REQ-057) +- Release baseline: `origin/main@914b3dc6e6b9e2cbd7068fb30435c83f0581eab5` +- Deployment runbook: [REL-004 운영 배포 런북](../runbooks/rel-004-m5-manual-ledger-deployment-runbook.md) +- Release approver: Justin Ko (pending) +- Operator: trex-op (pending) + +--- + +## 1. Release intent and scope + +### Included + +- **BAT-024 / REQ-056** — owner-scoped manual ledger API: + - `POST /v1/portfolios/{portfolio_id}/ledger/manual` + - `GET /v1/portfolios/{portfolio_id}/ledger/manual` + - user/session-token owner scope, service-token fail-closed, daemon-managed confirmation, reversal deduplication, transaction/row-lock semantics. +- **BAT-025 / REQ-057** — explicit Alembic revision `20260715_0028`: + - `portfolio_ledger.source TEXT NOT NULL DEFAULT 'broker'` + - `target_ledger_entry_id BIGINT NULL` + - `confirmation_evidence TEXT NULL` + - `actor_user_id TEXT NULL` + - existing rows/data preservation and runtime-precreated schema compatibility. + +### Excluded + +- Production DB migration, service restart, API activation, and any live manual-ledger write. These require the release approval and runbook preflight evidence below. +- `QUANTITY_ADJUSTMENT`, NXT/strategy service changes, and all M5-excluded cards. +- Any `alembic downgrade` or production column deletion. Recovery is forward-only: compatible code rollback while preserving the additive schema and revision. + +--- + +## 2. Release basis + +| Basis | Evidence | Result | +| --- | --- | --- | +| REQ-056 API delivery | BAT-024 implementation `b3447f6`; developer verification `594 passed, 12 warnings`; independent QC `deleg_f2337a39` | PASS | +| Draft baseline regression re-run | 2026-07-15 clean `origin/main@914b3dc` worktree: `594 passed, 3 skipped, 12 warnings in 39.10s`; skipped tests are PostgreSQL-dependent migration integration cases | PASS | +| REQ-057 migration delivery | PR #21, merged into `main` as `914b3dc`; implementation commits `1909caa`, `d48279f`, `2f82251`, `bd51330`, `aac08eb` | PASS | +| Migration/data compatibility | isolated loopback PostgreSQL suite: `3 passed in 6.10s`; fresh, existing-row, and runtime-precreated schema cases | PASS | +| Full regression at BAT-025 verification | `597 passed, 11 warnings in 41.41s`; Ruff, compileall, diff hygiene pass | PASS | +| Independent release-chain QC | PR #21 committed-diff QC `deleg_ae0c6f27`; patch-id mapping, migration semantics, loopback PostgreSQL, Ruff, clean worktree | PASS | +| Operational database evidence | backup, locks, current revision, row counts, schema/current postcheck, service smoke | **PENDING — must be captured by trex-op** | + +The test evidence is intentionally limited to isolated/local PostgreSQL. It is not evidence that an operating database has been backed up, is lock-free, or has safely completed the upgrade. + +--- + +## 3. Mandatory release gates + +The operator must stop before code deployment or restart when any gate fails. + +1. **Approval gate** — Justin Ko records explicit approval for this REL-004 execution window. +2. **Baseline gate** — artifact is built from `914b3dc` or a reviewed descendant; record immutable image/package digest. +3. **Backup gate** — restorable DB backup is complete and path, timestamp, size, and restore owner are recorded. +4. **Database gate** — `alembic heads`, `alembic current`, active-query/lock check, metadata schema snapshot, and `portfolio_ledger` row count are captured before upgrade. +5. **Migration gate** — `alembic upgrade 20260715_0028` succeeds; `current`, all four columns, `source` default/not-null, `source IS NULL = 0`, and unchanged row count are evidenced. +6. **Activation gate** — only after Gate 5, deploy the immutable artifact and perform the service's normal rolling restart procedure. +7. **Smoke gate** — health response and approved owner-token/API smoke pass. A production mutation smoke must use an explicitly designated test portfolio and authorization; it is not implicit approval to alter a customer or live trading portfolio. + +--- + +## 4. Deployment sequence + +```text +Approval → immutable artifact selection → DB backup/preflight + → Alembic 0028 upgrade → schema/current/data verification + → portfolio-hub rolling restart using the approved artifact + → health and minimum API smoke → observation window → released decision +``` + +The detailed commands, expected results, stop conditions, evidence template, and forward-only recovery procedure are in the linked runbook. + +--- + +## 5. Rollback and recovery + +### Triggers + +- migration command fails or postcheck differs from expected schema/current/row-count values; +- `GET /health` fails or returns `db_enabled != true` after activation; +- new manual API returns unexpected 5xx or authorization fail-open behavior; +- existing `/ledger/trades` or portfolio read flows show regression. + +### Policy + +- **Before migration succeeds:** stop; do not restart into the new code. Preserve logs and consult the DB owner using the backup and error evidence. +- **After migration succeeds:** do **not** run `alembic downgrade` and do not drop the four additive columns. Roll back only to a reviewed compatible code artifact, retaining revision `20260715_0028` and the schema. +- DB restore is an exceptional, separately approved recovery operation; it is not an automatic release action. + +--- + +## 6. Release execution evidence (to be completed by trex-op) + +| Field | Evidence | +| --- | --- | +| Approval time / approver | Pending | +| Maintenance window | Pending | +| Artifact ref + immutable digest | Pending | +| Operator | Pending | +| Backup path / timestamp / size / restore verification owner | Pending | +| Preflight `alembic heads/current` | Pending | +| Pre/post `portfolio_ledger` row counts | Pending | +| Lock/active-query result | Pending | +| `0028` upgrade command and exit/result | Pending | +| Post-migration schema/default/null/current evidence | Pending | +| Rolling restart evidence | Pending | +| Health and approved API smoke evidence | Pending | +| Observation window / alerts | Pending | +| Rollback needed | Pending | + +## 7. Release decision + +- Current decision: **draft — not authorized for production execution yet**. +- Transition to `released` requires all mandatory gates and the evidence table to be completed, then a release approver decision recorded in this document. diff --git a/docs/runbooks/rel-004-m5-manual-ledger-deployment-runbook.md b/docs/runbooks/rel-004-m5-manual-ledger-deployment-runbook.md new file mode 100644 index 0000000..77dabc5 --- /dev/null +++ b/docs/runbooks/rel-004-m5-manual-ledger-deployment-runbook.md @@ -0,0 +1,210 @@ +# REL-004 운영 배포 런북 — M5 수동 장부 API 및 ledger metadata migration + +- Service: `portfolio-hub` +- Release: [REL-004](../releases/rel-004_20260715_manual-ledger-api-and-metadata-migration.md) +- Target source baseline: `origin/main@914b3dc6e6b9e2cbd7068fb30435c83f0581eab5` +- Database revision: `20260715_0028` +- Classification: production DB migration + app-service deployment +- Required approver: Justin Ko +- Required operator: trex-op +- Estimated active window: 20–40 minutes plus observation + +> **Do not begin from this document alone.** Production execution needs an explicit REL-004 approval, a maintenance window, and the exact deployment-platform command approved by trex-op. This repository contains no production Docker Compose, systemd, Kubernetes, or Helm manifest; do not infer one from `docker-compose.local-test.yml`, which is local-test infrastructure only. + +## 1. Safety model + +- The migration is additive. It adds/normalizes four `portfolio_ledger` metadata columns and records Alembic revision `20260715_0028`. +- The permitted recovery path after a successful migration is **compatible code rollback while retaining the schema**. +- `alembic downgrade`, column deletion, and automatic `pg_restore` are prohibited in this runbook. Any restore is a separately approved incident recovery action. +- The new API can mutate ledger state. A production write smoke requires an explicitly designated test portfolio, a named owner, and approval; otherwise stop after read-only health and schema checks. +- Do not paste DB passwords, bearer tokens, credential payloads, or backups into this document, chat, CI logs, or release evidence. Record secret-free command outcomes only. + +## 2. Roles and stop authority + +| Role | Responsibility | +| --- | --- | +| Justin Ko | grants release-window approval; decides whether production mutation smoke is allowed; approves exceptional restore | +| trex-op | captures evidence, performs backup/preflight/migration/deployment using the real platform tooling, stops on any failed gate | +| TREX lead | reviews completed evidence and records final release decision; does not perform production commands under this draft | + +Any operator may stop the rollout for a failed gate, unexpected active workload, missing backup, wrong baseline, migration error, or unexpected API behavior. + +## 3. Inputs to obtain before window + +- [ ] approved window and approver/time recorded in REL-004 +- [ ] immutable deployable artifact built from `914b3dc` or reviewed descendant, with image/package digest +- [ ] exact production deployment command supplied by trex-op’s platform (for example, its existing rollout command); do not substitute a local-test Compose command +- [ ] secure database environment available to the migration runner: + - `TIMESCALEDB_HOST`, `TIMESCALEDB_PORT`, `TIMESCALEDB_NAME`, `TIMESCALEDB_USER`, `TIMESCALEDB_PASSWORD` + - `PORTFOLIOHUB_DB_ENABLED=true` +- [ ] confirmed DB backup destination and restore owner +- [ ] approved health base URL +- [ ] optional, separately approved test portfolio ID and owner-token handling plan for mutation smoke + +## 4. Preflight — capture, then decide go/no-go + +Run from the approved artifact or a clean checkout at the approved source baseline, with the secured DB environment loaded. + +```bash +# Identity: must resolve to the approved release source or its artifact provenance. +git rev-parse HEAD +alembic heads +alembic current +``` + +Expected head is `20260715_0028`. `current` may be an earlier valid deployed revision before the upgrade, but an unknown/branched state is a **stop** condition. + +Capture active work and locks before touching schema: + +```sql +SELECT pid, usename, application_name, state, wait_event_type, wait_event, query +FROM pg_stat_activity +WHERE query ILIKE '%portfolio_ledger%' + AND state <> 'idle'; + +SELECT locktype, mode, granted, pid, relation::regclass +FROM pg_locks +WHERE relation = 'portfolio_ledger'::regclass; +``` + +Capture schema and row-count baseline: + +```sql +SELECT column_name, data_type, is_nullable, column_default +FROM information_schema.columns +WHERE table_name = 'portfolio_ledger' + AND column_name IN ('source', 'target_ledger_entry_id', 'confirmation_evidence', 'actor_user_id') +ORDER BY column_name; + +SELECT COUNT(*) AS rows_before FROM portfolio_ledger; +SELECT COUNT(*) AS null_source_before FROM portfolio_ledger WHERE source IS NULL; +``` + +**Go only when:** backup is complete, active workload/locks are absent or the window owner explicitly accepts them, `heads` has one expected head, and the current state is understood. Otherwise stop and record the result. + +## 5. Backup gate + +Perform the established production backup procedure. The following is an interface, not a command to run blindly: + +```text + → +``` + +Record in REL-004: + +- backup reference/path (not credentials), timestamp, byte size/checksum if available; +- DB/database identifier; +- restore owner and how restore viability is known. + +**Stop** if the backup has not completed or cannot be located by the restore owner. + +## 6. Migration gate + +Execute exactly the reviewed target revision: + +```bash +alembic upgrade 20260715_0028 +alembic current +``` + +Then execute and retain secret-free output for: + +```sql +SELECT column_name, data_type, is_nullable, column_default +FROM information_schema.columns +WHERE table_name = 'portfolio_ledger' + AND column_name IN ('source', 'target_ledger_entry_id', 'confirmation_evidence', 'actor_user_id') +ORDER BY column_name; + +SELECT COUNT(*) AS rows_after FROM portfolio_ledger; +SELECT COUNT(*) AS null_source_rows +FROM portfolio_ledger +WHERE source IS NULL; + +SELECT column_name, is_nullable +FROM information_schema.columns +WHERE table_name = 'portfolio_ledger' AND column_name = 'source'; + +SELECT column_default +FROM information_schema.columns +WHERE table_name = 'portfolio_ledger' AND column_name = 'source'; +``` + +Pass criteria: + +- `alembic current` reports `20260715_0028`; +- all four columns exist with the REL-004/REQ-057 types; +- `source` is `NOT NULL`, default is `'broker'`, and `null_source_rows = 0`; +- `rows_after = rows_before`. + +**Stop** on a migration error, mismatched revision, missing/incorrect column, remaining null `source`, or row-count mismatch. Do not start/restart the new service code. + +## 7. Artifact deployment and activation gate + +Only after Section 6 passes: + +1. Record the exact immutable artifact ref/digest and source provenance in REL-004. +2. Use the already-approved **production** deploy/rolling-restart procedure maintained by trex-op. +3. Capture the deployment command class, rollout/restart result, active artifact digest, and service logs/status without secrets. +4. Do not invoke `ensure_schema()` as a substitute for this reviewed Alembic preflight/migration flow. + +There is no repo-owned production rollout command. `docker-compose.local-test.yml` must not be used for a production service replacement. + +## 8. Post-deploy verification + +### 8.1 Mandatory read-only checks + +```bash +curl --fail-with-body --silent --show-error "$PORTFOLIO_HUB_BASE_URL/health" +``` + +Pass: HTTP 200 and JSON includes `db_enabled: true`. + +Use authorized existing read probes to check that the service is reachable and that existing portfolio reads still work. Do not log bearer tokens or response PII in REL-004. + +### 8.2 Manual-ledger API authorization checks + +With an approved non-sensitive test identity or non-production endpoint: + +- owner user/session token: `GET /v1/portfolios/{portfolio_id}/ledger/manual` succeeds with HTTP 200; +- service/daemon token calling `POST .../ledger/manual` is rejected HTTP 403; +- non-owner user/session token is rejected HTTP 403. + +### 8.3 Optional mutation smoke — only with explicit approval + +Only after Justin Ko explicitly authorizes the named test portfolio and operation: + +- submit one bounded `INVEST` entry through the new manual endpoint; +- verify HTTP 201, returned `source="manual"`, and expected after-snapshot; +- retrieve the entry via manual history; +- if business policy permits, exercise approved reversal of that exact test entry and verify append-only behavior. + +This is business-data mutation. Never use an unapproved customer, live strategy-managed, or production trading portfolio as a convenience test target. A daemon-managed portfolio needs the REQ-056 confirmation evidence flow. + +## 9. Observation and stop/rollback decision + +Observe the service for the agreed window. Stop and roll back code if health fails, 5xx/authorization anomalies appear, or existing `/ledger/trades`/portfolio reads regress. + +- **Migration not successful:** do not deploy the new artifact; preserve command output and consult DB owner. +- **Migration successful, code problem:** use the platform’s approved rollback to a reviewed compatible prior artifact; retain revision `20260715_0028` and all four columns. +- **Restore needed:** halt and obtain separate incident/recovery approval. Do not self-initiate downgrade, column deletion, or restore. + +## 10. Evidence handoff template + +Send the following completed facts to the TREX lead for REL-004 decision: + +```text +REL-004 execution +- approval: +- operator: +- source/artifact: , +- backup: +- preflight: , , , +- migration: , , , , +- deployment: , +- read-only smoke: , +- authorization smoke: +- mutation smoke: +- observation: +- rollback: +```