docs(release): draft M5 manual ledger rollout
This commit is contained in:
@@ -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-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-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-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) |
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
<ops-approved pg_dump/snapshot procedure> → <backup reference>
|
||||||
|
```
|
||||||
|
|
||||||
|
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: <approver, time, window>
|
||||||
|
- operator: <name>
|
||||||
|
- source/artifact: <commit>, <immutable digest>
|
||||||
|
- backup: <reference, time, size/checksum, restore owner>
|
||||||
|
- preflight: <heads/current>, <locks result>, <rows_before>, <null_source_before>
|
||||||
|
- migration: <command exit/result>, <current_after>, <rows_after>, <null_source_rows>, <schema/default evidence>
|
||||||
|
- deployment: <platform procedure/result>, <active artifact digest>
|
||||||
|
- read-only smoke: <health result>, <existing read probes>
|
||||||
|
- authorization smoke: <owner/read, service 403, non-owner 403>
|
||||||
|
- mutation smoke: <not authorized | test portfolio approval + result>
|
||||||
|
- observation: <duration, alerts/errors>
|
||||||
|
- rollback: <not needed | reason/action>
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user