lead · Stage 5
チームインターフェースで社会技術アーキテクチャを設計する
ownership、dependency、cognitive load、SLOを明示したteam interfaceを作り、組織構造とsoftware境界が互いへ与える影響を観測可能にする。
到達目標
team ownership、owned capability、dependency、SLOを一つのinterfaceへ記述できる
- 所有権、依存、認知負荷、SLOを含むチームインターフェース
- 曖昧なownershipと過剰なcognitive loadを個人努力で補わない設計判断
assigned cognitive loadをcapacityと比較し、境界変更かenablementかを判断できる
- 所有権、依存、認知負荷、SLOを含むチームインターフェース
- Conway、DORA、Team Topologiesを組織図ではなくdelivery境界へ結ぶ5分発表
dependency latencyだけを変え、同じteam snapshotからSLOとenablement判断を再評価できる
- 所有権、依存、認知負荷、SLOを含むチームインターフェース
- dependency latencyだけを変えた同一team snapshotのSLO再評価
能力の進行
recognize
team、capability、dependency、SLO、cognitive loadの境界を識別できる
証拠: 所有権、依存、認知負荷、SLOを含むチームインターフェース
explain
communication pathとsoftware構造が相互に制約するConwayの観点を説明できる
証拠: Conway、DORA、Team Topologiesを組織図ではなくdelivery境界へ結ぶ5分発表
apply
owner、依存先、期待値、観測値、enablement actionをteam interfaceへ結べる
証拠: 所有権、依存、認知負荷、SLOを含むチームインターフェース
diagnose
共有ownership、同期依存、認知負荷超過、SLO不在を個人の遅さと誤診せず反証できる
証拠: 曖昧なownershipと過剰なcognitive loadを個人努力で補わない設計判断
lead
別teamが自律的にdeliveryできるdependency契約とenablementを共同運営できる
証拠: dependency latencyだけを変えた同一team snapshotのSLO再評価
なぜ重要か
software architectureはmoduleだけで決まらない。誰がどのcapabilityを所有し、どのteamへ依存し、どれだけのcognitive loadを引き受け、どのSLOを約束するかが、変更速度とreliabilityを同時に制約する。
Conwayが示したcommunication structureとsystem designの関係は、組織図をそのままcodeへ写す命令ではなく、望むarchitectureに合うinteractionを意図的に設計する問いである。DORAのloosely coupled teamsは、他teamとの細かな調整なしにtest、deploy、変更できる境界を重視する。Team Topologiesはownership、cognitive load、team interactionを具体化する語彙を提供する。
したがってteam interfaceは担当者一覧ではない。owned capability、dependency、期待値、観測値、SLO、enablement actionを別teamがreviewできる社会技術的な契約である。
メンタルモデル
team interfaceを、ownerが提供するcapability、receiving teamが持つdependency、双方が観測するSLO、変更を理解するためのcognitive load、境界を改善するenablementの組として捉える。
dependencyをゼロにすることが目的ではない。同期調整が必要な箇所を小さくし、残る依存を観測可能な契約へ変える。cognitive loadがcapacity内でもSLO breachならdeliveryは止まり得るため、teamとserviceの両方を同時に見る。
注記
図を読む際の補足情報です。
- この注記は旧図の読み順を保持する補助です。
- Ownership: checkout teamがcheckout capabilityのdecision rightを持つ。
- Dependency: checkoutはplatform capabilityへ依存する。
- Cognitive load: assigned領域をcapacityと比較し、過負荷を個人努力へ隠さない。
- SLO: dependency latencyのtargetとobservedを同じ単位で評価する。
- Enablement: 各interface snapshotから判断を再計算し、healthyならmonitor、breachedならescalateとしてcheckoutの自律判断を増やす。
ownershipとdependencyの設計は、認知負荷とdelivery SLOへどう現れるか。
- delivery interface
- team境界とsoftware dependencyを同じsnapshotで評価する社会技術範囲。
- Ownership
- checkout teamがcheckout capabilityのdecision rightを持つ。
- component
- delivery interface
- Dependency
- checkoutはplatform capabilityへ依存する。
- component
- delivery interface
- Cognitive load
- assigned領域をcapacityと比較し、過負荷を個人努力へ隠さない。
- component
- delivery interface
- SLO
- dependency latencyのtargetとobservedを同じ単位で評価する。
- component
- delivery interface
- Enablement
- 各interface snapshotから判断を再計算し、healthyならmonitor、breachedならescalateとしてcheckoutの自律判断を増やす。
- component
- delivery interface
- Ownership → Dependency: checkout capabilityのdecision rightとplatform依存を分ける
- Ownership → Cognitive load: assigned領域をcapacityと比較する
- Dependency → SLO: dependency latencyのtargetとobservedを比較する
- Cognitive load → Enablement: capacity内かをenablement判断へ渡す
- SLO → Enablement: dependency SLO statusをenablement判断へ渡す
checkoutとplatformの依存を、decision right、認知負荷、dependency latency、enablement判断まで結んで説明できる。
動く例で考える
checkoutからplatformへのdependency latencyを再評価する
- 前提
- checkout、platformの2 teamからなるlesson-defined simulated snapshotを使う。実組織のreporting lineやperformance評価ではない。
- 入力
- canonical team order、owned capability、dependency、assigned/capacity、dependency latency 200ms、SLO target 300msを与える。
- 操作
- baselineとtransferで独立したteam snapshotからdependency、cognitive load、SLO、enablementを全て再計算する。transferではdependency latencyだけを450msへ変える。
- 観測
- baselineはhealthyかつmonitor、transferはbreachedかつescalateになる。team ownership、dependency、cognitive load snapshotは同じなので、判断変化をlatencyへ限定できる。
- 結論
- report生成というcommand successと、自律的deliveryを支えるsystem outcomeは別である。各snapshotから再計算したinterfaceと、独立invariantで照合したmonitor/escalate判断をownerとreceiving teamがreviewできて初めてteam interfaceが働く。
python3.13 - <<'PY'
import json
HARNESS = "team_interface_lab_v1"
TEAM_ORDER = ("checkout", "platform")
BASELINE_TEAM_CONTEXT = {
"dependency_latency_ms": 200,
"slo_target_ms": 300,
}
TRANSFER_TEAM_CONTEXT = {
"dependency_latency_ms": 450,
"slo_target_ms": 300,
}
TEAMS = [
{
"team_id": "checkout",
"owned_capability": "checkout",
"dependencies": ["platform"],
"cognitive_load": {
"assigned": 3,
"capacity": 4,
},
"enablement_action": "consume published latency evidence",
},
{
"team_id": "platform",
"owned_capability": "runtime-platform",
"dependencies": [],
"cognitive_load": {
"assigned": 2,
"capacity": 4,
},
"enablement_action": "publish latency evidence and escalation path",
},
]
COMMAND_INPUT = {"exit_code": 0}
def is_nonempty_text(value):
return type(value) is str and bool(value)
def context_difference(baseline, transferred):
return [
field
for field in sorted(set(baseline) | set(transferred))
if (
field not in baseline
or field not in transferred
or baseline[field] != transferred[field]
)
]
def validate_team_contexts():
allowed_fields = {"dependency_latency_ms", "slo_target_ms"}
for context in (
BASELINE_TEAM_CONTEXT,
TRANSFER_TEAM_CONTEXT,
):
if (
type(context) is not dict
or set(context) != allowed_fields
or type(context["dependency_latency_ms"]) is not int
or context["dependency_latency_ms"] < 0
or type(context["slo_target_ms"]) is not int
or context["slo_target_ms"] <= 0
):
raise AssertionError(
"team-interface-transfer-invariant: "
"invalid dependency context schema"
)
if context_difference(
BASELINE_TEAM_CONTEXT,
TRANSFER_TEAM_CONTEXT,
) != ["dependency_latency_ms"]:
raise AssertionError(
"team-interface-transfer-invariant: "
"transfer changed wrong field"
)
def validate_team_snapshot(teams):
team_fields = {
"team_id",
"owned_capability",
"dependencies",
"cognitive_load",
"enablement_action",
}
basic_shape = (
type(teams) is list
and bool(teams)
and all(
type(team) is dict
and set(team) == team_fields
for team in teams
)
)
team_ids = (
[team["team_id"] for team in teams]
if basic_shape
else []
)
known_team_ids = set(team_ids)
valid = (
basic_shape
and all(
is_nonempty_text(team["team_id"])
and is_nonempty_text(team["owned_capability"])
and type(team["dependencies"]) is list
and len(set(team["dependencies"]))
== len(team["dependencies"])
and all(
is_nonempty_text(dependency)
and dependency in known_team_ids
and dependency != team["team_id"]
for dependency in team["dependencies"]
)
and type(team["cognitive_load"]) is dict
and set(team["cognitive_load"])
== {"assigned", "capacity"}
and type(team["cognitive_load"]["assigned"]) is int
and team["cognitive_load"]["assigned"] >= 0
and type(team["cognitive_load"]["capacity"]) is int
and team["cognitive_load"]["capacity"] > 0
and team["cognitive_load"]["assigned"]
<= team["cognitive_load"]["capacity"]
and is_nonempty_text(team["enablement_action"])
for team in teams
)
)
if (
not valid
or tuple(team_ids) != TEAM_ORDER
or len(set(team_ids)) != len(team_ids)
):
raise AssertionError(
"team-interface-input-invariant: invalid team snapshot"
)
def validate_all_inputs():
if TEAM_ORDER != ("checkout", "platform"):
raise AssertionError(
"team-interface-input-invariant: invalid canonical team order"
)
validate_team_contexts()
validate_team_snapshot(TEAMS)
if (
type(COMMAND_INPUT) is not dict
or set(COMMAND_INPUT) != {"exit_code"}
or type(COMMAND_INPUT["exit_code"]) is not int
):
raise AssertionError(
"team-interface-input-invariant: invalid command input"
)
def independent_snapshot(value):
# baselineとtransferを別のfull snapshotにして、片側の変更が
# ownershipやcognitive loadへ漏れるfalse comparisonを防ぐ。
return json.loads(json.dumps(value, ensure_ascii=False))
def evaluate_slo(dependency_latency_ms, slo_target_ms):
if dependency_latency_ms <= slo_target_ms:
return "healthy"
return "breached"
def build_team_interface(teams, context):
owner = next(
team
for team in teams
if team["team_id"] == "checkout"
)
dependency_latency_ms = context["dependency_latency_ms"]
slo_target_ms = context["slo_target_ms"]
slo_status = evaluate_slo(dependency_latency_ms, slo_target_ms)
return {
"owner": owner["team_id"],
"owned_capability": owner["owned_capability"],
"dependencies": list(owner["dependencies"]),
"cognitive_load": independent_snapshot(
owner["cognitive_load"]
),
"slo": {
"target": slo_target_ms,
"observed": dependency_latency_ms,
"status": slo_status,
},
}
def evaluate_enablement_decision(team_interface):
if (
team_interface["slo"]["status"] == "healthy"
and team_interface["cognitive_load"]["assigned"]
<= team_interface["cognitive_load"]["capacity"]
):
return "monitor"
return "escalate"
def build_contributor_enablement(
teams,
team_interface,
):
platform = next(
team
for team in teams
if team["team_id"] == "platform"
)
enablement_decision = evaluate_enablement_decision(team_interface)
return {
"other_contributor": team_interface["owner"],
"receiving_team": team_interface["owner"],
"enabling_team": platform["team_id"],
"decision": enablement_decision,
"action": (
enablement_decision
+ ": "
+ platform["enablement_action"]
),
"review_evidence": {
"dependency": team_interface["dependencies"][0],
"observed_ms": team_interface["slo"]["observed"],
"target_ms": team_interface["slo"]["target"],
"status": team_interface["slo"]["status"],
},
}
def validate_enablement_recomputation(
team_interface,
enablement,
):
expected_decision = (
"monitor"
if team_interface["slo"]["status"] == "healthy"
else "escalate"
)
evidence = enablement["review_evidence"]
if (
enablement["decision"] != expected_decision
or evidence["dependency"]
!= team_interface["dependencies"][0]
or evidence["observed_ms"]
!= team_interface["slo"]["observed"]
or evidence["target_ms"]
!= team_interface["slo"]["target"]
or evidence["status"]
!= team_interface["slo"]["status"]
):
raise AssertionError(
"team-interface-enablement-invariant: "
"enablement not independently derived"
)
def main():
validate_all_inputs()
baseline_context = independent_snapshot(BASELINE_TEAM_CONTEXT)
transferred_context = independent_snapshot(
TRANSFER_TEAM_CONTEXT
)
baseline_teams = independent_snapshot(TEAMS)
transferred_teams = independent_snapshot(TEAMS)
validate_team_snapshot(baseline_teams)
validate_team_snapshot(transferred_teams)
if (
baseline_teams is transferred_teams
or baseline_teams[0] is transferred_teams[0]
or baseline_teams != transferred_teams
):
raise AssertionError(
"team-interface-transfer-invariant: team snapshots drifted"
)
baseline_interface = build_team_interface(
baseline_teams,
baseline_context,
)
transferred_interface = build_team_interface(
transferred_teams,
transferred_context,
)
if (
baseline_interface["slo"]["status"] != "healthy"
or transferred_interface["slo"]["status"] != "breached"
or baseline_interface["slo"]["status"]
== transferred_interface["slo"]["status"]
):
raise AssertionError(
"team-interface-slo-invariant: SLO not input-derived"
)
changed_fields = context_difference(
baseline_context,
transferred_context,
)
baseline_enablement = build_contributor_enablement(
baseline_teams,
baseline_interface,
)
transferred_enablement = build_contributor_enablement(
transferred_teams,
transferred_interface,
)
validate_enablement_recomputation(
baseline_interface,
baseline_enablement,
)
validate_enablement_recomputation(
transferred_interface,
transferred_enablement,
)
if (
baseline_enablement["decision"] != "monitor"
or transferred_enablement["decision"] != "escalate"
):
raise AssertionError(
"team-interface-enablement-invariant: "
"decision did not change with SLO"
)
report = {
"harness": HARNESS,
"team_interface": baseline_interface,
"contributor_enablement": baseline_enablement,
"dependency_latency_transfer": {
"changed_fields": changed_fields,
"same_team_snapshot": baseline_teams
== transferred_teams,
"baseline_context": baseline_context,
"transferred_context": transferred_context,
"baseline_slo_status": baseline_interface["slo"][
"status"
],
"transferred_slo_status": transferred_interface["slo"][
"status"
],
"baseline_interface": baseline_interface,
"transferred_interface": transferred_interface,
"baseline_enablement": baseline_enablement,
"transferred_enablement": transferred_enablement,
},
"input_derived_evidence": {
"ownership": baseline_interface["owner"],
"dependency": baseline_interface["dependencies"],
"cognitive_load": baseline_interface[
"cognitive_load"
],
"slo": baseline_interface["slo"],
},
"fixture_metadata": {
"kind": "simulated",
"provenance": "lesson-defined checkout/platform fixture",
"limitations": (
"実組織のdelivery、support、incidentを観測しない"
),
"synthetic_or_observed_explicit": True,
},
"runtime_bound": {
"records": len(TEAMS) * 2,
"subprocesses": 0,
"maximum_iterations": len(TEAMS),
},
"external_network_used": False,
"command_success_distinction": {
"command_completed": COMMAND_INPUT["exit_code"] == 0,
"system_outcome_checked": (
baseline_interface["slo"]["status"] == "healthy"
),
"command_success_equals_system_outcome": False,
"outcome_evidence": baseline_interface["slo"],
},
"mastery_evidence": {
"lab_steps": [
{"step": 1, "evidence": baseline_teams},
{"step": 2, "evidence": baseline_interface},
{
"step": 3,
"evidence": baseline_interface["slo"],
},
{"step": 4, "evidence": baseline_enablement},
{
"step": 5,
"evidence": {
"interface": transferred_interface,
"enablement": transferred_enablement,
},
},
],
"assessments": [
{
"assessment": 1,
"evidence": baseline_enablement,
},
{
"assessment": 2,
"evidence": baseline_interface,
},
],
"rubric_dimensions": [
"technical-correctness",
"judgment",
"evidence",
"communication",
],
"transfer": {
"task": (
"依存サービスのlatencyだけを変え、同じteam "
"ownershipとcognitive load境界でSLOと"
"enablement判断を再評価する"
),
"changed_assumption": "dependency-latency",
"evidence": {
"changed_fields": changed_fields,
"baseline": baseline_interface["slo"]["status"],
"transferred": transferred_interface["slo"][
"status"
],
},
},
"contributor_enablement": baseline_enablement,
},
}
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
if __name__ == "__main__":
try:
main()
except AssertionError as error:
raise SystemExit(str(error)) from None
PY
トレードオフと失敗モード
| 観測 | 判断 | interaction | 残す証拠 |
|---|---|---|---|
| SLO healthy、loadはcapacity内 | monitor | serviceとして利用し観測を継続 | target、observed、owner |
| SLO breached、ownership明確 | escalate | 個人評価にせず期限付きcollaboration | dependency latency、impact、escalation |
| loadがcapacity超過 | 境界または責務を減らす | facilitatingで移管 | assigned領域、interrupt、学習時間 |
| ownerが複数でdecision right不明 | interfaceを再設計する | 単一のcapability ownerを合意 | 決定者、support、release境界 |
- 誤診: checkoutのdeliveryが遅いのでcheckout memberの生産性が低い。反証: dependency latency、SLO breach、ownership、handoff、cognitive loadを同じteam interfaceで確認する。
- 誤診: service ownershipを全員へ割り当てればbottleneckは消える。反証: capabilityごとのdecision right、support boundary、SLO、escalationを一意にし、receiving teamが自律判断できるか観測する。
知識チェック
- Conwayの観点を「現在の組織図を固定する法則」と誤解すると何が起きるか。
- DORAのloosely coupledをdependencyゼロではなく自律的deliveryとして測るには何を見るか。
- cognitive loadのassignedとcapacityを、個人の能力評価からどう分離するか。
- SLO breach後にteam interfaceを更新するowner、evidence、終了条件を挙げよ。
出典と次の学習
DORA Capability: Loosely coupled teams、ConwayのHow Do Committees Invent?、Team Topologies: Key concepts、ISO/IEC/IEEE 42010:2022を、delivery autonomy、communication structure、team interaction、architecture descriptionの根拠として用いた。
次は自分のdelivery flowからteam間dependencyを一つ選び、owner、owned capability、cognitive load、SLO targetとobserved、enablement actionを同じteam interfaceへ書く。別teamとreviewし、latencyだけを変えたとき判断が更新されるか検証する。
実践ラボ
checkoutとplatformのteam interfaceをSLOで再評価する
提出成果物: 所有権、依存、認知負荷、SLOを含むチームインターフェース
- simulated team snapshot、canonical team order、dependency contextのschemaと限界を固定する
- checkoutのowner、owned capability、platform dependency、cognitive loadを可視化する
- dependency latencyとSLO targetからbaselineのstatusを導く
- receiving teamを助けるplatformのenablement actionとreview evidenceを記録する
- dependency latencyだけを変えて同じteam snapshotのSLO statusとenablement判断を再評価する
説明して理解を確かめる
5分で、DORAのloosely coupled teams、Conwayのcommunication structure、Team Topologiesのteam interactionとcognitive loadを、変更可能なteam interfaceへどう結ぶか説明する。
アセスメント
問い: checkoutのdeliveryがplatform待ちで遅い。checkout memberの速度だけを改善すべきか。
期待する証拠: ownership、dependency latency、SLO、interaction mode、cognitive load、enablement action
問い: 全員が共有serviceを所有しているのでhandoffはないという主張を評価せよ。
期待する証拠: 明示的owner、decision rights、support boundary、SLO、escalation、system outcome
別問題へ転用する
依存サービスのlatencyだけを変え、同じteam ownershipとcognitive load境界でSLOとenablement判断を再評価する
復習スケジュール
- 1日後
ownershipを担当者名ではなくcapability境界で示す理由は何か
- 7日後
cognitive loadがcapacity以下でもdependencyがdeliveryを止める例は何か
- 30日後
SLO statusが変わったときteam interfaceのどこを再評価するか
- 90日後
ownershipを担当者名ではなくcapability境界で示す理由は何か
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | team名とservice名だけを並べる | ownershipとdependencyはあるがcognitive load、SLO、観測値が結び付かない | team snapshotとcontextからowner、dependency、load、SLO statusを一貫して導く | schema、順序、重複、snapshot drift、causal bypassをfail closedで検出できる |
| judgment | 遅延を個人の生産性問題と断定する | 組織課題を挙げるが境界変更かenablementかを選ばない | dependency、load、SLO、変更頻度、可逆性からinteractionを選ぶ | 一時的collaborationから長期API境界へ移る条件と終了条件を設計する |
| evidence | 組織図だけをarchitecture evidenceにする | SLO targetはあるがobserved latencyとteam snapshotの出自がない | 固定入力、ownership、load、target、observed、status、transferを監査可能に残す | deliveryとreliabilityの実績でteam interfaceを継続校正できる |
| communication | 担当者へ個別に聞くことをinterfaceとする | 窓口はあるが期待値、期限、escalationが曖昧である | owner、capability、dependency、SLO、enablement actionを一つの契約として伝える | 別teamが同期meetingなしで安全に変更とreviewを進められる |
出典
以下の外部資料は利用者が選択したときだけ開きます。