Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a35cade49c | ||
|
|
ef169cec58 | ||
|
|
e0afa78eb2 | ||
|
|
e087adab1e | ||
|
|
325363d89c | ||
|
|
ad7312b65a | ||
|
|
3238e1d809 | ||
|
|
fe4c3509d1 | ||
|
|
8a7dbea852 |
No files matched your search
@@ -2,138 +2,244 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Learn before expanding",
|
||||
"title": "TO-BE: Adoption",
|
||||
"output": "static/diagrams/strategy-adoption-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
880,
|
||||
2040
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "choose",
|
||||
"type": "backend",
|
||||
"label": "Choose workflow",
|
||||
"sublabel": "Department outcome",
|
||||
"id": "delivery",
|
||||
"type": "external",
|
||||
"label": "Delivery first",
|
||||
"tag": "Start with a real workflow",
|
||||
"sublabel": "Baseline + bounded canary",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
120
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "coach",
|
||||
"id": "human",
|
||||
"type": "backend",
|
||||
"label": "Coach people",
|
||||
"sublabel": "Verify + recover",
|
||||
"label": "Human",
|
||||
"tag": "Direct + guide + review",
|
||||
"sublabel": "Define outcome; inspect artifacts",
|
||||
"pos": [
|
||||
20,
|
||||
140
|
||||
60,
|
||||
1280
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "canary",
|
||||
"type": "backend",
|
||||
"label": "Run bounded canary",
|
||||
"sublabel": "Qualified skill + client",
|
||||
"id": "agent",
|
||||
"type": "external",
|
||||
"label": "AI Agent",
|
||||
"tag": "Supported client + approved skills",
|
||||
"sublabel": "Produce work within the boundary",
|
||||
"pos": [
|
||||
20,
|
||||
260
|
||||
60,
|
||||
910
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "accept",
|
||||
"type": "backend",
|
||||
"label": "Accept real work",
|
||||
"sublabel": "Independent review",
|
||||
"id": "learn",
|
||||
"type": "external",
|
||||
"label": "Learn from real work",
|
||||
"tag": "Ongoing, reviewed improvement",
|
||||
"sublabel": "Feedback + friction + measures\nRetain a manual path\nNo quotas or rankings",
|
||||
"pos": [
|
||||
20,
|
||||
380
|
||||
60,
|
||||
490
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "feedback",
|
||||
"type": "backend",
|
||||
"label": "Review feedback",
|
||||
"sublabel": "Usefulness + non-use",
|
||||
"pos": [
|
||||
20,
|
||||
500
|
||||
740,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "expand",
|
||||
"type": "backend",
|
||||
"label": "Expand or revise",
|
||||
"sublabel": "Keep a manual path",
|
||||
"label": "Expand on evidence",
|
||||
"tag": "Other SDLC phases",
|
||||
"sublabel": "Later order undecided\nDelivery keeps improving",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
60,
|
||||
1650
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
240
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "choose",
|
||||
"to": "coach",
|
||||
"label": "Prepare"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "coach",
|
||||
"to": "canary",
|
||||
"label": "support pilot"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "canary",
|
||||
"to": "accept",
|
||||
"label": "Verify"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "accept",
|
||||
"to": "feedback",
|
||||
"label": "learn"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "feedback",
|
||||
"to": "expand",
|
||||
"label": "decide"
|
||||
},
|
||||
{
|
||||
"id": "feedback-loop",
|
||||
"from": "expand",
|
||||
"to": "coach",
|
||||
"label": "Review revision",
|
||||
"variant": "dashed",
|
||||
"id": "delivery-agent",
|
||||
"from": "delivery",
|
||||
"to": "agent",
|
||||
"label": "Practice",
|
||||
"fromSide": "right",
|
||||
"toSide": "right"
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
230.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
1020.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
780,
|
||||
870
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "human-agent",
|
||||
"from": "human",
|
||||
"to": "agent",
|
||||
"label": "Direction",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
824,
|
||||
1390.0
|
||||
],
|
||||
[
|
||||
824,
|
||||
1020.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
770,
|
||||
1240
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "agent-human",
|
||||
"from": "agent",
|
||||
"to": "human",
|
||||
"label": "Work to inspect",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1185
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "agent-learn",
|
||||
"from": "agent",
|
||||
"to": "learn",
|
||||
"label": "Observe",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
815
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "learn-delivery",
|
||||
"from": "learn",
|
||||
"to": "delivery",
|
||||
"label": "Improve",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
42,
|
||||
625.0
|
||||
],
|
||||
[
|
||||
42,
|
||||
230.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
80
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "human-expand",
|
||||
"from": "human",
|
||||
"to": "expand",
|
||||
"label": "Accepted work",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1555
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "learn-expand",
|
||||
"from": "learn",
|
||||
"to": "expand",
|
||||
"label": "Feedback",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
20,
|
||||
625.0
|
||||
],
|
||||
[
|
||||
20,
|
||||
1770.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
1610
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,138 +2,242 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Learn before expanding",
|
||||
"title": "TO-BE: Adoption",
|
||||
"output": "static/diagrams/strategy-adoption/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
2040,
|
||||
1160
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "choose",
|
||||
"type": "backend",
|
||||
"label": "Choose workflow",
|
||||
"sublabel": "Department outcome",
|
||||
"id": "delivery",
|
||||
"type": "external",
|
||||
"label": "Delivery first",
|
||||
"tag": "Start with a real workflow",
|
||||
"sublabel": "Baseline + bounded canary",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
350
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
470,
|
||||
210
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "coach",
|
||||
"id": "human",
|
||||
"type": "backend",
|
||||
"label": "Coach people",
|
||||
"sublabel": "Verify + recover",
|
||||
"label": "Human",
|
||||
"tag": "Direct + guide + review",
|
||||
"sublabel": "Define outcome; inspect artifacts",
|
||||
"pos": [
|
||||
260,
|
||||
20
|
||||
700,
|
||||
90
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
570,
|
||||
190
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "canary",
|
||||
"type": "backend",
|
||||
"label": "Run bounded canary",
|
||||
"sublabel": "Qualified skill + client",
|
||||
"id": "agent",
|
||||
"type": "external",
|
||||
"label": "AI Agent",
|
||||
"tag": "Supported client + approved skills",
|
||||
"sublabel": "Produce work within the boundary",
|
||||
"pos": [
|
||||
260,
|
||||
150
|
||||
700,
|
||||
440
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
570,
|
||||
190
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "accept",
|
||||
"type": "backend",
|
||||
"label": "Accept real work",
|
||||
"sublabel": "Independent review",
|
||||
"id": "learn",
|
||||
"type": "external",
|
||||
"label": "Learn from real work",
|
||||
"tag": "Ongoing, reviewed improvement",
|
||||
"sublabel": "Feedback + friction + measures\nRetain a manual path\nNo quotas or rankings",
|
||||
"pos": [
|
||||
20,
|
||||
150
|
||||
700,
|
||||
800
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "feedback",
|
||||
"type": "backend",
|
||||
"label": "Review feedback",
|
||||
"sublabel": "Usefulness + non-use",
|
||||
"pos": [
|
||||
20,
|
||||
280
|
||||
570,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "expand",
|
||||
"type": "backend",
|
||||
"label": "Expand or revise",
|
||||
"sublabel": "Keep a manual path",
|
||||
"label": "Expand on evidence",
|
||||
"tag": "Other SDLC phases",
|
||||
"sublabel": "Later order undecided\nDelivery keeps improving",
|
||||
"pos": [
|
||||
260,
|
||||
280
|
||||
1460,
|
||||
360
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
520,
|
||||
240
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "choose",
|
||||
"to": "coach",
|
||||
"label": "Prepare"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "coach",
|
||||
"to": "canary",
|
||||
"label": "support pilot"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "canary",
|
||||
"to": "accept",
|
||||
"label": "Verify"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "accept",
|
||||
"to": "feedback",
|
||||
"label": "learn"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "feedback",
|
||||
"to": "expand",
|
||||
"label": "decide"
|
||||
},
|
||||
{
|
||||
"id": "feedback-loop",
|
||||
"from": "expand",
|
||||
"to": "coach",
|
||||
"label": "Review revision",
|
||||
"variant": "dashed",
|
||||
"id": "delivery-agent",
|
||||
"from": "delivery",
|
||||
"to": "agent",
|
||||
"label": "Practice",
|
||||
"fromSide": "right",
|
||||
"toSide": "right"
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
585,
|
||||
455
|
||||
],
|
||||
[
|
||||
585,
|
||||
535
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
570,
|
||||
600
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "human-agent",
|
||||
"from": "human",
|
||||
"to": "agent",
|
||||
"label": "Direction",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
1100,
|
||||
360
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "agent-human",
|
||||
"from": "agent",
|
||||
"to": "human",
|
||||
"label": "Work to inspect",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
630,
|
||||
535
|
||||
],
|
||||
[
|
||||
630,
|
||||
185
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
470,
|
||||
240
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "agent-learn",
|
||||
"from": "agent",
|
||||
"to": "learn",
|
||||
"label": "Observe",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
1100,
|
||||
720
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "learn-delivery",
|
||||
"from": "learn",
|
||||
"to": "delivery",
|
||||
"label": "Improve",
|
||||
"fromSide": "left",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
295,
|
||||
935
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
450,
|
||||
900
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "human-expand",
|
||||
"from": "human",
|
||||
"to": "expand",
|
||||
"label": "Accepted work",
|
||||
"fromSide": "right",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1720,
|
||||
185
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1550,
|
||||
140
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "learn-expand",
|
||||
"from": "learn",
|
||||
"to": "expand",
|
||||
"label": "Feedback",
|
||||
"fromSide": "right",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1720,
|
||||
935
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1480,
|
||||
895
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,246 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Independent approval",
|
||||
"title": "TO-BE: Governance",
|
||||
"output": "static/diagrams/strategy-governance-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
880,
|
||||
2710
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "owner",
|
||||
"type": "security",
|
||||
"label": "Owner proposes",
|
||||
"sublabel": "Existing accountable team",
|
||||
"type": "external",
|
||||
"label": "01 Propose",
|
||||
"tag": "Owning team",
|
||||
"sublabel": "Scope + testable outcome",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
120
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "review",
|
||||
"type": "security",
|
||||
"label": "Independent review",
|
||||
"sublabel": "Quality + risk evidence",
|
||||
"type": "external",
|
||||
"label": "02 Review independently",
|
||||
"tag": "Quality + Security",
|
||||
"sublabel": "Author is not sole approver",
|
||||
"pos": [
|
||||
20,
|
||||
140
|
||||
60,
|
||||
490
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "canary",
|
||||
"type": "security",
|
||||
"label": "Bounded canary",
|
||||
"sublabel": "Qualified client path",
|
||||
"type": "external",
|
||||
"label": "03 Qualify real use",
|
||||
"tag": "Human + AI Agent",
|
||||
"sublabel": "Exact client + bounded canary",
|
||||
"pos": [
|
||||
20,
|
||||
260
|
||||
60,
|
||||
860
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "approve",
|
||||
"type": "security",
|
||||
"label": "Approve revision",
|
||||
"sublabel": "Exact digest + scope",
|
||||
"type": "backend",
|
||||
"label": "04 Approve exact scope",
|
||||
"tag": "Accountable approvers",
|
||||
"sublabel": "Digest + permitted uses + expiry",
|
||||
"pos": [
|
||||
20,
|
||||
380
|
||||
60,
|
||||
1230
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "publish",
|
||||
"type": "security",
|
||||
"label": "Publish bundle",
|
||||
"sublabel": "Data & AI catalog",
|
||||
"type": "backend",
|
||||
"label": "05 Publish the bundle",
|
||||
"tag": "Data & AI catalog",
|
||||
"sublabel": "Pin skill, adapter + dependencies",
|
||||
"pos": [
|
||||
20,
|
||||
500
|
||||
60,
|
||||
1600
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "distribute",
|
||||
"type": "security",
|
||||
"label": "Verify effective state",
|
||||
"sublabel": "Platform & IT",
|
||||
"type": "backend",
|
||||
"label": "06 Verify loaded state",
|
||||
"tag": "Platform & IT",
|
||||
"sublabel": "Read revision; verify reload",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
60,
|
||||
1970
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "withdraw",
|
||||
"type": "security",
|
||||
"label": "Withdraw / roll back",
|
||||
"tag": "Unsafe revision",
|
||||
"sublabel": "Revoke; restore last-known-good",
|
||||
"pos": [
|
||||
60,
|
||||
2340
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"id": "owner-review",
|
||||
"from": "owner",
|
||||
"to": "review",
|
||||
"label": "submit"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
395
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"id": "review-canary",
|
||||
"from": "review",
|
||||
"to": "canary",
|
||||
"label": "qualify"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
765
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"id": "canary-approve",
|
||||
"from": "canary",
|
||||
"to": "approve",
|
||||
"label": "Accept"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1135
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"id": "approve-publish",
|
||||
"from": "approve",
|
||||
"to": "publish",
|
||||
"label": "record approval"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1505
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"id": "publish-distribute",
|
||||
"from": "publish",
|
||||
"to": "distribute",
|
||||
"label": "Deliver"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1875
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "approve-owner",
|
||||
"from": "approve",
|
||||
"to": "owner",
|
||||
"label": "Rework",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"via": [
|
||||
[
|
||||
20,
|
||||
1340.0
|
||||
],
|
||||
[
|
||||
20,
|
||||
230.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
80
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "distribute-withdraw",
|
||||
"from": "distribute",
|
||||
"to": "withdraw",
|
||||
"label": "Unsafe",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"labelAt": [
|
||||
610,
|
||||
2245
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,276 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Independent approval",
|
||||
"title": "TO-BE: Governance",
|
||||
"output": "static/diagrams/strategy-governance/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
1760,
|
||||
1420
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "owner",
|
||||
"type": "security",
|
||||
"label": "Owner proposes",
|
||||
"sublabel": "Existing accountable team",
|
||||
"type": "external",
|
||||
"label": "01 Propose",
|
||||
"tag": "Owning team",
|
||||
"sublabel": "Scope + testable outcome",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
80,
|
||||
60
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "review",
|
||||
"type": "security",
|
||||
"label": "Independent review",
|
||||
"sublabel": "Quality + risk evidence",
|
||||
"type": "external",
|
||||
"label": "02 Review independently",
|
||||
"tag": "Quality + Security",
|
||||
"sublabel": "Author is not sole approver",
|
||||
"pos": [
|
||||
260,
|
||||
20
|
||||
300,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "canary",
|
||||
"type": "security",
|
||||
"label": "Bounded canary",
|
||||
"sublabel": "Qualified client path",
|
||||
"type": "external",
|
||||
"label": "03 Qualify real use",
|
||||
"tag": "Human + AI Agent",
|
||||
"sublabel": "Exact client + bounded canary",
|
||||
"pos": [
|
||||
260,
|
||||
150
|
||||
520,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "approve",
|
||||
"type": "security",
|
||||
"label": "Approve revision",
|
||||
"sublabel": "Exact digest + scope",
|
||||
"type": "backend",
|
||||
"label": "04 Approve exact scope",
|
||||
"tag": "Accountable approvers",
|
||||
"sublabel": "Digest + permitted uses + expiry",
|
||||
"pos": [
|
||||
20,
|
||||
150
|
||||
740,
|
||||
720
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "publish",
|
||||
"type": "security",
|
||||
"label": "Publish bundle",
|
||||
"sublabel": "Data & AI catalog",
|
||||
"type": "backend",
|
||||
"label": "05 Publish the bundle",
|
||||
"tag": "Data & AI catalog",
|
||||
"sublabel": "Pin skill, adapter + dependencies",
|
||||
"pos": [
|
||||
20,
|
||||
280
|
||||
960,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "distribute",
|
||||
"type": "security",
|
||||
"label": "Verify effective state",
|
||||
"sublabel": "Platform & IT",
|
||||
"type": "backend",
|
||||
"label": "06 Verify loaded state",
|
||||
"tag": "Platform & IT",
|
||||
"sublabel": "Read revision; verify reload",
|
||||
"pos": [
|
||||
260,
|
||||
280
|
||||
1180,
|
||||
1160
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "withdraw",
|
||||
"type": "security",
|
||||
"label": "Withdraw / roll back",
|
||||
"tag": "Unsafe revision",
|
||||
"sublabel": "Revoke; restore last-known-good",
|
||||
"pos": [
|
||||
80,
|
||||
1160
|
||||
],
|
||||
"size": [
|
||||
660,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"id": "owner-review",
|
||||
"from": "owner",
|
||||
"to": "review",
|
||||
"label": "submit"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
330,
|
||||
255
|
||||
],
|
||||
[
|
||||
550,
|
||||
255
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"id": "review-canary",
|
||||
"from": "review",
|
||||
"to": "canary",
|
||||
"label": "qualify"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
550,
|
||||
475
|
||||
],
|
||||
[
|
||||
770,
|
||||
475
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"id": "canary-approve",
|
||||
"from": "canary",
|
||||
"to": "approve",
|
||||
"label": "Accept"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
770,
|
||||
695
|
||||
],
|
||||
[
|
||||
990,
|
||||
695
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"id": "approve-publish",
|
||||
"from": "approve",
|
||||
"to": "publish",
|
||||
"label": "record approval"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
990,
|
||||
915
|
||||
],
|
||||
[
|
||||
1210,
|
||||
915
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"id": "publish-distribute",
|
||||
"from": "publish",
|
||||
"to": "distribute",
|
||||
"label": "Deliver"
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1210,
|
||||
1135
|
||||
],
|
||||
[
|
||||
1430,
|
||||
1135
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "approve-owner",
|
||||
"from": "approve",
|
||||
"to": "owner",
|
||||
"label": "Rework",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"via": [
|
||||
[
|
||||
40,
|
||||
805
|
||||
],
|
||||
[
|
||||
40,
|
||||
145
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
180,
|
||||
680
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "distribute-withdraw",
|
||||
"from": "distribute",
|
||||
"to": "withdraw",
|
||||
"label": "Unsafe",
|
||||
"fromSide": "left",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"labelAt": [
|
||||
950,
|
||||
1220
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,140 +2,337 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Maintain trust over time",
|
||||
"title": "TO-BE: Maintenance",
|
||||
"output": "static/diagrams/strategy-maintenance-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
880,
|
||||
3080
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "monitor",
|
||||
"type": "backend",
|
||||
"label": "Monitor skills",
|
||||
"sublabel": "Usefulness + safety",
|
||||
"id": "observe",
|
||||
"type": "external",
|
||||
"label": "Observe use",
|
||||
"tag": "Owner + Data & AI",
|
||||
"sublabel": "Feedback, drift + review due",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
120
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "triage",
|
||||
"type": "backend",
|
||||
"label": "Triage changes",
|
||||
"sublabel": "Owner + risk review",
|
||||
"type": "external",
|
||||
"label": "Triage the signal",
|
||||
"tag": "Owning team",
|
||||
"sublabel": "Useful? Safe? Still needed?",
|
||||
"pos": [
|
||||
20,
|
||||
140
|
||||
60,
|
||||
1970
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "correct",
|
||||
"type": "backend",
|
||||
"label": "Correct and test",
|
||||
"sublabel": "Independent regression",
|
||||
"id": "change",
|
||||
"type": "external",
|
||||
"label": "Change the skill",
|
||||
"tag": "Reviewed correction",
|
||||
"sublabel": "New immutable revision",
|
||||
"pos": [
|
||||
20,
|
||||
260
|
||||
60,
|
||||
1230
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "decision",
|
||||
"type": "backend",
|
||||
"label": "Approve or hold",
|
||||
"sublabel": "Exact revised bundle",
|
||||
"id": "validate",
|
||||
"type": "external",
|
||||
"label": "Revalidate",
|
||||
"tag": "Independent reviewers",
|
||||
"sublabel": "Regression + client evidence",
|
||||
"pos": [
|
||||
20,
|
||||
380
|
||||
60,
|
||||
860
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "promote",
|
||||
"id": "release",
|
||||
"type": "backend",
|
||||
"label": "Promote safely",
|
||||
"sublabel": "Verify state + monitor",
|
||||
"label": "Approve + version",
|
||||
"tag": "Accountable human authority",
|
||||
"sublabel": "Verify distribution and recovery",
|
||||
"pos": [
|
||||
20,
|
||||
500
|
||||
60,
|
||||
490
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "observe",
|
||||
"id": "safe",
|
||||
"type": "backend",
|
||||
"label": "Quarantine / rollback",
|
||||
"sublabel": "Revoke unsafe revision",
|
||||
"label": "Continue safe use",
|
||||
"tag": "Substantive review passes",
|
||||
"sublabel": "Keep owner + next review date",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
60,
|
||||
2710
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "retire",
|
||||
"type": "external",
|
||||
"label": "Retire deliberately",
|
||||
"tag": "No longer useful",
|
||||
"sublabel": "Reconcile consumers; no new use",
|
||||
"pos": [
|
||||
60,
|
||||
2340
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "contain",
|
||||
"type": "security",
|
||||
"label": "Contain unsafe use",
|
||||
"tag": "Security + Platform & IT",
|
||||
"sublabel": "Revoke + quarantine + roll back\nNo automatic reinstatement",
|
||||
"pos": [
|
||||
60,
|
||||
1600
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "monitor",
|
||||
"to": "triage",
|
||||
"label": "Detect"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "triage",
|
||||
"to": "correct",
|
||||
"label": "scope correction"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "correct",
|
||||
"to": "decision",
|
||||
"label": "Prove"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "decision",
|
||||
"to": "promote",
|
||||
"label": "authorize"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "unsafe",
|
||||
"from": "triage",
|
||||
"to": "observe",
|
||||
"label": "Unsafe",
|
||||
"variant": "security",
|
||||
"fromSide": "right",
|
||||
"toSide": "right"
|
||||
},
|
||||
{
|
||||
"id": "review-loop",
|
||||
"id": "observe-triage",
|
||||
"from": "observe",
|
||||
"to": "correct",
|
||||
"label": "Requalify",
|
||||
"variant": "dashed"
|
||||
"to": "triage",
|
||||
"label": "",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
20,
|
||||
230.0
|
||||
],
|
||||
[
|
||||
20,
|
||||
2080.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
1930
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-change",
|
||||
"from": "triage",
|
||||
"to": "change",
|
||||
"label": "Correct",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
2080.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
1340.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
780,
|
||||
1560
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "change-validate",
|
||||
"from": "change",
|
||||
"to": "validate",
|
||||
"label": "",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1135
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "validate-release",
|
||||
"from": "validate",
|
||||
"to": "release",
|
||||
"label": "",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
765
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "release-observe",
|
||||
"from": "release",
|
||||
"to": "observe",
|
||||
"label": "",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
395
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-safe",
|
||||
"from": "triage",
|
||||
"to": "safe",
|
||||
"label": "Safe",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
824,
|
||||
2080.0
|
||||
],
|
||||
[
|
||||
824,
|
||||
2820.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
2670
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-retire",
|
||||
"from": "triage",
|
||||
"to": "retire",
|
||||
"label": "Retire",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
2245
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-contain",
|
||||
"from": "triage",
|
||||
"to": "contain",
|
||||
"label": "Unsafe",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"labelAt": [
|
||||
610,
|
||||
1875
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "contain-change",
|
||||
"from": "contain",
|
||||
"to": "change",
|
||||
"label": "Reviewed fix",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"labelAt": [
|
||||
610,
|
||||
1505
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "safe-observe",
|
||||
"from": "safe",
|
||||
"to": "observe",
|
||||
"label": "Keep watching",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
42,
|
||||
2820.0
|
||||
],
|
||||
[
|
||||
42,
|
||||
230.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
80
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,140 +2,369 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Maintain trust over time",
|
||||
"title": "TO-BE: Maintenance",
|
||||
"output": "static/diagrams/strategy-maintenance/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
2630,
|
||||
1300
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "monitor",
|
||||
"type": "backend",
|
||||
"label": "Monitor skills",
|
||||
"sublabel": "Usefulness + safety",
|
||||
"id": "observe",
|
||||
"type": "external",
|
||||
"label": "Observe use",
|
||||
"tag": "Owner + Data & AI",
|
||||
"sublabel": "Feedback, drift + review due",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
670,
|
||||
60
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
560,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "triage",
|
||||
"type": "backend",
|
||||
"label": "Triage changes",
|
||||
"sublabel": "Owner + risk review",
|
||||
"type": "external",
|
||||
"label": "Triage the signal",
|
||||
"tag": "Owning team",
|
||||
"sublabel": "Useful? Safe? Still needed?",
|
||||
"pos": [
|
||||
260,
|
||||
20
|
||||
1340,
|
||||
330
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
500,
|
||||
190
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "correct",
|
||||
"type": "backend",
|
||||
"label": "Correct and test",
|
||||
"sublabel": "Independent regression",
|
||||
"id": "change",
|
||||
"type": "external",
|
||||
"label": "Change the skill",
|
||||
"tag": "Reviewed correction",
|
||||
"sublabel": "New immutable revision",
|
||||
"pos": [
|
||||
260,
|
||||
150
|
||||
1240,
|
||||
750
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
520,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "decision",
|
||||
"type": "backend",
|
||||
"label": "Approve or hold",
|
||||
"sublabel": "Exact revised bundle",
|
||||
"id": "validate",
|
||||
"type": "external",
|
||||
"label": "Revalidate",
|
||||
"tag": "Independent reviewers",
|
||||
"sublabel": "Regression + client evidence",
|
||||
"pos": [
|
||||
20,
|
||||
150
|
||||
560,
|
||||
970
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
560,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "promote",
|
||||
"id": "release",
|
||||
"type": "backend",
|
||||
"label": "Promote safely",
|
||||
"sublabel": "Verify state + monitor",
|
||||
"label": "Approve + version",
|
||||
"tag": "Accountable human authority",
|
||||
"sublabel": "Verify distribution and recovery",
|
||||
"pos": [
|
||||
20,
|
||||
280
|
||||
70,
|
||||
570
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
540,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "observe",
|
||||
"id": "safe",
|
||||
"type": "backend",
|
||||
"label": "Quarantine / rollback",
|
||||
"sublabel": "Revoke unsafe revision",
|
||||
"label": "Continue safe use",
|
||||
"tag": "Substantive review passes",
|
||||
"sublabel": "Keep owner + next review date",
|
||||
"pos": [
|
||||
260,
|
||||
280
|
||||
2010,
|
||||
80
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
550,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "retire",
|
||||
"type": "external",
|
||||
"label": "Retire deliberately",
|
||||
"tag": "No longer useful",
|
||||
"sublabel": "Reconcile consumers; no new use",
|
||||
"pos": [
|
||||
2010,
|
||||
430
|
||||
],
|
||||
"size": [
|
||||
550,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "contain",
|
||||
"type": "security",
|
||||
"label": "Contain unsafe use",
|
||||
"tag": "Security + Platform & IT",
|
||||
"sublabel": "Revoke + quarantine + roll back\nNo automatic reinstatement",
|
||||
"pos": [
|
||||
2010,
|
||||
800
|
||||
],
|
||||
"size": [
|
||||
550,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "monitor",
|
||||
"to": "triage",
|
||||
"label": "Detect"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "triage",
|
||||
"to": "correct",
|
||||
"label": "scope correction"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "correct",
|
||||
"to": "decision",
|
||||
"label": "Prove"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "decision",
|
||||
"to": "promote",
|
||||
"label": "authorize"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "unsafe",
|
||||
"from": "triage",
|
||||
"to": "observe",
|
||||
"label": "Unsafe",
|
||||
"variant": "security",
|
||||
"fromSide": "right",
|
||||
"toSide": "right"
|
||||
},
|
||||
{
|
||||
"id": "review-loop",
|
||||
"id": "observe-triage",
|
||||
"from": "observe",
|
||||
"to": "correct",
|
||||
"label": "Requalify",
|
||||
"variant": "dashed"
|
||||
"to": "triage",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1590,
|
||||
145
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-change",
|
||||
"from": "triage",
|
||||
"to": "change",
|
||||
"label": "Correct",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1590,
|
||||
620
|
||||
],
|
||||
[
|
||||
1500,
|
||||
620
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1450,
|
||||
680
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "change-validate",
|
||||
"from": "change",
|
||||
"to": "validate",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1500,
|
||||
1060
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "validate-release",
|
||||
"from": "validate",
|
||||
"to": "release",
|
||||
"label": "",
|
||||
"fromSide": "left",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
340,
|
||||
1060
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "release-observe",
|
||||
"from": "release",
|
||||
"to": "observe",
|
||||
"label": "",
|
||||
"fromSide": "top",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
340,
|
||||
145
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-safe",
|
||||
"from": "triage",
|
||||
"to": "safe",
|
||||
"label": "Safe",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1920,
|
||||
425
|
||||
],
|
||||
[
|
||||
1920,
|
||||
170
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1930,
|
||||
210
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-retire",
|
||||
"from": "triage",
|
||||
"to": "retire",
|
||||
"label": "Retire",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1950,
|
||||
425
|
||||
],
|
||||
[
|
||||
1950,
|
||||
520
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1900,
|
||||
490
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "triage-contain",
|
||||
"from": "triage",
|
||||
"to": "contain",
|
||||
"label": "Unsafe",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"via": [
|
||||
[
|
||||
1590,
|
||||
580
|
||||
],
|
||||
[
|
||||
1850,
|
||||
580
|
||||
],
|
||||
[
|
||||
1850,
|
||||
910
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1900,
|
||||
720
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "contain-change",
|
||||
"from": "contain",
|
||||
"to": "change",
|
||||
"label": "Reviewed fix",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "bottom",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"via": [
|
||||
[
|
||||
2285,
|
||||
1230
|
||||
],
|
||||
[
|
||||
1500,
|
||||
1230
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1900,
|
||||
1190
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "safe-observe",
|
||||
"from": "safe",
|
||||
"to": "observe",
|
||||
"label": "Keep watching",
|
||||
"fromSide": "top",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
2285,
|
||||
20
|
||||
],
|
||||
[
|
||||
950,
|
||||
20
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1640,
|
||||
45
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,313 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Measure skills adoption",
|
||||
"title": "TO-BE: Measurement",
|
||||
"output": "static/diagrams/strategy-measurement-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
880,
|
||||
3630
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "collect",
|
||||
"type": "backend",
|
||||
"label": "Establish baseline",
|
||||
"sublabel": "Current workflow",
|
||||
"id": "success-source",
|
||||
"type": "external",
|
||||
"label": "Tracker + acceptance",
|
||||
"tag": "Matured task starts",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
120
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "validate",
|
||||
"type": "backend",
|
||||
"label": "Join evidence",
|
||||
"sublabel": "Tasks / versions / costs",
|
||||
"id": "success",
|
||||
"type": "frontend",
|
||||
"label": "Accepted / eligible starts \u00d7 100",
|
||||
"tag": "Task success (%)",
|
||||
"sublabel": "Failures + unresolved retained",
|
||||
"pos": [
|
||||
20,
|
||||
140
|
||||
60,
|
||||
490
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "calculate",
|
||||
"type": "backend",
|
||||
"label": "Measure the pilot",
|
||||
"sublabel": "Outcomes / quality",
|
||||
"id": "quality-source",
|
||||
"type": "external",
|
||||
"label": "Changes + defects",
|
||||
"tag": "Completed follow-up",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
20,
|
||||
260
|
||||
60,
|
||||
860
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "balance",
|
||||
"type": "backend",
|
||||
"label": "Report together",
|
||||
"sublabel": "Cost / confidence / gaps",
|
||||
"id": "quality",
|
||||
"type": "frontend",
|
||||
"label": "Affected / accepted changes \u00d7 100",
|
||||
"tag": "Escaped quality (%)",
|
||||
"sublabel": "Count each affected change once",
|
||||
"pos": [
|
||||
20,
|
||||
380
|
||||
60,
|
||||
1230
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "weigh",
|
||||
"type": "backend",
|
||||
"label": "Broader adoption",
|
||||
"sublabel": "Availability + use",
|
||||
"id": "cost-source",
|
||||
"type": "external",
|
||||
"label": "Invoices + review minutes",
|
||||
"tag": "Include failed attempts",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
20,
|
||||
500
|
||||
60,
|
||||
1600
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "decide",
|
||||
"type": "backend",
|
||||
"label": "Ongoing monitoring",
|
||||
"sublabel": "Review + maintenance",
|
||||
"id": "cost",
|
||||
"type": "frontend",
|
||||
"label": "Allocated cost / accepted tasks",
|
||||
"tag": "Cost per accepted outcome",
|
||||
"sublabel": "Missing buckets = partial",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
60,
|
||||
1970
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "confidence-source",
|
||||
"type": "external",
|
||||
"label": "Anonymous 1\u20135 survey",
|
||||
"tag": "Protect small groups",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
60,
|
||||
2340
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "confidence",
|
||||
"type": "frontend",
|
||||
"label": "Confidence sum / valid responses",
|
||||
"tag": "Verification confidence (1\u20135)",
|
||||
"sublabel": "Show distribution + response rate",
|
||||
"pos": [
|
||||
60,
|
||||
2710
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "interpret",
|
||||
"type": "backend",
|
||||
"label": "Compare with baseline",
|
||||
"tag": "Interpret, then act",
|
||||
"sublabel": "Like workflows + source coverage\nZero denominator = N/A\nTrends are not causal ROI\nFix gaps; improve the workflow",
|
||||
"pos": [
|
||||
60,
|
||||
3080
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
400
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "collect",
|
||||
"to": "validate",
|
||||
"label": "prepare"
|
||||
"id": "success-source-success",
|
||||
"from": "success-source",
|
||||
"to": "success",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
395
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "validate",
|
||||
"to": "calculate",
|
||||
"label": "collect"
|
||||
"id": "quality-source-quality",
|
||||
"from": "quality-source",
|
||||
"to": "quality",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1135
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "calculate",
|
||||
"to": "balance",
|
||||
"label": "calculate"
|
||||
"id": "cost-source-cost",
|
||||
"from": "cost-source",
|
||||
"to": "cost",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1875
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "balance",
|
||||
"to": "weigh",
|
||||
"label": "extend"
|
||||
"id": "confidence-source-confidence",
|
||||
"from": "confidence-source",
|
||||
"to": "confidence",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
2615
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "weigh",
|
||||
"to": "decide",
|
||||
"label": "repeat"
|
||||
"id": "success-interpret",
|
||||
"from": "success",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
600.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
3280.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
3040
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "quality-interpret",
|
||||
"from": "quality",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
1340.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
3280.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
3040
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cost-interpret",
|
||||
"from": "cost",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
2080.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
3280.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
3040
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "confidence-interpret",
|
||||
"from": "confidence",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
2985
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,291 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Measure skills adoption",
|
||||
"title": "TO-BE: Measurement",
|
||||
"output": "static/diagrams/strategy-measurement/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
2400,
|
||||
1120
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "collect",
|
||||
"type": "backend",
|
||||
"label": "Establish baseline",
|
||||
"sublabel": "Current workflow",
|
||||
"id": "success-source",
|
||||
"type": "external",
|
||||
"label": "Tracker + acceptance",
|
||||
"tag": "Matured task starts",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
50,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
580,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "validate",
|
||||
"type": "backend",
|
||||
"label": "Join evidence",
|
||||
"sublabel": "Tasks / versions / costs",
|
||||
"id": "success",
|
||||
"type": "frontend",
|
||||
"label": "Accepted / eligible starts \u00d7 100",
|
||||
"tag": "Task success (%)",
|
||||
"sublabel": "Failures + unresolved retained",
|
||||
"pos": [
|
||||
260,
|
||||
20
|
||||
770,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
700,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "calculate",
|
||||
"type": "backend",
|
||||
"label": "Measure the pilot",
|
||||
"sublabel": "Outcomes / quality",
|
||||
"id": "quality-source",
|
||||
"type": "external",
|
||||
"label": "Changes + defects",
|
||||
"tag": "Completed follow-up",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
260,
|
||||
150
|
||||
50,
|
||||
320
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
580,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "balance",
|
||||
"type": "backend",
|
||||
"label": "Report together",
|
||||
"sublabel": "Cost / confidence / gaps",
|
||||
"id": "quality",
|
||||
"type": "frontend",
|
||||
"label": "Affected / accepted changes \u00d7 100",
|
||||
"tag": "Escaped quality (%)",
|
||||
"sublabel": "Count each affected change once",
|
||||
"pos": [
|
||||
20,
|
||||
150
|
||||
770,
|
||||
320
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
700,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "weigh",
|
||||
"type": "backend",
|
||||
"label": "Broader adoption",
|
||||
"sublabel": "Availability + use",
|
||||
"id": "cost-source",
|
||||
"type": "external",
|
||||
"label": "Invoices + review minutes",
|
||||
"tag": "Include failed attempts",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
20,
|
||||
280
|
||||
50,
|
||||
570
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
580,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "decide",
|
||||
"type": "backend",
|
||||
"label": "Ongoing monitoring",
|
||||
"sublabel": "Review + maintenance",
|
||||
"id": "cost",
|
||||
"type": "frontend",
|
||||
"label": "Allocated cost / accepted tasks",
|
||||
"tag": "Cost per accepted outcome",
|
||||
"sublabel": "Missing buckets = partial",
|
||||
"pos": [
|
||||
260,
|
||||
280
|
||||
770,
|
||||
570
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
700,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "confidence-source",
|
||||
"type": "external",
|
||||
"label": "Anonymous 1\u20135 survey",
|
||||
"tag": "Protect small groups",
|
||||
"sublabel": "Join evidence to the workflow",
|
||||
"pos": [
|
||||
50,
|
||||
820
|
||||
],
|
||||
"size": [
|
||||
580,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "confidence",
|
||||
"type": "frontend",
|
||||
"label": "Confidence sum / valid responses",
|
||||
"tag": "Verification confidence (1\u20135)",
|
||||
"sublabel": "Show distribution + response rate",
|
||||
"pos": [
|
||||
770,
|
||||
820
|
||||
],
|
||||
"size": [
|
||||
700,
|
||||
180
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "interpret",
|
||||
"type": "backend",
|
||||
"label": "Compare with baseline",
|
||||
"tag": "Interpret, then act",
|
||||
"sublabel": "Like workflows + source coverage\nZero denominator = N/A\nTrends are not causal ROI\nFix gaps; improve the workflow",
|
||||
"pos": [
|
||||
1650,
|
||||
325
|
||||
],
|
||||
"size": [
|
||||
680,
|
||||
400
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "collect",
|
||||
"to": "validate",
|
||||
"label": "prepare"
|
||||
"id": "success-source-success",
|
||||
"from": "success-source",
|
||||
"to": "success",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "validate",
|
||||
"to": "calculate",
|
||||
"label": "collect"
|
||||
"id": "quality-source-quality",
|
||||
"from": "quality-source",
|
||||
"to": "quality",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "calculate",
|
||||
"to": "balance",
|
||||
"label": "calculate"
|
||||
"id": "cost-source-cost",
|
||||
"from": "cost-source",
|
||||
"to": "cost",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "balance",
|
||||
"to": "weigh",
|
||||
"label": "extend"
|
||||
"id": "confidence-source-confidence",
|
||||
"from": "confidence-source",
|
||||
"to": "confidence",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "weigh",
|
||||
"to": "decide",
|
||||
"label": "repeat"
|
||||
"id": "success-interpret",
|
||||
"from": "success",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1550,
|
||||
160
|
||||
],
|
||||
[
|
||||
1550,
|
||||
525
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "quality-interpret",
|
||||
"from": "quality",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1550,
|
||||
410
|
||||
],
|
||||
[
|
||||
1550,
|
||||
525
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cost-interpret",
|
||||
"from": "cost",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1550,
|
||||
660
|
||||
],
|
||||
[
|
||||
1550,
|
||||
525
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "confidence-interpret",
|
||||
"from": "confidence",
|
||||
"to": "interpret",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1550,
|
||||
910
|
||||
],
|
||||
[
|
||||
1550,
|
||||
525
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,261 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Risk-tiered qualification",
|
||||
"title": "TO-BE: Quality",
|
||||
"output": "static/diagrams/strategy-quality-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
880,
|
||||
2800
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "risk",
|
||||
"type": "security",
|
||||
"label": "Classify risk",
|
||||
"sublabel": "R1 / R2 / R3",
|
||||
"id": "selection",
|
||||
"type": "external",
|
||||
"label": "Right skill?",
|
||||
"tag": "Selection evidence",
|
||||
"sublabel": "Triggers, near misses, negatives",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
120
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "plan",
|
||||
"type": "security",
|
||||
"label": "Agree evaluation",
|
||||
"sublabel": "Task mix + thresholds",
|
||||
"id": "safety",
|
||||
"type": "external",
|
||||
"label": "Safe execution?",
|
||||
"tag": "Safety evidence",
|
||||
"sublabel": "Permissions + adversarial trials",
|
||||
"pos": [
|
||||
20,
|
||||
140
|
||||
60,
|
||||
490
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "trial",
|
||||
"type": "security",
|
||||
"label": "Repeat trials",
|
||||
"sublabel": "Candidate vs approved",
|
||||
"id": "artifact",
|
||||
"type": "external",
|
||||
"label": "Valid artifact?",
|
||||
"tag": "Independent inspection",
|
||||
"sublabel": "Tests + human assessment",
|
||||
"pos": [
|
||||
20,
|
||||
260
|
||||
60,
|
||||
860
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "verify",
|
||||
"type": "security",
|
||||
"label": "Verify artifacts",
|
||||
"sublabel": "Independent checks",
|
||||
"id": "outcome",
|
||||
"type": "external",
|
||||
"label": "Useful outcome?",
|
||||
"tag": "Workflow evidence",
|
||||
"sublabel": "Baseline + repeated trials",
|
||||
"pos": [
|
||||
20,
|
||||
380
|
||||
60,
|
||||
1230
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "gate",
|
||||
"type": "security",
|
||||
"label": "All gates pass?",
|
||||
"sublabel": "Safety cannot be traded",
|
||||
"label": "Every gate must pass",
|
||||
"tag": "Risk sets evaluation depth",
|
||||
"sublabel": "No averaging away harm\nCritical effect blocks promotion\nMissing coverage = incomplete",
|
||||
"pos": [
|
||||
20,
|
||||
500
|
||||
60,
|
||||
1600
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
310
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "promote",
|
||||
"type": "security",
|
||||
"label": "Promote or hold",
|
||||
"sublabel": "Owner decision",
|
||||
"id": "accept",
|
||||
"type": "backend",
|
||||
"label": "Accept evidence",
|
||||
"tag": "All mandatory gates passed",
|
||||
"sublabel": "Proceed to scoped approval",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
60,
|
||||
2060
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "rework",
|
||||
"type": "security",
|
||||
"label": "Rework / retest",
|
||||
"tag": "Any failure or missing evidence",
|
||||
"sublabel": "Keep failures in the record",
|
||||
"pos": [
|
||||
60,
|
||||
2430
|
||||
],
|
||||
"size": [
|
||||
740,
|
||||
220
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "risk",
|
||||
"to": "plan",
|
||||
"label": "Scope"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "plan",
|
||||
"to": "trial",
|
||||
"label": "freeze plan"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "trial",
|
||||
"to": "verify",
|
||||
"label": "Assess"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "verify",
|
||||
"id": "selection-gate",
|
||||
"from": "selection",
|
||||
"to": "gate",
|
||||
"label": "review evidence"
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
230.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
1755.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
1560
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"id": "safety-gate",
|
||||
"from": "safety",
|
||||
"to": "gate",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
600.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
1755.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
1560
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "artifact-gate",
|
||||
"from": "artifact",
|
||||
"to": "gate",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
850,
|
||||
970.0
|
||||
],
|
||||
[
|
||||
850,
|
||||
1755.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
1560
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "outcome-gate",
|
||||
"from": "outcome",
|
||||
"to": "gate",
|
||||
"label": "",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1505
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gate-accept",
|
||||
"from": "gate",
|
||||
"to": "promote",
|
||||
"label": "decide"
|
||||
"to": "accept",
|
||||
"label": "Pass",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"labelAt": [
|
||||
610,
|
||||
1965
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gate-rework",
|
||||
"from": "gate",
|
||||
"to": "rework",
|
||||
"label": "Fail",
|
||||
"fromSide": "right",
|
||||
"toSide": "right",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"via": [
|
||||
[
|
||||
824,
|
||||
1755.0
|
||||
],
|
||||
[
|
||||
824,
|
||||
2540.0
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
620,
|
||||
2390
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,265 @@
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: Risk-tiered qualification",
|
||||
"title": "TO-BE: Quality",
|
||||
"output": "static/diagrams/strategy-quality/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"viewBox": [
|
||||
2000,
|
||||
1050
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "risk",
|
||||
"type": "security",
|
||||
"label": "Classify risk",
|
||||
"sublabel": "R1 / R2 / R3",
|
||||
"id": "selection",
|
||||
"type": "external",
|
||||
"label": "Right skill?",
|
||||
"tag": "Selection evidence",
|
||||
"sublabel": "Triggers, near misses, negatives",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
60,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
510,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "plan",
|
||||
"type": "security",
|
||||
"label": "Agree evaluation",
|
||||
"sublabel": "Task mix + thresholds",
|
||||
"id": "safety",
|
||||
"type": "external",
|
||||
"label": "Safe execution?",
|
||||
"tag": "Safety evidence",
|
||||
"sublabel": "Permissions + adversarial trials",
|
||||
"pos": [
|
||||
260,
|
||||
20
|
||||
60,
|
||||
310
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
510,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "trial",
|
||||
"type": "security",
|
||||
"label": "Repeat trials",
|
||||
"sublabel": "Candidate vs approved",
|
||||
"id": "artifact",
|
||||
"type": "external",
|
||||
"label": "Valid artifact?",
|
||||
"tag": "Independent inspection",
|
||||
"sublabel": "Tests + human assessment",
|
||||
"pos": [
|
||||
260,
|
||||
150
|
||||
60,
|
||||
550
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
510,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "verify",
|
||||
"type": "security",
|
||||
"label": "Verify artifacts",
|
||||
"sublabel": "Independent checks",
|
||||
"id": "outcome",
|
||||
"type": "external",
|
||||
"label": "Useful outcome?",
|
||||
"tag": "Workflow evidence",
|
||||
"sublabel": "Baseline + repeated trials",
|
||||
"pos": [
|
||||
20,
|
||||
150
|
||||
60,
|
||||
790
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
510,
|
||||
170
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "gate",
|
||||
"type": "security",
|
||||
"label": "All gates pass?",
|
||||
"sublabel": "Safety cannot be traded",
|
||||
"label": "Every gate must pass",
|
||||
"tag": "Risk sets evaluation depth",
|
||||
"sublabel": "No averaging away harm\nCritical effect blocks promotion\nMissing coverage = incomplete",
|
||||
"pos": [
|
||||
20,
|
||||
280
|
||||
740,
|
||||
350
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
520,
|
||||
310
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "promote",
|
||||
"type": "security",
|
||||
"label": "Promote or hold",
|
||||
"sublabel": "Owner decision",
|
||||
"id": "accept",
|
||||
"type": "backend",
|
||||
"label": "Accept evidence",
|
||||
"tag": "All mandatory gates passed",
|
||||
"sublabel": "Proceed to scoped approval",
|
||||
"pos": [
|
||||
260,
|
||||
280
|
||||
1430,
|
||||
160
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
510,
|
||||
190
|
||||
],
|
||||
"icon": "none"
|
||||
},
|
||||
{
|
||||
"id": "rework",
|
||||
"type": "security",
|
||||
"label": "Rework / retest",
|
||||
"tag": "Any failure or missing evidence",
|
||||
"sublabel": "Keep failures in the record",
|
||||
"pos": [
|
||||
1430,
|
||||
760
|
||||
],
|
||||
"size": [
|
||||
510,
|
||||
190
|
||||
],
|
||||
"icon": "none"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "risk",
|
||||
"to": "plan",
|
||||
"label": "Scope"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "plan",
|
||||
"to": "trial",
|
||||
"label": "freeze plan"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "trial",
|
||||
"to": "verify",
|
||||
"label": "Assess"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "verify",
|
||||
"id": "selection-gate",
|
||||
"from": "selection",
|
||||
"to": "gate",
|
||||
"label": "review evidence"
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
650,
|
||||
155
|
||||
],
|
||||
[
|
||||
650,
|
||||
505
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"id": "safety-gate",
|
||||
"from": "safety",
|
||||
"to": "gate",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
650,
|
||||
395
|
||||
],
|
||||
[
|
||||
650,
|
||||
505
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "artifact-gate",
|
||||
"from": "artifact",
|
||||
"to": "gate",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
650,
|
||||
635
|
||||
],
|
||||
[
|
||||
650,
|
||||
505
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "outcome-gate",
|
||||
"from": "outcome",
|
||||
"to": "gate",
|
||||
"label": "",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
650,
|
||||
875
|
||||
],
|
||||
[
|
||||
650,
|
||||
505
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gate-accept",
|
||||
"from": "gate",
|
||||
"to": "promote",
|
||||
"label": "decide"
|
||||
"to": "accept",
|
||||
"label": "Pass",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "default",
|
||||
"via": [
|
||||
[
|
||||
1340,
|
||||
505
|
||||
],
|
||||
[
|
||||
1340,
|
||||
255
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1330,
|
||||
210
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gate-rework",
|
||||
"from": "gate",
|
||||
"to": "rework",
|
||||
"label": "Fail",
|
||||
"fromSide": "right",
|
||||
"toSide": "left",
|
||||
"route": "straight",
|
||||
"width": 4,
|
||||
"variant": "security",
|
||||
"via": [
|
||||
[
|
||||
1340,
|
||||
505
|
||||
],
|
||||
[
|
||||
1340,
|
||||
855
|
||||
]
|
||||
],
|
||||
"labelAt": [
|
||||
1325,
|
||||
900
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,9 @@ Include `title` and `description` frontmatter. Place each page in its owning sec
|
||||
| Portal maintenance and reference | `docs/core/` | `sidebars.ts` |
|
||||
|
||||
Keep these content roots disjoint. Preserve existing URLs when moving pages. Specific AI initiatives and organizational reporting lines remain subject to agreement.
|
||||
Every published documentation page must have a navigable sidebar entry. Nested references are welcome; hidden documentation is not. Linked category overviews count as entries, but an in-page anchor is not a separate document. Portal-maintenance pages keep their existing `/docs/` routes and dedicated sidebar, reachable from Contribution → Portal maintenance.
|
||||
|
||||
`pnpm verify:build` compares all Docusaurus-generated document IDs/routes with resolved sidebar trees (including linked category overviews), checks reachability from the navbar, rejects missing/duplicate entries and unclassified HTML routes, and checks every document’s local links and anchors. The inventory is written to `.cache/sidebar-coverage.json`. Only the homepage, not-found page, search-results UI, legacy Strategy redirect and standalone diagram viewers are non-document HTML routes; no documentation page is exempt.
|
||||
|
||||
## Validate the change
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: measurement
|
||||
title: Skills Measurement
|
||||
sidebar_label: Skills Measurement
|
||||
sidebar_label: Measurement
|
||||
description: Which KPIs to collect during skills adoption, where the evidence comes from, how to calculate them and when to report.
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
@@ -68,6 +68,6 @@ Stage 2 builds on proven skills, measurements and working practices—not permis
|
||||
|
||||
</details>
|
||||
|
||||
**Next:** [Return to the strategy destination →](./target-operating-model.mdx)
|
||||
**Next:** [Stage - 01 - Skills Adoption →](./stage-01-skills-adoption.mdx)
|
||||
|
||||
</div>
|
||||
@@ -44,6 +44,6 @@ Owner ratification is required before adoption. Review earlier after incidents o
|
||||
|
||||
</details>
|
||||
|
||||
**Next:** [Use exit evidence to choose the next rollout stage →](./roadmap.mdx)
|
||||
**Next:** [Stage - 02 - Autonomous Agent →](./stage-02-autonomous-agent.mdx)
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: stage-01-skills-adoption
|
||||
title: Stage - 01 - Skills Adoption
|
||||
description: Build proven skills and measurable working practices through human and AI Agent collaboration, starting with Delivery.
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
<div className="strategy-content">
|
||||
|
||||
**Human + AI Agent → proven skills and working practices → future Autonomous Agent.**
|
||||
|
||||
This first stage of the [Roadmap](./roadmap.mdx) standardizes skills in real human + AI Agent work. It builds the foundation for [Target](./target-operating-model.mdx), while people direct the work, verify outcomes and improve the skills.
|
||||
|
||||
## Delivery first
|
||||
|
||||
Start with the engineering path: codebase structure and existing principles → system-logic source → tests → builds and packaging → deployments. Understand actual practices, tooling, controls and problems before changing them.
|
||||
|
||||
Adoption then extends to other SDLC phases; their order is not yet decided. Delivery keeps improving as adoption expands.
|
||||
|
||||
## Repeat within each phase
|
||||
|
||||
**Understand the current situation → establish KPIs and a baseline → engineer skills topology → use skills in real work → collect metrics and feedback → understand the new current situation → repeat.**
|
||||
|
||||
Skills topology connects what skills exist, how they relate, when they are used and how they work together. Compare accepted outcomes, quality, cost and review effort with the baseline; use friction and feedback to revise the skills and working practices. These are repeating steps within each phase, not additional roadmap stages.
|
||||
|
||||
## Put the foundation into practice
|
||||
|
||||
- [Governance](./skills-governance.mdx) — define ownership, authority and approval of skills.
|
||||
- [Quality](./skills-quality.mdx) — qualify skills and verify their outcomes before wider use.
|
||||
- [Adoption](./skills-adoption.mdx) — coach bounded workflows with a supported client and a manual path.
|
||||
- [Measurement](./measurement.mdx) — establish KPIs, collect evidence and compare outcomes with the baseline.
|
||||
- [Maintenance](./skills-maintenance.mdx) — review feedback, control versions and recover from unsafe updates.
|
||||
|
||||
Proven skills, measurements and working practices support [Stage - 02 - Autonomous Agent](./stage-02-autonomous-agent.mdx); adoption alone does not grant autonomous execution authority.
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
id: stage-02-autonomous-agent
|
||||
title: Stage - 02 - Autonomous Agent
|
||||
description: Introduce the future Autonomous Agent on proven skills and measured practices while humans retain direction and review.
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
<div className="strategy-content">
|
||||
|
||||
**Proven skills and measured practices → future Autonomous Agent → human-directed SDLC.**
|
||||
|
||||
This future stage of the [Roadmap](./roadmap.mdx) builds on [Stage - 01 - Skills Adoption](./stage-01-skills-adoption.mdx). The destination is [Target](./target-operating-model.mdx): an Autonomous Agent that executes and governs the SDLC cycle, with humans retaining direction and review.
|
||||
|
||||
## Build on what has been proven
|
||||
|
||||
Carry forward proven skills, measurements and working practices from real human + AI Agent work. The skills foundation provides [governance](./skills-governance.mdx), [quality checks](./skills-quality.mdx), [adoption feedback](./skills-adoption.mdx), [measurement](./measurement.mdx) and [maintenance](./skills-maintenance.mdx); it is not permission to expand authority automatically.
|
||||
|
||||
## Introduce authority deliberately
|
||||
|
||||
Establish reliable observation and evidence assembly, then proposing and routing work, before granting scoped execution authority. The Roadmap describes recommended controls and hold conditions, including exact-artifact approvals, independent review, recovery and human takeover. Sensitive decisions remain human; missing evidence never means permission.
|
||||
|
||||
This is future direction, not deployed autonomy, agreed delivery dates or approved KPI thresholds. The transition depends on reviewed evidence and explicit authority decisions, not adoption volume alone.
|
||||
|
||||
</div>
|
||||
@@ -162,6 +162,6 @@ Success means accepted outcomes with maintained quality, controlled risk and und
|
||||
|
||||
</details>
|
||||
|
||||
**Next:** [Decide who owns and approves each skill →](./skills-governance.mdx)
|
||||
**Next:** [Follow the Roadmap toward Target →](./roadmap.mdx)
|
||||
|
||||
</div>
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
"verify:build": "node scripts/verify.mjs --build",
|
||||
"diagrams:build": "node scripts/build-diagrams.mjs",
|
||||
"test": "node --test scripts/*.test.mjs",
|
||||
"test:browser": "node scripts/browser/strategy-story.cjs && node scripts/browser/inline-controls.cjs && node scripts/browser/sdlc-cycle.cjs && node scripts/browser/target-midpoints.cjs && node scripts/browser/skills-measurement.cjs && node scripts/browser/roadmap.cjs && node scripts/browser/roadmap-timeline.cjs",
|
||||
"test:browser": "node scripts/browser/sidebar-coverage.cjs && node scripts/browser/skills-stories.cjs && node scripts/browser/strategy-navigation.cjs && node scripts/browser/strategy-story.cjs && node scripts/browser/inline-controls.cjs && node scripts/browser/sdlc-cycle.cjs && node scripts/browser/target-midpoints.cjs && node scripts/browser/skills-measurement.cjs && node scripts/browser/roadmap.cjs && node scripts/browser/roadmap-timeline.cjs",
|
||||
"kpi:example": "node scripts/ai-kpi.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -125,9 +125,9 @@ const fs = require('node:fs');
|
||||
await page.screenshot({path:`${output}/${width}.png`, fullPage:true});
|
||||
await frame.locator('#btn-theme').click(); await theme('dark');
|
||||
// Exercise keyboard navigation without racing the portal's smooth scroll.
|
||||
await page.locator('.strategy-content a[href$="/strategy/skills-governance/"]').filter({visible:true}).focus();
|
||||
await page.locator('.strategy-content a[href$="/strategy/roadmap/"]').filter({visible:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForURL('**/strategy/skills-governance/');
|
||||
await page.waitForURL('**/strategy/roadmap/');
|
||||
await page.goBack({waitUntil:'domcontentloaded'});
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
await page.getByRole('heading', {name:'Target operating model', exact:true, level:1}).waitFor();
|
||||
|
||||
@@ -23,7 +23,7 @@ const fs = require('node:fs');
|
||||
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true});
|
||||
await menu.first().waitFor({state:'visible'});
|
||||
assert.deepEqual(await menu.allTextContents(), ['Target', 'Roadmap', 'Skills governance', 'Skills quality', 'Skills adoption', 'Skills Measurement', 'Skills maintenance']);
|
||||
assert.deepEqual(await menu.allTextContents(), ['Target', 'Roadmap', 'Stage - 01 - Skills Adoption', 'Stage - 02 - Autonomous Agent']);
|
||||
assert((await menu.nth(0).getAttribute('href')).endsWith('/strategy/target-operating-model/'));
|
||||
assert((await menu.nth(1).getAttribute('href')).endsWith('/strategy/roadmap/'));
|
||||
assert.equal(await menu.filter({hasText:/^Target$/}).count(), 1);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
const {chromium} = require('playwright');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
(async () => {
|
||||
const base = process.argv[2] || 'http://127.0.0.1:4173';
|
||||
const output = `.cache/sidebar-browser-${process.argv[3] || 'local'}`;
|
||||
const {documents} = JSON.parse(fs.readFileSync('.cache/sidebar-coverage.json', 'utf8'));
|
||||
assert(documents.length > 0);
|
||||
fs.mkdirSync(output, {recursive:true});
|
||||
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
for (const width of [1440,390,320]) {
|
||||
const context = await browser.newContext({viewport:{width,height:900}});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
const menu = page.locator('.theme-doc-sidebar-menu').filter({visible:true});
|
||||
const open = async () => {
|
||||
if (width < 997) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
await menu.waitFor();
|
||||
};
|
||||
for (const doc of documents) {
|
||||
assert.equal((await page.goto(`${base}${doc.route}/`, {waitUntil:'networkidle'})).status(), 200);
|
||||
await page.locator('article h1').waitFor();
|
||||
await open();
|
||||
const links = menu.locator('a[href]:not([role="button"])').filter({visible:true});
|
||||
const active = await links.evaluateAll((nodes, route) => nodes.filter(n => n.pathname.replace(/\/$/,'') === route).map(n => n.textContent), doc.route);
|
||||
assert.equal(active.length, 1, `${width}: visible unique owning-sidebar entry for ${doc.route}`);
|
||||
results.push({width, route:doc.route, visibleEntry:active[0]});
|
||||
}
|
||||
// Contribution exposes all legacy maintenance pages without changing routes.
|
||||
for (const [label, route] of [['Starting point','overview'],['Update and publish','maintain'],['Site configuration','reference']]) {
|
||||
await page.goto(`${base}/eldorado-v1/documentation/contribution/`, {waitUntil:'networkidle'});
|
||||
await open();
|
||||
const toggle = menu.getByRole('button', {name:'Portal maintenance', exact:true});
|
||||
await toggle.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await menu.getByRole('link', {name:label, exact:true}).click();
|
||||
await page.waitForURL(`${base}/eldorado-v1/documentation/docs/${route}/`);
|
||||
await page.locator('article h1').waitFor();
|
||||
}
|
||||
const reference = '/eldorado-v1/documentation/strategy/reference/measurement-contracts/';
|
||||
await page.goto(`${base}${reference}#supported-bundle-coverage`, {waitUntil:'networkidle'});
|
||||
await page.locator('#supported-bundle-coverage').waitFor();
|
||||
assert((await page.locator('article').innerText()).includes('Supported bundle coverage'));
|
||||
assert.equal(await page.locator('.breadcrumbs a').filter({hasText:'Stage - 01 - Skills Adoption'}).getAttribute('href'), '/eldorado-v1/documentation/strategy/stage-01-skills-adoption/');
|
||||
assert((await page.locator('.breadcrumbs').innerText()).includes('Reference'));
|
||||
await open();
|
||||
await menu.getByRole('link', {name:'Measurement Contracts', exact:true}).waitFor({state:'visible'});
|
||||
await page.screenshot({path:`${output}/${width}-reference.png`, animations:'disabled'});
|
||||
assert.deepEqual(errors, []);
|
||||
} finally {await context.close();}
|
||||
}
|
||||
assert.equal(results.length, documents.length * 3);
|
||||
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||
console.log(`PASS: all ${documents.length} docs have visible unique sidebar entries at 3 viewports; Contribution links, Reference breadcrumbs and reported anchor work`);
|
||||
} finally {await browser.close();}
|
||||
})().catch(e => {console.error(e); process.exitCode = 1;});
|
||||
@@ -0,0 +1,42 @@
|
||||
const {chromium}=require('playwright');
|
||||
const fs=require('node:fs');
|
||||
const path=require('node:path');
|
||||
const assert=require('node:assert/strict');
|
||||
(async()=>{
|
||||
const dir=process.argv[2]||'.cache/skills-candidate';
|
||||
const browser=await chromium.launch({executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,args:['--no-sandbox']});
|
||||
const results=[];
|
||||
try {
|
||||
for(const topic of ['governance','quality','adoption','measurement','maintenance']) for(const width of [1440,1024,390,320]){
|
||||
const spec=JSON.parse(fs.readFileSync(`diagrams/strategy-${topic}${width<=1100?'-mobile':''}.architecture.json`));
|
||||
const page=await browser.newPage({viewport:{width:1200,height:1000}});
|
||||
await page.goto('file://'+path.resolve(`${dir}/${topic}-${width}.svg`));
|
||||
const measured=await page.locator('svg').evaluate(svg=>{
|
||||
const v=svg.viewBox.baseVal;const issues=[];
|
||||
for(const el of svg.querySelectorAll('[data-node-id],path[data-edge-id],g[data-edge-id]')){
|
||||
const b=el.getBBox();if(b.x<v.x-1||b.y<v.y-1||b.x+b.width>v.x+v.width+1||b.y+b.height>v.y+v.height+1)issues.push(el.getAttribute('data-node-id')||el.getAttribute('data-edge-id'));
|
||||
}
|
||||
// Measure the whole rendered label group, including its opaque mask:
|
||||
// text presence alone cannot detect one label hiding another.
|
||||
const labels=[...svg.querySelectorAll('g[data-edge-id]')].filter(el=>el.querySelector('text')).map(el=>({id:el.dataset.edgeId,box:el.getBoundingClientRect()}));
|
||||
const labelOverlaps=[];
|
||||
for(let i=0;i<labels.length;i++)for(let j=i+1;j<labels.length;j++){
|
||||
const a=labels[i],b=labels[j];
|
||||
if(Math.min(a.box.right,b.box.right)>Math.max(a.box.left,b.box.left)&&Math.min(a.box.bottom,b.box.bottom)>Math.max(a.box.top,b.box.top))labelOverlaps.push([a.id,b.id]);
|
||||
}
|
||||
return {labelOverlaps,viewBox:[v.width,v.height],nodes:[...svg.querySelectorAll('[data-node-id]')].map(n=>n.dataset.nodeId),edges:[...svg.querySelectorAll('path[data-edge-id]')].map(n=>n.dataset.edgeId),text:svg.textContent,issues};
|
||||
});
|
||||
assert.deepEqual(measured.viewBox,spec.meta.viewBox);
|
||||
assert.deepEqual(measured.nodes.sort(),spec.components.map(n=>n.id).sort());
|
||||
assert.deepEqual(measured.edges.sort(),spec.connections.map(e=>e.id).sort());
|
||||
assert.deepEqual(measured.issues,[]);
|
||||
assert.deepEqual(measured.labelOverlaps,[],`${topic}-${width}: rendered edge labels overlap`);
|
||||
for(const n of spec.components){assert(measured.text.includes(n.label));assert(measured.text.includes(n.tag));for(const line of n.sublabel.split('\n'))assert(measured.text.includes(line));}
|
||||
for(const e of spec.connections)if(e.label)assert(measured.text.includes(e.label));
|
||||
if(width===1440)await page.screenshot({path:`${dir}/${topic}-standalone-export.png`});
|
||||
results.push({topic,width,nodes:measured.nodes.length,edges:measured.edges.length,complete:true});await page.close();
|
||||
}
|
||||
fs.writeFileSync(`${dir}/export-results.json`,JSON.stringify(results,null,2));
|
||||
console.log(`PASS ${results.length} standalone SVGs: exact graph, text, full-canvas containment and non-overlapping edge labels`);
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e);process.exitCode=1});
|
||||
@@ -22,7 +22,7 @@ const fs = require('node:fs');
|
||||
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true});
|
||||
await menu.first().waitFor({state:'visible'});
|
||||
assert.deepEqual(await menu.allTextContents(), ['Target','Roadmap','Skills governance','Skills quality','Skills adoption','Skills Measurement','Skills maintenance']);
|
||||
assert.deepEqual(await menu.allTextContents(), ['Target','Roadmap','Stage - 01 - Skills Adoption','Governance','Quality','Adoption','Measurement','Maintenance','Reference','Stage - 02 - Autonomous Agent']);
|
||||
if (width < 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||
const panel = page.locator('.archify-panel--story');
|
||||
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
|
||||
@@ -33,7 +33,7 @@ const fs = require('node:fs');
|
||||
const nodes = [...svg.querySelectorAll('[data-node-id]')].map(n => n.getBoundingClientRect().toJSON());
|
||||
return {nodes, width:innerWidth, height:innerHeight, minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n => parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))};
|
||||
});
|
||||
assert.equal(geometry.nodes.length, 6);
|
||||
assert.equal(geometry.nodes.length, JSON.parse(fs.readFileSync('diagrams/strategy-measurement.architecture.json')).components.length);
|
||||
for (const n of geometry.nodes) assert(n.x >= 0 && n.y >= 0 && n.right <= geometry.width + 1 && n.bottom <= geometry.height + 1);
|
||||
assert(geometry.minLabel >= 10, `readable node labels: ${geometry.minLabel}`);
|
||||
await frame.locator('#btn-theme').click();
|
||||
@@ -50,7 +50,7 @@ const fs = require('node:fs');
|
||||
assert.equal(await download.failure(), null);
|
||||
const path = `${output}/${width}.svg`; await download.saveAs(path);
|
||||
const svg = fs.readFileSync(path, 'utf8');
|
||||
assert(svg.includes('Establish baseline') && svg.includes('Ongoing monitoring'));
|
||||
assert(svg.includes('Compare with baseline') && svg.includes('Zero denominator = N/A'));
|
||||
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||
await opener.click();
|
||||
const close = panel.getByRole('button', {name:'Close full-screen diagram', exact:true});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
const {chromium}=require('playwright');
|
||||
const fs=require('node:fs');
|
||||
const assert=require('node:assert/strict');
|
||||
(async()=>{
|
||||
const base=process.argv[2]||'http://127.0.0.1:4173';
|
||||
const out=`.cache/skills-${process.argv[3]||'candidate'}`;fs.mkdirSync(out,{recursive:true});
|
||||
const browser=await chromium.launch({executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,args:['--no-sandbox']}); const results=[];
|
||||
try {
|
||||
for(const topic of ['governance','quality','adoption','measurement','maintenance']) for(const width of [1440,1024,390,320]) {
|
||||
const page=await browser.newPage({viewport:{width,height:900},colorScheme:'light'});const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||
const route=topic==='measurement'?topic:`skills-${topic}`;
|
||||
assert.equal((await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}/`,{waitUntil:'networkidle'})).status(),200);
|
||||
const panel=page.locator('.archify-panel--skills');assert.equal(await panel.count(),1);
|
||||
const iframe=panel.locator(width>1100?'.story-desktop':'.story-mobile');await iframe.scrollIntoViewIfNeeded();
|
||||
const frame=await(await iframe.elementHandle()).contentFrame();await frame.locator('svg[role="img"]').waitFor();
|
||||
const geometry=await frame.locator('svg[role="img"]').evaluate(svg=>{
|
||||
const nodes=[...svg.querySelectorAll('g[data-node-id]')];const issues=[];let min=Infinity;
|
||||
for(const n of nodes){const rect=n.querySelector('rect').getBBox();for(const t of n.querySelectorAll('text')){
|
||||
const b=t.getBBox();min=Math.min(min,parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a);
|
||||
if(b.x<rect.x||b.y<rect.y||b.x+b.width>rect.x+rect.width||b.y+b.height>rect.y+rect.height)issues.push(n.id+':'+t.textContent);
|
||||
}}
|
||||
return {nodes:nodes.length,min,issues,width:innerWidth,svgWidth:svg.getBoundingClientRect().width};
|
||||
});
|
||||
const source=JSON.parse(fs.readFileSync(`diagrams/strategy-${topic}.architecture.json`));
|
||||
assert.equal(geometry.nodes,source.components.length);assert.deepEqual(geometry.issues,[]);assert(geometry.min>=10,JSON.stringify(geometry));
|
||||
for(const theme of ['light','dark']){
|
||||
if(await frame.locator('html').getAttribute('data-theme')!==theme)await frame.locator('#btn-theme').click();
|
||||
await page.waitForFunction(t=>document.documentElement.dataset.theme===t,theme);
|
||||
await panel.scrollIntoViewIfNeeded();await page.screenshot({path:`${out}/${topic}-${width}-${theme}-inline.png`});
|
||||
const open=panel.getByRole('button',{name:/^Expand /});await open.click();const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
||||
const expanded=await(await panel.locator('dialog iframe').elementHandle()).contentFrame();await expanded.locator('svg[role="img"]').waitFor();
|
||||
assert((await expanded.url()).includes(width<=1100?`${topic}-mobile/`:`${topic}/`));
|
||||
await page.screenshot({path:`${out}/${topic}-${width}-${theme}-fullscreen.png`});
|
||||
if(width<=700){await page.setViewportSize({width,height:480});assert(await panel.locator('dialog').evaluate(n=>n.scrollHeight>n.clientHeight));await panel.locator('dialog').evaluate(n=>n.scrollTop=n.scrollHeight);await page.screenshot({path:`${out}/${topic}-${width}-${theme}-short-bottom.png`});await page.setViewportSize({width,height:900});}
|
||||
await close.focus();await page.keyboard.press('Escape');assert(await open.evaluate(n=>n===document.activeElement));
|
||||
}
|
||||
await frame.locator('#btn-preset').click();await frame.locator('[data-preset-value="blueprint"]').click();
|
||||
await frame.locator('#btn-preset').click();await frame.locator('[data-preset-value="classic"]').click();
|
||||
await frame.locator('#btn-export').click();const [download]=await Promise.all([page.waitForEvent('download'),frame.locator('[data-format="svg"]').click()]);assert.equal(await download.failure(),null);
|
||||
await download.saveAs(`${out}/${topic}-${width}.svg`);assert(fs.readFileSync(`${out}/${topic}-${width}.svg`,'utf8').includes('eldorado-skills-typography'));
|
||||
assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));assert.deepEqual(errors,[]);
|
||||
results.push({topic,width,geometry,controls:true,export:true,themes:true,fullscreen:true});fs.writeFileSync(`${out}/results.json`,JSON.stringify(results,null,2));await page.close();
|
||||
}
|
||||
console.log(`PASS ${results.length} Skills story contexts: geometry, themes, controls, exports, fullscreen and short-phone scrolling`);
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e);process.exitCode=1});
|
||||
@@ -0,0 +1,89 @@
|
||||
const {chromium} = require('playwright');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
(async () => {
|
||||
const base = process.argv[2] || 'http://127.0.0.1:4173';
|
||||
const output = `.cache/strategy-navigation-${process.argv[3] || 'local'}`;
|
||||
fs.mkdirSync(output, {recursive:true});
|
||||
const prefix = '/eldorado-v1/documentation/strategy/';
|
||||
const first = 'Stage - 01 - Skills Adoption';
|
||||
const second = 'Stage - 02 - Autonomous Agent';
|
||||
const topics = ['Governance','Quality','Adoption','Measurement','Maintenance','Reference'];
|
||||
const ids = ['target-operating-model','roadmap','stage-01-skills-adoption','skills-governance','skills-quality','skills-adoption','measurement','skills-maintenance','reference/measurement-contracts','stage-02-autonomous-agent'];
|
||||
const titles = ['Target','Roadmap',first,'Skills governance','Skills quality','Skills adoption','Skills Measurement','Skills maintenance','Skills KPI reference',second];
|
||||
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
for (const width of [1440,1024,390,320]) {
|
||||
const context = await browser.newContext({viewport:{width,height:900}, colorScheme:'light'});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
const errors = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
const openMenu = async () => {
|
||||
if (width < 997) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const menu = page.locator('.theme-doc-sidebar-menu').filter({visible:true});
|
||||
await menu.locator('.menu__link').first().waitFor({state:'visible'});
|
||||
return menu;
|
||||
};
|
||||
const closeMenu = async () => {
|
||||
if (width < 997) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||
};
|
||||
assert.equal((await page.goto(`${base}${prefix}roadmap/`, {waitUntil:'networkidle'})).status(), 200);
|
||||
let menu = await openMenu();
|
||||
const visibleLabels = () => menu.locator('.menu__link').filter({visible:true}).allTextContents();
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,second]);
|
||||
const category = menu.locator('.theme-doc-sidebar-item-category').filter({has:page.getByRole('link', {name:first, exact:true})});
|
||||
const caret = category.locator(':scope > .menu__list-item-collapsible > button.menu__caret');
|
||||
await caret.click();
|
||||
await category.getByRole('link', {name:'Governance', exact:true}).waitFor({state:'visible'});
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,...topics,second]);
|
||||
assert.deepEqual(await category.locator('ul .menu__link').filter({visible:true}).allTextContents(), topics);
|
||||
const reference = category.getByRole('button', {name:'Reference', exact:true});
|
||||
await reference.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await category.getByRole('link', {name:'Measurement Contracts', exact:true}).waitFor({state:'visible'});
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,...topics,'Measurement Contracts',second]);
|
||||
await reference.click();
|
||||
await page.screenshot({path:`${output}/${width}-expanded.png`, animations:'disabled'});
|
||||
await caret.click();
|
||||
await category.getByRole('link', {name:'Governance', exact:true}).waitFor({state:'hidden'});
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,second]);
|
||||
await menu.getByRole('link', {name:first, exact:true}).click();
|
||||
await page.waitForURL(`${base}${prefix}stage-01-skills-adoption/`);
|
||||
await page.getByRole('heading', {level:1, name:first, exact:true}).waitFor();
|
||||
assert.equal(await page.locator('h1').innerText(), first);
|
||||
assert((await page.locator('article').innerText()).includes('Delivery first'));
|
||||
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
|
||||
await page.screenshot({path:`${output}/${width}-stage-01.png`, fullPage:true, animations:'disabled'});
|
||||
for (const [index, id] of ids.entries()) {
|
||||
assert.equal((await page.goto(`${base}${prefix}${id}/`, {waitUntil:'networkidle'})).status(), 200);
|
||||
assert.equal(new URL(page.url()).pathname, `${prefix}${id}/`);
|
||||
// Target has a longer canonical page heading but keeps its short navigation label.
|
||||
if (index) assert.equal(await page.locator('h1').innerText(), titles[index]);
|
||||
else assert((await page.locator('h1').innerText()).includes('Target'));
|
||||
const prev = page.locator('.pagination-nav__link--prev');
|
||||
const next = page.locator('.pagination-nav__link--next');
|
||||
if (index) assert.equal(await prev.getAttribute('href'), `${prefix}${ids[index-1]}/`);
|
||||
else assert.equal(await prev.count(), 0);
|
||||
if (index < ids.length-1) assert.equal(await next.getAttribute('href'), `${prefix}${ids[index+1]}/`);
|
||||
else assert.equal(await next.count(), 0);
|
||||
if (index >= 3 && index <= 7) {
|
||||
assert.equal(await page.locator('.breadcrumbs a').filter({hasText:first}).getAttribute('href'), `${prefix}stage-01-skills-adoption/`);
|
||||
menu = await openMenu();
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,...topics,second]);
|
||||
await closeMenu();
|
||||
}
|
||||
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), `${width}: overflow on ${id}`);
|
||||
}
|
||||
assert((await page.locator('article').innerText()).includes('not deployed autonomy'));
|
||||
await page.screenshot({path:`${output}/${width}-stage-02.png`, fullPage:true, animations:'disabled'});
|
||||
assert.deepEqual(errors, []);
|
||||
results.push({width, routes:ids.length, linkedCategory:true, expandCollapse:true, nesting:true, breadcrumbs:true, pagination:true, overflow:false, errors});
|
||||
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||
} finally {await context.close();}
|
||||
}
|
||||
assert.equal(results.length, 4);
|
||||
console.log('PASS: 4 desktop/mobile viewports; all 10 routes; linked category, keyboard expansion, nested Reference, breadcrumbs, next/previous and no overflow/errors');
|
||||
} finally {await browser.close();}
|
||||
})().catch(error => {console.error(error); process.exitCode = 1;});
|
||||
@@ -19,7 +19,9 @@ const fs=require('node:fs');
|
||||
const nodes=[...svg.querySelectorAll('[data-node-id]')].map(n=>{const r=n.getBoundingClientRect();return {id:n.id,x:r.x,y:r.y,right:r.right,bottom:r.bottom};});
|
||||
return {width:innerWidth,height:innerHeight,nodes,svg:svg.getBoundingClientRect().toJSON(),minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n=>parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))};
|
||||
});
|
||||
assert(geometry.nodes.length>=(id==='roadmap'?2:5) && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?18:7),id);
|
||||
const name=await panel.getAttribute('data-diagram');
|
||||
const source=JSON.parse(fs.readFileSync(`diagrams/${name}.${name==='strategy-roadmap'?'workflow':'architecture'}.json`));
|
||||
assert.equal(geometry.nodes.length,(source.components||source.nodes).length,id);
|
||||
for(const n of geometry.nodes) assert(n.x>=0 && n.y>=0 && n.right<=geometry.width+1 && n.bottom<=geometry.height+1,`${id} ${width} clipped ${JSON.stringify(n)} / ${geometry.height}`);
|
||||
const data=await page.locator('.strategy-content').evaluate(el=>({words:el.innerText.trim().split(/\s+/).length,overflow:document.documentElement.scrollWidth>innerWidth,openDetails:el.querySelectorAll('details[open]').length}));
|
||||
assert(data.words<=(id==='measurement'?1100:id==='roadmap'?600:200),id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {skillsViewer, isSkillsStory} from './skills-viewer.mjs';
|
||||
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||
import {targetRouting} from './target-routing.mjs';
|
||||
import {createHash} from 'node:crypto';
|
||||
@@ -27,11 +28,12 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
if (spec.diagram_type !== type || spec.meta.output !== output) throw new Error(`Diagram type/output mismatch: ${file}`);
|
||||
const run = args => execFileSync(process.execPath,[cli,...args],{stdio:'inherit',env:{...process.env,ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
const roadmap = /^strategy-roadmap(?:-(?:adoption|loop))?(?:-mobile)?$/.test(name);
|
||||
if (roadmap) {
|
||||
const skills = isSkillsStory(name);
|
||||
if (roadmap || skills) {
|
||||
// Native renderer validates schema and compiles layout. Check readability
|
||||
// after multiline typography, not upstream's single-line preview.
|
||||
execFileSync(process.execPath, ['vendor/archify/renderers/workflow/render-workflow.mjs', input, output], {stdio:'inherit', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
writeFileSync(output, roadmapViewer(readFileSync(output, 'utf8'), spec));
|
||||
execFileSync(process.execPath, [`vendor/archify/renderers/${type}/render-${type}.mjs`, input, output], {stdio:'inherit', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
writeFileSync(output, (skills ? skillsViewer : roadmapViewer)(readFileSync(output, 'utf8'), spec));
|
||||
run(['check',output,'--json']);
|
||||
} else {
|
||||
run(['validate',type,input,'--quality','showcase','--json']);
|
||||
@@ -61,13 +63,13 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
}
|
||||
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style><style id="eldorado-cycle-labels">${storyStyles}</style></head>`)
|
||||
.replace('</body>', `<script>${themeBridge}</script></body>`));
|
||||
if (roadmap) {
|
||||
if (roadmap || skills) {
|
||||
// This is a portal receipt, not upstream deliver provenance: formatting
|
||||
// happens between the native schema/layout compiler and artifact checker.
|
||||
const hash = path => createHash('sha256').update(readFileSync(path)).digest('hex');
|
||||
const checked = JSON.parse(execFileSync(process.execPath, [cli,'check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}));
|
||||
if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Roadmap final artifact failed');
|
||||
writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash('scripts/roadmap-viewer.mjs'), artifactSha256:hash(output), checker:checked}, null, 2));
|
||||
if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Story final artifact failed');
|
||||
writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash(skills ? 'scripts/skills-viewer.mjs' : 'scripts/roadmap-viewer.mjs'), artifactSha256:hash(output), checker:checked}, null, 2));
|
||||
} else {
|
||||
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
||||
if (name === 'strategy-target') {
|
||||
|
||||
@@ -5,7 +5,7 @@ const read = p => readFileSync(p, 'utf8');
|
||||
const stages = ['Scope', 'Architecture', 'UI/UX', 'Planning', 'Delivery', 'UAT', 'Security', 'Releases'];
|
||||
|
||||
test('Strategy opens at the canonical autonomous lifecycle', () => {
|
||||
assert.match(read('sidebars/strategy.ts'), /strategySidebar: \['target-operating-model', 'roadmap'/);
|
||||
assert.match(read('sidebars/strategy.ts'), /strategySidebar:\s*\[\s*'target-operating-model',\s*'roadmap'/);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||
for (const id of ['target-operating-model']) {
|
||||
const page = read(`docs/strategy/${id}.mdx`);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync, readdirSync, mkdirSync, writeFileSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
|
||||
const normalize = path => path.replace(/\/$/, '');
|
||||
const read = path => JSON.parse(readFileSync(path, 'utf8'));
|
||||
|
||||
// Use Docusaurus's resolved IDs/routes and sidebar trees, not filename guesses.
|
||||
export function auditSidebars(instances, entrySidebars) {
|
||||
const docs = new Map();
|
||||
const menus = new Map();
|
||||
for (const {plugin, version, documents, sidebars} of instances) {
|
||||
const ids = new Set();
|
||||
for (const doc of documents) {
|
||||
assert(!ids.has(doc.id), `Duplicate document ID: ${plugin}:${version}:${doc.id}`);
|
||||
ids.add(doc.id);
|
||||
const route = normalize(doc.path);
|
||||
assert(!docs.has(route), `Duplicate document route: ${route}`);
|
||||
docs.set(route, {...doc, plugin, version, menu: `${plugin}:${version}:${doc.sidebar}`});
|
||||
}
|
||||
for (const [name, items] of Object.entries(sidebars)) menus.set(`${plugin}:${version}:${name}`, items);
|
||||
}
|
||||
const links = new Map();
|
||||
for (const [menu, items] of menus) {
|
||||
const found = new Set();
|
||||
const walk = items => {
|
||||
for (const item of items) {
|
||||
assert(['category', 'link', 'html'].includes(item.type), `Unresolved sidebar type: ${item.type}`);
|
||||
assert(!item.unlisted, `Hidden sidebar item: ${item.label}`);
|
||||
if (item.href && !/^(https?:|mailto:)/.test(item.href)) {
|
||||
const route = normalize(item.href);
|
||||
if (item.docId) assert.equal(docs.get(route)?.id, item.docId, `Sidebar ID/route mismatch: ${item.docId}`);
|
||||
assert(!found.has(route), `Duplicate sidebar route in ${menu}: ${route}`);
|
||||
found.add(route);
|
||||
}
|
||||
if (item.type === 'category') walk(item.items);
|
||||
}
|
||||
};
|
||||
walk(items);
|
||||
links.set(menu, found);
|
||||
}
|
||||
const reachable = new Set(entrySidebars);
|
||||
for (const menu of reachable) {
|
||||
assert(menus.has(menu), `Unknown navigation sidebar: ${menu}`);
|
||||
for (const route of links.get(menu)) {
|
||||
const doc = docs.get(route);
|
||||
if (doc) reachable.add(doc.menu);
|
||||
}
|
||||
}
|
||||
for (const [route, doc] of docs) {
|
||||
assert(reachable.has(doc.menu), `Unreachable sidebar for ${doc.plugin}:${doc.id}`);
|
||||
assert(links.get(doc.menu)?.has(route), `Missing sidebar document: ${doc.plugin}:${doc.id}`);
|
||||
}
|
||||
return [...docs.entries()].map(([route, doc]) => ({route, id:doc.id, plugin:doc.plugin, sidebar:doc.sidebar}));
|
||||
}
|
||||
|
||||
export async function verifySidebarCoverage() {
|
||||
const global = read('.docusaurus/globalData.json')['docusaurus-plugin-content-docs'];
|
||||
assert(global && Object.keys(global).length, 'No generated document inventory');
|
||||
const instances = [];
|
||||
for (const [plugin, data] of Object.entries(global)) {
|
||||
const folder = `.docusaurus/docusaurus-plugin-content-docs/${plugin}/p`;
|
||||
const resolved = readdirSync(folder).filter(f => f.endsWith('.json')).map(f => read(join(folder, f)).version);
|
||||
for (const version of data.versions) {
|
||||
const matches = resolved.filter(v => v.version === version.name);
|
||||
assert.equal(matches.length, 1, `Missing/duplicate resolved version: ${plugin}:${version.name}`);
|
||||
instances.push({plugin, version:version.name, documents:version.docs, sidebars:matches[0].docsSidebars});
|
||||
}
|
||||
}
|
||||
const {default:config} = await import('../.docusaurus/docusaurus.config.mjs');
|
||||
const entries = config.themeConfig.navbar.items.filter(i => i.type === 'docSidebar').map(i => `${i.docsPluginId || 'default'}:current:${i.sidebarId}`);
|
||||
const documents = auditSidebars(instances, entries);
|
||||
const routes = new Set(documents.map(d => `${d.route.replace(config.baseUrl, '')}/index.html`));
|
||||
const exclusions = [];
|
||||
const html = readdirSync('build', {recursive:true}).filter(f => f.endsWith('.html')).sort();
|
||||
for (const file of html) {
|
||||
if (routes.delete(file)) continue;
|
||||
let reason;
|
||||
if (file === 'index.html') reason = 'Dedicated React homepage, not a documentation page';
|
||||
else if (file === '404.html') reason = 'Docusaurus not-found system page';
|
||||
else if (file === 'search/index.html') reason = 'Local-search results UI, not a documentation page';
|
||||
else if (file === 'strategy/index.html') {
|
||||
assert(readFileSync(join('build',file),'utf8').includes('http-equiv="refresh"'), 'Strategy alias must remain a redirect');
|
||||
reason = 'Legacy Strategy redirect alias to Target';
|
||||
} else if (file.startsWith('diagrams/') && readFileSync(join('build',file),'utf8').includes('<svg')) {
|
||||
assert.equal(readFileSync(join('build',file),'utf8'), readFileSync(join('static',file),'utf8'), `Viewer must match generated static asset: ${file}`);
|
||||
reason = 'Standalone diagram viewer asset, not documentation';
|
||||
}
|
||||
assert(reason, `Unclassified published HTML route: ${file}`);
|
||||
exclusions.push({file, reason});
|
||||
}
|
||||
assert.equal(routes.size, 0, `Missing published docs: ${[...routes]}`);
|
||||
// Check all local links and anchors on every document, not only selected Strategy pages.
|
||||
for (const {route} of documents) {
|
||||
const file = `build/${route.replace(config.baseUrl, '')}/index.html`;
|
||||
const body = readFileSync(file, 'utf8');
|
||||
for (const [, href] of body.matchAll(/href="([^" ]+)"/g)) {
|
||||
if (!href.startsWith(config.baseUrl) && !href.startsWith('#')) continue;
|
||||
const url = new URL(href, `https://docs.invalid${route}/`);
|
||||
let target = url.pathname.slice(config.baseUrl.length);
|
||||
if (!target || target.endsWith('/')) target += 'index.html';
|
||||
else if (!target.split('/').at(-1).includes('.')) target += '/index.html';
|
||||
const targetBody = readFileSync(join('build', decodeURIComponent(target)), 'utf8');
|
||||
if (url.hash) assert(targetBody.includes(`id="${decodeURIComponent(url.hash.slice(1))}"`), `Broken anchor: ${route} -> ${href}`);
|
||||
}
|
||||
}
|
||||
mkdirSync('.cache', {recursive:true});
|
||||
writeFileSync('.cache/sidebar-coverage.json', JSON.stringify({documents, exclusions}, null, 2));
|
||||
console.log(`PASS: ${documents.length} published docs reachable in resolved sidebars; ${exclusions.length} classified non-doc HTML routes; all doc links/anchors checked`);
|
||||
return {documents, exclusions};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {auditSidebars} from './sidebar-coverage.mjs';
|
||||
|
||||
const fixture = () => [{plugin:'strategy', version:'current', documents:[
|
||||
{id:'overview', path:'/strategy/overview/', sidebar:'main'},
|
||||
{id:'nested/reference', path:'/strategy/reference/', sidebar:'main'},
|
||||
], sidebars:{main:[{type:'category', label:'Stage', href:'/strategy/overview', items:[
|
||||
{type:'category', label:'Reference', items:[{type:'link', docId:'nested/reference', href:'/strategy/reference'}]},
|
||||
]}]}}];
|
||||
const entries = ['strategy:current:main'];
|
||||
test('resolved nested entries and linked category overviews both count', () => {
|
||||
assert.equal(auditSidebars(fixture(), entries).length, 2);
|
||||
});
|
||||
test('missing docs fail even if Docusaurus assigns a displayed sidebar', () => {
|
||||
const data = fixture();
|
||||
data[0].sidebars.main[0].items = [];
|
||||
assert.throws(() => auditSidebars(data, entries), /Missing sidebar document/);
|
||||
});
|
||||
test('new publishable docs and absent category overview links fail closed', () => {
|
||||
const data = fixture();
|
||||
data[0].documents.push({id:'new', path:'/new'});
|
||||
assert.throws(() => auditSidebars(data, entries), /Unreachable sidebar/);
|
||||
const withoutLink = fixture();
|
||||
delete withoutLink[0].sidebars.main[0].href;
|
||||
assert.throws(() => auditSidebars(withoutLink, entries), /Missing sidebar document/);
|
||||
});
|
||||
test('duplicate normalized routes and duplicate sidebar entries fail', () => {
|
||||
const data = fixture();
|
||||
data[0].documents.push({...data[0].documents[0], path:'/strategy/overview'});
|
||||
assert.throws(() => auditSidebars(data, entries), /Duplicate document ID/);
|
||||
data[0].documents.at(-1).id = 'different';
|
||||
assert.throws(() => auditSidebars(data, entries), /Duplicate document route/);
|
||||
const duplicate = fixture();
|
||||
duplicate[0].sidebars.main.push({type:'link', href:'/strategy/overview/'});
|
||||
assert.throws(() => auditSidebars(duplicate, entries), /Duplicate sidebar route/);
|
||||
});
|
||||
test('unlisted entries and unresolved sidebar types fail', () => {
|
||||
const data = fixture();
|
||||
data[0].sidebars.main[0].unlisted = true;
|
||||
assert.throws(() => auditSidebars(data, entries), /Hidden sidebar item/);
|
||||
data[0].sidebars.main[0] = {type:'autogenerated'};
|
||||
assert.throws(() => auditSidebars(data, entries), /Unresolved sidebar type/);
|
||||
});
|
||||
test('disconnected plugin menus fail, cross-plugin sidebar links make them reachable', () => {
|
||||
const data = fixture();
|
||||
data.push({plugin:'default', version:'current', documents:[{id:'help', path:'/docs/help', sidebar:'docs'}], sidebars:{docs:[{type:'link', href:'/docs/help'}]}});
|
||||
assert.throws(() => auditSidebars(data, entries), /Unreachable sidebar/);
|
||||
data[0].sidebars.main.push({type:'link', href:'/docs/help/'});
|
||||
assert.equal(auditSidebars(data, entries).length, 3);
|
||||
});
|
||||
@@ -6,7 +6,7 @@ const read = path => readFileSync(path, 'utf8');
|
||||
|
||||
test('Skills Measurement is an open adoption collection plan, not a hidden catalogue', () => {
|
||||
const page = read('docs/strategy/measurement.mdx');
|
||||
assert.match(page, /title: Skills Measurement\nsidebar_label: Skills Measurement/);
|
||||
assert.match(page, /title: Skills Measurement\nsidebar_label: Measurement/);
|
||||
for (const heading of ['Establish the baseline', 'Measure the pilot', 'Extend collection as adoption grows', 'Keep the collection running']) assert(page.includes(heading), heading);
|
||||
assert(!page.includes('<details>'));
|
||||
assert(page.includes('./reference/measurement-contracts.mdx'));
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync, mkdtempSync, rmSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {skillsViewer,isSkillsStory} from './skills-viewer.mjs';
|
||||
const read = p => readFileSync(p,'utf8');
|
||||
const topics = ['governance','quality','adoption','measurement','maintenance'];
|
||||
test('all six Skills Adoption pages account for exactly five diagrams and ten sources',()=>{
|
||||
let count=0;
|
||||
for(const route of ['stage-01-skills-adoption',...topics.map(t=>t==='measurement'?t:`skills-${t}`)]){
|
||||
const diagrams=[...read(`docs/strategy/${route}.mdx`).matchAll(/<ArchifyDiagram name="([^"]+)"/g)];
|
||||
assert.equal(diagrams.length,route.startsWith('stage-')?0:1);
|
||||
for(const [,name] of diagrams){assert(isSkillsStory(name));for(const suffix of ['','-mobile'])JSON.parse(read(`diagrams/${name}${suffix}.architecture.json`));count++;}
|
||||
}
|
||||
assert.equal(count,5);assert(!isSkillsStory('strategy-target'));assert(!isSkillsStory('strategy-roadmap'));
|
||||
});
|
||||
for(const topic of topics) test(`${topic}: rich native source, wide desktop, mobile parity and checked adapter`,()=>{
|
||||
const name=`strategy-${topic}`;const desktop=JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||
const mobile=JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||
const semantic = d=>d.components.map(({pos,size,...n})=>n);
|
||||
assert.deepEqual(semantic(desktop),semantic(mobile));
|
||||
assert(new Set(desktop.components.map(n=>n.pos[0])).size>1);
|
||||
assert.equal(new Set(mobile.components.map(n=>n.pos[0])).size,1);
|
||||
for(const n of desktop.components){assert(n.tag);assert(n.sublabel);assert(n.sublabel.split('\n').length<=4);}
|
||||
const dir=mkdtempSync(join(tmpdir(),'skills-viewer-'));
|
||||
try{
|
||||
const path=join(dir,'native.html');
|
||||
execFileSync(process.execPath,['vendor/archify/renderers/architecture/render-architecture.mjs',`diagrams/${name}.architecture.json`,path]);
|
||||
const native=read(path);const html=skillsViewer(native,desktop);
|
||||
assert.equal((html.match(/<g\b[^>]*data-node-id=/g)||[]).length,desktop.components.length);
|
||||
assert.equal((html.match(/<text data-node-label=/g)||[]).length,0); // Adapter uses explicit position before semantic attributes.
|
||||
assert.equal((html.match(/data-node-label=""/g)||[]).length,desktop.components.length);
|
||||
assert(html.includes('eldorado-skills-typography'));
|
||||
assert.throws(()=>skillsViewer(native.replace('data-node-id="'+desktop.components[0].id+'"','data-node-id="missing"'),desktop));
|
||||
const bad=structuredClone(desktop);bad.components[0].sublabel='';assert.throws(()=>skillsViewer(native,bad));
|
||||
}finally{rmSync(dir,{recursive:true,force:true});}
|
||||
});
|
||||
test('domain contracts retain human authority, honest measures and Delivery-first rollout',()=>{
|
||||
const text=t=>JSON.stringify(JSON.parse(read(`diagrams/strategy-${t}.architecture.json`)));
|
||||
assert(text('governance').includes('Author is not sole approver'));
|
||||
assert(text('quality').includes('Critical effect blocks'));
|
||||
assert(text('adoption').includes('Delivery first')&&text('adoption').includes('Later order undecided'));
|
||||
assert(text('adoption').includes('No quotas or rankings'));
|
||||
for(const marker of ['Tracker + acceptance','Invoices + review minutes','Changes + defects','Anonymous 1–5 survey','Missing buckets = partial','Zero denominator = N/A'])assert(text('measurement').includes(marker),marker);
|
||||
assert(text('maintenance').includes('No automatic reinstatement'));
|
||||
});
|
||||
test('topic compositions explain distinct relationships rather than a fixed card count',()=>{
|
||||
const spec=t=>JSON.parse(read(`diagrams/strategy-${t}.architecture.json`));
|
||||
const has=(d,a,b)=>assert(d.connections.some(e=>e.from===a&&e.to===b),`${a} → ${b}`);
|
||||
const g=spec('governance');const steps=g.components.filter(n=>/^0[1-6]/.test(n.label));
|
||||
assert.equal(steps.length,6);
|
||||
for(let i=1;i<steps.length;i++){assert(steps[i].pos[0]>steps[i-1].pos[0]);assert(steps[i].pos[1]>steps[i-1].pos[1]);has(g,steps[i-1].id,steps[i].id);}
|
||||
has(g,'approve','owner');has(g,'distribute','withdraw');
|
||||
const q=spec('quality');for(const id of ['selection','safety','artifact','outcome'])has(q,id,'gate');has(q,'gate','accept');has(q,'gate','rework');
|
||||
const a=spec('adoption');for(const [x,y] of [['human','agent'],['agent','human'],['agent','learn'],['learn','delivery'],['learn','expand'],['human','expand']])has(a,x,y);
|
||||
const m=spec('measurement');for(const id of ['success','quality','cost','confidence']){has(m,id+'-source',id);has(m,id,'interpret');}
|
||||
const c=spec('maintenance');for(const [x,y] of [['observe','triage'],['triage','change'],['change','validate'],['validate','release'],['release','observe'],['triage','safe'],['safe','observe'],['triage','retire'],['triage','contain'],['contain','change']])has(c,x,y);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
// Native editable geometry and edges; scoped editorial typography for Stage 01.
|
||||
export const isSkillsStory = name => /^strategy-(governance|quality|adoption|measurement|maintenance)(-mobile)?$/.test(name);
|
||||
const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
||||
export function skillsViewer(html, spec) {
|
||||
if (spec.diagram_type !== 'architecture' || !spec.components.length) throw new Error('Skills story contract drift');
|
||||
const detailSize = spec.meta.output.includes('-mobile/') ? 34 : 32;
|
||||
for (const node of spec.components) {
|
||||
const pattern = new RegExp(`<g\\b[^>]*data-node-id="${node.id}"[^>]*>[\\s\\S]*?<text data-node-label=[\\s\\S]*?</g>`);
|
||||
const match = html.match(pattern);
|
||||
if (!match) throw new Error(`Missing Skills node: ${node.id}`);
|
||||
const original = match[0];
|
||||
const rectangles = [...original.matchAll(/<rect\b[^>]*class="c-[^"]+"[^>]*\/>/g)];
|
||||
if (rectangles.length !== 2) throw new Error('Skills shape contract drift');
|
||||
const [x,y] = node.pos;
|
||||
const lines = node.sublabel.split('\n');
|
||||
if (!node.tag || !node.sublabel || lines.length > 4) throw new Error(`Skills copy exceeds capacity: ${node.id}`);
|
||||
const text = (value, top, size, attrs) => `<text x="${x+28}" y="${y+top}" font-size="${size}" ${attrs}>${escape(value)}</text>`;
|
||||
const content = text(node.tag, 40, detailSize, 'class="t-muted" data-detail="context"')
|
||||
+ text(node.label, 89, 36, 'class="t-primary" data-node-label="" font-weight="700"')
|
||||
+ lines.map((line,i) => text(line, 138+i*42, detailSize, 'class="t-primary" data-detail="context"')).join('');
|
||||
const prefix = original.slice(0,rectangles[1].index+rectangles[1][0].length).replaceAll('rx="6"','rx="0"');
|
||||
html = html.replace(original,prefix+content+'</g>');
|
||||
}
|
||||
const roots = [...html.matchAll(/<svg\b[^>]*role="img"[^>]*>/g)];
|
||||
if (roots.length !== 1) throw new Error('Skills SVG contract drift');
|
||||
const [width,height] = spec.meta.viewBox;
|
||||
const root = roots[0][0].replace(/viewBox="[^"]*"/,`viewBox="0 0 ${width} ${height}"`);
|
||||
// Embedded in SVG so downloaded files retain typography without the viewer.
|
||||
html = html.replace(roots[0][0],root+`<style id="eldorado-skills-typography">
|
||||
[data-node-id] text {font-family:Arial,sans-serif;}
|
||||
rect[fill="url(#grid)"] {opacity:0;}
|
||||
[data-node-id] > rect:not(.c-mask) {stroke-width:3;}
|
||||
[data-edge-id] {opacity:1;}
|
||||
</style>`);
|
||||
// Native labels use tiny technical annotations. Enlarge both text and mask,
|
||||
// retaining the native edge identity, routing and explicit label position.
|
||||
html = html.replace(/(<g data-detail="context"[^>]*data-edge-id[^>]*>)([\s\S]*?)(<\/g>)/g, (all,open,body,close) => {
|
||||
const text = body.match(/<text x="([^"]+)" y="([^"]+)"([^>]*)>([\s\S]*?)<\/text>/);
|
||||
if (!text) throw new Error('Skills edge label contract drift');
|
||||
const x=Number(text[1]), y=Number(text[2]), width=text[4].replace(/&[^;]+;/g,'x').length*17+24;
|
||||
return open+`<rect x="${x-width/2}" y="${y-34}" width="${width}" height="44" class="c-mask"/>`+text[0].replace('font-size="8"',`font-size="${detailSize}"`)+close;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
@@ -1,17 +1,47 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync, existsSync} from 'node:fs';
|
||||
import {runInNewContext} from 'node:vm';
|
||||
import ts from 'typescript';
|
||||
const read = p => readFileSync(p, 'utf8');
|
||||
|
||||
test('Strategy has one canonical Target, then Roadmap, without a landing menu duplicate', () => {
|
||||
const sidebar = read('sidebars/strategy.ts').split('strategySidebar: [')[1].split(']')[0];
|
||||
const ids = [...sidebar.matchAll(/'([^']+)'/g)].map(match => match[1]);
|
||||
assert.deepEqual(ids, ['target-operating-model', 'roadmap', 'skills-governance', 'skills-quality', 'skills-adoption', 'measurement', 'skills-maintenance']);
|
||||
assert.equal(ids.filter(id => id === 'target-operating-model').length, 1);
|
||||
test('Strategy has Target, Roadmap, a linked Skills Adoption category and Autonomous Agent', () => {
|
||||
const compiled = ts.transpileModule(read('sidebars/strategy.ts'), {compilerOptions:{module:ts.ModuleKind.CommonJS}}).outputText;
|
||||
const sandbox = {exports:{}};
|
||||
runInNewContext(compiled, sandbox);
|
||||
const sidebar = JSON.parse(JSON.stringify(sandbox.exports.default.strategySidebar));
|
||||
assert.deepEqual(sidebar, ['target-operating-model', 'roadmap', {
|
||||
type:'category', label:'Stage - 01 - Skills Adoption',
|
||||
link:{type:'doc', id:'stage-01-skills-adoption'}, collapsed:true,
|
||||
items:[
|
||||
{type:'doc', id:'skills-governance', label:'Governance'},
|
||||
{type:'doc', id:'skills-quality', label:'Quality'},
|
||||
{type:'doc', id:'skills-adoption', label:'Adoption'},
|
||||
{type:'doc', id:'measurement', label:'Measurement'},
|
||||
{type:'doc', id:'skills-maintenance', label:'Maintenance'},
|
||||
{type:'category', label:'Reference', collapsed:true, items:[{type:'doc', id:'reference/measurement-contracts', label:'Measurement Contracts'}]},
|
||||
],
|
||||
}, 'stage-02-autonomous-agent']);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||
assert.match(read('docusaurus.config.ts'), /type: 'docSidebar', docsPluginId: 'strategy', sidebarId: 'strategySidebar', label: 'Strategy'/);
|
||||
});
|
||||
|
||||
test('stage overviews explain the foundation and future without replacing topic content', () => {
|
||||
const first = read('docs/strategy/stage-01-skills-adoption.mdx');
|
||||
const second = read('docs/strategy/stage-02-autonomous-agent.mdx');
|
||||
for (const [id, title, page] of [
|
||||
['stage-01-skills-adoption', 'Stage - 01 - Skills Adoption', first],
|
||||
['stage-02-autonomous-agent', 'Stage - 02 - Autonomous Agent', second],
|
||||
]) {
|
||||
assert(page.includes(`id: ${id}\ntitle: ${title}`));
|
||||
for (const link of ['./roadmap.mdx', './target-operating-model.mdx']) assert(page.includes(link));
|
||||
}
|
||||
for (const id of ['skills-governance','skills-quality','skills-adoption','measurement','skills-maintenance']) assert(first.includes(`./${id}.mdx`));
|
||||
for (const marker of ['human + AI Agent','Delivery first','order is not yet decided','KPIs','baseline','topology','feedback','new current situation']) assert(first.includes(marker), marker);
|
||||
for (const marker of ['future','proven skills','measurements','working practices','direction and review','not deployed autonomy','approved KPI thresholds']) assert(second.includes(marker), marker);
|
||||
assert.match(read('docs/strategy/measurement.mdx'), /^title: Skills Measurement\nsidebar_label: Measurement$/m);
|
||||
});
|
||||
|
||||
test('old Strategy entry redirects to Target instead of maintaining a second document', () => {
|
||||
assert(!existsSync('docs/strategy/index.mdx'));
|
||||
assert.match(read('docusaurus.config.ts'), /'@docusaurus\/plugin-client-redirects'/);
|
||||
|
||||
@@ -35,10 +35,10 @@ test('mobile story diagrams preserve the same nodes and directed relationships',
|
||||
}
|
||||
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 18 : 7), name);
|
||||
assert(desktop.components.length >= 5, name);
|
||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,route,via,labelAt,...e})=>e)});
|
||||
assert.deepEqual(semantic(desktop), semantic(mobile), name);
|
||||
assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-')) && e.from !== e.to), name);
|
||||
assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-') || /^strategy-(governance|quality|adoption|measurement|maintenance)$/.test(name)) && e.from !== e.to), name);
|
||||
assert.equal(desktop.meta.legend.mode, 'hidden');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,9 +3,10 @@ import assert from 'node:assert/strict';
|
||||
import {readFileSync, readdirSync} from 'node:fs';
|
||||
const read = p => readFileSync(p, 'utf8');
|
||||
const model = JSON.parse(read('research/ai-adoption/strategy-model.json'));
|
||||
test('seven canonical strategy pages preserve target boundary and separate evidence', () => {
|
||||
test('seven existing strategy pages and two stage overviews preserve canonical routes', () => {
|
||||
assert.equal(model.pages.length, 7);
|
||||
assert.equal(readdirSync('docs/strategy').filter(p => p.endsWith('.mdx')).length, 7);
|
||||
assert.deepEqual(readdirSync('docs/strategy').filter(p => p.endsWith('.mdx')).sort(),
|
||||
[...model.pages.map(({id}) => `${id}.mdx`), 'stage-01-skills-adoption.mdx', 'stage-02-autonomous-agent.mdx'].sort());
|
||||
for (const {id} of model.pages) {
|
||||
const page = read(`docs/strategy/${id}.mdx`);
|
||||
assert(page.includes(`id: ${id}`));
|
||||
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync, readdirSync, existsSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
import {verifySidebarCoverage} from './sidebar-coverage.mjs';
|
||||
const read = p => readFileSync(p, 'utf8');
|
||||
const config = read('docusaurus.config.ts');
|
||||
assert(config.includes("baseUrl: '/eldorado-v1/documentation/'"));
|
||||
@@ -16,15 +17,21 @@ const workflow = read('.gitea/workflows/pages.yml');
|
||||
assert(workflow.includes('mountpoint -q'));
|
||||
assert(workflow.includes("gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'"));
|
||||
if (process.argv.includes('--build')) {
|
||||
await verifySidebarCoverage();
|
||||
const model = JSON.parse(read('research/ai-adoption/strategy-model.json'));
|
||||
const indexes = readdirSync('build').filter(f => /^search-index.*\.json$/.test(f)).map(f => read(join('build',f))).join('\n');
|
||||
const redirect = read('build/strategy/index.html');
|
||||
assert(redirect.includes('/eldorado-v1/documentation/strategy/target-operating-model/'));
|
||||
assert(redirect.includes('window.location.href') && redirect.includes('http-equiv="refresh"') && !redirect.includes('archify-panel'), 'Landing must redirect, not duplicate Target');
|
||||
for (const {id, title} of model.pages) {
|
||||
const strategyPages = [...model.pages,
|
||||
{id:'stage-01-skills-adoption', title:'Stage - 01 - Skills Adoption'},
|
||||
{id:'stage-02-autonomous-agent', title:'Stage - 02 - Autonomous Agent'},
|
||||
];
|
||||
for (const {id, title} of strategyPages) {
|
||||
const route = `strategy/${id === 'index' ? '' : `${id}/`}index.html`;
|
||||
const page = read(join('build',route));
|
||||
assert(page.includes(title) && page.includes('Target operating model'), route);
|
||||
assert(page.includes(title), route);
|
||||
if (!id.startsWith('stage-')) assert(page.includes('Target operating model'), route);
|
||||
assert(indexes.includes(title), `Missing indexed Strategy page: ${id}`);
|
||||
for (const match of page.matchAll(/href="(\/eldorado-v1\/documentation\/[^"?#]*)/g)) {
|
||||
const local = decodeURIComponent(match[1].replace('/eldorado-v1/documentation/',''));
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
export default {contributionSidebar: ['contribute', 'diagrams']} satisfies SidebarsConfig;
|
||||
export default {
|
||||
contributionSidebar: ['contribute', 'diagrams', {
|
||||
type: 'category', label: 'Portal maintenance', collapsed: true,
|
||||
items: [
|
||||
{type: 'link', label: 'Starting point', href: '/eldorado-v1/documentation/docs/overview/'},
|
||||
{type: 'link', label: 'Update and publish', href: '/eldorado-v1/documentation/docs/maintain/'},
|
||||
{type: 'link', label: 'Site configuration', href: '/eldorado-v1/documentation/docs/reference/'},
|
||||
],
|
||||
}],
|
||||
} satisfies SidebarsConfig;
|
||||
+24
-1
@@ -1,2 +1,25 @@
|
||||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
export default {strategySidebar: ['target-operating-model', 'roadmap', 'skills-governance', 'skills-quality', 'skills-adoption', 'measurement', 'skills-maintenance']} satisfies SidebarsConfig;
|
||||
export default {
|
||||
strategySidebar: [
|
||||
'target-operating-model',
|
||||
'roadmap',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Stage - 01 - Skills Adoption',
|
||||
link: {type: 'doc', id: 'stage-01-skills-adoption'},
|
||||
collapsed: true,
|
||||
items: [
|
||||
{type: 'doc', id: 'skills-governance', label: 'Governance'},
|
||||
{type: 'doc', id: 'skills-quality', label: 'Quality'},
|
||||
{type: 'doc', id: 'skills-adoption', label: 'Adoption'},
|
||||
{type: 'doc', id: 'measurement', label: 'Measurement'},
|
||||
{type: 'doc', id: 'skills-maintenance', label: 'Maintenance'},
|
||||
{
|
||||
type: 'category', label: 'Reference', collapsed: true,
|
||||
items: [{type: 'doc', id: 'reference/measurement-contracts', label: 'Measurement Contracts'}],
|
||||
},
|
||||
],
|
||||
},
|
||||
'stage-02-autonomous-agent',
|
||||
],
|
||||
} satisfies SidebarsConfig;
|
||||
@@ -12,14 +12,15 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
const headingId = useId();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [narrowRoadmap, setNarrowRoadmap] = useState(false);
|
||||
const skillsStory = /^strategy-(governance|quality|adoption|measurement|maintenance)$/.test(name);
|
||||
useEffect(() => {
|
||||
if (!name.startsWith('strategy-roadmap')) return;
|
||||
const media = window.matchMedia('(max-width: 999px)');
|
||||
if (!name.startsWith('strategy-roadmap') && !skillsStory) return;
|
||||
const media = window.matchMedia(skillsStory ? '(max-width: 1100px)' : '(max-width: 999px)');
|
||||
const update = () => setNarrowRoadmap(media.matches);
|
||||
update();
|
||||
media.addEventListener('change', update);
|
||||
return () => media.removeEventListener('change', update);
|
||||
}, [name]);
|
||||
}, [name, skillsStory]);
|
||||
const {colorMode, setColorMode} = useColorMode();
|
||||
const initialTheme = useRef(colorMode);
|
||||
const desktop = useRef<HTMLIFrameElement>(null);
|
||||
@@ -66,7 +67,7 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
return () => {document.body.style.overflow = previousOverflow;};
|
||||
}, [expanded]);
|
||||
const close = () => {dialog.current?.close();};
|
||||
return <section className={`archify-panel${compact ? ' archify-panel--compact' : ''}${story ? ' archify-panel--story' : ''}`} data-diagram={name} aria-label={title}>
|
||||
return <section className={`archify-panel${compact ? ' archify-panel--compact' : ''}${story ? ' archify-panel--story' : ''}${skillsStory ? ' archify-panel--skills' : ''}`} data-diagram={name} aria-label={title}>
|
||||
<div className="archify-toolbar"><button ref={opener} type="button" className="archify-expand" aria-label={`Expand ${title}`} title="Expand diagram" onClick={() => setExpanded(true)}><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="M8 3H3v5M16 3h5v5M21 16v5h-5M8 21H3v-5" /></svg></button></div>
|
||||
{story ? <>
|
||||
<iframe className="story-desktop" ref={desktop} onLoad={() => syncTheme(desktop.current)} src={`${source}?embed=1&theme=${initialTheme.current}`} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />
|
||||
@@ -74,7 +75,7 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
</> : <iframe ref={desktop} onLoad={() => syncTheme(desktop.current)} src={`${source}?theme=${initialTheme.current}`} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />}
|
||||
<dialog ref={dialog} className="archify-modal" aria-labelledby={headingId} onClose={() => {setExpanded(false); opener.current?.focus();}}>
|
||||
<div className="archify-modal-toolbar"><strong id={headingId}>{title}</strong><button type="button" onClick={close} autoFocus aria-label="Close full-screen diagram"><span aria-hidden="true">×</span></button></div>
|
||||
{expanded && <iframe ref={fullscreen} onLoad={() => syncTheme(fullscreen.current)} src={`${narrowRoadmap ? mobileSource : source}?${name.startsWith('strategy-roadmap') ? 'embed=1&' : ''}theme=${initialTheme.current}`} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||
{expanded && <iframe ref={fullscreen} onLoad={() => syncTheme(fullscreen.current)} src={`${narrowRoadmap ? mobileSource : source}?${name.startsWith('strategy-roadmap') || skillsStory ? 'embed=1&' : ''}theme=${initialTheme.current}`} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||
</dialog>
|
||||
</section>;
|
||||
}
|
||||
+19
-4
@@ -116,8 +116,7 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
.archify-panel--story[data-diagram="strategy-roadmap-loop"] {--roadmap-height: 1840px;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap-loop"] > .story-desktop {height: 460px;}
|
||||
.archify-panel--story[data-diagram="strategy-target"] > iframe {aspect-ratio: 1620 / 1430;}
|
||||
.archify-panel--story[data-diagram="strategy-adoption"] > iframe {aspect-ratio: 545 / 408;}
|
||||
.archify-panel--story[data-diagram="strategy-maintenance"] > iframe {aspect-ratio: 524 / 408;}
|
||||
|
||||
.strategy-content > details {margin: .65rem 0; border: 1px solid var(--line); padding: .8rem 1rem; background: var(--surface);}
|
||||
.strategy-content summary {cursor: pointer; font-weight: 700;}
|
||||
.strategy-content summary:focus-visible {outline: 3px solid #b88719; outline-offset: 4px;}
|
||||
@@ -127,8 +126,7 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
.archify-panel--story > .story-desktop {display: none;}
|
||||
.archify-panel--story > .story-mobile {display: block; height: auto; aspect-ratio: 240 / 748;}
|
||||
|
||||
.archify-panel--story[data-diagram="strategy-adoption"] > .story-mobile {aspect-ratio: 305 / 748;}
|
||||
.archify-panel--story[data-diagram="strategy-maintenance"] > .story-mobile {aspect-ratio: 284 / 748;}
|
||||
|
||||
.archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 680 / 2340;}
|
||||
}
|
||||
/* Keep the three-card loop readable beside the documentation sidebar. */
|
||||
@@ -152,3 +150,20 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
[data-diagram^="strategy-roadmap"] .archify-modal iframe {flex: none; height: var(--roadmap-height); min-height: var(--roadmap-height);}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {.start-card {transition: none;} .start-card:hover {transform: none;}}
|
||||
|
||||
/* Skills Adoption alone uses the full reading column, not a narrow preview. */
|
||||
.theme-doc-markdown:has(.archify-panel--skills) {max-width: none;}
|
||||
.archify-panel--story.archify-panel--skills {max-width: none;}
|
||||
.archify-panel--skills {--skills-wide: 2000 / 1200; --skills-narrow: 880 / 2900;}
|
||||
.archify-panel--skills[data-diagram="strategy-governance"] {--skills-wide: 1760 / 1550;}
|
||||
.archify-panel--skills[data-diagram="strategy-adoption"] {--skills-wide: 2040 / 1300; --skills-narrow: 880 / 2200;}
|
||||
.archify-panel--skills[data-diagram="strategy-measurement"] {--skills-wide: 2400 / 1280; --skills-narrow: 880 / 3900;}
|
||||
.archify-panel--skills[data-diagram="strategy-maintenance"] {--skills-wide: 2630 / 1480; --skills-narrow: 880 / 3250;}
|
||||
.archify-panel--story.archify-panel--skills > .story-desktop {aspect-ratio: var(--skills-wide); height: auto;}
|
||||
@media (max-width: 1100px) {
|
||||
.archify-panel--story.archify-panel--skills > .story-desktop {display: none;}
|
||||
.archify-panel--story.archify-panel--skills > .story-mobile {display: block; aspect-ratio: var(--skills-narrow); height: auto; max-width: 440px; margin-inline: auto;}
|
||||
.archify-panel--skills .archify-modal {overflow-y: auto; overscroll-behavior: contain;}
|
||||
.archify-panel--skills .archify-modal-toolbar {position: sticky; top: 0; z-index: 1; flex-shrink: 0; background: var(--surface);}
|
||||
.archify-panel--skills .archify-modal iframe {flex: none; height: auto; min-height: 0; aspect-ratio: var(--skills-narrow); max-width: 600px; margin-inline: auto;}
|
||||
}
|
||||
Reference in new issue
Block a user