キャップストーン: 組織境界を守る冪等な注文API
目標
注文API1つを、本番の責任まで付けて完成させます。データ契約、トランザクションの 冪等性、組織境界の認可、実行されるHTTP、3つの観測シグナル、安全なデプロイオブジェクト、 そして他人の手に渡せるドキュメントまでを、1つのプロジェクトとして結びます。
8つのステップが、それぞれ異なる採点の軸(正確性・セキュリティ・問題解決・運用・ドキュメント)を見ます。
時間と保存に関する警告: 先に読んでください
- セッションは60分で始まり、
+시간(韓国語で「時間」を意味する語が付いたボタン)で、合計180分までしか延長できません。 - ラボのPodにはボリュームがありません。セッションが終わると、
/root/workがまるごと 消え、次のセッションは空のPodで始まります。
そのため、一度に座って終えることを前提に設計されています。時間が足りなそうなら、
ステップ8まで浅くてもひととおり回っておくほうが、ステップ3まで深くやって失うよりも
ましです。途中で離れなければならないなら、プロジェクトのフォルダーをtarでまとめて、手で
ダウンロードしておいてください。
環境
PostgreSQL 16が、すでに起動しています。
postgresql://lab@127.0.0.1:5432/labdb
プロジェクトのルートは、/root/work/backend-capstoneディレクトリで、採点ツールはこのパスだけを
見ます。先に作成しておいてください。
mkdir -p /root/work/backend-capstone
cd /root/work/backend-capstone
mkdir -p migrations tests docs deploy
作成するファイル
app.py psycopg 로 붙고 --host/--port 로 뜬다
migrations/001_create_orders.sql BEGIN/COMMIT 으로 감싼 순방향 마이그레이션
tests/test_integration.py 재시도와 다른 조직 반례를 행동으로 검증
deploy/deployment.yaml JSON 으로도 읽히는 YAML
docs/ADR-001.md status/context/alternatives/decision/consequences
docs/incident-report.md impact/timeline/root cause/detection/corrective actions
portfolio.json backend-capstone-v2 스키마
このコードブロックの韓国語コメントは、各ファイルの説明で、app.pyはpsycopgで接続して--host/--portで起動すること、migrationsのSQLはBEGIN/COMMITで囲んだ順方向のマイグレーションであること、テストは再試行と別組織の反例を動作で検証すること、deployment.yamlはJSONとしても読めるYAMLであること、portfolio.jsonはbackend-capstone-v2スキーマであることを述べています。
すべてのステップで最初にかかる検査
採点ツールは、ステップの検査の前に、プロジェクト全体をなめて秘密を探します。1つでも 見つかれば、どのステップを採点しても同じ文言で失敗するので、原因をそのステップで 探して時間を無駄にしがちです。
비밀번호가 들어간 접속 문자열 postgresql://lab:1234@...
클러스터 내부 주소 *.svc.cluster.local
Bearer 토큰 원문 Authorization: Bearer eyJ...
password/api_key/secret 대입 password = "hunter2"
개인 키 -----BEGIN PRIVATE KEY-----
このコードブロックの韓国語コメントは、左の列が検出対象の種類で、パスワードを含む接続文字列、クラスター内部のアドレス、Bearerトークンの原文、password・api_key・secretへの代入、という意味です。
接続情報は環境変数で受け取り、例のトークンは<token>のように、プレースホルダーとして書いてください。
サイズの制限も併せて見ます。ファイル4,096個・1ファイル1MB・合計8MBの範囲内です。
.gitと__pycache__は数えません。
ステップ
- データ契約:
ordersテーブルに、identity PK、amount_cents > 0の検査、owner_id・org_id、created_at timestamptz、unique(org_id, idempotency_key)、 そして(org_id, owner_id)の参照用インデックスを作ります。全体をBEGIN;/COMMIT;で囲みます。 - トランザクションの冪等性:
ON CONFLICT (org_id, idempotency_key)でリトライを 吸収し、例外でロールバックします。値は必ずパラメータとしてバインドします。 - 認可:
authenticateは身元を作り、authorize(identity, order)は 組織を先に見てから、所有者・管理者を見ます。このファイルは、importしただけでは 何も出力してはいけません。 - HTTP:
python3 app.py --host 127.0.0.1 --port <포트>で起動できなければなりません(プレースホルダーはポート番号です)。POST /orders・GET /orders/{id}・POST /orders/{id}/cancel・/healthz、 そしてすべてのレスポンスにX-Trace-Id。ステータスコードは、201/200/400/401/403を区別します。 - 観測:
orders_created_total、http_request_duration_seconds、そしてevent・route・status・duration_ms・trace_idを含むJSONログ。authorizationヘッダーと内部DNSは残しません。 - デプロイ:
deploy/deployment.yamlに、非root、seccompProfile: RuntimeDefault、allowPrivilegeEscalation: false、読み取り専用のルート、requests/limits、 2つのプローブ、secretKeyRef、そしてダイジェストで固定したイメージを入れます。 - ドキュメント: ADRとインシデントレポート。インシデントレポートには、措置ごとに担当者と期限を書きます。
- ポートフォリオ:
portfolio.jsonに、許可された5つの要約だけを入れます。 元のヘッダー・トークン・プライベートIP・内部DNS・ターミナルの記録は入れません。
参考
ステップ3の「importしても静かでなければならない」という条件が、毎回かかる場所です。モジュールの
先頭でデータベースに接続したり、printをしたりすると、採点ツールがその出力を見ます。
接続はmain()の中に、診断はロガーに移してください。
PostgreSQL 16のスキーマを実行する
データ契約: ordersテーブルに、identity PK、amount_cents > 0の検査、
owner_id・org_id、created_at timestamptz、unique(org_id, idempotency_key)、
そして(org_id, owner_id)の参照用インデックスを作ります。全体をBEGIN;/COMMIT;で囲みます。
migrations/001_create_orders.sqlに、identity PK、正の金額、owner/org、組織の範囲の冪等UNIQUE、TIMESTAMPTZと参照用インデックスを作って、トランザクションで囲んでください。app.pyはpsycopgを使わなければなりません。
トランザクションの冪等な作成を実装する
トランザクションの冪等性: ON CONFLICT (org_id, idempotency_key)でリトライを
吸収し、例外でロールバックします。値は必ずパラメータとしてバインドします。
psycopgのtransaction/cursorとパラメータのバインド、ON CONFLICT (org_id, idempotency_key)を使い、例外のときはrollbackしてください。リトライと別組織の反例を、動作のテストとして作成してください。
認証と、所有者・組織の認可を分離する
認可: authenticateは身元を作り、authorize(identity, order)は
組織を先に見てから、所有者・管理者を見ます。このファイルは、importしただけでは
何も出力してはいけません。
authenticateはBearerの身元を作り、authorize(identity, order)は組織を先に、その次にowner/adminの関係を判断してください。importとエラー出力に、トークンを残さないでください。
実際のHTTP APIの契約を公開する
HTTP: python3 app.py --host 127.0.0.1 --port <포트>で起動できなければなりません(プレースホルダーはポート番号です)。
POST /orders・GET /orders/{id}・POST /orders/{id}/cancel・/healthz、
そしてすべてのレスポンスにX-Trace-Id。ステータスコードは、201/200/400/401/403を区別します。
標準ライブラリまたはインストール済みのランタイムで、app.py --host ... --port ...をサポートしてください。POST /orders、GET /orders/id、POST /orders/id/cancel、health/readiness、そしてステータス・JSON・X-Trace-Idの契約を実装してください。
3つの観測シグナルをリクエストに結びつける
観測: orders_created_total、http_request_duration_seconds、そして
event・route・status・duration_ms・trace_idを含むJSONログ。
authorizationヘッダーと内部DNSは残しません。
orders_created_total、http_request_duration_seconds、JSONログのevent/route/status/duration_msと、レスポンスのtrace IDを結びつけてください。Authorizationと内部DNSは記録しないでください。
パースできる安全なデプロイオブジェクトを作る
デプロイ: deploy/deployment.yamlに、非root、seccompProfile: RuntimeDefault、
allowPrivilegeEscalation: false、読み取り専用のルート、requests/limits、
2つのプローブ、secretKeyRef、そしてダイジェストで固定したイメージを入れます。
deploy/deployment.yamlは、JSON互換のYAMLで書いてください。非root/seccomp/権限昇格の禁止/読み取り専用のルート、リソース、2つのプローブ、Secretの参照、digestのイメージを、正確なオブジェクトのパスに置きます。
ADRとインシデントからの学びを結びつける
ドキュメント: ADRとインシデントレポート。インシデントレポートには、措置ごとに担当者と期限を書きます。
ADRにstatus/context/alternatives/decision/consequencesを、インシデントレポートにimpact/timeline/root cause/detection/corrective actionsと、担当者・期限を書いて、秘密と内部アドレスは取り除いてください。
許可リストのポートフォリオを発行する
ポートフォリオ: portfolio.jsonに、許可された5つの要約だけを入れます。
元のヘッダー・トークン・プライベートIP・内部DNS・ターミナルの記録は入れません。
portfolio.jsonをbackend-capstone-v2に固定して、test_summary、metric_names、deployment_summary、adr_summary、incident_summaryだけを入れてください。元のヘッダー・トークン・プライベートIP・内部DNS・ターミナルのログは禁止です。