Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0248baa97b | ||
|
|
7e3696d2d7 | ||
|
|
15c5dcb31d | ||
|
|
64b2d6dae8 | ||
|
|
20e6f4899c | ||
|
|
2f68e60cec | ||
|
|
e149131307 | ||
|
|
2d63313180 | ||
|
|
1de7d16d09 | ||
|
|
b679e6b2b5 | ||
|
|
8dd54f0ab6 | ||
|
|
8bfcb3f81d | ||
|
|
baf79e125b | ||
|
|
9651417ba7 | ||
|
|
4cc2bb4dfd | ||
|
|
afb6a97e97 | ||
|
|
1057433c2a | ||
|
|
504d9f8845 | ||
|
|
29beebb379 | ||
|
|
e95b85afee |
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"diagram_type": "architecture",
|
"diagram_type": "architecture",
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "TO-BE: Evidence before claims",
|
"title": "TO-BE: Measure skills adoption",
|
||||||
"output": "static/diagrams/strategy-measurement-mobile/index.html",
|
"output": "static/diagrams/strategy-measurement-mobile/index.html",
|
||||||
"quality_profile": "showcase",
|
"quality_profile": "showcase",
|
||||||
"legend": {
|
"legend": {
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
{
|
{
|
||||||
"id": "collect",
|
"id": "collect",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Collect evidence",
|
"label": "Establish baseline",
|
||||||
"sublabel": "Task / CI / incident",
|
"sublabel": "Current workflow",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
20
|
20
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
{
|
{
|
||||||
"id": "validate",
|
"id": "validate",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Validate sources",
|
"label": "Join evidence",
|
||||||
"sublabel": "Joins + missingness",
|
"sublabel": "Tasks / versions / costs",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
140
|
140
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
{
|
{
|
||||||
"id": "calculate",
|
"id": "calculate",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Calculate measures",
|
"label": "Measure the pilot",
|
||||||
"sublabel": "16 exact contracts",
|
"sublabel": "Outcomes / quality",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
260
|
260
|
||||||
@@ -55,8 +55,8 @@
|
|||||||
{
|
{
|
||||||
"id": "balance",
|
"id": "balance",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Read balanced signals",
|
"label": "Report together",
|
||||||
"sublabel": "Quality / flow / risk",
|
"sublabel": "Cost / confidence / gaps",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
380
|
380
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
{
|
{
|
||||||
"id": "weigh",
|
"id": "weigh",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Weigh benefit and cost",
|
"label": "Broader adoption",
|
||||||
"sublabel": "Outcomes / cost / people",
|
"sublabel": "Availability + use",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
500
|
500
|
||||||
@@ -83,8 +83,8 @@
|
|||||||
{
|
{
|
||||||
"id": "decide",
|
"id": "decide",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Continue or change",
|
"label": "Ongoing monitoring",
|
||||||
"sublabel": "No causal ROI claim",
|
"sublabel": "Review + maintenance",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
620
|
620
|
||||||
@@ -100,31 +100,31 @@
|
|||||||
"id": "flow-0",
|
"id": "flow-0",
|
||||||
"from": "collect",
|
"from": "collect",
|
||||||
"to": "validate",
|
"to": "validate",
|
||||||
"label": "Check"
|
"label": "prepare"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-1",
|
"id": "flow-1",
|
||||||
"from": "validate",
|
"from": "validate",
|
||||||
"to": "calculate",
|
"to": "calculate",
|
||||||
"label": "apply definitions"
|
"label": "collect"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-2",
|
"id": "flow-2",
|
||||||
"from": "calculate",
|
"from": "calculate",
|
||||||
"to": "balance",
|
"to": "balance",
|
||||||
"label": "compare"
|
"label": "calculate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-3",
|
"id": "flow-3",
|
||||||
"from": "balance",
|
"from": "balance",
|
||||||
"to": "weigh",
|
"to": "weigh",
|
||||||
"label": "consider together"
|
"label": "extend"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-4",
|
"id": "flow-4",
|
||||||
"from": "weigh",
|
"from": "weigh",
|
||||||
"to": "decide",
|
"to": "decide",
|
||||||
"label": "decide"
|
"label": "repeat"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"diagram_type": "architecture",
|
"diagram_type": "architecture",
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "TO-BE: Evidence before claims",
|
"title": "TO-BE: Measure skills adoption",
|
||||||
"output": "static/diagrams/strategy-measurement/index.html",
|
"output": "static/diagrams/strategy-measurement/index.html",
|
||||||
"quality_profile": "showcase",
|
"quality_profile": "showcase",
|
||||||
"legend": {
|
"legend": {
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
{
|
{
|
||||||
"id": "collect",
|
"id": "collect",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Collect evidence",
|
"label": "Establish baseline",
|
||||||
"sublabel": "Task / CI / incident",
|
"sublabel": "Current workflow",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
20
|
20
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
{
|
{
|
||||||
"id": "validate",
|
"id": "validate",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Validate sources",
|
"label": "Join evidence",
|
||||||
"sublabel": "Joins + missingness",
|
"sublabel": "Tasks / versions / costs",
|
||||||
"pos": [
|
"pos": [
|
||||||
260,
|
260,
|
||||||
20
|
20
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
{
|
{
|
||||||
"id": "calculate",
|
"id": "calculate",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Calculate measures",
|
"label": "Measure the pilot",
|
||||||
"sublabel": "16 exact contracts",
|
"sublabel": "Outcomes / quality",
|
||||||
"pos": [
|
"pos": [
|
||||||
260,
|
260,
|
||||||
150
|
150
|
||||||
@@ -55,8 +55,8 @@
|
|||||||
{
|
{
|
||||||
"id": "balance",
|
"id": "balance",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Read balanced signals",
|
"label": "Report together",
|
||||||
"sublabel": "Quality / flow / risk",
|
"sublabel": "Cost / confidence / gaps",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
150
|
150
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
{
|
{
|
||||||
"id": "weigh",
|
"id": "weigh",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Weigh benefit and cost",
|
"label": "Broader adoption",
|
||||||
"sublabel": "Outcomes / cost / people",
|
"sublabel": "Availability + use",
|
||||||
"pos": [
|
"pos": [
|
||||||
20,
|
20,
|
||||||
280
|
280
|
||||||
@@ -83,8 +83,8 @@
|
|||||||
{
|
{
|
||||||
"id": "decide",
|
"id": "decide",
|
||||||
"type": "backend",
|
"type": "backend",
|
||||||
"label": "Continue or change",
|
"label": "Ongoing monitoring",
|
||||||
"sublabel": "No causal ROI claim",
|
"sublabel": "Review + maintenance",
|
||||||
"pos": [
|
"pos": [
|
||||||
260,
|
260,
|
||||||
280
|
280
|
||||||
@@ -100,31 +100,31 @@
|
|||||||
"id": "flow-0",
|
"id": "flow-0",
|
||||||
"from": "collect",
|
"from": "collect",
|
||||||
"to": "validate",
|
"to": "validate",
|
||||||
"label": "Check"
|
"label": "prepare"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-1",
|
"id": "flow-1",
|
||||||
"from": "validate",
|
"from": "validate",
|
||||||
"to": "calculate",
|
"to": "calculate",
|
||||||
"label": "apply definitions"
|
"label": "collect"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-2",
|
"id": "flow-2",
|
||||||
"from": "calculate",
|
"from": "calculate",
|
||||||
"to": "balance",
|
"to": "balance",
|
||||||
"label": "compare"
|
"label": "calculate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-3",
|
"id": "flow-3",
|
||||||
"from": "balance",
|
"from": "balance",
|
||||||
"to": "weigh",
|
"to": "weigh",
|
||||||
"label": "consider together"
|
"label": "extend"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow-4",
|
"id": "flow-4",
|
||||||
"from": "weigh",
|
"from": "weigh",
|
||||||
"to": "decide",
|
"to": "decide",
|
||||||
"label": "decide"
|
"label": "repeat"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
{
|
|
||||||
"schema_version": 1,
|
|
||||||
"diagram_type": "architecture",
|
|
||||||
"meta": {
|
|
||||||
"title": "TO-BE: Evidence-gated rollout",
|
|
||||||
"output": "static/diagrams/strategy-roadmap-mobile/index.html",
|
|
||||||
"quality_profile": "showcase",
|
|
||||||
"legend": {
|
|
||||||
"mode": "hidden"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"id": "foundation",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "1 \u00b7 Foundation",
|
|
||||||
"sublabel": "People + data + baseline",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
20
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "pilot",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "2 \u00b7 Skills pilot",
|
|
||||||
"sublabel": "Qualified, useful work",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
140
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "distribution",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "3 \u00b7 Distribution",
|
|
||||||
"sublabel": "Revocation + recovery",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
260
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "coordination",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "4 \u00b7 Coordination",
|
|
||||||
"sublabel": "Human takeover proven",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
380
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "expansion",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "5 \u00b7 Departments",
|
|
||||||
"sublabel": "Outcomes + cost + risk",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
500
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"connections": [
|
|
||||||
{
|
|
||||||
"id": "flow-0",
|
|
||||||
"from": "foundation",
|
|
||||||
"to": "pilot",
|
|
||||||
"label": "Charter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-1",
|
|
||||||
"from": "pilot",
|
|
||||||
"to": "distribution",
|
|
||||||
"label": "quality + coverage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-2",
|
|
||||||
"from": "distribution",
|
|
||||||
"to": "coordination",
|
|
||||||
"label": "Controls"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-3",
|
|
||||||
"from": "coordination",
|
|
||||||
"to": "expansion",
|
|
||||||
"label": "authority drills pass"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 2,
|
||||||
|
"diagram_type": "workflow",
|
||||||
|
"meta": {
|
||||||
|
"title": "Recommended adoption roadmap",
|
||||||
|
"output": "static/diagrams/strategy-roadmap-mobile/index.html",
|
||||||
|
"quality_profile": "showcase",
|
||||||
|
"animation": "none",
|
||||||
|
"legend": {
|
||||||
|
"mode": "hidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lanes": [
|
||||||
|
{
|
||||||
|
"id": "adoption",
|
||||||
|
"label": "Recommended sequence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mainPath": [
|
||||||
|
"assessment",
|
||||||
|
"readiness",
|
||||||
|
"pilot",
|
||||||
|
"expansion",
|
||||||
|
"sustain"
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "assessment",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 0,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Understand today",
|
||||||
|
"sublabel": "Assess code quality and real delivery.\nOutput: baseline, process map, prioritized gaps.",
|
||||||
|
"tag": "Exit: Workflow understood",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460,
|
||||||
|
"yOffset": -1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "readiness",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 0,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Prepare foundations",
|
||||||
|
"sublabel": "Make the pilot safe.\nOutput: readiness decision and pilot scope.",
|
||||||
|
"tag": "Exit: Critical gaps closed",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460,
|
||||||
|
"yOffset": -500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pilot",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 0,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Pilot",
|
||||||
|
"sublabel": "Try reviewed skills.\nOutput: results compared with the baseline.",
|
||||||
|
"tag": "Exit: Continue, adjust or stop",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460,
|
||||||
|
"yOffset": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "expansion",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 0,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Expand",
|
||||||
|
"sublabel": "Add a workflow or team.\nOutput: evidence for the next scope.",
|
||||||
|
"tag": "Exit: Controls proven",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460,
|
||||||
|
"yOffset": 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sustain",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 0,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Sustain",
|
||||||
|
"sublabel": "Review and improve.\nOutput: maintained skills and refreshed backlog.",
|
||||||
|
"tag": "Exit: Reassess when changed",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460,
|
||||||
|
"yOffset": 1000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "flow-0",
|
||||||
|
"from": "assessment",
|
||||||
|
"to": "readiness",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flow-1",
|
||||||
|
"from": "readiness",
|
||||||
|
"to": "pilot",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flow-2",
|
||||||
|
"from": "pilot",
|
||||||
|
"to": "expansion",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flow-3",
|
||||||
|
"from": "expansion",
|
||||||
|
"to": "sustain",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toSide": "top"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
{
|
|
||||||
"schema_version": 1,
|
|
||||||
"diagram_type": "architecture",
|
|
||||||
"meta": {
|
|
||||||
"title": "TO-BE: Evidence-gated rollout",
|
|
||||||
"output": "static/diagrams/strategy-roadmap/index.html",
|
|
||||||
"quality_profile": "showcase",
|
|
||||||
"legend": {
|
|
||||||
"mode": "hidden"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"id": "foundation",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "1 \u00b7 Foundation",
|
|
||||||
"sublabel": "People + data + baseline",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
20
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "pilot",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "2 \u00b7 Skills pilot",
|
|
||||||
"sublabel": "Qualified, useful work",
|
|
||||||
"pos": [
|
|
||||||
260,
|
|
||||||
20
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "distribution",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "3 \u00b7 Distribution",
|
|
||||||
"sublabel": "Revocation + recovery",
|
|
||||||
"pos": [
|
|
||||||
260,
|
|
||||||
150
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "coordination",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "4 \u00b7 Coordination",
|
|
||||||
"sublabel": "Human takeover proven",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
150
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "expansion",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "5 \u00b7 Departments",
|
|
||||||
"sublabel": "Outcomes + cost + risk",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
280
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"connections": [
|
|
||||||
{
|
|
||||||
"id": "flow-0",
|
|
||||||
"from": "foundation",
|
|
||||||
"to": "pilot",
|
|
||||||
"label": "Charter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-1",
|
|
||||||
"from": "pilot",
|
|
||||||
"to": "distribution",
|
|
||||||
"label": "quality + coverage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-2",
|
|
||||||
"from": "distribution",
|
|
||||||
"to": "coordination",
|
|
||||||
"label": "Controls"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-3",
|
|
||||||
"from": "coordination",
|
|
||||||
"to": "expansion",
|
|
||||||
"label": "authority drills pass"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 2,
|
||||||
|
"diagram_type": "workflow",
|
||||||
|
"meta": {
|
||||||
|
"title": "Recommended adoption roadmap",
|
||||||
|
"output": "static/diagrams/strategy-roadmap/index.html",
|
||||||
|
"quality_profile": "showcase",
|
||||||
|
"animation": "none",
|
||||||
|
"legend": {
|
||||||
|
"mode": "hidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lanes": [
|
||||||
|
{
|
||||||
|
"id": "adoption",
|
||||||
|
"label": "Recommended sequence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mainPath": [
|
||||||
|
"assessment",
|
||||||
|
"readiness",
|
||||||
|
"pilot",
|
||||||
|
"expansion",
|
||||||
|
"sustain"
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "assessment",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 0,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Understand today",
|
||||||
|
"sublabel": "Assess code quality and real delivery.\nOutput: baseline, process map, prioritized gaps.",
|
||||||
|
"tag": "Exit: Workflow understood",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "readiness",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 1,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Prepare foundations",
|
||||||
|
"sublabel": "Make the pilot safe.\nOutput: readiness decision and pilot scope.",
|
||||||
|
"tag": "Exit: Critical gaps closed",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pilot",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 2,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Pilot",
|
||||||
|
"sublabel": "Try reviewed skills.\nOutput: results compared with the baseline.",
|
||||||
|
"tag": "Exit: Continue, adjust or stop",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "expansion",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 3,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Expand",
|
||||||
|
"sublabel": "Add a workflow or team.\nOutput: evidence for the next scope.",
|
||||||
|
"tag": "Exit: Controls proven",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sustain",
|
||||||
|
"lane": "adoption",
|
||||||
|
"col": 4,
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Sustain",
|
||||||
|
"sublabel": "Review and improve.\nOutput: maintained skills and refreshed backlog.",
|
||||||
|
"tag": "Exit: Reassess when changed",
|
||||||
|
"width": 340,
|
||||||
|
"height": 460
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "flow-0",
|
||||||
|
"from": "assessment",
|
||||||
|
"to": "readiness",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toSide": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flow-1",
|
||||||
|
"from": "readiness",
|
||||||
|
"to": "pilot",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toSide": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flow-2",
|
||||||
|
"from": "pilot",
|
||||||
|
"to": "expansion",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toSide": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flow-3",
|
||||||
|
"from": "expansion",
|
||||||
|
"to": "sustain",
|
||||||
|
"role": "main",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toSide": "left"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,156 +1,51 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"diagram_type": "architecture",
|
"diagram_type": "architecture",
|
||||||
"meta": {
|
"meta": {"title": "TO-BE: autonomous SDLC, human direction", "output": "static/diagrams/strategy-target-mobile/index.html", "quality_profile": "showcase", "legend": {"mode": "hidden"}},
|
||||||
"title": "TO-BE: coordination without authority transfer",
|
|
||||||
"output": "static/diagrams/strategy-target-mobile/index.html",
|
|
||||||
"quality_profile": "showcase",
|
|
||||||
"legend": {
|
|
||||||
"mode": "hidden"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{"id":"agent","type":"security","label":"Autonomous agent","sublabel":"Human Controlled","pos":[140,200],"size":[360,120]},
|
||||||
"id": "people",
|
{"id":"scope","type":"backend","label":"Scope","sublabel":"01","pos":[100,440],"size":[240,100]},
|
||||||
"type": "backend",
|
{"id":"architecture","type":"backend","label":"Architecture","sublabel":"02","pos":[100,660],"size":[240,100]},
|
||||||
"label": "People authorize",
|
{"id":"ui-ux","type":"backend","label":"UI/UX","sublabel":"03","pos":[100,880],"size":[240,100]},
|
||||||
"sublabel": "Retain accountability",
|
{"id":"planning","type":"backend","label":"Planning","sublabel":"04","pos":[100,1100],"size":[240,100]},
|
||||||
"pos": [
|
{"id":"delivery","type":"backend","label":"Delivery","sublabel":"05","pos":[100,1320],"size":[240,100]},
|
||||||
40,
|
{"id":"uat","type":"backend","label":"UAT","sublabel":"06","pos":[100,1540],"size":[240,100]},
|
||||||
20
|
{"id":"security","type":"backend","label":"Security","sublabel":"07","pos":[100,1760],"size":[240,100]},
|
||||||
],
|
{"id":"releases","type":"backend","label":"Releases","sublabel":"08","pos":[100,1980],"size":[240,100]},
|
||||||
"size": [
|
{"id":"code-scope","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,440],"size":[240,100]},
|
||||||
180,
|
{"id":"code-architecture","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,660],"size":[240,100]},
|
||||||
60
|
{"id":"code-ui-ux","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,880],"size":[240,100]},
|
||||||
]
|
{"id":"code-planning","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,1100],"size":[240,100]},
|
||||||
},
|
{"id":"code-delivery","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,1320],"size":[240,100]},
|
||||||
{
|
{"id":"code-uat","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,1540],"size":[240,100]},
|
||||||
"id": "coordinator",
|
{"id":"code-security","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,1760],"size":[240,100]},
|
||||||
"type": "backend",
|
{"id":"code-releases","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[400,1980],"size":[240,100]}
|
||||||
"label": "SDLC coordinator",
|
|
||||||
"sublabel": "Routes approved work",
|
|
||||||
"pos": [
|
|
||||||
40,
|
|
||||||
260
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "clients",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "Local coding agents",
|
|
||||||
"sublabel": "Propose changes",
|
|
||||||
"pos": [
|
|
||||||
40,
|
|
||||||
380
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gates",
|
|
||||||
"type": "security",
|
|
||||||
"label": "Repository + CI",
|
|
||||||
"sublabel": "Verify exact revision",
|
|
||||||
"pos": [
|
|
||||||
40,
|
|
||||||
500
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "registry",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "Governed skills",
|
|
||||||
"sublabel": "Pinned + qualified",
|
|
||||||
"pos": [
|
|
||||||
40,
|
|
||||||
140
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "approval",
|
|
||||||
"type": "security",
|
|
||||||
"label": "Human approval",
|
|
||||||
"sublabel": "Exact artifact + scope",
|
|
||||||
"pos": [
|
|
||||||
40,
|
|
||||||
620
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "release",
|
|
||||||
"type": "security",
|
|
||||||
"label": "Release service",
|
|
||||||
"sublabel": "Production credentials",
|
|
||||||
"pos": [
|
|
||||||
40,
|
|
||||||
740
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
|
||||||
"id": "flow-0",
|
{"id":"govern-scope","from":"agent","to":"scope","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,490]]},
|
||||||
"from": "people",
|
{"id":"govern-architecture","from":"agent","to":"architecture","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,710]]},
|
||||||
"to": "coordinator",
|
{"id":"govern-ui-ux","from":"agent","to":"ui-ux","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,930]]},
|
||||||
"label": "Authorize scope"
|
{"id":"govern-planning","from":"agent","to":"planning","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1150]]},
|
||||||
},
|
{"id":"govern-delivery","from":"agent","to":"delivery","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1370]]},
|
||||||
{
|
{"id":"govern-uat","from":"agent","to":"uat","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1590]]},
|
||||||
"id": "flow-1",
|
{"id":"govern-security","from":"agent","to":"security","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1810]]},
|
||||||
"from": "coordinator",
|
{"id":"govern-releases","from":"agent","to":"releases","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,2030]]},
|
||||||
"to": "clients",
|
{"id":"stage-1","from":"scope","to":"architecture","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
"label": "Route tasks"
|
{"id":"stage-2","from":"architecture","to":"ui-ux","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
},
|
{"id":"stage-3","from":"ui-ux","to":"planning","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
{
|
{"id":"stage-4","from":"planning","to":"delivery","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
"id": "flow-2",
|
{"id":"stage-5","from":"delivery","to":"uat","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
"from": "registry",
|
{"id":"stage-6","from":"uat","to":"security","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
"to": "coordinator",
|
{"id":"stage-7","from":"security","to":"releases","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
"label": "Guide"
|
{"id":"stage-8","from":"releases","to":"scope","label":"Repeat indefinitely","variant":"emphasis","fromSide":"bottom","toSide":"top","via":[[220,2140],[680,2140],[680,380],[220,380]],"labelAt":[440,2180]},
|
||||||
},
|
{"id":"contribute-scope","from":"code-scope","to":"scope","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
{
|
{"id":"contribute-architecture","from":"code-architecture","to":"architecture","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
"id": "flow-3",
|
{"id":"contribute-ui-ux","from":"code-ui-ux","to":"ui-ux","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
"from": "registry",
|
{"id":"contribute-planning","from":"code-planning","to":"planning","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
"to": "clients",
|
{"id":"contribute-delivery","from":"code-delivery","to":"delivery","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
"label": "Guide"
|
{"id":"contribute-uat","from":"code-uat","to":"uat","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
},
|
{"id":"contribute-security","from":"code-security","to":"security","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||||
{
|
{"id":"contribute-releases","from":"code-releases","to":"releases","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"}
|
||||||
"id": "flow-4",
|
|
||||||
"from": "clients",
|
|
||||||
"to": "gates",
|
|
||||||
"label": "Propose"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-5",
|
|
||||||
"from": "gates",
|
|
||||||
"to": "approval",
|
|
||||||
"label": "Evidence"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-6",
|
|
||||||
"from": "approval",
|
|
||||||
"to": "release",
|
|
||||||
"label": "Authorize"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,156 +1,51 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"diagram_type": "architecture",
|
"diagram_type": "architecture",
|
||||||
"meta": {
|
"meta": {"title": "TO-BE: autonomous SDLC, human direction", "output": "static/diagrams/strategy-target/index.html", "quality_profile": "showcase", "legend": {"mode": "hidden"}},
|
||||||
"title": "TO-BE: coordination without authority transfer",
|
|
||||||
"output": "static/diagrams/strategy-target/index.html",
|
|
||||||
"quality_profile": "showcase",
|
|
||||||
"legend": {
|
|
||||||
"mode": "hidden"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{"id":"agent","type":"security","label":"Autonomous agent","sublabel":"Human Controlled","pos":[465,450],"size":[270,90]},
|
||||||
"id": "people",
|
{"id":"scope","type":"backend","label":"Scope","sublabel":"01","pos":[510,187.5],"size":[180,75]},
|
||||||
"type": "backend",
|
{"id":"architecture","type":"backend","label":"Architecture","sublabel":"02","pos":[780,187.5],"size":[180,75]},
|
||||||
"label": "People authorize",
|
{"id":"ui-ux","type":"backend","label":"UI/UX","sublabel":"03","pos":[780,457.5],"size":[180,75]},
|
||||||
"sublabel": "Retain accountability",
|
{"id":"planning","type":"backend","label":"Planning","sublabel":"04","pos":[780,727.5],"size":[180,75]},
|
||||||
"pos": [
|
{"id":"delivery","type":"backend","label":"Delivery","sublabel":"05","pos":[510,727.5],"size":[180,75]},
|
||||||
20,
|
{"id":"uat","type":"backend","label":"UAT","sublabel":"06","pos":[240,727.5],"size":[180,75]},
|
||||||
20
|
{"id":"security","type":"backend","label":"Security","sublabel":"07","pos":[240,457.5],"size":[180,75]},
|
||||||
],
|
{"id":"releases","type":"backend","label":"Releases","sublabel":"08","pos":[240,187.5],"size":[180,75]},
|
||||||
"size": [
|
{"id":"code-scope","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[510,37.5],"size":[180,75]},
|
||||||
180,
|
{"id":"code-architecture","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[780,37.5],"size":[180,75]},
|
||||||
60
|
{"id":"code-ui-ux","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[1005,457.5],"size":[180,75]},
|
||||||
]
|
{"id":"code-planning","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[780,877.5],"size":[180,75]},
|
||||||
},
|
{"id":"code-delivery","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[510,877.5],"size":[180,75]},
|
||||||
{
|
{"id":"code-uat","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[240,877.5],"size":[180,75]},
|
||||||
"id": "coordinator",
|
{"id":"code-security","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[15,457.5],"size":[180,75]},
|
||||||
"type": "backend",
|
{"id":"code-releases","type":"frontend","label":"AI Agent","sublabel":"Human-controlled","pos":[240,37.5],"size":[180,75]}
|
||||||
"label": "SDLC coordinator",
|
|
||||||
"sublabel": "Routes approved work",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
140
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "clients",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "Local coding agents",
|
|
||||||
"sublabel": "Propose changes",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
260
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gates",
|
|
||||||
"type": "security",
|
|
||||||
"label": "Repository + CI",
|
|
||||||
"sublabel": "Verify exact revision",
|
|
||||||
"pos": [
|
|
||||||
20,
|
|
||||||
380
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "registry",
|
|
||||||
"type": "backend",
|
|
||||||
"label": "Governed skills",
|
|
||||||
"sublabel": "Pinned + qualified",
|
|
||||||
"pos": [
|
|
||||||
260,
|
|
||||||
140
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "approval",
|
|
||||||
"type": "security",
|
|
||||||
"label": "Human approval",
|
|
||||||
"sublabel": "Exact artifact + scope",
|
|
||||||
"pos": [
|
|
||||||
260,
|
|
||||||
380
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "release",
|
|
||||||
"type": "security",
|
|
||||||
"label": "Release service",
|
|
||||||
"sublabel": "Production credentials",
|
|
||||||
"pos": [
|
|
||||||
260,
|
|
||||||
500
|
|
||||||
],
|
|
||||||
"size": [
|
|
||||||
180,
|
|
||||||
60
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
|
||||||
"id": "flow-0",
|
{"id":"govern-scope","from":"agent","to":"scope","label":"","variant":"security","fromSide":"top","toSide":"bottom","route":"straight"},
|
||||||
"from": "people",
|
{"id":"govern-architecture","from":"agent","to":"architecture","label":"","variant":"security","fromSide":"top","toSide":"bottom","via":[[600,345],[870,345]]},
|
||||||
"to": "coordinator",
|
{"id":"govern-ui-ux","from":"agent","to":"ui-ux","label":"","variant":"security","fromSide":"right","toSide":"left","route":"straight"},
|
||||||
"label": "Authorize scope"
|
{"id":"govern-planning","from":"agent","to":"planning","label":"","variant":"security","fromSide":"bottom","toSide":"top","via":[[600,645],[870,645]]},
|
||||||
},
|
{"id":"govern-delivery","from":"agent","to":"delivery","label":"","variant":"security","fromSide":"bottom","toSide":"top","route":"straight"},
|
||||||
{
|
{"id":"govern-uat","from":"agent","to":"uat","label":"","variant":"security","fromSide":"bottom","toSide":"top","via":[[600,645],[330,645]]},
|
||||||
"id": "flow-1",
|
{"id":"govern-security","from":"agent","to":"security","label":"","variant":"security","fromSide":"left","toSide":"right","route":"straight"},
|
||||||
"from": "coordinator",
|
{"id":"govern-releases","from":"agent","to":"releases","label":"","variant":"security","fromSide":"top","toSide":"bottom","via":[[600,345],[330,345]]},
|
||||||
"to": "clients",
|
{"id":"stage-1","from":"scope","to":"architecture","label":"Then","variant":"emphasis","fromSide":"right","toSide":"left"},
|
||||||
"label": "Route tasks"
|
{"id":"stage-2","from":"architecture","to":"ui-ux","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
},
|
{"id":"stage-3","from":"ui-ux","to":"planning","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||||
{
|
{"id":"stage-4","from":"planning","to":"delivery","label":"Then","variant":"emphasis","fromSide":"left","toSide":"right"},
|
||||||
"id": "flow-2",
|
{"id":"stage-5","from":"delivery","to":"uat","label":"Then","variant":"emphasis","fromSide":"left","toSide":"right"},
|
||||||
"from": "registry",
|
{"id":"stage-6","from":"uat","to":"security","label":"Then","variant":"emphasis","fromSide":"top","toSide":"bottom"},
|
||||||
"to": "coordinator",
|
{"id":"stage-7","from":"security","to":"releases","label":"Then","variant":"emphasis","fromSide":"top","toSide":"bottom"},
|
||||||
"label": "Guide"
|
{"id":"stage-8","from":"releases","to":"scope","label":"Repeat indefinitely","variant":"emphasis","fromSide":"right","toSide":"left","labelAt":[465,170]},
|
||||||
},
|
{"id":"contribute-scope","from":"code-scope","to":"scope","label":"Contribute","variant":"dashed","fromSide":"bottom","toSide":"top"},
|
||||||
{
|
{"id":"contribute-architecture","from":"code-architecture","to":"architecture","label":"Contribute","variant":"dashed","fromSide":"bottom","toSide":"top"},
|
||||||
"id": "flow-3",
|
{"id":"contribute-ui-ux","from":"code-ui-ux","to":"ui-ux","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right","labelAt":[990,570]},
|
||||||
"from": "registry",
|
{"id":"contribute-planning","from":"code-planning","to":"planning","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"},
|
||||||
"to": "clients",
|
{"id":"contribute-delivery","from":"code-delivery","to":"delivery","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"},
|
||||||
"label": "Guide"
|
{"id":"contribute-uat","from":"code-uat","to":"uat","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"},
|
||||||
},
|
{"id":"contribute-security","from":"code-security","to":"security","label":"Contribute","variant":"dashed","fromSide":"right","toSide":"left","labelAt":[220,570]},
|
||||||
{
|
{"id":"contribute-releases","from":"code-releases","to":"releases","label":"Contribute","variant":"dashed","fromSide":"bottom","toSide":"top"}
|
||||||
"id": "flow-4",
|
|
||||||
"from": "clients",
|
|
||||||
"to": "gates",
|
|
||||||
"label": "Propose"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-5",
|
|
||||||
"from": "gates",
|
|
||||||
"to": "approval",
|
|
||||||
"label": "Evidence"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flow-6",
|
|
||||||
"from": "approval",
|
|
||||||
"to": "release",
|
|
||||||
"label": "Authorize"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ description: Author typed JSON diagrams and embed their local interactive viewer
|
|||||||
| Dataflow | Sources, processing and destinations |
|
| Dataflow | Sources, processing and destinations |
|
||||||
| Lifecycle | States and transitions |
|
| Lifecycle | States and transitions |
|
||||||
|
|
||||||
The standalone viewer provides pan/zoom, node inspection, search/focus, relationship tracing, light/dark themes, and an export menu. Motion is opt-in. The Organization views exercise architecture mode; other diagram types use the same build interface but do not yet have portal-specific examples. Browser-dependent exports such as video are not acceptance-tested by this integration.
|
The standalone viewer provides pan/zoom, node inspection, search/focus, relationship tracing, light/dark themes, and an export menu. Motion is opt-in. The Organization views exercise architecture mode; Roadmap uses native Workflow v2 with a horizontal desktop path and the same stages stacked on mobile. Other diagram types use the same build interface but do not yet have portal-specific examples. Browser-dependent exports such as video are not acceptance-tested by this integration.
|
||||||
|
|
||||||
## Author a diagram
|
## Author a diagram
|
||||||
|
|
||||||
@@ -23,6 +23,8 @@ Create `diagrams/<name>.<type>.json` using the matching schema and authoring gui
|
|||||||
|
|
||||||
Set `meta.output` to `static/diagrams/<name>/index.html`. The build validates the typed source with the upstream showcase profile, renders locally, checks the artifact, and publishes the standalone HTML. Editable JSON stays in the repository; the portal does not publish source-download files.
|
Set `meta.output` to `static/diagrams/<name>/index.html`. The build validates the typed source with the upstream showcase profile, renders locally, checks the artifact, and publishes the standalone HTML. Editable JSON stays in the repository; the portal does not publish source-download files.
|
||||||
|
|
||||||
|
Roadmap has a scoped multiline typography adapter in `scripts/roadmap-viewer.mjs`. Its two `.workflow.json` sources use the native v2 schema, one lane, `mainPath`, logical columns and native routes. The native renderer validates the schema and solves geometry; the adapter wraps the existing action/output/exit copy, removes the redundant lane frame and trims empty canvas. The final artifact then passes the upstream showcase composition checker. This path records source, adapter and final artifact hashes in private `.cache/archify/*.portal.json` receipts, not upstream `deliver` provenance. Keep the vendor unchanged and run the Roadmap browser check at 1440, 390 and 320 pixels after any copy or sizing change. Typography is embedded in the SVG so exports retain it.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm diagrams:build
|
pnpm diagrams:build
|
||||||
pnpm test
|
pnpm test
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
id: index
|
|
||||||
title: AI adoption strategy
|
|
||||||
description: Govern skills first; outcomes before activity; explicit target status.
|
|
||||||
slug: /
|
|
||||||
hide_table_of_contents: true
|
|
||||||
---
|
|
||||||
|
|
||||||
import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
|
||||||
|
|
||||||
<div className="strategy-content">
|
|
||||||
|
|
||||||
**TO-BE · Target operating model — not installed controls or company results.**
|
|
||||||
|
|
||||||
## How do we reach dependable AI-assisted work?
|
|
||||||
|
|
||||||
Start with governed skills, then widen coordination only when evidence supports it. The destination is one logical SDLC coordinator alongside people using qualified local coding agents.
|
|
||||||
|
|
||||||
<ArchifyDiagram name="strategy-journey" title="Skills and quality evidence precede wider coordination" story />
|
|
||||||
|
|
||||||
- **Skills first:** reusable working knowledge, not permission to act.
|
|
||||||
- **People stay accountable:** the six departments and eleven teams keep their decisions.
|
|
||||||
- **Outcomes before activity:** quality, risk and total cost determine whether to continue.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Strategy rationale and reading map</summary>
|
|
||||||
|
|
||||||
**TO-BE · Target operating model.** Introduction depends on the [roadmap gates](./roadmap.mdx); these pages do not describe installed controls or measured company results.
|
|
||||||
|
|
||||||
### Govern skills first, expand authority only with evidence
|
|
||||||
|
|
||||||
Eldorado's target is to make AI-assisted work dependable across the organization: useful guidance, clear human ownership, controlled execution and measurable outcomes. The first investment is a governed skills system, not an autonomous replacement for the organization. A skill packages reusable working knowledge, prerequisites and verification steps. It helps an agent perform a task; it does not grant permission or guarantee that the work is correct.
|
|
||||||
|
|
||||||
The target combines one logical SDLC coordinating agent with developer choice on local laptops. Copilot, Claude Code and Cline remain client options within a qualified support matrix. They receive the same reviewed portable skill content through client-specific adapters. Repository, CI and release services remain the enforcement points, independently of what any agent says or which local tool produced a change.
|
|
||||||
|
|
||||||
The strategy describes the intended operating model and the evidence required to introduce it. It does not establish that these controls are installed, that every client is supported, or that AI has improved Eldorado's performance. The current reference is the documented six-department organization. Rollout begins by validating actual clients, responsibilities, data boundaries and available evidence.
|
|
||||||
|
|
||||||
Success means accepted outcomes with maintained quality, controlled risk and understood total cost. Faster activity without reliable outcomes is not success. More skill invocations, generated lines or tool subscriptions are not productivity targets. People remain accountable for product, customer, financial and personnel decisions.
|
|
||||||
|
|
||||||
## Navigate the strategy
|
|
||||||
|
|
||||||
- [Target operating model](./target-operating-model.mdx) — One logical coordinator; local client choice; independent authority; all departments.
|
|
||||||
- [Skills governance](./skills-governance.mdx) — Contract, lifecycle, decision rights, adapters, locking, exceptions.
|
|
||||||
- [Skills quality](./skills-quality.mdx) — Risk tiers, independent evaluation, repeated trials, adversarial conformance.
|
|
||||||
- [Skills adoption](./skills-adoption.mdx) — Workflow-led coaching, client choice, feedback, manual path.
|
|
||||||
- [Measurement](./measurement.mdx) — Full KPI contracts, minimal events, illustrative calculations, privacy and limits.
|
|
||||||
- [Skills maintenance](./skills-maintenance.mdx) — Review, drift, deprecation, rollback and incident handling.
|
|
||||||
- [Roadmap](./roadmap.mdx) — Evidence-gated phases, no promised dates or budgets.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
**Next:** [See how people, skills and delivery controls connect →](./target-operating-model.mdx)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
+32
-346
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
id: measurement
|
id: measurement
|
||||||
title: Measurement
|
title: Skills Measurement
|
||||||
description: Full KPI contracts, minimal events, illustrative calculations, privacy and limits.
|
sidebar_label: Skills 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
|
hide_table_of_contents: true
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,377 +10,62 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
|||||||
|
|
||||||
<div className="strategy-content">
|
<div className="strategy-content">
|
||||||
|
|
||||||
**TO-BE · Target operating model — not installed controls or company results.**
|
Measure adoption in stages: first establish today’s performance, then follow the pilot, extend coverage and keep skills current. Count use separately from outcomes—more use does not by itself mean better work.
|
||||||
|
|
||||||
## Is AI-assisted work worth continuing?
|
<ArchifyDiagram name="strategy-measurement" title="Skills adoption measurement: baseline, pilot, broader adoption and monitoring" story />
|
||||||
|
|
||||||
Judge accepted outcomes alongside quality, delivery flow, risk, cost and human experience. Validate source coverage before calculating; missing evidence is unknown, not success.
|
**Recommended collection plan · Target operating model, not installed telemetry.** Confirm the proposed roles and reporting cadence below before collecting data.
|
||||||
|
|
||||||
<ArchifyDiagram name="strategy-measurement" title="Collect evidence, validate it, calculate balanced measures and decide" story />
|
## 1. Establish the baseline
|
||||||
|
|
||||||
**Illustrative only:** 1 accepted task ÷ 4 matured starts × 100 = **25%** confirmed success. One unresolved task stays in the denominator; known-outcome coverage is **75%**. These are synthetic data, not Eldorado results.
|
Choose one workflow, such as reviewing a code change with a review skill. Agree what counts as a started task, independent acceptance and the follow-up period before counting outcomes. Collect current-practice evidence over a complete comparable workflow cycle and until the agreed sample requirement is met; use the same rules in the pilot.
|
||||||
|
|
||||||
- **16 contracts:** exact definitions below; 9 prototype calculations, 7 explicitly unimplemented metrics.
|
Start with a controlled worksheet or export. Join task IDs to acceptance records, PR/commit IDs, CI results and later defects. Record the exact skill revision and client configuration for pilot work; keep missing versions unknown. Repository and CI evidence support verification, but a green build alone is not task acceptance.
|
||||||
- **Balanced evidence:** no productivity rankings, composite score or causal ROI claim.
|
|
||||||
- **Privacy first:** no raw prompts or code by default; protect small cohorts and agree retention before collection.
|
|
||||||
|
|
||||||
<details>
|
## 2. Measure the pilot
|
||||||
<summary>Collection, baseline, event and privacy contracts</summary>
|
|
||||||
|
|
||||||
**TO-BE · Target operating model.** Introduction depends on the [roadmap gates](./roadmap.mdx); these pages do not describe installed controls or measured company results.
|
Start with these four questions. Capture inputs as work happens; do not reconstruct review effort from elapsed PR time.
|
||||||
|
|
||||||
### Decide what counts before counting
|
### Are tasks reaching acceptance?
|
||||||
|
|
||||||
Use the metric contracts below for exact definitions. Every report states its eligible population, observation window, numerator and denominator or distribution sample count, source coverage, exclusions and calculation version. Zero denominators return “not available.” Missing telemetry is unknown, not inactivity, compliance or success. Examples are explicitly synthetic and illustrative.
|
**Collect:** task starts and independent acceptance, failure or abandonment records from the work tracker. **Calculate:** accepted tasks / all eligible starts whose observation period has ended × 100. Failed, abandoned and unresolved tasks stay in the denominator; show unknown outcomes and coverage separately. This is [validated task success](./reference/measurement-contracts.mdx#validated-task-success), not a count of agent completions. **Weekly:** Quality Engineering reviews the pilot cohort.
|
||||||
|
|
||||||
Begin with task acceptance, repository, CI, deployment and incident systems because they can independently establish outcomes. Add catalog/version inventory and task-level declarations. Client telemetry is supplementary and version-dependent; enabling rich tool details may collect sensitive commands and identifiers. Do not enable it merely to count skills. A normalized adapter may report approved skill identifiers without forwarding raw arguments.
|
### Is quality holding up?
|
||||||
|
|
||||||
Validated success uses all eligible initiated tasks whose predeclared observation window has matured. Failed and abandoned tasks remain nonsuccess. Unresolved or unobserved outcomes remain in the denominator, with known-outcome coverage shown alongside the conservative confirmed-success share. They are not relabeled as observed failure. A task cancelled before start is excluded; cancellation after start remains visible. Count a task once across retries and multiple skills. For timeliness, also show unfinished work and age; accepted-only lead-time distributions have survivorship limitations.
|
**Collect:** accepted change IDs, linked confirmed defects, severity and follow-up dates from review and defect records; retain PR/CI evidence. **Calculate:** changes with at least one confirmed defect / accepted changes with completed follow-up × 100. Count each affected change once, not each defect. Missing links do not mean clean work. **Monthly:** Quality Engineering reviews [escaped quality](./reference/measurement-contracts.mdx#changes-with-escaped-quality-issues) after follow-up, alongside severe incident counts.
|
||||||
|
|
||||||
Track all five service-level DORA measures: commit-to-production lead time, deployment frequency, change fail rate, failed deployment recovery time and deployment rework rate. Do not substitute PR-open-to-merge time or all-incident MTTR. Pair them with matured escaped-quality rates, policy effects, total cost per accepted outcome, support coverage, adoption, human experience and catalog maintenance. Keep customer/business outcomes linked to their departmental hypotheses; do not infer conversion, trust or revenue improvement from delivery telemetry.
|
### What does an accepted outcome cost?
|
||||||
|
|
||||||
### Baseline and interpretation
|
**Collect:** invoices and usage costs plus sampled human review/rework minutes at approved rates, including unsuccessful attempts and allocated licenses/tooling. **Calculate:** total allocated workflow cost / independently accepted tasks in the same matured start cohort. Keep effort separate from waiting time. Missing buckets make this a partial estimate, not total cost. **Monthly:** Finance & Accounting reconciles [cost per accepted outcome](./reference/measurement-contracts.mdx#cost-per-accepted-outcome), restating late invoices.
|
||||||
|
|
||||||
Before a pilot, freeze eligibility and acceptance rules and verify event joins. Collect prospective no-skill/current-practice evidence over a complete comparable workflow cycle and until the preregistered precision/sample requirement is met. Do not select a convenient end date after seeing results. Retain task class, service, skill, client/model configuration and concurrent changes. A matched or randomized comparison may be used when practical and ethical; label observational trends as descriptive and report confounding, selection and uncertainty.
|
### Can people confidently verify the work?
|
||||||
|
|
||||||
Elapsed time and human effort are different quantities. Parallel agents may reduce waiting while increasing review or compute. Include failed attempts and human review/rework in cost, do not multiply a multi-skill task's value across every invocation, and do not convert survey perceptions into proven time savings. Small or incomplete populations yield evidence limits, not an ROI percentage.
|
**Collect:** an anonymous 1–5 survey after a pilot workflow cycle: verification confidence, manageable workload and usefulness as separate items. **Calculate:** sum of valid confidence scores / valid responses; also show the distribution and responses / invitations × 100. This guides enablement, not claims of saved time. **After each pilot cycle:** People & Administration reviews [confidence and cognitive load](./reference/measurement-contracts.mdx#verification-confidence-and-cognitive-load); protect small groups.
|
||||||
|
|
||||||
### Event and privacy contract
|
### What a pilot report looks like
|
||||||
|
|
||||||
Use a versioned event envelope and required fields per event. Source producer plus event ID deduplicates transport retries. Conflicting payloads under the same key are quarantined. Attempts have distinct IDs under one task. Domain IDs deduplicate deployments, defects and approvals even when multiple feeds describe them. Late arrivals produce versioned aggregate corrections; never silently overwrite a published result. Preserve occurrence and receipt times, reject impossible orderings and flag clock skew. Missing terminal events remain unresolved until the work owner reconciles them.
|
**Illustrative arithmetic, not company results or targets.** The bundled synthetic fixture shows four matured starts: one accepted, one failed, one abandoned and one unresolved. Confirmed success is **1 / 4 × 100 = 25%**, with **75%** known-outcome coverage. A newer start has not finished its observation period and is shown separately.
|
||||||
|
|
||||||
Many-to-many task/attempt/skill/client associations preserve exact immutable versions. Unknown version stays null with a reason; never assign the latest revision retroactively. Client declarations, server facts and self-reports remain separately labeled. A skill-loaded event is evidence of loading only. The minimal prototype can operate without actor identity except where a lawful, necessary adoption denominator requires pseudonymous cohort membership.
|
Recorded compute spend is **EUR 60 / 1 = EUR 60** per accepted outcome—a **partial estimate** because licenses, tooling, review and rework are missing. Five survey responses total 20 points: **20 / 5 = 4** out of 5; response rate is **5 / 10 × 100 = 50%**. The report calls for reconciling the unknown task and missing costs, not claiming a skills benefit. Reproduce it with `node scripts/ai-kpi.mjs`; [fixture and limits](./reference/measurement-contracts.mdx#run-the-synthetic-measurement-example).
|
||||||
|
|
||||||
The privacy starting proposal is 30 days for raw structured analytics events and 12 months for aggregated trends, subject to the responsible owners' review. Keep the approved catalog/approval record for the supported revision's lifetime and its required audit period, which must be decided separately. Incident evidence uses a restricted, explicitly authorized hold and deletion schedule, not an indefinite analytics exception. Suppress reports below five people and prohibit drill-down or overlapping cohort slices that reconstruct identities. Five is a proposed minimum, not a guarantee against re-identification. No raw prompts, code, screens, customer cases or secrets are collected by default. Publish the purpose, lawful collection basis, access/deletion route and retention before collecting. Consent is not presumed to be an appropriate employment-law basis.
|
## 3. Extend collection as adoption grows
|
||||||
|
|
||||||
</details>
|
Keep those outcome measures, then add reach. An adoption percentage is meaningless without a known eligible population.
|
||||||
|
|
||||||
<details>
|
**Are supported skills available?** Freeze the expected active installation inventory; compare observed bundle/adapter revisions with the approved support and revocation records. [Supported bundle coverage](./reference/measurement-contracts.mdx#supported-bundle-coverage) = fresh, verified supported installations / all expected active installations × 100. Offline or stale entries stay in the denominator. **Weekly and at revocation:** Platform & IT reconciles coverage.
|
||||||
<summary>All 16 KPI contracts — formulas, populations, owners and limits</summary>
|
|
||||||
|
|
||||||
## Metric contracts
|
**Are approved workflows being used?** Freeze the lawfully enrolled participant cohort; collect workflow-linked use of an approved revision through a qualified adapter or a separately labeled declaration. [Approved-workflow adoption](./reference/measurement-contracts.mdx#approved-workflow-adoption) = eligible participants with at least one observed approved use / eligible invited participants × 100. Report this as an observed lower bound with source coverage: missing use is unknown. Installation and loading are not use. **Monthly:** Data & AI reviews reach without invocation quotas or employee rankings.
|
||||||
|
|
||||||
Each contract is a prospective collection design. No live source or observed baseline is established. N and D refer to the numerator and denominator shown for that metric.
|
## 4. Keep the collection running
|
||||||
|
|
||||||
### Validated task success
|
**Are skills still maintained?** From the versioned catalog, join each active approved revision to its owner, substantive review evidence and due date. [Review coverage](./reference/measurement-contracts.mdx#skills-within-substantive-review-window) = revisions with all three current / all active approved revisions × 100. **Monthly and each release:** Data & AI reports overdue/ownerless revisions and deprecated or revoked versions still in use—not just a percentage.
|
||||||
|
|
||||||
Confirmed independently accepted tasks as a share of all eligible initiated tasks in a matured start cohort; a conservative confirmed-success share, not observed failure rate.
|
At each report, compare like workflows with the baseline and publish the period, counts, exclusions and source coverage. Zero denominators mean “not available.” Trends are descriptive, not causal ROI. Act on critical policy effects immediately; add selection precision/recall at qualification and service-level delivery or departmental outcome measures when their source links are ready.
|
||||||
|
|
||||||
| Contract | Definition |
|
Agree purpose, lawful basis, access and retention first. No raw prompts, code or secrets by default; suppress people cohorts below five and prevent identifying drill-downs.
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct matured task_ids with valid independent task.accepted evidence by as_of |
|
|
||||||
| Denominator | All distinct eligible task_ids started in window with started_at + preregistered task_observation_hours <= as_of |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Tasks in a preregistered workflow cohort, started in window; each counted once across retries, skills and clients. |
|
|
||||||
| Exclusions | Cancelled before start, sandbox/evaluation tasks and out-of-scope workflows; never post-start failures/abandonments. |
|
|
||||||
| Events and source | task.started; task.accepted; task.failed; task.abandoned; task.cancelled; work-management acceptance record |
|
|
||||||
| Owner / cadence | Quality Engineering / Weekly pilot; monthly departmental outcome review |
|
|
||||||
| Missing evidence | Unresolved/missing terminal outcomes remain in denominator with unknown count and known-outcome coverage; do not label them failed. Invalid/missing start excluded from calculation but flagged as orphan evidence. |
|
|
||||||
| Gaming and limits | Split easy tasks or omit starts; reconcile starts to approved backlog and audit acceptance rubrics. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Maturation horizon is workflow-specific and must be approved; no automatic abandonment on timeout. |
|
|
||||||
|
|
||||||
|
[Full KPI reference →](./reference/measurement-contracts.mdx) preserves all definitions, collection safeguards and the distinction between synthetic calculations and unimplemented metrics.
|
||||||
|
|
||||||
### Skill selection precision
|
**Next:** [Maintain skills using the evidence →](./skills-maintenance.mdx)
|
||||||
|
|
||||||
Correct activations among activations on an independently labeled evaluation corpus.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | True-positive activations for the requested workflow |
|
|
||||||
| Denominator | True-positive plus false-positive activations |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | All candidate/client labeled evaluation task-trials with known activation and relevance. |
|
|
||||||
| Exclusions | Unlabeled/unknown activations excluded with coverage; no cherry-picked failed trials. |
|
|
||||||
| Events and source | eval.selection; versioned labeled corpus and runner |
|
|
||||||
| Owner / cadence | Data & AI / Each skill/client qualification |
|
|
||||||
| Missing evidence | Unknown activation/relevance is unscorable; publish missing count and deny qualification if planned coverage incomplete. |
|
|
||||||
| Gaming and limits | Choose easy labels or hide negative cases; freeze corpus and independently review labels. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Does not show execution success; precision depends on corpus prevalence. |
|
|
||||||
|
|
||||||
|
|
||||||
### Skill selection recall
|
|
||||||
|
|
||||||
Relevant task-trials in which the intended skill activates.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | True-positive activations |
|
|
||||||
| Denominator | True-positive plus false-negative task-trials |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Independently labeled relevant evaluation task-trials for a skill/client tuple. |
|
|
||||||
| Exclusions | Irrelevant tasks; missing relevance or activation reported as unscorable, not negative. |
|
|
||||||
| Events and source | eval.selection; versioned labeled corpus and runner |
|
|
||||||
| Owner / cadence | Data & AI / Each skill/client qualification |
|
|
||||||
| Missing evidence | Report evaluation coverage; null if no relevant labeled tasks. |
|
|
||||||
| Gaming and limits | Overtrigger to maximize recall; always pair with precision and negative cases. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Loading the right skill is not adherence. |
|
|
||||||
|
|
||||||
|
|
||||||
### Supported bundle coverage
|
|
||||||
|
|
||||||
Inventoried active installations with verified effective approved, supported and nonrevoked bundle/adapter tuple.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Eligible installation_ids with valid inventory evidence within approved freshness window and matching support matrix |
|
|
||||||
| Denominator | All expected active installation_ids in the managed pilot inventory |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Active installation inventory frozen for report, including invisible/offline entries until authoritative offboarding. |
|
|
||||||
| Exclusions | Verified decommissioned installations and nonpilot assets; lack of telemetry alone never excludes. |
|
|
||||||
| Events and source | inventory.observed; catalog.published; catalog.revoked; device inventory |
|
|
||||||
| Owner / cadence | Platform & IT / Weekly and at revocation |
|
|
||||||
| Missing evidence | Unknown/stale versions count outside numerator, inside denominator. Show inventory discovery coverage versus eligible device roster; if roster unknown metric is provisional. |
|
|
||||||
| Gaming and limits | Remove silent devices or trust declared versions; reconcile managed inventory and separately label attestation trust. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Even verified file digests do not prove a private model followed the content. |
|
|
||||||
|
|
||||||
|
|
||||||
### Approved-workflow adoption
|
|
||||||
|
|
||||||
Eligible participants with at least one approved-skill workflow use; usefulness assessed separately through accepted outcomes and feedback.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct eligible pseudonymous participants with workflow-linked skill.used on approved revision |
|
|
||||||
| Denominator | Distinct eligible participants in frozen invited cohort |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Consented or otherwise lawfully enrolled pilot cohort for the period; supported workflow available. |
|
|
||||||
| Exclusions | Outside pilot and formally recorded leave before period; nonresponders remain eligible, not excluded. |
|
|
||||||
| Events and source | skill.used; cohort.snapshot; optional qualified client adapter or declaration |
|
|
||||||
| Owner / cadence | Data & AI / Monthly |
|
|
||||||
| Missing evidence | Show observed lower bound and telemetry/response coverage; missing usage unknown rather than non-use. |
|
|
||||||
| Gaming and limits | Invocation quotas and coercion; no target usage count, split server/client/self-report origins. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Self-report is not verified use; installation/loading is not use or benefit. Suppress small groups. |
|
|
||||||
|
|
||||||
|
|
||||||
### Change lead time
|
|
||||||
|
|
||||||
Arithmetic mean commit-to-first-successful-production-deployment duration; also publish median/p90 and sample count.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Sum of elapsed hours from committed_at to first successful production deployment for each service+commit |
|
|
||||||
| Denominator | Count of eligible service+commit pairs with valid mappings |
|
|
||||||
| Expression | N / D |
|
|
||||||
| Aggregation | mean |
|
|
||||||
| Unit | hours/change |
|
|
||||||
| Eligible population | Changes first successfully deployed to a service in reporting window; rollout retries collapse to same logical deployment. |
|
|
||||||
| Exclusions | Nonproduction, duplicates, no-op deployment with no new commits; missing mappings shown as coverage loss. |
|
|
||||||
| Events and source | change.committed; deployment.completed; VCS immutable commit timestamps and release manifest |
|
|
||||||
| Owner / cadence | Platform & IT / Weekly per service |
|
|
||||||
| Missing evidence | Missing commit mapping or negative/skewed duration excluded from numeric sample, reported explicitly; no zero substitution. |
|
|
||||||
| Gaming and limits | Timestamp rewriting and cherry-picking fast work; lock change mapping and show undeployed backlog age. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Not PR cycle time, effort saved, or a complete view of unfinished changes. p90 uses nearest-rank sorted sample. |
|
|
||||||
|
|
||||||
|
|
||||||
### Deployment frequency
|
|
||||||
|
|
||||||
Distinct successful logical production deployments per complete service-day of observation.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct service_id+deployment_id successful production deployments in window |
|
|
||||||
| Denominator | Observed service-days in window |
|
|
||||||
| Expression | N / D |
|
|
||||||
| Aggregation | ratio |
|
|
||||||
| Unit | deployments/service-day |
|
|
||||||
| Eligible population | Declared production services and complete UTC observation days; report each service before pooled views. |
|
|
||||||
| Exclusions | Test/staging, failed attempts without production effect, duplicate callbacks and retries of the same rollout. |
|
|
||||||
| Events and source | deployment.completed; authoritative deployment service |
|
|
||||||
| Owner / cadence | Platform & IT / Weekly per service |
|
|
||||||
| Missing evidence | Missing feed periods not zero; report coverage and no unqualified overall rate when period incomplete. |
|
|
||||||
| Gaming and limits | Split deployments without value; pair with success, rework and failure measures. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. More deployments alone do not establish better performance; affected unsuccessful changes remain in change-fail population. |
|
|
||||||
|
|
||||||
|
|
||||||
### Change fail rate
|
|
||||||
|
|
||||||
Production deployment effects requiring unplanned remediation, rollback or hotfix, not solely outage declarations.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct matured production deployment_ids linked to qualifying immediate intervention |
|
|
||||||
| Denominator | All distinct production deployment_ids that reached production in the matured deployment cohort |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Deployments with production effect during window and approved intervention follow-up horizon elapsed. |
|
|
||||||
| Exclusions | Failed preproduction attempts, duplicate callbacks and no-effect retries; failures after production effect stay included. |
|
|
||||||
| Events and source | deployment.completed; deployment.failed; incident.linked; release and incident records |
|
|
||||||
| Owner / cadence | Quality Engineering / Weekly; finalized after horizon |
|
|
||||||
| Missing evidence | Known failures / all eligible deployments is a lower bound where incident linkage incomplete; label provisional and report unknown links. |
|
|
||||||
| Gaming and limits | Avoid incident links or relabel intervention planned; reconcile rollbacks/hotfixes independently. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Define immediate intervention and follow-up horizon before pilot; no universal incident-to-AI attribution. |
|
|
||||||
|
|
||||||
|
|
||||||
### Failed deployment recovery time
|
|
||||||
|
|
||||||
Mean elapsed time from production deployment failure onset to service restoration, with median/p90 and unresolved count/age.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Sum of recovery_at minus failure_started_at in hours for recovered eligible deployment failures |
|
|
||||||
| Denominator | Count of recovered eligible deployment failures with valid timestamps |
|
|
||||||
| Expression | N / D |
|
|
||||||
| Aggregation | mean |
|
|
||||||
| Unit | hours/failure |
|
|
||||||
| Eligible population | Production deployment failures beginning in window; latest verified restoration if a recovery is rescinded. |
|
|
||||||
| Exclusions | Nondeployment incidents; invalid timestamp pairs reported missing, not treated as instantaneous recovery. |
|
|
||||||
| Events and source | deployment.failed; service.restored; incident service |
|
|
||||||
| Owner / cadence | Platform & IT / Weekly with unresolved failures |
|
|
||||||
| Missing evidence | Unresolved failures reported separately and excluded only from recovered-duration distribution; prevent declaring improvement without their visibility. |
|
|
||||||
| Gaming and limits | Premature recovery declarations or omit hard cases; require operational readback and carry unresolved ages. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Not all-incident MTTR; recovered-only sample has survivorship bias. p90 nearest rank. |
|
|
||||||
|
|
||||||
|
|
||||||
### Deployment rework rate
|
|
||||||
|
|
||||||
Unplanned production deployment effects triggered by a production incident as a share of deployment effects.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct incident-linked deployment_ids classified unplanned remediation |
|
|
||||||
| Denominator | All distinct production deployment_ids with production effect in window |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Production deployment effects in window with immutable planned/unplanned classification and incident linkage. |
|
|
||||||
| Exclusions | Nonproduction, no-effect retries and duplicates; do not exclude unclassified changes silently. |
|
|
||||||
| Events and source | deployment.completed; incident.linked; release intent classification |
|
|
||||||
| Owner / cadence | Platform & IT / Weekly |
|
|
||||||
| Missing evidence | Unknown classification stays denominator; observed numerator is lower bound until reconciliation; show classification coverage. |
|
|
||||||
| Gaming and limits | Call hotfixes planned; freeze reason and independently reconcile incident/deployment links. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Distinct from failure rate: one failed deployment can lead to multiple separate rework deployments. |
|
|
||||||
|
|
||||||
|
|
||||||
### Changes with escaped quality issues
|
|
||||||
|
|
||||||
Accepted changes with at least one confirmed post-acceptance defect during approved follow-up, split by severity.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct eligible change_ids with one or more linked confirmed defects in follow-up |
|
|
||||||
| Denominator | Accepted change_ids with completed predeclared follow-up horizon |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Accepted changes in acceptance window whose follow-up is complete by as_of. |
|
|
||||||
| Exclusions | Immature changes shown separately; duplicates/unconfirmed reports excluded but counted as pending triage. |
|
|
||||||
| Events and source | change.accepted; defect.confirmed; defect tracking and acceptance evidence |
|
|
||||||
| Owner / cadence | Quality Engineering / Monthly mature cohorts |
|
|
||||||
| Missing evidence | Unknown linkage is a coverage warning; no fabricated clean outcome. Show absolute defect and severe incident counts as well. |
|
|
||||||
| Gaming and limits | Shorten follow-up or downgrade severity; freeze horizon and rubric and independently sample defects. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Counts affected changes, not raw defects per change; no presumption defects are AI-caused. |
|
|
||||||
|
|
||||||
|
|
||||||
### Confirmed policy effects
|
|
||||||
|
|
||||||
Eligible evaluated attempts with an actual confirmed prohibited effect; report attempts, blocked attempts and effects separately.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct attempt_ids with confirmed prohibited action effect |
|
|
||||||
| Denominator | All evaluated eligible attempt_ids including failed and abandoned runs |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Governed skill trials or supported workflow attempts, reported separately for eval and live populations. |
|
|
||||||
| Exclusions | Out-of-scope runs; detection gaps remain coverage issues, not exclusions from known initiated population. |
|
|
||||||
| Events and source | attempt.started; policy.violation; gate.blocked; security audit and incident triage |
|
|
||||||
| Owner / cadence | Architecture & Security / Immediate incident action; weekly aggregates |
|
|
||||||
| Missing evidence | Unknown outcomes remain visible; zero detected effects never proves safety. Missing audits make rate provisional. |
|
|
||||||
| Gaming and limits | Hide incidents or inflate harmless trials; absolute severity counts and fixed task mix accompany rate. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Blocked attempts are not actual effects; any critical effect blocks promotion regardless of aggregate rate. |
|
|
||||||
|
|
||||||
|
|
||||||
### Cost per accepted outcome
|
|
||||||
|
|
||||||
Allocated total workflow cost including unsuccessful attempts, licenses, compute, tooling and human review/rework divided by accepted outcomes.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Sum allocated costs in one approved reporting currency for all tasks/attempts in matured start cohort |
|
|
||||||
| Denominator | Distinct independently accepted tasks in same cohort |
|
|
||||||
| Expression | N / D |
|
|
||||||
| Aggregation | ratio |
|
|
||||||
| Unit | currency/accepted outcome |
|
|
||||||
| Eligible population | Matured eligible started-task cohort, with all attempt costs through as_of and allocated shared period costs. |
|
|
||||||
| Exclusions | Unrelated projects and documented taxes/transfers outside chosen accounting basis; never failed/abandoned attempts. |
|
|
||||||
| Events and source | cost.recorded; task.accepted; finance invoices, usage ledger, sampled effort with approved rates |
|
|
||||||
| Owner / cadence | Finance & Accounting Squad / Monthly with late invoice restatement |
|
|
||||||
| Missing evidence | Missing cost buckets produce partial estimate plus coverage, not a total. Mixed currency requires dated approved conversion; zero accepted outcomes = null with full cost shown. |
|
|
||||||
| Gaming and limits | Count tokens only or hide labor/failures; reconcile invoice and allocation totals without double counting. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Allocation and sampled labor carry uncertainty; no ROI without independently measured business benefit. |
|
|
||||||
|
|
||||||
|
|
||||||
### Verification confidence and cognitive load
|
|
||||||
|
|
||||||
Three separate anonymous 1–5 item distributions: confidence in verifying output, manageable workload, usefulness. Primary item: verification confidence mean; report full distributions and response rate.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Sum valid 1–5 verification-confidence responses, one per participant per survey wave |
|
|
||||||
| Denominator | Count valid responses to that item |
|
|
||||||
| Expression | N / D |
|
|
||||||
| Aggregation | mean |
|
|
||||||
| Unit | points (1–5) |
|
|
||||||
| Eligible population | Invited eligible cohort at fixed survey wave; voluntary responses. |
|
|
||||||
| Exclusions | Invalid/out-of-range answers and superseded duplicate submission; nonresponders stay in response-rate denominator. |
|
|
||||||
| Events and source | survey.response; anonymous survey with single-use token and aggregate invitation count |
|
|
||||||
| Owner / cadence | People & Administration Squad / After pilot workflow cycle; periodic follow-up |
|
|
||||||
| Missing evidence | Report valid response count / invited count and item nonresponse; no imputation. Suppress people cohorts below five. |
|
|
||||||
| Gaming and limits | Pressure to respond positively; no manager access to individual responses, avoid identifying free text. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Perception and selection bias; not measured time savings. No composite productivity index. |
|
|
||||||
|
|
||||||
|
|
||||||
### Skills within substantive review window
|
|
||||||
|
|
||||||
Active approved skill revisions with an accountable owner and completed substantive review not past due.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Distinct active approved skill revisions with current owner, review evidence and review_due_at >= as_of |
|
|
||||||
| Denominator | All active approved skill revisions in catalog snapshot |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | Approved revisions currently supported for use at as_of. |
|
|
||||||
| Exclusions | Experimental and retired revisions; deprecated/revoked in-use counts reported independently, not hidden. |
|
|
||||||
| Events and source | catalog.published; catalog.reviewed; catalog.deprecated; catalog.revoked; version inventory |
|
|
||||||
| Owner / cadence | Data & AI / Monthly and each release |
|
|
||||||
| Missing evidence | Missing owner/date/evidence outside numerator; show ownerless, deprecated-in-use and revoked-in-use absolute counts. |
|
|
||||||
| Gaming and limits | Rubber-stamp review dates; require linked regression/security/usefulness evidence. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. A current review is not proof of effectiveness; consumer inventory may miss shadow copies. |
|
|
||||||
|
|
||||||
|
|
||||||
### Workflow-specific business acceptance
|
|
||||||
|
|
||||||
Independently accepted domain outcomes against a predeclared business rubric, separate from technical completion.
|
|
||||||
|
|
||||||
| Contract | Definition |
|
|
||||||
| --- | --- |
|
|
||||||
| Numerator | Eligible completed-observation task_ids meeting the department-specific outcome rubric |
|
|
||||||
| Denominator | All eligible initiated workflow task_ids with matured business-outcome horizon |
|
|
||||||
| Expression | N / D * 100 |
|
|
||||||
| Aggregation | percent |
|
|
||||||
| Unit | % |
|
|
||||||
| Eligible population | One named departmental workflow and predeclared start cohort; never pool unlike rubrics. |
|
|
||||||
| Exclusions | Cancelled-before-start and unrelated workflows; unsuccessful or abandoned eligible work remains. |
|
|
||||||
| Events and source | task.started; business.accepted; departmental case/content/reconciliation/onboarding review record |
|
|
||||||
| Owner / cadence | Product Management / Each pilot cycle with owning department |
|
|
||||||
| Missing evidence | Unknown follow-up stays denominator with unresolved count; no claim actual business outcome changed without source evidence. |
|
|
||||||
| Gaming and limits | Weaken rubric or count drafts as customer benefit; lock rubric and independently audit acceptance. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Domain owner signs acceptance: Operations service accuracy; Marketing claims/rights; Finance reconciliation accuracy; HR policy correctness; Product usability/value. Not revenue uplift. |
|
|
||||||
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Run the nine-metric synthetic prototype and inspect omissions</summary>
|
|
||||||
|
|
||||||
## Run the synthetic measurement example
|
|
||||||
|
|
||||||
**Executed synthetic example — not Eldorado results.** The bundled fixture produces 1 independently accepted task from 4 matured starts: 25% confirmed success. One unresolved task remains in that denominator; known-outcome coverage is 75%. Selection precision and recall are each 1 / 2 = 50%. Allocated synthetic cost is EUR 60 / 1 accepted outcome = EUR 60 per accepted outcome. These deliberately small samples demonstrate arithmetic and missingness, not a baseline, target or ROI claim.
|
|
||||||
|
|
||||||
From the private documentation checkout, run `node scripts/ai-kpi.mjs` and `node --test scripts/ai-kpi.test.mjs`. The fixture `scripts/fixtures/ai-kpi-synthetic.json` is labeled synthetic; every result carries the same label and calculation version. This prototype has no network collectors and does not enable employee telemetry.
|
|
||||||
|
|
||||||
The executable covers validated success, selection precision/recall, change fail and rework rates, escaped quality, policy effects, cost and human experience. Other catalog metrics remain contracts rather than implemented collectors; output lists the omissions. Snapshot inputs are simplified, pre-reconciled records, not a production ingestion system. Conflicting duplicates reject the calculation rather than silently choosing a value.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
**Next:** [Use findings to maintain, quarantine or replace skills →](./skills-maintenance.mdx)
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
---
|
||||||
|
id: measurement-contracts
|
||||||
|
title: Skills KPI reference
|
||||||
|
description: Full KPI contracts, minimal events, illustrative calculations, privacy and limits.
|
||||||
|
---
|
||||||
|
|
||||||
|
<div className="strategy-content">
|
||||||
|
|
||||||
|
Return to the [Skills Measurement collection plan](../measurement.mdx).
|
||||||
|
|
||||||
|
**Target operating model — recommended contracts, not installed collection.** All owner and cadence entries below are proposals to confirm before the pilot, not assigned responsibilities or approved schedules.
|
||||||
|
|
||||||
|
There are **16 contracts: 9 prototype calculations and 7 unimplemented metrics**. The prototype has no network collectors. None of these counts establishes live telemetry.
|
||||||
|
|
||||||
|
The nine calculations cover task success, selection precision and recall, change fail rate, deployment rework, escaped quality, policy effects, cost and the primary human-experience item. The seven omissions are supported bundle coverage, approved-workflow adoption, change lead time, deployment frequency, failed deployment recovery time, substantive-review coverage and workflow-specific business acceptance.
|
||||||
|
|
||||||
|
## Collection and safeguards
|
||||||
|
|
||||||
|
Introduction depends on the [roadmap gates](../roadmap.mdx); these pages do not describe installed controls or measured company results.
|
||||||
|
|
||||||
|
### Decide what counts before counting
|
||||||
|
|
||||||
|
Use the metric contracts below for exact definitions. Every report states its eligible population, observation window, numerator and denominator or distribution sample count, source coverage, exclusions and calculation version. Zero denominators return “not available.” Missing telemetry is unknown, not inactivity, compliance or success. Examples are explicitly synthetic and illustrative.
|
||||||
|
|
||||||
|
Begin with task acceptance, repository, CI, deployment and incident systems because they can independently establish outcomes. Add catalog/version inventory and task-level declarations. Client telemetry is supplementary and version-dependent; enabling rich tool details may collect sensitive commands and identifiers. Do not enable it merely to count skills. A normalized adapter may report approved skill identifiers without forwarding raw arguments.
|
||||||
|
|
||||||
|
Validated success uses all eligible initiated tasks whose predeclared observation window has matured. Failed and abandoned tasks remain nonsuccess. Unresolved or unobserved outcomes remain in the denominator, with known-outcome coverage shown alongside the conservative confirmed-success share. They are not relabeled as observed failure. A task cancelled before start is excluded; cancellation after start remains visible. Count a task once across retries and multiple skills. For timeliness, also show unfinished work and age; accepted-only lead-time distributions have survivorship limitations.
|
||||||
|
|
||||||
|
Track all five service-level DORA measures: commit-to-production lead time, deployment frequency, change fail rate, failed deployment recovery time and deployment rework rate. Do not substitute PR-open-to-merge time or all-incident MTTR. Pair them with matured escaped-quality rates, policy effects, total cost per accepted outcome, support coverage, adoption, human experience and catalog maintenance. Keep customer/business outcomes linked to their departmental hypotheses; do not infer conversion, trust or revenue improvement from delivery telemetry.
|
||||||
|
|
||||||
|
### Baseline and interpretation
|
||||||
|
|
||||||
|
Before a pilot, freeze eligibility and acceptance rules and verify event joins. Collect prospective no-skill/current-practice evidence over a complete comparable workflow cycle and until the preregistered precision/sample requirement is met. Do not select a convenient end date after seeing results. Retain task class, service, skill, client/model configuration and concurrent changes. A matched or randomized comparison may be used when practical and ethical; label observational trends as descriptive and report confounding, selection and uncertainty.
|
||||||
|
|
||||||
|
Elapsed time and human effort are different quantities. Parallel agents may reduce waiting while increasing review or compute. Include failed attempts and human review/rework in cost, do not multiply a multi-skill task's value across every invocation, and do not convert survey perceptions into proven time savings. Small or incomplete populations yield evidence limits, not an ROI percentage.
|
||||||
|
|
||||||
|
### Event and privacy contract
|
||||||
|
|
||||||
|
Use a versioned event envelope and required fields per event. Source producer plus event ID deduplicates transport retries. Conflicting payloads under the same key are quarantined. Attempts have distinct IDs under one task. Domain IDs deduplicate deployments, defects and approvals even when multiple feeds describe them. Late arrivals produce versioned aggregate corrections; never silently overwrite a published result. Preserve occurrence and receipt times, reject impossible orderings and flag clock skew. Missing terminal events remain unresolved until the work owner reconciles them.
|
||||||
|
|
||||||
|
Many-to-many task/attempt/skill/client associations preserve exact immutable versions. Unknown version stays null with a reason; never assign the latest revision retroactively. Client declarations, server facts and self-reports remain separately labeled. A skill-loaded event is evidence of loading only. The minimal prototype can operate without actor identity except where a lawful, necessary adoption denominator requires pseudonymous cohort membership.
|
||||||
|
|
||||||
|
The privacy starting proposal is 30 days for raw structured analytics events and 12 months for aggregated trends, subject to the responsible owners' review. Keep the approved catalog/approval record for the supported revision's lifetime and its required audit period, which must be decided separately. Incident evidence uses a restricted, explicitly authorized hold and deletion schedule, not an indefinite analytics exception. Suppress reports below five people and prohibit drill-down or overlapping cohort slices that reconstruct identities. Five is a proposed minimum, not a guarantee against re-identification. No raw prompts, code, screens, customer cases or secrets are collected by default. Publish the purpose, lawful collection basis, access/deletion route and retention before collecting. Consent is not presumed to be an appropriate employment-law basis.
|
||||||
|
|
||||||
|
## Metric contracts
|
||||||
|
|
||||||
|
Each contract is a prospective collection design. No live source or observed baseline is established. N and D refer to the numerator and denominator shown for that metric.
|
||||||
|
|
||||||
|
### Validated task success
|
||||||
|
|
||||||
|
Confirmed independently accepted tasks as a share of all eligible initiated tasks in a matured start cohort; a conservative confirmed-success share, not observed failure rate.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct matured task_ids with valid independent task.accepted evidence by as_of |
|
||||||
|
| Denominator | All distinct eligible task_ids started in window with started_at + preregistered task_observation_hours <= as_of |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Tasks in a preregistered workflow cohort, started in window; each counted once across retries, skills and clients. |
|
||||||
|
| Exclusions | Cancelled before start, sandbox/evaluation tasks and out-of-scope workflows; never post-start failures/abandonments. |
|
||||||
|
| Events and source | task.started; task.accepted; task.failed; task.abandoned; task.cancelled; work-management acceptance record |
|
||||||
|
| Owner / cadence | Quality Engineering / Weekly pilot; monthly departmental outcome review |
|
||||||
|
| Missing evidence | Unresolved/missing terminal outcomes remain in denominator with unknown count and known-outcome coverage; do not label them failed. Invalid/missing start excluded from calculation but flagged as orphan evidence. |
|
||||||
|
| Gaming and limits | Split easy tasks or omit starts; reconcile starts to approved backlog and audit acceptance rubrics. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Maturation horizon is workflow-specific and must be approved; no automatic abandonment on timeout. |
|
||||||
|
|
||||||
|
|
||||||
|
### Skill selection precision
|
||||||
|
|
||||||
|
Correct activations among activations on an independently labeled evaluation corpus.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | True-positive activations for the requested workflow |
|
||||||
|
| Denominator | True-positive plus false-positive activations |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | All candidate/client labeled evaluation task-trials with known activation and relevance. |
|
||||||
|
| Exclusions | Unlabeled/unknown activations excluded with coverage; no cherry-picked failed trials. |
|
||||||
|
| Events and source | eval.selection; versioned labeled corpus and runner |
|
||||||
|
| Owner / cadence | Data & AI / Each skill/client qualification |
|
||||||
|
| Missing evidence | Unknown activation/relevance is unscorable; publish missing count and deny qualification if planned coverage incomplete. |
|
||||||
|
| Gaming and limits | Choose easy labels or hide negative cases; freeze corpus and independently review labels. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Does not show execution success; precision depends on corpus prevalence. |
|
||||||
|
|
||||||
|
|
||||||
|
### Skill selection recall
|
||||||
|
|
||||||
|
Relevant task-trials in which the intended skill activates.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | True-positive activations |
|
||||||
|
| Denominator | True-positive plus false-negative task-trials |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Independently labeled relevant evaluation task-trials for a skill/client tuple. |
|
||||||
|
| Exclusions | Irrelevant tasks; missing relevance or activation reported as unscorable, not negative. |
|
||||||
|
| Events and source | eval.selection; versioned labeled corpus and runner |
|
||||||
|
| Owner / cadence | Data & AI / Each skill/client qualification |
|
||||||
|
| Missing evidence | Report evaluation coverage; null if no relevant labeled tasks. |
|
||||||
|
| Gaming and limits | Overtrigger to maximize recall; always pair with precision and negative cases. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Loading the right skill is not adherence. |
|
||||||
|
|
||||||
|
|
||||||
|
### Supported bundle coverage
|
||||||
|
|
||||||
|
Inventoried active installations with verified effective approved, supported and nonrevoked bundle/adapter tuple.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Eligible installation_ids with valid inventory evidence within approved freshness window and matching support matrix |
|
||||||
|
| Denominator | All expected active installation_ids in the managed pilot inventory |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Active installation inventory frozen for report, including invisible/offline entries until authoritative offboarding. |
|
||||||
|
| Exclusions | Verified decommissioned installations and nonpilot assets; lack of telemetry alone never excludes. |
|
||||||
|
| Events and source | inventory.observed; catalog.published; catalog.revoked; device inventory |
|
||||||
|
| Owner / cadence | Platform & IT / Weekly and at revocation |
|
||||||
|
| Missing evidence | Unknown/stale versions count outside numerator, inside denominator. Show inventory discovery coverage versus eligible device roster; if roster unknown metric is provisional. |
|
||||||
|
| Gaming and limits | Remove silent devices or trust declared versions; reconcile managed inventory and separately label attestation trust. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Even verified file digests do not prove a private model followed the content. |
|
||||||
|
|
||||||
|
|
||||||
|
### Approved-workflow adoption
|
||||||
|
|
||||||
|
Eligible participants with at least one approved-skill workflow use; usefulness assessed separately through accepted outcomes and feedback.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct eligible pseudonymous participants with workflow-linked skill.used on approved revision |
|
||||||
|
| Denominator | Distinct eligible participants in frozen invited cohort |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Consented or otherwise lawfully enrolled pilot cohort for the period; supported workflow available. |
|
||||||
|
| Exclusions | Outside pilot and formally recorded leave before period; nonresponders remain eligible, not excluded. |
|
||||||
|
| Events and source | skill.used; cohort.snapshot; optional qualified client adapter or declaration |
|
||||||
|
| Owner / cadence | Data & AI / Monthly |
|
||||||
|
| Missing evidence | Show observed lower bound and telemetry/response coverage; missing usage unknown rather than non-use. |
|
||||||
|
| Gaming and limits | Invocation quotas and coercion; no target usage count, split server/client/self-report origins. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Self-report is not verified use; installation/loading is not use or benefit. Suppress small groups. |
|
||||||
|
|
||||||
|
|
||||||
|
### Change lead time
|
||||||
|
|
||||||
|
Arithmetic mean commit-to-first-successful-production-deployment duration; also publish median/p90 and sample count.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Sum of elapsed hours from committed_at to first successful production deployment for each service+commit |
|
||||||
|
| Denominator | Count of eligible service+commit pairs with valid mappings |
|
||||||
|
| Expression | N / D |
|
||||||
|
| Aggregation | mean |
|
||||||
|
| Unit | hours/change |
|
||||||
|
| Eligible population | Changes first successfully deployed to a service in reporting window; rollout retries collapse to same logical deployment. |
|
||||||
|
| Exclusions | Nonproduction, duplicates, no-op deployment with no new commits; missing mappings shown as coverage loss. |
|
||||||
|
| Events and source | change.committed; deployment.completed; VCS immutable commit timestamps and release manifest |
|
||||||
|
| Owner / cadence | Platform & IT / Weekly per service |
|
||||||
|
| Missing evidence | Missing commit mapping or negative/skewed duration excluded from numeric sample, reported explicitly; no zero substitution. |
|
||||||
|
| Gaming and limits | Timestamp rewriting and cherry-picking fast work; lock change mapping and show undeployed backlog age. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Not PR cycle time, effort saved, or a complete view of unfinished changes. p90 uses nearest-rank sorted sample. |
|
||||||
|
|
||||||
|
|
||||||
|
### Deployment frequency
|
||||||
|
|
||||||
|
Distinct successful logical production deployments per complete service-day of observation.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct service_id+deployment_id successful production deployments in window |
|
||||||
|
| Denominator | Observed service-days in window |
|
||||||
|
| Expression | N / D |
|
||||||
|
| Aggregation | ratio |
|
||||||
|
| Unit | deployments/service-day |
|
||||||
|
| Eligible population | Declared production services and complete UTC observation days; report each service before pooled views. |
|
||||||
|
| Exclusions | Test/staging, failed attempts without production effect, duplicate callbacks and retries of the same rollout. |
|
||||||
|
| Events and source | deployment.completed; authoritative deployment service |
|
||||||
|
| Owner / cadence | Platform & IT / Weekly per service |
|
||||||
|
| Missing evidence | Missing feed periods not zero; report coverage and no unqualified overall rate when period incomplete. |
|
||||||
|
| Gaming and limits | Split deployments without value; pair with success, rework and failure measures. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. More deployments alone do not establish better performance; affected unsuccessful changes remain in change-fail population. |
|
||||||
|
|
||||||
|
|
||||||
|
### Change fail rate
|
||||||
|
|
||||||
|
Production deployment effects requiring unplanned remediation, rollback or hotfix, not solely outage declarations.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct matured production deployment_ids linked to qualifying immediate intervention |
|
||||||
|
| Denominator | All distinct production deployment_ids that reached production in the matured deployment cohort |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Deployments with production effect during window and approved intervention follow-up horizon elapsed. |
|
||||||
|
| Exclusions | Failed preproduction attempts, duplicate callbacks and no-effect retries; failures after production effect stay included. |
|
||||||
|
| Events and source | deployment.completed; deployment.failed; incident.linked; release and incident records |
|
||||||
|
| Owner / cadence | Quality Engineering / Weekly; finalized after horizon |
|
||||||
|
| Missing evidence | Known failures / all eligible deployments is a lower bound where incident linkage incomplete; label provisional and report unknown links. |
|
||||||
|
| Gaming and limits | Avoid incident links or relabel intervention planned; reconcile rollbacks/hotfixes independently. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Define immediate intervention and follow-up horizon before pilot; no universal incident-to-AI attribution. |
|
||||||
|
|
||||||
|
|
||||||
|
### Failed deployment recovery time
|
||||||
|
|
||||||
|
Mean elapsed time from production deployment failure onset to service restoration, with median/p90 and unresolved count/age.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Sum of recovery_at minus failure_started_at in hours for recovered eligible deployment failures |
|
||||||
|
| Denominator | Count of recovered eligible deployment failures with valid timestamps |
|
||||||
|
| Expression | N / D |
|
||||||
|
| Aggregation | mean |
|
||||||
|
| Unit | hours/failure |
|
||||||
|
| Eligible population | Production deployment failures beginning in window; latest verified restoration if a recovery is rescinded. |
|
||||||
|
| Exclusions | Nondeployment incidents; invalid timestamp pairs reported missing, not treated as instantaneous recovery. |
|
||||||
|
| Events and source | deployment.failed; service.restored; incident service |
|
||||||
|
| Owner / cadence | Platform & IT / Weekly with unresolved failures |
|
||||||
|
| Missing evidence | Unresolved failures reported separately and excluded only from recovered-duration distribution; prevent declaring improvement without their visibility. |
|
||||||
|
| Gaming and limits | Premature recovery declarations or omit hard cases; require operational readback and carry unresolved ages. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Not all-incident MTTR; recovered-only sample has survivorship bias. p90 nearest rank. |
|
||||||
|
|
||||||
|
|
||||||
|
### Deployment rework rate
|
||||||
|
|
||||||
|
Unplanned production deployment effects triggered by a production incident as a share of deployment effects.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct incident-linked deployment_ids classified unplanned remediation |
|
||||||
|
| Denominator | All distinct production deployment_ids with production effect in window |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Production deployment effects in window with immutable planned/unplanned classification and incident linkage. |
|
||||||
|
| Exclusions | Nonproduction, no-effect retries and duplicates; do not exclude unclassified changes silently. |
|
||||||
|
| Events and source | deployment.completed; incident.linked; release intent classification |
|
||||||
|
| Owner / cadence | Platform & IT / Weekly |
|
||||||
|
| Missing evidence | Unknown classification stays denominator; observed numerator is lower bound until reconciliation; show classification coverage. |
|
||||||
|
| Gaming and limits | Call hotfixes planned; freeze reason and independently reconcile incident/deployment links. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Distinct from failure rate: one failed deployment can lead to multiple separate rework deployments. |
|
||||||
|
|
||||||
|
|
||||||
|
### Changes with escaped quality issues
|
||||||
|
|
||||||
|
Accepted changes with at least one confirmed post-acceptance defect during approved follow-up, split by severity.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct eligible change_ids with one or more linked confirmed defects in follow-up |
|
||||||
|
| Denominator | Accepted change_ids with completed predeclared follow-up horizon |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Accepted changes in acceptance window whose follow-up is complete by as_of. |
|
||||||
|
| Exclusions | Immature changes shown separately; duplicates/unconfirmed reports excluded but counted as pending triage. |
|
||||||
|
| Events and source | change.accepted; defect.confirmed; defect tracking and acceptance evidence |
|
||||||
|
| Owner / cadence | Quality Engineering / Monthly mature cohorts |
|
||||||
|
| Missing evidence | Unknown linkage is a coverage warning; no fabricated clean outcome. Show absolute defect and severe incident counts as well. |
|
||||||
|
| Gaming and limits | Shorten follow-up or downgrade severity; freeze horizon and rubric and independently sample defects. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Counts affected changes, not raw defects per change; no presumption defects are AI-caused. |
|
||||||
|
|
||||||
|
|
||||||
|
### Confirmed policy effects
|
||||||
|
|
||||||
|
Eligible evaluated attempts with an actual confirmed prohibited effect; report attempts, blocked attempts and effects separately.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct attempt_ids with confirmed prohibited action effect |
|
||||||
|
| Denominator | All evaluated eligible attempt_ids including failed and abandoned runs |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Governed skill trials or supported workflow attempts, reported separately for eval and live populations. |
|
||||||
|
| Exclusions | Out-of-scope runs; detection gaps remain coverage issues, not exclusions from known initiated population. |
|
||||||
|
| Events and source | attempt.started; policy.violation; gate.blocked; security audit and incident triage |
|
||||||
|
| Owner / cadence | Architecture & Security / Immediate incident action; weekly aggregates |
|
||||||
|
| Missing evidence | Unknown outcomes remain visible; zero detected effects never proves safety. Missing audits make rate provisional. |
|
||||||
|
| Gaming and limits | Hide incidents or inflate harmless trials; absolute severity counts and fixed task mix accompany rate. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Blocked attempts are not actual effects; any critical effect blocks promotion regardless of aggregate rate. |
|
||||||
|
|
||||||
|
|
||||||
|
### Cost per accepted outcome
|
||||||
|
|
||||||
|
Allocated total workflow cost including unsuccessful attempts, licenses, compute, tooling and human review/rework divided by accepted outcomes.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Sum allocated costs in one approved reporting currency for all tasks/attempts in matured start cohort |
|
||||||
|
| Denominator | Distinct independently accepted tasks in same cohort |
|
||||||
|
| Expression | N / D |
|
||||||
|
| Aggregation | ratio |
|
||||||
|
| Unit | currency/accepted outcome |
|
||||||
|
| Eligible population | Matured eligible started-task cohort, with all attempt costs through as_of and allocated shared period costs. |
|
||||||
|
| Exclusions | Unrelated projects and documented taxes/transfers outside chosen accounting basis; never failed/abandoned attempts. |
|
||||||
|
| Events and source | cost.recorded; task.accepted; finance invoices, usage ledger, sampled effort with approved rates |
|
||||||
|
| Owner / cadence | Finance & Accounting Squad / Monthly with late invoice restatement |
|
||||||
|
| Missing evidence | Missing cost buckets produce partial estimate plus coverage, not a total. Mixed currency requires dated approved conversion; zero accepted outcomes = null with full cost shown. |
|
||||||
|
| Gaming and limits | Count tokens only or hide labor/failures; reconcile invoice and allocation totals without double counting. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Allocation and sampled labor carry uncertainty; no ROI without independently measured business benefit. |
|
||||||
|
|
||||||
|
|
||||||
|
### Verification confidence and cognitive load
|
||||||
|
|
||||||
|
Three separate anonymous 1–5 item distributions: confidence in verifying output, manageable workload, usefulness. Primary item: verification confidence mean; report full distributions and response rate.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Sum valid 1–5 verification-confidence responses, one per participant per survey wave |
|
||||||
|
| Denominator | Count valid responses to that item |
|
||||||
|
| Expression | N / D |
|
||||||
|
| Aggregation | mean |
|
||||||
|
| Unit | points (1–5) |
|
||||||
|
| Eligible population | Invited eligible cohort at fixed survey wave; voluntary responses. |
|
||||||
|
| Exclusions | Invalid/out-of-range answers and superseded duplicate submission; nonresponders stay in response-rate denominator. |
|
||||||
|
| Events and source | survey.response; anonymous survey with single-use token and aggregate invitation count |
|
||||||
|
| Owner / cadence | People & Administration Squad / After pilot workflow cycle; periodic follow-up |
|
||||||
|
| Missing evidence | Report valid response count / invited count and item nonresponse; no imputation. Suppress people cohorts below five. |
|
||||||
|
| Gaming and limits | Pressure to respond positively; no manager access to individual responses, avoid identifying free text. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Perception and selection bias; not measured time savings. No composite productivity index. |
|
||||||
|
|
||||||
|
|
||||||
|
### Skills within substantive review window
|
||||||
|
|
||||||
|
Active approved skill revisions with an accountable owner and completed substantive review not past due.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Distinct active approved skill revisions with current owner, review evidence and review_due_at >= as_of |
|
||||||
|
| Denominator | All active approved skill revisions in catalog snapshot |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | Approved revisions currently supported for use at as_of. |
|
||||||
|
| Exclusions | Experimental and retired revisions; deprecated/revoked in-use counts reported independently, not hidden. |
|
||||||
|
| Events and source | catalog.published; catalog.reviewed; catalog.deprecated; catalog.revoked; version inventory |
|
||||||
|
| Owner / cadence | Data & AI / Monthly and each release |
|
||||||
|
| Missing evidence | Missing owner/date/evidence outside numerator; show ownerless, deprecated-in-use and revoked-in-use absolute counts. |
|
||||||
|
| Gaming and limits | Rubber-stamp review dates; require linked regression/security/usefulness evidence. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. A current review is not proof of effectiveness; consumer inventory may miss shadow copies. |
|
||||||
|
|
||||||
|
|
||||||
|
### Workflow-specific business acceptance
|
||||||
|
|
||||||
|
Independently accepted domain outcomes against a predeclared business rubric, separate from technical completion.
|
||||||
|
|
||||||
|
| Contract | Definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Numerator | Eligible completed-observation task_ids meeting the department-specific outcome rubric |
|
||||||
|
| Denominator | All eligible initiated workflow task_ids with matured business-outcome horizon |
|
||||||
|
| Expression | N / D * 100 |
|
||||||
|
| Aggregation | percent |
|
||||||
|
| Unit | % |
|
||||||
|
| Eligible population | One named departmental workflow and predeclared start cohort; never pool unlike rubrics. |
|
||||||
|
| Exclusions | Cancelled-before-start and unrelated workflows; unsuccessful or abandoned eligible work remains. |
|
||||||
|
| Events and source | task.started; business.accepted; departmental case/content/reconciliation/onboarding review record |
|
||||||
|
| Owner / cadence | Product Management / Each pilot cycle with owning department |
|
||||||
|
| Missing evidence | Unknown follow-up stays denominator with unresolved count; no claim actual business outcome changed without source evidence. |
|
||||||
|
| Gaming and limits | Weaken rubric or count drafts as customer benefit; lock rubric and independently audit acceptance. Descriptive signal, not causal AI productivity attribution. Source completeness, task mix and sampling limit inference; report uncertainty. Domain owner signs acceptance: Operations service accuracy; Marketing claims/rights; Finance reconciliation accuracy; HR policy correctness; Product usability/value. Not revenue uplift. |
|
||||||
|
|
||||||
|
|
||||||
|
## Run the synthetic measurement example
|
||||||
|
|
||||||
|
**Executed synthetic example — not Eldorado results.** The bundled fixture produces 1 independently accepted task from 4 matured starts: 25% confirmed success. One unresolved task remains in that denominator; known-outcome coverage is 75%. Selection precision and recall are each 1 / 2 = 50%. Recorded synthetic compute cost is EUR 60 / 1 accepted outcome = EUR 60 per accepted outcome, a partial estimate: licenses, tooling, review and rework are missing. These deliberately small samples demonstrate arithmetic and missingness, not a baseline, target or ROI claim.
|
||||||
|
|
||||||
|
From the private documentation checkout, run `node scripts/ai-kpi.mjs` and `node --test scripts/ai-kpi.test.mjs`. The fixture `scripts/fixtures/ai-kpi-synthetic.json` is labeled synthetic; every result carries the same label and calculation version. This prototype has no network collectors and does not enable employee telemetry.
|
||||||
|
|
||||||
|
The executable covers validated success, selection precision/recall, change fail and rework rates, escaped quality, policy effects, cost and human experience. Other catalog metrics remain contracts rather than implemented collectors; output lists the omissions. Snapshot inputs are simplified, pre-reconciled records, not a production ingestion system. Conflicting duplicates reject the calculation rather than silently choosing a value.
|
||||||
|
|
||||||
|
**Next:** [Use findings to maintain, quarantine or replace skills →](../skills-maintenance.mdx)
|
||||||
|
|
||||||
|
</div>
|
||||||
+46
-42
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id: roadmap
|
id: roadmap
|
||||||
title: Roadmap
|
title: Roadmap
|
||||||
description: Evidence-gated phases, no promised dates or budgets.
|
description: Assess current code quality and delivery processes, close critical gaps, then pilot, expand and sustain AI adoption.
|
||||||
hide_table_of_contents: true
|
hide_table_of_contents: true
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,67 +9,71 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
|||||||
|
|
||||||
<div className="strategy-content">
|
<div className="strategy-content">
|
||||||
|
|
||||||
**TO-BE · Target operating model — not installed controls or company results.**
|
Understand today’s code and how work really reaches production before introducing AI into the workflow. Fix what would make a pilot unsafe or impossible to evaluate—not every item of technical debt.
|
||||||
|
|
||||||
## What must be true before widening the rollout?
|
<ArchifyDiagram name="strategy-roadmap" title="Understand today → Prepare foundations → Pilot → Expand → Sustain" story />
|
||||||
|
|
||||||
Advance by exit evidence, not promised dates or usage volume. Each stage can hold, narrow or stop; changed client, data or authority boundaries reopen earlier gates.
|
**Recommended sequence · Target operating model, not an approved schedule or deployed governance.**
|
||||||
|
|
||||||
<ArchifyDiagram name="strategy-roadmap" title="Foundation, pilot, reliable distribution, coordination and departmental expansion have exit gates" story />
|
## 1. Understand code quality and delivery
|
||||||
|
|
||||||
- **Start bounded:** named people, lawful data handling and a reproducible baseline precede the pilot.
|
Inspect representative repositories across important services and change types: tests and CI reliability, maintainability, technical debt and security findings. Trace recent changes through the actual SDLC with the people doing the work—from request to production and feedback. Check ownership, review and release practices, handoffs, rework and bottlenecks; do not assume the documented process is the real one.
|
||||||
- **Prove recovery before coordination:** verified versions, revocation and human takeover are prerequisites.
|
|
||||||
- **Expand by department:** accepted outcomes, guardrails, total cost and experience justify continuation; sensitive decisions remain human.
|
**Output:** baseline findings, a process map and prioritized gaps. Use these to identify a low-risk, repeatable workflow with observable acceptance—not simply the easiest repository.
|
||||||
|
|
||||||
|
## 2. Fix critical readiness gaps
|
||||||
|
|
||||||
|
Resolve gaps that threaten safe execution or a trustworthy comparison: for example, exposed secrets, unclear approval authority or unreliable checks needed to accept the pilot’s changes. Establish accountable people, data boundaries and recovery for the selected scope. Broader refactoring and non-critical debt can run alongside the pilot with a separate improvement backlog.
|
||||||
|
|
||||||
|
**Output:** a readiness decision and bounded pilot charter. Proceed only when the critical gaps for that workflow are closed; otherwise narrow the scope or hold.
|
||||||
|
|
||||||
|
## 3. Run a bounded pilot
|
||||||
|
|
||||||
|
Try a small set of reviewed skills in the selected workflow, with human review and no new privileged autonomous actions. Compare equivalent work with the baseline: accepted outcomes, escaped defects, full cost and verification effort. [Skills Measurement](./measurement.mdx) explains what to collect, calculate and report; agree the comparison rules before starting.
|
||||||
|
|
||||||
|
**Output:** a pilot report and a continue, adjust or stop decision. Missing evidence is a reason to investigate, not proof of improvement.
|
||||||
|
|
||||||
|
## 4. Expand only on evidence
|
||||||
|
|
||||||
|
Extend to another workflow or team only when quality, usefulness, cost and support capacity justify it. Prove reliable skill distribution, revocation and recovery before expanding authority. Introduce autonomous SDLC execution progressively under human direction; new departmental workflows need their own data boundaries and acceptance evidence.
|
||||||
|
|
||||||
|
**Output:** an evidenced expansion decision with verified controls for the next scope—not organization-wide rollout by default.
|
||||||
|
|
||||||
|
## 5. Sustain and improve
|
||||||
|
|
||||||
|
Review outcomes, drift, incidents and maintenance effort. Retire ineffective skills, revisit process bottlenecks and repeat assessment when repositories, clients, data or authority change. Work toward [Target](./target-operating-model.mdx): an autonomous agent executes and governs the repeating SDLC while humans direct, guide and review.
|
||||||
|
|
||||||
|
**Output:** maintained skills, a refreshed improvement backlog and evidence for the next cycle. Keep human takeover available; narrow or stop workflows when evidence no longer supports them.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Stage-by-stage entry evidence, work, exit gates and hold conditions</summary>
|
<summary>Supporting gate evidence and hold conditions</summary>
|
||||||
|
|
||||||
**TO-BE · Target operating model.** Introduction depends on the [roadmap gates](./roadmap.mdx); these pages do not describe installed controls or measured company results.
|
These are recommended implementation gates, not assigned human owners, approved thresholds or installed integrations. Agree acceptance criteria for each selected workflow before evaluating it.
|
||||||
|
|
||||||
### Foundation
|
### Assessment → readiness
|
||||||
|
|
||||||
**Entry evidence** — Strategy scope accepted for planning; existing teams identified.
|
Retain the sampled repository revisions, test/CI results, debt and security findings, and traced changes behind the process map. Record coverage and unknowns. Distinguish critical pilot blockers from parallel improvements; hold selection if the sample cannot explain how the candidate workflow is delivered and accepted.
|
||||||
|
|
||||||
**Work** — Inventory actual client hosts/builds, provider/data terms, repositories, authority, owners and event availability; select low-risk workflows and metric definitions.
|
### Readiness → pilot
|
||||||
|
|
||||||
**Exit evidence and hold condition** — Named accountable people and independent reviewers, bounded pilot charter, forbidden actions, data/retention decision and reproducible baseline plan. Hold if accountability or lawful collection is unresolved.
|
Inventory actual client hosts/builds, provider/data terms, authority and event availability. Confirm accountable people and independent reviewers, forbidden actions, data/retention decisions, recovery and a reproducible baseline. Hold if accountability, lawful collection or critical acceptance checks remain unresolved.
|
||||||
|
|
||||||
### Governed skills pilot
|
### Pilot → expansion
|
||||||
|
|
||||||
**Entry evidence** — Foundation evidence approved; no privileged autonomous effects.
|
Use a small registry, immutable skill contracts, qualified client adapters, evaluations and coaching. Verify the measurement calculations with synthetic fixtures before interpreting real outcomes. Require mandatory safety checks, independent task acceptance, reconciled source coverage and user feedback. Hold or narrow scope if quality or coverage is inadequate; usage alone does not justify expansion.
|
||||||
|
|
||||||
**Work** — Build a small registry, immutable contract, client adapters, evaluations, coaching and synthetic measurement verification.
|
### Expansion → wider authority
|
||||||
|
|
||||||
**Exit evidence and hold condition** — Qualified client/skill tuples, mandatory safety checks, independent task acceptance, reconciled coverage and user feedback. Hold or narrow scope if quality or data coverage is inadequate.
|
Prove canary updates, effective-version readback, version locking, exception expiry, stale/offline handling, rollback and revocation. Hold if effective versions or controls cannot be verified or the catalog cannot be supported.
|
||||||
|
|
||||||
### Reliable distribution
|
For autonomous execution, begin with observation and evidence assembly, then proposing and routing work, before granting scoped execution authority. Recommended controls include exact-artifact approvals, idempotency, duplicate/outage recovery, separation of duties, audit and kill-switch drills. Prove human takeover; never infer permission from missing evidence. The agent ultimately executes and governs the cycle, not only routes tasks.
|
||||||
|
|
||||||
**Entry evidence** — Pilot quality and usefulness support continuing.
|
### Sustained operation
|
||||||
|
|
||||||
**Work** — Canary updates, effective-version readback, version locking, exception expiry, stale/offline handling and rollback.
|
Operations, Marketing, Finance & Accounting and HR & Administration workflows each need a workflow owner, outcome, data boundary, capacity and domain-specific review; Product and Technology continue quality improvement. Keep sensitive decisions human. Use accepted outcomes, guardrails, total cost and experience to continue, narrow, stop or roll back. Changed client, provider, data or authority boundaries reopen the relevant gates.
|
||||||
|
|
||||||
**Exit evidence and hold condition** — Tested revocation and recovery, no bypass of central privileged gates, owners can support the catalog. Hold if effective versions or controls cannot be verified.
|
|
||||||
|
|
||||||
### Logical SDLC coordination
|
|
||||||
|
|
||||||
**Entry evidence** — Distribution, authority and measurement evidence stable.
|
|
||||||
|
|
||||||
**Work** — Observe and assemble evidence first, then propose and route work within scoped identities and budgets.
|
|
||||||
|
|
||||||
**Exit evidence and hold condition** — Exact-artifact approvals, idempotency, duplicate/outage recovery, separation of duties, audit and kill-switch drills pass; humans can take over. No authority expansion based on usage alone.
|
|
||||||
|
|
||||||
### Departmental expansion
|
|
||||||
|
|
||||||
**Entry evidence** — Each department supplies a workflow owner, outcome, data boundary and capacity.
|
|
||||||
|
|
||||||
**Work** — Add Operations, Marketing, Finance & Accounting and HR & Administration workflows with domain-specific review; Product and Technology continue quality improvement.
|
|
||||||
|
|
||||||
**Exit evidence and hold condition** — Departmental accepted outcomes, guardrails, total cost and experience justify continuation; sensitive decisions stay human. Stop or roll back workflows without adequate evidence.
|
|
||||||
|
|
||||||
There are no invented launch dates, budgets, staffing commitments or guaranteed gains. Phase gates permit narrowing or stopping. Re-evaluate earlier phases when the underlying client, provider, data scope or authority changes.
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
**Next:** [Return to the strategy destination →](./index.mdx)
|
**Next:** [Return to the strategy destination →](./target-operating-model.mdx)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
id: target-operating-model
|
id: target-operating-model
|
||||||
title: Target operating model
|
title: Target operating model
|
||||||
description: One logical coordinator; local client choice; independent authority; all departments.
|
sidebar_label: Target
|
||||||
|
description: An autonomous agent executes and governs the SDLC; humans direct, guide and review the repeating cycle.
|
||||||
hide_table_of_contents: true
|
hide_table_of_contents: true
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,42 +10,44 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
|||||||
|
|
||||||
<div className="strategy-content">
|
<div className="strategy-content">
|
||||||
|
|
||||||
**TO-BE · Target operating model — not installed controls or company results.**
|
**TO-BE · Target operating model — the intended strategy, not a deployed system.**
|
||||||
|
|
||||||
## Who coordinates, and who can authorize delivery?
|
## Autonomous SDLC, human direction
|
||||||
|
|
||||||
One logical coordinator routes work; people retain authority. Local agents propose changes, while independent CI and human approval govern what the release service can execute.
|
<ArchifyDiagram name="strategy-target" title="Autonomous agent executes and governs the SDLC; humans direct, guide and review" story />
|
||||||
|
|
||||||
<ArchifyDiagram name="strategy-target" title="People authorize; skills guide; independent gates protect release" story />
|
An autonomous agent executes and governs every phase. Humans direct, guide and review it; a separate human-controlled AI Agent contributes to each phase from outside the cycle.
|
||||||
|
|
||||||
- **Local choice:** Copilot, Claude Code and Cline use qualified adapters and pinned skills.
|
**Solid arrows:** lifecycle · **Dotted spokes:** execution and governance · **Dashed arrows:** contributions.
|
||||||
- **Central controls:** neither laptops nor the coordinator hold production credentials.
|
|
||||||
- **Existing organization:** six departments, eleven teams; Data & AI owns coordination, not their decisions.
|
**Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope**
|
||||||
|
|
||||||
|
Each stage follows the previous one. Releases returns to Scope; the cycle repeats indefinitely, with human direction informing the next iteration.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Trust boundaries, recovery and detailed delivery path</summary>
|
<summary>Supporting implementation options: trust boundaries and recovery</summary>
|
||||||
|
|
||||||
## Coordination without transferred authority
|
## Controls that support autonomous execution
|
||||||
|
|
||||||
The diagram is an overview; the equivalent delivery path below remains readable on narrow screens. Expand the diagram to inspect its trust boundaries and use its zoom controls for individual relationships.
|
The lifecycle above is the strategic direction. The earlier control design below is a supporting implementation option, not an agreed assignment of approval roles or a claim of installed controls. Detailed approval policy and responsibilities remain to be defined with humans.
|
||||||
|
|
||||||
1. Accountable people authorize the work scope; one logical coordinator routes tasks.
|
1. Humans direct, guide and review; the autonomous agent executes and governs work across the lifecycle.
|
||||||
2. The reviewed registry supplies pinned skills and qualified adapters to coordinator and local clients.
|
2. The reviewed registry supplies pinned skills and qualified adapters to coordinator and local clients.
|
||||||
3. Laptop contributors propose changes; independent repository and CI checks verify the exact revision.
|
3. Laptop contributors can participate alongside the agent; independent repository and CI checks verify the exact revision.
|
||||||
4. A human approves that artifact; only the release service holds production credentials.
|
4. A separate release service and artifact-scoped approval are possible controls, subject to the policy humans define.
|
||||||
5. Restricted audit and measurement evidence returns to people and informs the next authorized task.
|
5. Restricted audit and measurement evidence supports human review and the next Scope iteration.
|
||||||
|
|
||||||
<ArchifyDiagram name="ai-adoption-target" title="TO-BE AI adoption: human accountability, no direct production authority for laptops or coordinator" />
|
<ArchifyDiagram name="ai-adoption-target" title="Supporting control option, not agreed approval policy: isolated release authority" />
|
||||||
|
|
||||||
**TO-BE · Target operating model.** Introduction depends on the [roadmap gates](./roadmap.mdx); these pages do not describe installed controls or measured company results.
|
**TO-BE · Target operating model.** Introduction depends on the [roadmap gates](./roadmap.mdx); these pages do not describe installed controls or measured company results.
|
||||||
|
|
||||||
### One coordination function, distributed contributions
|
### One coordination function, distributed contributions
|
||||||
|
|
||||||
Data & AI owns the coordinating function, with the existing AI Architect function connecting it to Technology leadership. The logical coordinator maintains approved work records, dependencies, evidence requests, bounded retries and escalation. It can summarize, propose work and route decisions. It cannot declare its own work approved, change protected checks, authorize production release or replace the responsible human.
|
The autonomous agent is more than a task router: it executes and governs the repeating SDLC under human direction, guidance and review. A coordinating function maintains work records, dependencies, evidence requests, bounded retries and escalation. Data & AI and the existing AI Architect function are possible implementation contributors, not newly assigned approval authorities. Autonomous execution does not remove human involvement or allow the agent to redefine its own controls.
|
||||||
|
|
||||||
One logical coordinator does not mean one fragile process. A durable task ledger, explicit state transitions, leases and idempotency keys allow recovery or multiple replicas without duplicate delivery. Inference outages stop new agent decisions, not the organization's ability to take over. People can resume work through the normal deterministic workflow. Evidence and authorization services fail closed for privileged actions; missing evidence never becomes permission.
|
One logical coordinator does not mean one fragile process. A durable task ledger, explicit state transitions, leases and idempotency keys allow recovery or multiple replicas without duplicate delivery. Inference outages stop new agent decisions, not the organization's ability to take over. People can resume work through the normal deterministic workflow. Evidence and authorization services fail closed for privileged actions; missing evidence never becomes permission.
|
||||||
|
|
||||||
The delivery path is: an approved problem and acceptance criteria; an authorized task; local or isolated worker proposals; independently reviewed repository changes; exact-revision CI evidence; a human release decision; execution by the release service; then operational feedback. Product Management accepts the outcome hypothesis and business result. Product Design reviews experience and accessibility. Product Engineering owns the code and maintainability. Quality Engineering assesses quality independently. Architecture & Security assesses risk. Platform & IT operates the delivery boundary. No arrow from a laptop or coordinator goes directly to production authority.
|
These controls support the sequence Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope; they do not replace it with a separate lifecycle. Product, engineering, design, quality, security and platform expertise inform human direction and review. Specific approval assignments and release permissions are implementation decisions still to be defined.
|
||||||
|
|
||||||
### Trust and authority boundaries
|
### Trust and authority boundaries
|
||||||
|
|
||||||
@@ -57,10 +60,12 @@ Central controls verify protected-branch review, actual artifact identity, exact
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Responsibilities and first workflows for all eleven teams</summary>
|
<summary>Candidate contributions and first workflows for all eleven teams</summary>
|
||||||
|
|
||||||
### Department contributions
|
### Department contributions
|
||||||
|
|
||||||
|
The existing team structure provides a starting point. The contributions below are implementation proposals, not settled approval roles or lifecycle gates.
|
||||||
|
|
||||||
### Product / Product Management
|
### Product / Product Management
|
||||||
|
|
||||||
**Contribution and retained human decision** — Own pilot priorities, acceptance criteria and outcome hypotheses; decide product value.
|
**Contribution and retained human decision** — Own pilot priorities, acceptance criteria and outcome hypotheses; decide product value.
|
||||||
@@ -133,6 +138,30 @@ See the existing [organization structure](/organization/structure/), [department
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Strategy rationale and reading map</summary>
|
||||||
|
|
||||||
|
### Govern skills first, expand authority only with evidence
|
||||||
|
|
||||||
|
Eldorado's target is to make AI-assisted work dependable across the organization: useful guidance, clear human ownership, controlled execution and measurable outcomes. The first investment is a governed skills system, not an autonomous replacement for the organization. A skill packages reusable working knowledge, prerequisites and verification steps. It helps an agent perform a task; it does not grant permission or guarantee that the work is correct.
|
||||||
|
|
||||||
|
The target combines an autonomous agent executing and governing the SDLC with humans directing, guiding and reviewing it. Developer choice on local laptops remains a supporting option: Copilot, Claude Code and Cline can receive reviewed portable skill content through qualified adapters. Repository, CI and release controls support execution; detailed approval policies remain implementation decisions, not new roles established by this strategy.
|
||||||
|
|
||||||
|
The strategy describes the intended operating model and the evidence required to introduce it. It does not establish that these controls are installed, that every client is supported, or that AI has improved Eldorado's performance. The current reference is the documented six-department organization. Rollout begins by validating actual clients, responsibilities, data boundaries and available evidence.
|
||||||
|
|
||||||
|
Success means accepted outcomes with maintained quality, controlled risk and understood total cost. Faster activity without reliable outcomes is not success. More skill invocations, generated lines or tool subscriptions are not productivity targets. People remain accountable for product, customer, financial and personnel decisions.
|
||||||
|
|
||||||
|
### Navigate the strategy
|
||||||
|
|
||||||
|
- [Roadmap](./roadmap.mdx) — Evidence-gated phases, no promised dates or budgets.
|
||||||
|
- [Skills governance](./skills-governance.mdx) — Contract, lifecycle, decision rights, adapters, locking, exceptions.
|
||||||
|
- [Skills quality](./skills-quality.mdx) — Risk tiers, independent evaluation, repeated trials, adversarial conformance.
|
||||||
|
- [Skills adoption](./skills-adoption.mdx) — Workflow-led coaching, client choice, feedback, manual path.
|
||||||
|
- [Measurement](./measurement.mdx) — Full KPI contracts, minimal events, illustrative calculations, privacy and limits.
|
||||||
|
- [Skills maintenance](./skills-maintenance.mdx) — Review, drift, deprecation, rollback and incident handling.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
**Next:** [Decide who owns and approves each skill →](./skills-governance.mdx)
|
**Next:** [Decide who owns and approves each skill →](./skills-governance.mdx)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -18,6 +18,7 @@ const config: Config = {
|
|||||||
theme: {customCss: './src/css/custom.css'},
|
theme: {customCss: './src/css/custom.css'},
|
||||||
} satisfies Preset.Options]],
|
} satisfies Preset.Options]],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
['@docusaurus/plugin-client-redirects', {redirects: [{from: '/strategy/', to: '/strategy/target-operating-model/'}]}],
|
||||||
['@docusaurus/plugin-content-docs', {id: 'organization', path: 'docs/organization', routeBasePath: 'organization', sidebarPath: './sidebars/organization.ts'}],
|
['@docusaurus/plugin-content-docs', {id: 'organization', path: 'docs/organization', routeBasePath: 'organization', sidebarPath: './sidebars/organization.ts'}],
|
||||||
['@docusaurus/plugin-content-docs', {id: 'strategy', path: 'docs/strategy', routeBasePath: 'strategy', sidebarPath: './sidebars/strategy.ts'}],
|
['@docusaurus/plugin-content-docs', {id: 'strategy', path: 'docs/strategy', routeBasePath: 'strategy', sidebarPath: './sidebars/strategy.ts'}],
|
||||||
['@docusaurus/plugin-content-docs', {id: 'contribution', path: 'docs/contribution', routeBasePath: 'contribution', sidebarPath: './sidebars/contribution.ts'}],
|
['@docusaurus/plugin-content-docs', {id: 'contribution', path: 'docs/contribution', routeBasePath: 'contribution', sidebarPath: './sidebars/contribution.ts'}],
|
||||||
|
|||||||
+2
-1
@@ -15,11 +15,12 @@
|
|||||||
"verify:build": "node scripts/verify.mjs --build",
|
"verify:build": "node scripts/verify.mjs --build",
|
||||||
"diagrams:build": "node scripts/build-diagrams.mjs",
|
"diagrams:build": "node scripts/build-diagrams.mjs",
|
||||||
"test": "node --test scripts/*.test.mjs",
|
"test": "node --test scripts/*.test.mjs",
|
||||||
"test:browser": "node scripts/browser/strategy-story.cjs && node scripts/browser/inline-controls.cjs",
|
"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",
|
||||||
"kpi:example": "node scripts/ai-kpi.mjs"
|
"kpi:example": "node scripts/ai-kpi.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.10.2",
|
"@docusaurus/core": "3.10.2",
|
||||||
|
"@docusaurus/plugin-client-redirects": "3.10.2",
|
||||||
"@docusaurus/preset-classic": "3.10.2",
|
"@docusaurus/preset-classic": "3.10.2",
|
||||||
"@docusaurus/theme-common": "3.10.2",
|
"@docusaurus/theme-common": "3.10.2",
|
||||||
"@easyops-cn/docusaurus-search-local": "0.55.3",
|
"@easyops-cn/docusaurus-search-local": "0.55.3",
|
||||||
|
|||||||
Generated
+51
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@docusaurus/core':
|
'@docusaurus/core':
|
||||||
specifier: 3.10.2
|
specifier: 3.10.2
|
||||||
version: 3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(debug@4.4.3)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
version: 3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(debug@4.4.3)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
||||||
|
'@docusaurus/plugin-client-redirects':
|
||||||
|
specifier: 3.10.2
|
||||||
|
version: 3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
||||||
'@docusaurus/preset-classic':
|
'@docusaurus/preset-classic':
|
||||||
specifier: 3.10.2
|
specifier: 3.10.2
|
||||||
version: 3.10.2(@algolia/client-search@5.59.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(search-insights@2.17.3)(typescript@5.9.3)
|
version: 3.10.2(@algolia/client-search@5.59.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(search-insights@2.17.3)(typescript@5.9.3)
|
||||||
@@ -1115,6 +1118,13 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
|
|
||||||
|
'@docusaurus/plugin-client-redirects@3.10.2':
|
||||||
|
resolution: {integrity: sha512-z5I5ttCXw+8y2gHVZvqAMmUw4Rb0ZzKA5eCPk87SfM/jCKOTvE24yDpPAwwipvug96ij7mOwEHXWw8G4LlWdGA==}
|
||||||
|
engines: {node: '>=20.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
'@docusaurus/plugin-content-blog@3.10.2':
|
'@docusaurus/plugin-content-blog@3.10.2':
|
||||||
resolution: {integrity: sha512-0cbEnNKf0InmLkhj/+nVRmqEnWEoOE8Mh+2x1qOXI0qYpCnphq4RXknVJ8BvybKRXqYVvbmdMfiJSup+k4tm5w==}
|
resolution: {integrity: sha512-0cbEnNKf0InmLkhj/+nVRmqEnWEoOE8Mh+2x1qOXI0qYpCnphq4RXknVJ8BvybKRXqYVvbmdMfiJSup+k4tm5w==}
|
||||||
engines: {node: '>=20.0'}
|
engines: {node: '>=20.0'}
|
||||||
@@ -6784,6 +6794,47 @@ snapshots:
|
|||||||
- uglify-js
|
- uglify-js
|
||||||
- webpack-cli
|
- webpack-cli
|
||||||
|
|
||||||
|
'@docusaurus/plugin-client-redirects@3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@docusaurus/core': 3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(debug@4.4.3)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
||||||
|
'@docusaurus/logger': 3.10.2
|
||||||
|
'@docusaurus/utils': 3.10.2(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.28))(html-minifier-terser@7.2.0)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@docusaurus/utils-common': 3.10.2(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.28))(html-minifier-terser@7.2.0)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@docusaurus/utils-validation': 3.10.2(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
eta: 2.2.0
|
||||||
|
fs-extra: 11.4.1
|
||||||
|
lodash: 4.18.1
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
tslib: 2.8.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@docusaurus/faster'
|
||||||
|
- '@mdx-js/react'
|
||||||
|
- '@minify-html/node'
|
||||||
|
- '@napi-rs/image'
|
||||||
|
- '@parcel/css'
|
||||||
|
- '@rspack/core'
|
||||||
|
- '@swc/core'
|
||||||
|
- '@swc/css'
|
||||||
|
- '@swc/html'
|
||||||
|
- bufferutil
|
||||||
|
- clean-css
|
||||||
|
- cssnano
|
||||||
|
- csso
|
||||||
|
- debug
|
||||||
|
- esbuild
|
||||||
|
- html-minifier-terser
|
||||||
|
- imagemin
|
||||||
|
- lightningcss
|
||||||
|
- postcss
|
||||||
|
- sharp
|
||||||
|
- supports-color
|
||||||
|
- svgo
|
||||||
|
- typescript
|
||||||
|
- uglify-js
|
||||||
|
- utf-8-validate
|
||||||
|
- webpack-cli
|
||||||
|
|
||||||
'@docusaurus/plugin-content-blog@3.10.2(@docusaurus/plugin-content-docs@3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)':
|
'@docusaurus/plugin-content-blog@3.10.2(@docusaurus/plugin-content-docs@3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@docusaurus/core': 3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(debug@4.4.3)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
'@docusaurus/core': 3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(debug@4.4.3)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
||||||
|
|||||||
@@ -28,11 +28,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
|
||||||
"id": "index",
|
|
||||||
"title": "AI adoption strategy",
|
|
||||||
"purpose": "Govern skills first; outcomes before activity; explicit target status."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "target-operating-model",
|
"id": "target-operating-model",
|
||||||
"title": "Target operating model",
|
"title": "Target operating model",
|
||||||
@@ -55,8 +50,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "measurement",
|
"id": "measurement",
|
||||||
"title": "Measurement",
|
"title": "Skills Measurement",
|
||||||
"purpose": "Full KPI contracts, minimal events, illustrative calculations, privacy and limits."
|
"purpose": "Which KPIs to collect during skills adoption, how to calculate them and when to report."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "skills-maintenance",
|
"id": "skills-maintenance",
|
||||||
@@ -66,7 +61,7 @@
|
|||||||
{
|
{
|
||||||
"id": "roadmap",
|
"id": "roadmap",
|
||||||
"title": "Roadmap",
|
"title": "Roadmap",
|
||||||
"purpose": "Evidence-gated phases, no promised dates or budgets."
|
"purpose": "Assess current code quality and delivery processes, close critical gaps, then pilot, expand and sustain AI adoption."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"diagram": {
|
"diagram": {
|
||||||
|
|||||||
@@ -88,7 +88,10 @@ const fs = require('node:fs');
|
|||||||
if (format.startsWith('svg')) {
|
if (format.startsWith('svg')) {
|
||||||
const valid = await page.evaluate(xml => {
|
const valid = await page.evaluate(xml => {
|
||||||
const doc = new DOMParser().parseFromString(xml, 'image/svg+xml');
|
const doc = new DOMParser().parseFromString(xml, 'image/svg+xml');
|
||||||
return !doc.querySelector('parsererror') && doc.documentElement.localName === 'svg' && doc.querySelectorAll('text').length > 0;
|
if (doc.querySelector('[data-node-id="humans"], [data-edge-id="human-direction"]')) return false;
|
||||||
|
if (![...doc.querySelectorAll('[data-node-id="agent"] text')].some(n => n.textContent === 'Human Controlled')) return false;
|
||||||
|
if (doc.querySelectorAll('[data-node-id]').length !== 17 || doc.querySelectorAll('path[data-edge-id]').length !== 24) return false;
|
||||||
|
return !doc.querySelector('parsererror') && doc.documentElement.localName === 'svg' && doc.querySelectorAll('text').length > 0 && doc.querySelector('#eldorado-cycle-export')?.textContent.includes('stroke-dasharray: 3 9');
|
||||||
}, bytes.toString());
|
}, bytes.toString());
|
||||||
assert(valid, format);
|
assert(valid, format);
|
||||||
} else {
|
} else {
|
||||||
@@ -117,10 +120,17 @@ const fs = require('node:fs');
|
|||||||
await close.focus(); await page.keyboard.press('Escape');
|
await close.focus(); await page.keyboard.press('Escape');
|
||||||
assert(await opener.evaluate(n => n === document.activeElement));
|
assert(await opener.evaluate(n => n === document.activeElement));
|
||||||
await iframe.scrollIntoViewIfNeeded();
|
await iframe.scrollIntoViewIfNeeded();
|
||||||
await page.evaluate(() => scrollTo(0, 0));
|
await page.evaluate(() => scrollTo({top: 0, behavior: 'instant'}));
|
||||||
|
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
||||||
await page.screenshot({path:`${output}/${width}.png`, fullPage:true});
|
await page.screenshot({path:`${output}/${width}.png`, fullPage:true});
|
||||||
await frame.locator('#btn-theme').click(); await theme('dark');
|
await frame.locator('#btn-theme').click(); await theme('dark');
|
||||||
await page.locator('.strategy-content a[href$="/strategy/target-operating-model/"]').filter({visible:true}).click();
|
// 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.keyboard.press('Enter');
|
||||||
|
await page.waitForURL('**/strategy/skills-governance/');
|
||||||
|
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();
|
||||||
const next = page.locator(`.archify-panel--story[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
const next = page.locator(`.archify-panel--story[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||||
await next.scrollIntoViewIfNeeded();
|
await next.scrollIntoViewIfNeeded();
|
||||||
await (await (await next.elementHandle()).contentFrame()).waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
await (await (await next.elementHandle()).contentFrame()).waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
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/roadmap-${process.argv[3] || 'local'}`;
|
||||||
|
fs.mkdirSync(output, {recursive:true});
|
||||||
|
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||||
|
const results = [];
|
||||||
|
try {
|
||||||
|
// Intermediate widths must use the readable vertical Roadmap, not shrink five cards.
|
||||||
|
for (const width of [768, 1024, 1280]) {
|
||||||
|
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||||
|
try {
|
||||||
|
const page = await context.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', error => errors.push(error.message));
|
||||||
|
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'});
|
||||||
|
assert.equal(response.status(), 200);
|
||||||
|
const panel = page.locator('.archify-panel--story[data-diagram="strategy-roadmap"]');
|
||||||
|
const iframe = panel.locator('.story-mobile');
|
||||||
|
assert(await iframe.isVisible(), `${width}: vertical Roadmap iframe must be visible`);
|
||||||
|
assert(!await panel.locator('.story-desktop').isVisible(), `${width}: horizontal Roadmap must be hidden`);
|
||||||
|
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||||
|
await frame.locator('svg[role="img"]').waitFor();
|
||||||
|
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
|
||||||
|
width:innerWidth, height:innerHeight,
|
||||||
|
overflow:document.documentElement.scrollWidth > innerWidth || document.documentElement.scrollHeight > innerHeight,
|
||||||
|
nodes:[...svg.querySelectorAll('[data-node-id]')].map(node => ({
|
||||||
|
id:node.dataset.nodeId, box:node.querySelector('rect').getBoundingClientRect().toJSON(),
|
||||||
|
text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))
|
||||||
|
})).sort((a,b) => a.box.y-b.box.y || a.box.x-b.box.x)
|
||||||
|
}));
|
||||||
|
assert.deepEqual(geometry.nodes.map(n => n.id), ['assessment','readiness','pilot','expansion','sustain']);
|
||||||
|
for (const [index, card] of geometry.nodes.entries()) {
|
||||||
|
assert(card.text.length > 0, `${width}: missing text in ${card.id}`);
|
||||||
|
assert(card.box.left >= 0 && card.box.top >= 0 && card.box.right <= geometry.width + 1 && card.box.bottom <= geometry.height + 1, `${width}: card outside iframe: ${card.id}`);
|
||||||
|
for (const t of card.text) {
|
||||||
|
assert(t.box.left >= card.box.left && t.box.right <= card.box.right + 1 && t.box.top >= card.box.top && t.box.bottom <= card.box.bottom + 1, `${width}: clipped ${card.id} ${t.text}`);
|
||||||
|
assert(t.size >= 11, `${width}: small ${card.id} ${t.text}: ${t.size}`);
|
||||||
|
}
|
||||||
|
if (index) {
|
||||||
|
const previous = geometry.nodes[index - 1].box;
|
||||||
|
assert.equal(previous.x, card.box.x);
|
||||||
|
assert(previous.bottom < card.box.top, `${width}: stages must flow vertically`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(!geometry.overflow, `${width}: iframe overflow`);
|
||||||
|
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), `${width}: page overflow`);
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
results.push({width, geometry, responsiveReadability:true});
|
||||||
|
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||||
|
} finally {await context.close();}
|
||||||
|
}
|
||||||
|
for (const width of [1440, 390, 320]) {
|
||||||
|
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||||
|
const page = await context.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', error => errors.push(error.message));
|
||||||
|
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'});
|
||||||
|
assert.equal(response.status(), 200);
|
||||||
|
assert.equal(await page.locator('h1').innerText(), 'Roadmap');
|
||||||
|
const headings = page.locator('.strategy-content > h2');
|
||||||
|
assert.deepEqual((await headings.allTextContents()).map(s => s.replace(/\u200b/g, '')), ['1. Understand code quality and delivery','2. Fix critical readiness gaps','3. Run a bounded pilot','4. Expand only on evidence','5. Sustain and improve']);
|
||||||
|
const text = await page.locator('.strategy-content').innerText();
|
||||||
|
for (const marker of ['baseline findings', 'process map', 'prioritized gaps', 'alongside the pilot', 'Recommended sequence']) assert(text.includes(marker), marker);
|
||||||
|
assert(!text.includes('Supporting gate evidence and hold conditions\n\nThese are'));
|
||||||
|
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()).slice(0,2), ['Target','Roadmap']);
|
||||||
|
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');
|
||||||
|
await iframe.scrollIntoViewIfNeeded();
|
||||||
|
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||||
|
await frame.locator('svg[role="img"]').waitFor();
|
||||||
|
const checkCards = async () => {
|
||||||
|
const cards = await frame.locator('svg[role="img"] [data-node-id]').evaluateAll(nodes => nodes.map(node => {
|
||||||
|
const box = node.querySelector('rect').getBoundingClientRect();
|
||||||
|
return {id:node.dataset.nodeId, box:box.toJSON(), text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))};
|
||||||
|
}));
|
||||||
|
for (const card of cards) for (const t of card.text) {
|
||||||
|
assert(t.box.left >= card.box.left && t.box.right <= card.box.right + 1 && t.box.top >= card.box.top && t.box.bottom <= card.box.bottom + 1, `${width}: clipped ${card.id} ${t.text}`);
|
||||||
|
assert(t.size >= 11, `${width}: small ${card.id} ${t.text}: ${t.size}`);
|
||||||
|
}
|
||||||
|
return cards;
|
||||||
|
};
|
||||||
|
await checkCards();
|
||||||
|
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
|
||||||
|
nodes:[...svg.querySelectorAll('[data-node-id]')].map(n => ({id:n.dataset.nodeId,...n.getBoundingClientRect().toJSON()})).sort((a,b) => a.y-b.y || a.x-b.x),
|
||||||
|
width:innerWidth, height:innerHeight,
|
||||||
|
minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n => parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))
|
||||||
|
}));
|
||||||
|
assert.deepEqual(geometry.nodes.map(n => n.id), ['assessment','readiness','pilot','expansion','sustain']);
|
||||||
|
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}`);
|
||||||
|
for (let i=1; i<geometry.nodes.length; i++) {
|
||||||
|
const a=geometry.nodes[i-1], b=geometry.nodes[i];
|
||||||
|
if(width > 700) {assert.equal(a.y,b.y); assert(a.right < b.x);}
|
||||||
|
else {assert.equal(a.x,b.x); assert(a.bottom < b.y);}
|
||||||
|
}
|
||||||
|
// Portal -> diagram and diagram -> portal are both part of the contract.
|
||||||
|
if (width <= 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||||
|
for (let attempt=0; attempt<3; attempt++) {
|
||||||
|
await page.getByRole('button', {name:/Switch between dark and light mode/}).filter({visible:true}).click();
|
||||||
|
if (await page.locator('html').getAttribute('data-theme') === 'dark') break;
|
||||||
|
}
|
||||||
|
if (width <= 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||||
|
await frame.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||||
|
await frame.locator('#btn-theme').click();
|
||||||
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
|
||||||
|
await frame.locator('#btn-theme').click();
|
||||||
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||||
|
await checkCards();
|
||||||
|
await frame.locator('#btn-theme').click();
|
||||||
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
|
||||||
|
for (const preset of ['blueprint','classic']) {
|
||||||
|
await frame.locator('#btn-preset').click();
|
||||||
|
await frame.locator(`[data-preset-value="${preset}"]`).click();
|
||||||
|
assert.equal(await frame.locator('html').getAttribute('data-preset'), preset);
|
||||||
|
await checkCards();
|
||||||
|
}
|
||||||
|
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(`${output}/${width}.svg`);
|
||||||
|
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Understand today'));
|
||||||
|
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('eldorado-roadmap-typography'));
|
||||||
|
for (const id of ['assessment','readiness','pilot','expansion','sustain']) {
|
||||||
|
await frame.locator(`svg[role="img"] [data-node-id="${id}"]`).screenshot({path:`${output}/${width}-${id}-card.png`});
|
||||||
|
}
|
||||||
|
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||||
|
await opener.click();
|
||||||
|
const close = panel.getByRole('button', {name:'Close full-screen diagram', exact:true});
|
||||||
|
await close.waitFor({state:'visible'}); assert.equal(await close.innerText(), '×');
|
||||||
|
await close.click(); assert(await opener.evaluate(n => n === document.activeElement));
|
||||||
|
await opener.click(); await close.waitFor({state:'visible'}); await page.keyboard.press('Escape');
|
||||||
|
await page.waitForFunction(() => !document.querySelector('.archify-panel--story dialog').open);
|
||||||
|
assert(await opener.evaluate(n => n === document.activeElement));
|
||||||
|
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
|
||||||
|
await page.evaluate(() => scrollTo({top:0, behavior:'instant'}));
|
||||||
|
await page.screenshot({path:`${output}/${width}-top.png`});
|
||||||
|
await headings.first().scrollIntoViewIfNeeded();
|
||||||
|
await page.screenshot({path:`${output}/${width}-assessment.png`});
|
||||||
|
await page.screenshot({path:`${output}/${width}-full.png`, fullPage:true});
|
||||||
|
await page.locator('.strategy-content summary').focus();
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.waitForFunction(() => document.querySelector('.strategy-content details').open);
|
||||||
|
await page.locator('.strategy-content details p').last().waitFor({state:'visible'});
|
||||||
|
assert((await page.locator('.strategy-content details').innerText()).includes('exact-artifact approvals'));
|
||||||
|
const links = await page.locator('.strategy-content a').evaluateAll(nodes => nodes.filter(n => !n.classList.contains('hash-link')).map(n => n.href));
|
||||||
|
for (const href of [...new Set(links)]) {
|
||||||
|
const r = await page.goto(href, {waitUntil:'networkidle'}); assert.equal(r.status(), 200);
|
||||||
|
assert.equal(page.url(), href);
|
||||||
|
}
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
results.push({width, geometry, links:links.length, controls:true, export:true, sidebar:true, visibleAssessment:true});
|
||||||
|
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
console.log(`PASS ${results.length} Roadmap viewports: visible stages/outputs, sidebar, diagram geometry, controls, export and links`);
|
||||||
|
} finally {await browser.close();}
|
||||||
|
})().catch(error => {console.error(error); process.exitCode = 1;});
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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/cycle-${process.argv[3] || 'local'}`;
|
||||||
|
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:1000}, colorScheme:'light'});
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto(`${base}/eldorado-v1/documentation/`, {waitUntil:'networkidle'});
|
||||||
|
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||||
|
const nav = page.getByRole('link', {name:'Strategy', exact:true}).filter({visible:true});
|
||||||
|
assert((await nav.getAttribute('href')).endsWith('/strategy/target-operating-model/'));
|
||||||
|
await nav.click();
|
||||||
|
await page.waitForURL('**/strategy/target-operating-model/');
|
||||||
|
for (const route of ['target-operating-model/', '']) {
|
||||||
|
await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}`, {waitUntil:'networkidle'});
|
||||||
|
await page.waitForURL('**/strategy/target-operating-model/');
|
||||||
|
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((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);
|
||||||
|
await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}-navigation.png`,fullPage:true,animations:'disabled'});
|
||||||
|
await menu.nth(1).click();
|
||||||
|
await page.waitForURL('**/strategy/roadmap/');
|
||||||
|
await page.getByRole('heading', {level:1,name:'Roadmap',exact:true}).waitFor();
|
||||||
|
await page.goBack({waitUntil:'domcontentloaded'});
|
||||||
|
await page.waitForURL('**/strategy/target-operating-model/');
|
||||||
|
const iframe = page.locator(`.archify-panel--story > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||||
|
await iframe.scrollIntoViewIfNeeded();
|
||||||
|
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||||
|
await frame.locator('svg[role="img"]').waitFor();
|
||||||
|
const metrics = await frame.evaluate(() => {
|
||||||
|
const svg = document.querySelector('svg[role="img"]');
|
||||||
|
const labels = [...svg.querySelectorAll('text[data-node-label]')].map(n => ({text:n.textContent,px:parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a}));
|
||||||
|
const edges = [...svg.querySelectorAll('path[data-edge-id^="stage-"]')].map(n => [n.dataset.edgeFrom,n.dataset.edgeTo,n.getAttribute('marker-end')]);
|
||||||
|
const overflow = [...svg.querySelectorAll('[data-node-id]')].flatMap(n => {
|
||||||
|
const box = n.querySelector('rect').getBoundingClientRect();
|
||||||
|
return [...n.querySelectorAll('text')].filter(t => {const b=t.getBoundingClientRect();return b.left<box.left||b.right>box.right||b.top<box.top||b.bottom>box.bottom;}).map(t=>t.textContent);
|
||||||
|
});
|
||||||
|
const relationships = [...svg.querySelectorAll('path[data-edge-id]')].map(n => ({id:n.dataset.edgeId,from:n.dataset.edgeFrom,to:n.dataset.edgeTo,dash:getComputedStyle(n).strokeDasharray,arrow:n.getAttribute('marker-end')}));
|
||||||
|
const nodes = [...svg.querySelectorAll('[data-node-id]')].map(n => {const r=n.querySelector('rect').getBoundingClientRect();return {id:n.dataset.nodeId,x:r.x+r.width/2,y:r.y+r.height/2};});
|
||||||
|
return {labels,edges,overflow,relationships,nodes};
|
||||||
|
});
|
||||||
|
const ids = ['scope','architecture','ui-ux','planning','delivery','uat','security','releases'];
|
||||||
|
assert.deepEqual(metrics.edges.map(e=>e.slice(0,2)), ids.map((id,i)=>[id,ids[(i+1)%ids.length]]));
|
||||||
|
assert(metrics.edges.every(e=>e[2]?.startsWith('url(')), 'all stages must have arrowheads');
|
||||||
|
assert.equal(metrics.nodes.length, 17);
|
||||||
|
assert.equal(metrics.labels.filter(n => n.text === 'AI Agent').length, 8);
|
||||||
|
assert(!metrics.labels.some(n => /code[ -]agent/i.test(n.text)));
|
||||||
|
assert((await page.locator('.strategy-content').innerText()).includes('human-controlled AI Agent'));
|
||||||
|
assert(!metrics.nodes.some(n=>n.id==='humans'));
|
||||||
|
assert.equal(await frame.locator('[data-node-id="agent"] text').allTextContents().then(texts=>texts.includes('Human Controlled')), true);
|
||||||
|
assert.equal(metrics.relationships.length, 24);
|
||||||
|
const governs = metrics.relationships.filter(e=>e.id.startsWith('govern-'));
|
||||||
|
const contributions = metrics.relationships.filter(e=>e.id.startsWith('contribute-'));
|
||||||
|
assert.deepEqual(governs.map(e=>[e.from,e.to]), ids.map(id=>['agent',id]));
|
||||||
|
assert.deepEqual(contributions.map(e=>[e.from,e.to]), ids.map(id=>[`code-${id}`,id]));
|
||||||
|
assert(governs.every(e=>e.dash==='3px, 9px' && e.arrow?.startsWith('url(')));
|
||||||
|
assert(contributions.every(e=>e.dash==='12px, 8px' && e.arrow?.startsWith('url(')));
|
||||||
|
assert(!metrics.relationships.some(e=>e.id==='human-direction' || e.from==='humans' || e.to==='humans'));
|
||||||
|
if (width > 700) {
|
||||||
|
const agent = metrics.nodes.find(n=>n.id==='agent');
|
||||||
|
const phases = metrics.nodes.filter(n=>ids.includes(n.id));
|
||||||
|
for (const axis of ['x','y']) assert(Math.abs(agent[axis]-(Math.min(...phases.map(n=>n[axis]))+Math.max(...phases.map(n=>n[axis])))/2)<1);
|
||||||
|
for (const id of ids) {
|
||||||
|
const phase=phases.find(n=>n.id===id), code=metrics.nodes.find(n=>n.id===`code-${id}`);
|
||||||
|
assert(Math.hypot(code.x-agent.x,code.y-agent.y)>Math.hypot(phase.x-agent.x,phase.y-agent.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(metrics.labels.every(n=>n.px>=14), JSON.stringify(metrics.labels));
|
||||||
|
assert.deepEqual(metrics.overflow, []);
|
||||||
|
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);
|
||||||
|
const exportPath = `${output}/${width}-${route ? 'target' : 'landing'}.svg`;
|
||||||
|
await download.saveAs(exportPath);
|
||||||
|
const exported = fs.readFileSync(exportPath, 'utf8');
|
||||||
|
assert.equal((exported.match(/>AI Agent<\/text>/g) || []).length, 8);
|
||||||
|
assert(!/code[ -]agent/i.test(exported));
|
||||||
|
assert(exported.includes('Autonomous agent') && exported.includes('Human Controlled'));
|
||||||
|
assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));
|
||||||
|
await page.evaluate(()=>scrollTo({top:0,behavior:'instant'}));
|
||||||
|
await page.waitForFunction(()=>scrollY===0);
|
||||||
|
await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));
|
||||||
|
await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}.png`,fullPage:true});
|
||||||
|
results.push({width,route,...metrics});
|
||||||
|
fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||||
|
}
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
assert.equal(results.length,6);
|
||||||
|
console.log('PASS 6 cycle routes/viewports: navigation, sidebar order, exact directed loop, arrowheads, >=14px labels, text bounds and no overflow');
|
||||||
|
} finally {await browser.close();}
|
||||||
|
})().catch(error=>{console.error(error);process.exitCode=1;});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
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/measurement-${process.argv[3] || 'local'}`;
|
||||||
|
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:1000}, colorScheme:'light'});
|
||||||
|
const page = await context.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', error => errors.push(error.message));
|
||||||
|
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/measurement/`, {waitUntil:'networkidle'});
|
||||||
|
assert.equal(response.status(), 200);
|
||||||
|
assert.equal(await page.locator('h1').innerText(), 'Skills Measurement');
|
||||||
|
assert((await page.title()).startsWith('Skills Measurement |'));
|
||||||
|
assert.equal(await page.locator('.strategy-content details').count(), 0);
|
||||||
|
assert.equal(await page.locator('.strategy-content h2').count(), 4);
|
||||||
|
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']);
|
||||||
|
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');
|
||||||
|
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('[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);
|
||||||
|
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();
|
||||||
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||||
|
await frame.locator('#btn-theme').click();
|
||||||
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
|
||||||
|
for (const preset of ['blueprint','classic']) {
|
||||||
|
await frame.locator('#btn-preset').click();
|
||||||
|
await frame.locator(`[data-preset-value="${preset}"]`).click();
|
||||||
|
assert.equal(await frame.locator('html').getAttribute('data-preset'), preset);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
const path = `${output}/${width}.svg`; await download.saveAs(path);
|
||||||
|
const svg = fs.readFileSync(path, 'utf8');
|
||||||
|
assert(svg.includes('Establish baseline') && svg.includes('Ongoing monitoring'));
|
||||||
|
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||||
|
await opener.click();
|
||||||
|
const close = panel.getByRole('button', {name:'Close full-screen diagram', exact:true});
|
||||||
|
await close.waitFor({state:'visible'}); assert.equal(await close.innerText(), '×');
|
||||||
|
await close.click(); assert(await opener.evaluate(n => n === document.activeElement));
|
||||||
|
await opener.click(); await close.waitFor({state:'visible'}); await close.focus(); await page.keyboard.press('Escape');
|
||||||
|
await page.waitForFunction(() => !document.querySelector('.archify-panel--story dialog').open);
|
||||||
|
assert(await opener.evaluate(n => n === document.activeElement));
|
||||||
|
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
|
||||||
|
await page.evaluate(() => scrollTo({top:0, behavior:'instant'}));
|
||||||
|
await page.screenshot({path:`${output}/${width}-top.png`});
|
||||||
|
await page.locator('.strategy-content h2').nth(1).scrollIntoViewIfNeeded();
|
||||||
|
await page.screenshot({path:`${output}/${width}-pilot.png`});
|
||||||
|
await page.screenshot({path:`${output}/${width}-full.png`, fullPage:true});
|
||||||
|
const links = await page.locator('.strategy-content a[href*="measurement-contracts"]').evaluateAll(nodes => nodes.map(n => n.href));
|
||||||
|
for (const href of [...new Set(links)]) {
|
||||||
|
const navigation = await page.goto(href, {waitUntil:'networkidle'});
|
||||||
|
if (navigation) assert.equal(navigation.status(), 200);
|
||||||
|
assert.equal(page.url(), href);
|
||||||
|
if (new URL(href).hash) await page.locator(new URL(href).hash).waitFor({state:'visible'});
|
||||||
|
}
|
||||||
|
assert.equal(await page.locator('.strategy-content h3').count(), 19);
|
||||||
|
assert((await page.locator('.strategy-content').innerText()).includes('9 prototype calculations and 7 unimplemented metrics'));
|
||||||
|
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
results.push({width, geometry, links:links.length, title:true, sidebar:true, controls:true, export:true, reference:true});
|
||||||
|
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
console.log(`PASS ${results.length} Skills Measurement viewports: title/sidebar, open plan, diagram geometry/controls/export, all reference links and no page errors`);
|
||||||
|
} finally {await browser.close();}
|
||||||
|
})().catch(error => {console.error(error); process.exitCode = 1;});
|
||||||
@@ -19,23 +19,31 @@ 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};});
|
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))};
|
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>=5 && geometry.nodes.length<=7,id);
|
assert(geometry.nodes.length>=5 && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?18:7),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}`);
|
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}));
|
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<=200,id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
assert(data.words<=(id==='measurement'?1100:id==='roadmap'?600:200),id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||||
await page.evaluate(()=>scrollTo(0,0));await page.screenshot({path:`.cache/story-${label}/${width}-${id}.png`,fullPage:true});
|
await page.evaluate(()=>scrollTo({top:0,behavior:'instant'}));await page.screenshot({path:`.cache/story-${label}/${width}-${id}.png`,fullPage:true});
|
||||||
const opener=panel.getByRole('button',{name:/^Expand /});await opener.click();
|
const opener=panel.getByRole('button',{name:/^Expand /});await opener.click();
|
||||||
const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
||||||
assert.equal(await close.innerText(),'×');assert(await close.evaluate(n=>n===document.activeElement));
|
assert.equal(await close.innerText(),'×');assert(await close.evaluate(n=>n===document.activeElement));
|
||||||
const box=await close.boundingBox();assert.equal(box.width,44);assert.equal(box.height,44);
|
const box=await close.boundingBox();assert.equal(box.width,44);assert.equal(box.height,44);
|
||||||
await close.click();assert(await opener.evaluate(n=>n===document.activeElement));
|
await close.click();await panel.locator('dialog iframe').waitFor({state:'detached'});assert(await opener.evaluate(n=>n===document.activeElement));
|
||||||
await opener.click();await close.waitFor({state:'visible'});await page.keyboard.press('Escape');await page.waitForFunction(()=>!document.querySelector('.archify-panel--story dialog').open);assert(await opener.evaluate(n=>n===document.activeElement));
|
await opener.click();await close.waitFor({state:'visible'});await page.keyboard.press('Escape');await page.waitForFunction(()=>!document.querySelector('.archify-panel--story dialog').open);
|
||||||
const summary=page.locator('.strategy-content > details > summary').first();await summary.click();assert(await summary.evaluate(n=>n.parentElement.open));await summary.click();
|
// Native close clears open before the queued React onClose restores focus.
|
||||||
results.push({id,width,...data,geometry,modalClick:true,modalEscape:true,focusRestored:true,details:true});fs.writeFileSync(`.cache/story-${label}/results.json`,JSON.stringify(results,null,2));
|
// Wait for its unmount before focusing the next keyboard interaction target.
|
||||||
|
await panel.locator('dialog iframe').waitFor({state:'detached'});assert(await opener.evaluate(n=>n===document.activeElement));
|
||||||
|
if(id !== 'measurement') {
|
||||||
|
const summary=page.locator('.strategy-content > details > summary').first();
|
||||||
|
await summary.focus();await page.keyboard.press('Enter');
|
||||||
|
await page.waitForFunction(()=>document.querySelector('.strategy-content > details').open);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
}
|
||||||
|
results.push({id,width,...data,geometry,modalClick:true,modalEscape:true,focusRestored:true,details:id!=='measurement'});fs.writeFileSync(`.cache/story-${label}/results.json`,JSON.stringify(results,null,2));
|
||||||
}
|
}
|
||||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/measurement/#validated-task-success`,{waitUntil:'networkidle'});
|
await page.goto(`${base}/eldorado-v1/documentation/strategy/reference/measurement-contracts/#validated-task-success`,{waitUntil:'networkidle'});
|
||||||
const heading=page.locator('#validated-task-success');await heading.waitFor({state:'visible'});
|
const heading=page.locator('#validated-task-success');await heading.waitFor({state:'visible'});
|
||||||
assert(await heading.evaluate(n=>n.closest('details').open),'hash should open metric reference');
|
assert.equal(await page.locator('.strategy-content details').count(),0,'reference is directly readable');
|
||||||
await context.close();
|
await context.close();
|
||||||
}
|
}
|
||||||
console.log(`PASS ${results.length} desktop/mobile cases, clipping, default word count, details, modal click/Escape/focus and KPI hash navigation`);
|
console.log(`PASS ${results.length} desktop/mobile cases, clipping, default word count, details, modal click/Escape/focus and KPI hash navigation`);
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
const {chromium} = require('playwright');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const {pathToFileURL} = require('node:url');
|
||||||
|
const {resolve} = require('node:path');
|
||||||
|
|
||||||
|
// Inspect the rendered paths, not just the authored JSON. Also runs on downloaded SVGs.
|
||||||
|
function inspect({mobile}) {
|
||||||
|
const svg = document.querySelector('svg[role="img"]');
|
||||||
|
const point = (element, p) => new DOMPoint(p.x, p.y).matrixTransform(element.getCTM());
|
||||||
|
const boxes = [...svg.querySelectorAll('[data-node-id]')].map(node => {
|
||||||
|
const rect = node.querySelector('rect'), box = rect.getBBox();
|
||||||
|
const a = point(rect, box), b = point(rect, {x:box.x + box.width, y:box.y + box.height});
|
||||||
|
return {id:node.dataset.nodeId, x:a.x, y:a.y, right:b.x, bottom:b.y, cx:(a.x+b.x)/2, cy:(a.y+b.y)/2};
|
||||||
|
});
|
||||||
|
const agent = boxes.find(n => n.id === 'agent');
|
||||||
|
const errors = [], endpoints = {};
|
||||||
|
const phases = ['scope','architecture','ui-ux','planning','delivery','uat','security','releases'];
|
||||||
|
const edges = [...svg.querySelectorAll('path[data-edge-id]')];
|
||||||
|
if (edges.length !== 24) errors.push(`expected 24 graph edges, got ${edges.length}`);
|
||||||
|
phases.forEach((phase, index) => {
|
||||||
|
for (const [id, from, to] of [
|
||||||
|
[`govern-${phase}`,'agent',phase],
|
||||||
|
[`stage-${index+1}`,phase,phases[(index+1)%phases.length]],
|
||||||
|
[`contribute-${phase}`,`code-${phase}`,phase],
|
||||||
|
]) {
|
||||||
|
const matches = edges.filter(p => p.dataset.edgeId === id);
|
||||||
|
if (matches.length !== 1 || matches[0].dataset.edgeFrom !== from || matches[0].dataset.edgeTo !== to || !matches[0].getAttribute('marker-end') || matches[0].getAttribute('marker-start')) errors.push(`${id}: graph or direction changed`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// CTMs put boxes and paths in the same viewport coordinate system.
|
||||||
|
// 0.01 covers floating-point error against bounds, NOT source spreading:
|
||||||
|
// shared endpoints below must be exactly identical.
|
||||||
|
const paths = [...svg.querySelectorAll('path[data-edge-id^="govern-"]')];
|
||||||
|
if (paths.length !== 8) errors.push(`expected eight governance paths, got ${paths.length}`);
|
||||||
|
for (const path of paths) {
|
||||||
|
const id = path.dataset.edgeTo;
|
||||||
|
const side = mobile ? 'left' : ['scope','architecture','releases'].includes(id) ? 'top'
|
||||||
|
: ['planning','delivery','uat'].includes(id) ? 'bottom' : id === 'security' ? 'left' : 'right';
|
||||||
|
const expected = {top:[agent.cx,agent.y],bottom:[agent.cx,agent.bottom],left:[agent.x,agent.cy],right:[agent.right,agent.cy]}[side];
|
||||||
|
const start = point(path,path.getPointAtLength(0));
|
||||||
|
endpoints[id] = [start.x,start.y];
|
||||||
|
if (Math.hypot(start.x-expected[0],start.y-expected[1]) > 0.01) errors.push(`${id}: wrong ${side} midpoint`);
|
||||||
|
const length = path.getTotalLength();
|
||||||
|
const target = boxes.find(n => n.id === id);
|
||||||
|
const destination = mobile ? [target.x,target.cy]
|
||||||
|
: side === 'top' ? [target.cx,target.bottom] : side === 'bottom' ? [target.cx,target.y]
|
||||||
|
: side === 'left' ? [target.right,target.cy] : [target.x,target.cy];
|
||||||
|
const end = point(path,path.getPointAtLength(length));
|
||||||
|
if (Math.hypot(end.x-destination[0],end.y-destination[1]) > 0.01) errors.push(`${id}: destination midpoint changed`);
|
||||||
|
for (let distance = 1; distance < length; distance += 2) {
|
||||||
|
const p = point(path,path.getPointAtLength(distance));
|
||||||
|
for (const box of boxes) {
|
||||||
|
if (box.id === id) continue;
|
||||||
|
if (p.x > box.x+0.1 && p.x < box.right-0.1 && p.y > box.y+0.1 && p.y < box.bottom-0.1) {
|
||||||
|
errors.push(`${id}: crosses ${box.id}`); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const group of mobile ? [phases] : [['scope','architecture','releases'],['planning','delivery','uat']]) {
|
||||||
|
const first = endpoints[group[0]];
|
||||||
|
if (group.some(id => !endpoints[id] || endpoints[id][0] !== first?.[0] || endpoints[id][1] !== first?.[1])) errors.push(`source-port spreading: ${group.join(',')}`);
|
||||||
|
}
|
||||||
|
const lifecycle = [];
|
||||||
|
if (!mobile) {
|
||||||
|
for (const [id, x, y1, y2, peer] of [
|
||||||
|
['stage-2',850,262.5,457.5,'architecture'], ['stage-3',850,532.5,727.5,'planning'],
|
||||||
|
['stage-6',350,727.5,532.5,'uat'], ['stage-7',350,457.5,262.5,'releases'],
|
||||||
|
]) {
|
||||||
|
const path = svg.querySelector(`path[data-edge-id="${id}"]`);
|
||||||
|
if (!path) {errors.push(`${id}: missing lifecycle path`); continue;}
|
||||||
|
const length = path.getTotalLength();
|
||||||
|
const a = path.getPointAtLength(0), b = path.getPointAtLength(length);
|
||||||
|
// Local SVG coordinates test exact offsets; transformed coordinates test
|
||||||
|
// node clearance at any inline/fullscreen/export scale.
|
||||||
|
if (Math.hypot(a.x-x,a.y-y1) > 0.01 || Math.hypot(b.x-x,b.y-y2) > 0.01) errors.push(`${id}: wrong shifted endpoints`);
|
||||||
|
if (!path.getAttribute('marker-end')) errors.push(`${id}: missing direction`);
|
||||||
|
const governance = paths.find(p => p.dataset.edgeTo === peer);
|
||||||
|
if (!governance) {errors.push(`${id}: missing governance peer ${peer}`); continue;}
|
||||||
|
const end = governance.getPointAtLength(governance.getTotalLength());
|
||||||
|
if (Math.abs(Math.abs(a.x-end.x)-20) > 0.01) errors.push(`${id}: wrong attachment clearance`);
|
||||||
|
for (let distance = 1; distance < length; distance += 2) {
|
||||||
|
const local = path.getPointAtLength(distance), p = point(path,local);
|
||||||
|
if (Math.abs(local.x-x) > 0.01) errors.push(`${id}: channel not straight`);
|
||||||
|
for (const box of boxes) {
|
||||||
|
if (p.x > box.x+0.1 && p.x < box.right-0.1 && p.y > box.y+0.1 && p.y < box.bottom-0.1) errors.push(`${id}: crosses ${box.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No positive-length collinear overlap with ANY governance segment.
|
||||||
|
// Perpendicular crossings remain legitimate in this fixed radial layout.
|
||||||
|
for (const govern of paths) {
|
||||||
|
const total = govern.getTotalLength();
|
||||||
|
for (let d = 0; d < total - 1; d += 1) {
|
||||||
|
const p = govern.getPointAtLength(d), q = govern.getPointAtLength(d+1);
|
||||||
|
if (Math.abs(p.x-x) < 0.01 && Math.abs(q.x-x) < 0.01 && Math.min(Math.max(p.y,q.y),Math.max(y1,y2)) - Math.max(Math.min(p.y,q.y),Math.min(y1,y2)) > 0.01) errors.push(`${id}: overlaps ${govern.dataset.edgeId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycle.push({id,start:[a.x,a.y],end:[b.x,b.y],attachmentClearance:Math.abs(a.x-end.x)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {errors:[...new Set(errors)],endpoints,lifecycle};
|
||||||
|
}
|
||||||
|
|
||||||
|
function inspectTypography({mobile}) {
|
||||||
|
const errors = [];
|
||||||
|
const nodes = [...document.querySelectorAll('[data-node-id]')].map(node => {
|
||||||
|
const box = node.querySelector('rect').getBoundingClientRect();
|
||||||
|
const texts = [...node.querySelectorAll(':scope > text')];
|
||||||
|
const bounds = texts.map(text => text.getBoundingClientRect());
|
||||||
|
texts.forEach((text, index) => {
|
||||||
|
const expected = text.hasAttribute('data-node-label') ? (mobile ? 38 : 27) : (mobile ? 28 : 21);
|
||||||
|
if (parseFloat(getComputedStyle(text).fontSize) !== expected) errors.push(`${node.id}: wrong text size`);
|
||||||
|
const b = bounds[index];
|
||||||
|
if (b.width <= 0 || b.height <= 0 || b.x < box.x - 1 || b.right > box.right + 1 || b.y < box.y - 1 || b.bottom > box.bottom + 1) errors.push(`${node.id}: text outside node`);
|
||||||
|
});
|
||||||
|
if (bounds.length === 2 && bounds[0].bottom > bounds[1].top) errors.push(`${node.id}: title/subtitle overlap`);
|
||||||
|
return {id:node.id, text:texts.map(t => t.textContent), sizes:texts.map(t => getComputedStyle(t).fontSize)};
|
||||||
|
});
|
||||||
|
if (nodes.length !== 17) errors.push(`expected 17 nodes, got ${nodes.length}`);
|
||||||
|
return {errors, nodes};
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const base = process.argv[2] || 'http://127.0.0.1:4173';
|
||||||
|
const output = `.cache/midpoints-${process.argv[3] || 'local'}`;
|
||||||
|
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 page = await browser.newPage({viewport:{width,height:1000}});
|
||||||
|
await page.goto(`${base}/eldorado-v1/documentation/strategy/target-operating-model/`,{waitUntil:'networkidle'});
|
||||||
|
const panel = page.locator('[data-diagram="strategy-target"]');
|
||||||
|
const iframe = panel.locator(`:scope > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||||
|
await iframe.scrollIntoViewIfNeeded();
|
||||||
|
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||||
|
await frame.locator('svg[role="img"]').waitFor();
|
||||||
|
const inline = await frame.evaluate(inspect,{mobile:width < 700});
|
||||||
|
assert.deepEqual(inline.errors,[]);
|
||||||
|
await page.screenshot({path:`${output}/${width}-inline.png`,fullPage:true});
|
||||||
|
const exported = [];
|
||||||
|
for (const theme of ['light','dark']) {
|
||||||
|
if (await frame.locator('html').getAttribute('data-theme') !== theme) await frame.locator('#btn-theme').click();
|
||||||
|
await frame.waitForFunction(t => document.documentElement.dataset.theme === t, theme);
|
||||||
|
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);
|
||||||
|
const path = `${output}/${width}-${theme}.svg`;
|
||||||
|
await download.saveAs(path);
|
||||||
|
// A separate SVG document has no viewer CSS to hide export regressions.
|
||||||
|
const standalone = await browser.newPage({viewport:{width:1440,height:1100},colorScheme:theme});
|
||||||
|
await standalone.goto(pathToFileURL(resolve(path)).href);
|
||||||
|
assert.equal(await standalone.evaluate(() => document.contentType),'image/svg+xml');
|
||||||
|
await standalone.evaluate(() => document.fonts.ready);
|
||||||
|
const geometry = await standalone.evaluate(inspect,{mobile:width < 700});
|
||||||
|
const typography = await standalone.evaluate(inspectTypography,{mobile:width < 700});
|
||||||
|
assert.deepEqual(geometry.errors,[]);
|
||||||
|
assert.deepEqual(typography.errors,[]);
|
||||||
|
const background = await standalone.locator('rect.c-bg-rect').evaluate(n => getComputedStyle(n).fill);
|
||||||
|
if (exported.length) assert.notEqual(background,exported[0].background,'standalone theme must change');
|
||||||
|
// CDP captures SVG documents without Playwright's HTML-body screenshot wait.
|
||||||
|
const client = await standalone.context().newCDPSession(standalone);
|
||||||
|
const size = await standalone.evaluate(() => ({width:document.documentElement.width.baseVal.value,height:document.documentElement.height.baseVal.value}));
|
||||||
|
const image = await client.send('Page.captureScreenshot',{format:'png',captureBeyondViewport:true,clip:{x:0,y:0,...size,scale:1}});
|
||||||
|
fs.writeFileSync(`${output}/${width}-${theme}-standalone.png`,Buffer.from(image.data,'base64'));
|
||||||
|
exported.push({theme,background,geometry,typography});
|
||||||
|
await standalone.close();
|
||||||
|
}
|
||||||
|
await panel.getByRole('button',{name:/^Expand /}).click();
|
||||||
|
const full = await (await panel.locator('dialog iframe').elementHandle()).contentFrame();
|
||||||
|
await full.locator('svg[role="img"]').waitFor();
|
||||||
|
// The existing fullscreen viewer always opens the radial desktop source.
|
||||||
|
const fullscreen = await full.evaluate(inspect,{mobile:false});
|
||||||
|
assert.deepEqual(fullscreen.errors,[]);
|
||||||
|
await page.screenshot({path:`${output}/${width}-fullscreen.png`});
|
||||||
|
await panel.getByRole('button',{name:'Close full-screen diagram'}).click();
|
||||||
|
results.push({width,inline,exported,fullscreen});
|
||||||
|
fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
assert.equal(results.length,3);
|
||||||
|
console.log('PASS midpoint geometry: 3 viewports × inline/fullscreen; standalone SVG geometry and typography in light/dark');
|
||||||
|
} finally {await browser.close();}
|
||||||
|
})().catch(error => {console.error(error);process.exitCode=1;});
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
|
import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
|
||||||
import {join} from 'node:path';
|
import {join} from 'node:path';
|
||||||
import {execFileSync} from 'node:child_process';
|
import {execFileSync} from 'node:child_process';
|
||||||
|
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||||
|
import {targetRouting} from './target-routing.mjs';
|
||||||
|
import {createHash} from 'node:crypto';
|
||||||
const cli = 'vendor/archify/bin/archify.mjs';
|
const cli = 'vendor/archify/bin/archify.mjs';
|
||||||
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
|
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
|
||||||
const inlineStyles = readFileSync(new URL('./inline-viewer.css', import.meta.url), 'utf8');
|
const inlineStyles = readFileSync(new URL('./inline-viewer.css', import.meta.url), 'utf8');
|
||||||
|
const cycleStyles = readFileSync(new URL('./cycle-viewer.css', import.meta.url), 'utf8');
|
||||||
const themeBridge = readFileSync(new URL('./viewer-theme-bridge.js', import.meta.url), 'utf8');
|
const themeBridge = readFileSync(new URL('./viewer-theme-bridge.js', import.meta.url), 'utf8');
|
||||||
// This directory is generated-only and ignored by Git. Remove stale artifacts.
|
// This directory is generated-only and ignored by Git. Remove stale artifacts.
|
||||||
rmSync('static/diagrams', {recursive: true, force: true});
|
rmSync('static/diagrams', {recursive: true, force: true});
|
||||||
@@ -22,11 +26,21 @@ for (const file of readdirSync('diagrams').sort()) {
|
|||||||
mkdirSync(`static/diagrams/${name}`, {recursive: true});
|
mkdirSync(`static/diagrams/${name}`, {recursive: true});
|
||||||
if (spec.diagram_type !== type || spec.meta.output !== output) throw new Error(`Diagram type/output mismatch: ${file}`);
|
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 run = args => execFileSync(process.execPath,[cli,...args],{stdio:'inherit',env:{...process.env,ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||||
run(['validate',type,input,'--quality','showcase','--json']);
|
const roadmap = ['strategy-roadmap', 'strategy-roadmap-mobile'].includes(name);
|
||||||
run(['deliver',type,input,output,'--quality','showcase','--json']);
|
if (roadmap) {
|
||||||
run(['check',output,'--json','--require-provenance']);
|
// 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));
|
||||||
|
run(['check',output,'--json']);
|
||||||
|
} else {
|
||||||
|
run(['validate',type,input,'--quality','showcase','--json']);
|
||||||
|
run(['deliver',type,input,output,'--quality','showcase','--json']);
|
||||||
|
run(['check',output,'--json','--require-provenance']);
|
||||||
|
}
|
||||||
// Verify the upstream artifact first, then add the scoped portal integration.
|
// Verify the upstream artifact first, then add the scoped portal integration.
|
||||||
let viewer = readFileSync(output, 'utf8');
|
let viewer = readFileSync(output, 'utf8');
|
||||||
|
if (name === 'strategy-target') viewer = targetRouting(viewer);
|
||||||
// Upstream intentionally disables preset selection in gallery embeds. Enable
|
// Upstream intentionally disables preset selection in gallery embeds. Enable
|
||||||
// only those two guards; fail closed if a future vendor update changes them.
|
// only those two guards; fail closed if a future vendor update changes them.
|
||||||
const start = viewer.indexOf('Archify.preset = (function () {');
|
const start = viewer.indexOf('Archify.preset = (function () {');
|
||||||
@@ -36,9 +50,34 @@ for (const file of readdirSync('diagrams').sort()) {
|
|||||||
if (start < 0 || end < start || preset.split(guard).length !== 3) throw new Error('Archify preset integration drift');
|
if (start < 0 || end < start || preset.split(guard).length !== 3) throw new Error('Archify preset integration drift');
|
||||||
viewer = viewer.slice(0, start) + preset.replaceAll(guard, '') + viewer.slice(end);
|
viewer = viewer.slice(0, start) + preset.replaceAll(guard, '') + viewer.slice(end);
|
||||||
if (!viewer.includes('</head>') || !viewer.includes('</body>')) throw new Error(`Invalid viewer: ${output}`);
|
if (!viewer.includes('</head>') || !viewer.includes('</body>')) throw new Error(`Invalid viewer: ${output}`);
|
||||||
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style></head>`)
|
const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name)
|
||||||
|
? `${name === 'strategy-target' ? ':root {--cycle-label: 27px; --cycle-detail: 21px;}' : ':root {--cycle-label: 38px; --cycle-detail: 28px;}'}${cycleStyles}` : '';
|
||||||
|
if (storyStyles) {
|
||||||
|
// Keep typography and relationship styles inside exported standalone SVGs.
|
||||||
|
const diagramRoot = /<svg\b[^>]*role="img"[^>]*>/g;
|
||||||
|
if ([...viewer.matchAll(diagramRoot)].length !== 1) throw new Error('Cycle SVG integration drift');
|
||||||
|
const exportStyles = storyStyles.replaceAll(':root', 'svg[role="img"]').replaceAll('.diagram-container > svg', 'svg[role="img"]');
|
||||||
|
viewer = viewer.replace(diagramRoot, root => `${root}<style id="eldorado-cycle-export">${exportStyles}</style>`);
|
||||||
|
}
|
||||||
|
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>`));
|
.replace('</body>', `<script>${themeBridge}</script></body>`));
|
||||||
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
if (roadmap) {
|
||||||
|
// 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));
|
||||||
|
} else {
|
||||||
|
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
||||||
|
if (name === 'strategy-target') {
|
||||||
|
// The native receipt describes the PRE-integration artifact only.
|
||||||
|
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('Target final artifact failed');
|
||||||
|
writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash('scripts/target-routing.mjs'), artifactSha256:hash(output), nativeReceiptScope:'before portal integration', checker:checked}, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
copyFileSync('vendor/archify/LICENSE','static/diagrams/ARCHIFY-LICENSE.txt');
|
copyFileSync('vendor/archify/LICENSE','static/diagrams/ARCHIFY-LICENSE.txt');
|
||||||
copyFileSync('vendor/archify/THIRD_PARTY_NOTICES.md','static/diagrams/THIRD-PARTY-NOTICES.md');
|
copyFileSync('vendor/archify/THIRD_PARTY_NOTICES.md','static/diagrams/THIRD-PARTY-NOTICES.md');
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/* Readable lifecycle story at inline scale, isolated from other diagrams. */
|
||||||
|
.diagram-container > svg text {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
letter-spacing: 0;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.diagram-container > svg text[data-node-label] {
|
||||||
|
font-size: var(--cycle-label);
|
||||||
|
transform: translateY(-6px);
|
||||||
|
}
|
||||||
|
/* Export strips data-detail; node ownership and the muted class survive. */
|
||||||
|
.diagram-container > svg [data-node-id] > text.t-muted {
|
||||||
|
font-size: var(--cycle-detail);
|
||||||
|
transform: translateY(16px);
|
||||||
|
}
|
||||||
|
/* Edge semantics stay in SVG metadata; one page legend avoids repeated labels. */
|
||||||
|
.diagram-container > svg g[data-edge-id] {display: none;}
|
||||||
|
.diagram-container > svg path[data-edge-id^="stage-"] {stroke-width: 2.5;}
|
||||||
|
.diagram-container > svg path[data-edge-id^="govern-"] {stroke-dasharray: 3 9; stroke-width: 3;}
|
||||||
|
.diagram-container > svg path[data-edge-id^="contribute-"] {stroke-dasharray: 12 8; stroke-width: 3;}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Native Workflow v2 owns node geometry, mainPath and routing. The portal adds
|
||||||
|
// multiline typography: upstream's three single-line labels are too small for
|
||||||
|
// a five-stage reading view. Do not change the pinned vendor or graph semantics.
|
||||||
|
const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
||||||
|
const wrap = (text, limit = 21) => {
|
||||||
|
const lines = [];
|
||||||
|
for (const word of text.split(/\s+/)) {
|
||||||
|
if (!lines.length || `${lines.at(-1)} ${word}`.length > limit) lines.push(word);
|
||||||
|
else lines[lines.length - 1] += ` ${word}`;
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
};
|
||||||
|
export function roadmapViewer(html, spec) {
|
||||||
|
if (spec.diagram_type !== 'workflow' || spec.schema_version !== 2 || spec.nodes.length !== 5) throw new Error('Roadmap workflow contract drift');
|
||||||
|
const boxes = [];
|
||||||
|
for (const [index, node] of spec.nodes.entries()) {
|
||||||
|
const start = html.indexOf(`<g id="node-${node.id}"`);
|
||||||
|
const next = html.indexOf('<text data-node-label=', start);
|
||||||
|
const end = html.indexOf('</g>', next) + 4;
|
||||||
|
if (start < 0 || next < start || end < next) throw new Error(`Roadmap node drift: ${node.id}`);
|
||||||
|
const original = html.slice(start, end);
|
||||||
|
const rect = /<rect x="([\d.-]+)" y="([\d.-]+)" width="([\d.]+)" height="([\d.]+)"/.exec(original);
|
||||||
|
if (!rect || (original.match(/<text /g) || []).length !== 3) throw new Error(`Roadmap text contract drift: ${node.id}`);
|
||||||
|
const [x,y,w,h] = rect.slice(1).map(Number);
|
||||||
|
boxes.push({x,y,w,h});
|
||||||
|
const text = (value, top, size, cls, attrs = 'data-detail="context"') => {
|
||||||
|
const lines = wrap(value, size === 30 ? 18 : 21);
|
||||||
|
const maxLines = top === 166 || top === 310 ? 3 : 2;
|
||||||
|
if (lines.length > maxLines || lines.some(line => line.length > 21)) throw new Error(`Roadmap copy exceeds card capacity: ${node.id}`);
|
||||||
|
return `<text x="${x+24}" y="${y+top}" font-size="${size}" class="${cls}" ${attrs}>${lines.map((line,i) => `<tspan x="${x+24}" dy="${i ? 34 : 0}">${escape(line)}</tspan>`).join('')}</text>`;
|
||||||
|
};
|
||||||
|
const [action, output] = node.sublabel.split('\nOutput: ');
|
||||||
|
if (!action || !output || !node.tag.startsWith('Exit: ')) throw new Error(`Roadmap content contract drift: ${node.id}`);
|
||||||
|
const content = text(String(index+1).padStart(2,'0'), 40, 26, 't-muted')
|
||||||
|
+ text(node.label, 88, 30, 't-primary', 'data-node-label="" font-weight="700"')
|
||||||
|
+ text(action, 166, 26, 't-primary')
|
||||||
|
+ text('OUTPUT', 274, 20, 't-muted', 'font-weight="700"')
|
||||||
|
+ text(output, 310, 26, 't-primary')
|
||||||
|
+ text(node.tag, 416, 24, 't-primary');
|
||||||
|
// Preserve native node title/metadata and rectangles; replace sigil/text only.
|
||||||
|
// Sigils are nested SVG groups. Anchor on the two native node rectangles.
|
||||||
|
const rectangles = [...original.matchAll(/<rect\b[^>]*\/>/g)];
|
||||||
|
if (rectangles.length !== 2) throw new Error('Roadmap shape contract drift');
|
||||||
|
const prefixEnd = rectangles[1].index + rectangles[1][0].length;
|
||||||
|
const replacement = original.slice(0,prefixEnd).replaceAll('rx="6"', 'rx="0"') + content + '</g>';
|
||||||
|
html = html.slice(0,start) + replacement + html.slice(end);
|
||||||
|
}
|
||||||
|
const left = Math.min(...boxes.map(b => b.x)) - 12;
|
||||||
|
const top = Math.min(...boxes.map(b => b.y)) - 12;
|
||||||
|
const width = Math.max(...boxes.map(b => b.x+b.w)) - left + 12;
|
||||||
|
const height = Math.max(...boxes.map(b => b.y+b.h)) - top + 12;
|
||||||
|
// One editorial lane; its title/frame would repeat the page's framing.
|
||||||
|
html = html.replace(/<rect\b[^>]*data-composition-frame-kind="lane"[^>]*\/>\s*<text\b[^>]*>[\s\S]*?<\/text>/g, '');
|
||||||
|
const roots = [...html.matchAll(/<svg\b[^>]*role="img"[^>]*>/g)];
|
||||||
|
if (roots.length !== 1) throw new Error('Roadmap SVG contract drift');
|
||||||
|
const root = roots[0][0].replace(/viewBox="[^"]*"/, `viewBox="${left} ${top} ${width} ${height}"`);
|
||||||
|
const styles = `<style id="eldorado-roadmap-typography">
|
||||||
|
[data-node-id] text {font-family: Arial, sans-serif;}
|
||||||
|
svg[data-preset="classic"] [data-node-id] > rect.c-backend,
|
||||||
|
svg[data-preset="editorial"] [data-node-id] > rect.c-backend {fill:var(--panel); stroke:var(--border);}
|
||||||
|
svg[data-preset="classic"] .c-grid {opacity:0;}
|
||||||
|
</style>`;
|
||||||
|
html = html.replace(roots[0][0], root + styles);
|
||||||
|
return html;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {readFileSync, writeFileSync, mkdtempSync, rmSync, mkdirSync} from 'node:fs';
|
||||||
|
import {execFileSync} from 'node:child_process';
|
||||||
|
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||||
|
|
||||||
|
for (const suffix of ['', '-mobile']) test(`native Workflow${suffix} retains graph and passes final composition`, () => {
|
||||||
|
mkdirSync('.cache', {recursive:true});
|
||||||
|
const dir = mkdtempSync('.cache/roadmap-test-');
|
||||||
|
try {
|
||||||
|
const source = `diagrams/strategy-roadmap${suffix}.workflow.json`;
|
||||||
|
const spec = JSON.parse(readFileSync(source, 'utf8'));
|
||||||
|
const output = `${dir}/index.html`;
|
||||||
|
execFileSync(process.execPath, ['vendor/archify/renderers/workflow/render-workflow.mjs', source, output], {env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||||
|
const native = readFileSync(output, 'utf8');
|
||||||
|
const styled = roadmapViewer(native, spec);
|
||||||
|
const graph = html => [...html.matchAll(/<path\b[^>]*data-edge-id="[^"]+"[^>]*>/g)].map(m => m[0]);
|
||||||
|
assert.equal(graph(native).length, 4);
|
||||||
|
assert.deepEqual(graph(styled), graph(native), 'native paths must not be rewritten');
|
||||||
|
for (const node of spec.nodes) assert(styled.includes(`data-node-id="${node.id}"`));
|
||||||
|
assert.equal((styled.match(/<text data-node-label=/g) || []).length, 0, 'single-line preview replaced');
|
||||||
|
assert(styled.includes('eldorado-roadmap-typography'));
|
||||||
|
writeFileSync(output, styled);
|
||||||
|
const checked = JSON.parse(execFileSync(process.execPath, ['vendor/archify/bin/archify.mjs','check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}));
|
||||||
|
assert(checked.ok);
|
||||||
|
assert.equal(checked.composition.status, 'pass');
|
||||||
|
assert.equal(checked.composition.metrics.properCrossings, 0);
|
||||||
|
assert.equal(checked.composition.metrics.labelCanvasOverflowIssues, 0);
|
||||||
|
assert.throws(() => roadmapViewer('', spec), /drift/);
|
||||||
|
const oversized = structuredClone(spec);
|
||||||
|
oversized.nodes[0].sublabel = 'Too much copy '.repeat(20) + '\nOutput: baseline';
|
||||||
|
assert.throws(() => roadmapViewer(native, oversized), /capacity/);
|
||||||
|
assert.throws(() => roadmapViewer(native, {...spec,diagram_type:'architecture'}), /contract drift/);
|
||||||
|
} finally {rmSync(dir, {recursive:true, force:true});}
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
const read = path => readFileSync(path, 'utf8');
|
||||||
|
|
||||||
|
test('Roadmap visibly assesses code and real delivery before adoption', () => {
|
||||||
|
const page = read('docs/strategy/roadmap.mdx');
|
||||||
|
const visible = page.split('<details>')[0];
|
||||||
|
const headings = [...visible.matchAll(/^## (.+)$/gm)].map(match => match[1]);
|
||||||
|
assert.deepEqual(headings, ['1. Understand code quality and delivery', '2. Fix critical readiness gaps', '3. Run a bounded pilot', '4. Expand only on evidence', '5. Sustain and improve']);
|
||||||
|
assert(visible.indexOf(' story />') < visible.indexOf('## 1.'));
|
||||||
|
for (const marker of ['representative repositories', 'tests and CI', 'maintainability', 'technical debt', 'security findings', 'actual SDLC', 'ownership', 'review and release', 'bottlenecks', 'baseline findings', 'process map', 'prioritized gaps', 'alongside the pilot', './measurement.mdx', './target-operating-model.mdx']) assert(visible.includes(marker), marker);
|
||||||
|
assert.equal((visible.match(/\*\*Output:/g) || []).length, 5);
|
||||||
|
assert.match(visible, /Recommended sequence/);
|
||||||
|
assert(!page.includes('### Foundation'));
|
||||||
|
for (const marker of ['revocation', 'exact-artifact approvals', 'idempotency', 'human takeover', 'sensitive decisions']) assert(page.includes(marker), marker);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('both roadmap diagrams make assessment and readiness prerequisites to the pilot', () => {
|
||||||
|
const ids = ['assessment', 'readiness', 'pilot', 'expansion', 'sustain'];
|
||||||
|
for (const suffix of ['', '-mobile']) {
|
||||||
|
const diagram = JSON.parse(read(`diagrams/strategy-roadmap${suffix}.workflow.json`));
|
||||||
|
assert.equal(diagram.schema_version, 2);
|
||||||
|
assert.equal(diagram.diagram_type, 'workflow');
|
||||||
|
assert.deepEqual(diagram.mainPath, ids);
|
||||||
|
assert.deepEqual(diagram.nodes.map(n => n.id), ids);
|
||||||
|
assert.deepEqual(diagram.nodes.map(n => n.label), ['Understand today', 'Prepare foundations', 'Pilot', 'Expand', 'Sustain']);
|
||||||
|
assert.deepEqual(diagram.edges.map(e => [e.from,e.to]), ids.slice(1).map((id, i) => [ids[i],id]));
|
||||||
|
assert.match(diagram.nodes[0].sublabel, /code quality and real delivery/);
|
||||||
|
assert.match(diagram.nodes[0].sublabel, /baseline, process map, prioritized gaps/);
|
||||||
|
assert.match(diagram.nodes[1].tag, /Critical gaps closed/);
|
||||||
|
assert(diagram.nodes.every(n => n.sublabel.includes('Output:') && n.tag.startsWith('Exit:')));
|
||||||
|
assert.deepEqual(diagram.nodes.map(n => n.col), suffix ? [0,0,0,0,0] : [0,1,2,3,4]);
|
||||||
|
assert.equal(diagram.lanes.length, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
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('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||||
|
for (const id of ['target-operating-model']) {
|
||||||
|
const page = read(`docs/strategy/${id}.mdx`);
|
||||||
|
const primary = page.split('<details>')[0];
|
||||||
|
assert(primary.includes('name="strategy-target"'));
|
||||||
|
assert(primary.includes('autonomous agent'));
|
||||||
|
assert(primary.includes('direct, guide and review'));
|
||||||
|
assert(primary.includes('Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope'));
|
||||||
|
assert(primary.includes('human-controlled AI Agent'));
|
||||||
|
assert(!/code[ -]agent/i.test(page));
|
||||||
|
assert(primary.includes('TO-BE'));
|
||||||
|
assert(!/only routes|People authorize|independent gates protect release/.test(primary));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('both target diagrams have eight stages, eight governed phases and eight human-controlled contributors', () => {
|
||||||
|
for (const suffix of ['', '-mobile']) {
|
||||||
|
const d = JSON.parse(read(`diagrams/strategy-target${suffix}.architecture.json`));
|
||||||
|
const ids = stages.map(label => d.components.find(n => n.label === label)?.id);
|
||||||
|
assert(ids.every(Boolean));
|
||||||
|
assert.equal(new Set(ids).size, 8);
|
||||||
|
const flow = d.connections.filter(e => ids.includes(e.from) && ids.includes(e.to));
|
||||||
|
assert.deepEqual(flow.map(e => [e.from, e.to]), ids.map((id, i) => [id, ids[(i + 1) % ids.length]]));
|
||||||
|
assert.match(flow.at(-1).label, /Repeat/);
|
||||||
|
|
||||||
|
assert(d.components.some(n => n.id === 'agent' && n.label === 'Autonomous agent' && n.sublabel === 'Human Controlled'));
|
||||||
|
assert(!d.components.some(n => n.id === 'humans' || n.label === 'Humans'));
|
||||||
|
assert(!d.connections.some(e => e.id === 'human-direction' || e.from === 'humans' || e.to === 'humans'));
|
||||||
|
assert.equal(d.connections.length, 24);
|
||||||
|
assert.deepEqual(d.connections.filter(e => e.from === 'agent').map(e => e.to), ids);
|
||||||
|
const contributors = d.components.filter(n => n.id.startsWith('code-'));
|
||||||
|
assert.equal(contributors.length, 8);
|
||||||
|
assert(contributors.every(n => n.label === 'AI Agent' && n.sublabel === 'Human-controlled'));
|
||||||
|
for (const id of ids) {
|
||||||
|
assert(d.connections.some(e => e.from === `code-${id}` && e.to === id && e.variant === 'dashed'));
|
||||||
|
assert(d.connections.some(e => e.from === 'agent' && e.to === id && e.variant === 'security'));
|
||||||
|
}
|
||||||
|
assert.equal(d.components.length, 17);
|
||||||
|
if (!suffix) {
|
||||||
|
const center = n => n.pos.map((v, i) => v + n.size[i] / 2);
|
||||||
|
const agent = center(d.components.find(n => n.id === 'agent'));
|
||||||
|
const phaseCenters = ids.map(id => center(d.components.find(n => n.id === id)));
|
||||||
|
for (const axis of [0, 1]) {
|
||||||
|
const values = phaseCenters.map(c => c[axis]);
|
||||||
|
assert.equal(agent[axis], (Math.min(...values) + Math.max(...values)) / 2);
|
||||||
|
}
|
||||||
|
for (const code of contributors) {
|
||||||
|
const phase = d.components.find(n => n.id === code.id.slice(5));
|
||||||
|
const distance = n => Math.hypot(...center(n).map((v, i) => v - agent[i]));
|
||||||
|
assert(distance(code) > distance(phase), `${code.id} is exterior`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
import {calculate} from './ai-kpi.mjs';
|
||||||
|
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/);
|
||||||
|
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'));
|
||||||
|
assert(!page.includes('Is AI-assisted work worth continuing?'));
|
||||||
|
for (const word of ['Recommended', 'not installed', 'unknown', 'review', 'CI', 'revision', 'Monthly', 'Weekly']) assert(page.includes(word), word);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('all sixteen metric definitions remain in the reference with explicit implementation limits', () => {
|
||||||
|
const page = read('docs/strategy/reference/measurement-contracts.mdx');
|
||||||
|
const model = JSON.parse(read('research/ai-adoption/strategy-model.json'));
|
||||||
|
for (const k of model.kpis) {
|
||||||
|
const section = page.split(`### ${k.name}\n`)[1]?.split('\n### ')[0];
|
||||||
|
assert(section, k.name);
|
||||||
|
for (const field of ['Numerator','Denominator','Expression','Aggregation','Unit','Eligible population','Exclusions','Events and source','Owner / cadence','Missing evidence','Gaming and limits']) assert(section.includes(`| ${field} |`));
|
||||||
|
}
|
||||||
|
for (const text of ['9 prototype calculations', '7 unimplemented metrics', 'no network collectors', '30 days', '12 months', 'below five people', 'Missing telemetry is unknown']) assert(page.includes(text), text);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('published pilot arithmetic matches the executable fixture, including partial cost', () => {
|
||||||
|
const page = read('docs/strategy/measurement.mdx');
|
||||||
|
const fixture = JSON.parse(read('scripts/fixtures/ai-kpi-synthetic.json'));
|
||||||
|
const report = calculate(fixture);
|
||||||
|
const success = report.kpis['validated-task-success'];
|
||||||
|
assert(page.includes(`${success.numerator} / ${success.denominator} × 100 = ${success.value_or_null}%`));
|
||||||
|
assert(page.includes(`${success.known_outcome_coverage}%`));
|
||||||
|
const cost = report.kpis['cost-per-accepted-outcome'];
|
||||||
|
assert.equal(cost.partial_estimate, true);
|
||||||
|
assert(page.includes(`EUR ${cost.numerator} / ${cost.denominator} = EUR ${cost.value_or_null}`));
|
||||||
|
assert(page.includes('partial estimate'));
|
||||||
|
const experience = report.kpis['human-experience'];
|
||||||
|
assert(page.includes(`${experience.numerator} / ${experience.denominator} = ${experience.value_or_null}`));
|
||||||
|
assert(page.includes(`${experience.denominator} / ${fixture.survey.invited} × 100 = ${experience.coverage.response_percent}%`));
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {readFileSync, existsSync} from 'node:fs';
|
||||||
|
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);
|
||||||
|
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('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'/);
|
||||||
|
assert.match(read('docusaurus.config.ts'), /from: '\/strategy\/', to: '\/strategy\/target-operating-model\/'/);
|
||||||
|
});
|
||||||
@@ -10,15 +10,15 @@ test('every strategy page leads with a compact diagram story and closes its refe
|
|||||||
const narrative = page.split('<div className="strategy-content">')[1].replace(/<details>[\s\S]*?<\/details>/g, '');
|
const narrative = page.split('<div className="strategy-content">')[1].replace(/<details>[\s\S]*?<\/details>/g, '');
|
||||||
assert.equal((narrative.match(/<ArchifyDiagram /g) || []).length, 1, id);
|
assert.equal((narrative.match(/<ArchifyDiagram /g) || []).length, 1, id);
|
||||||
assert(narrative.includes(' story />'), id);
|
assert(narrative.includes(' story />'), id);
|
||||||
assert(page.indexOf(' story />') < page.indexOf('<details>'), id);
|
assert(!page.includes('<details>') || page.indexOf(' story />') < page.indexOf('<details>'), id);
|
||||||
assert(!/<details\s+open/.test(page), id);
|
assert(!/<details\s+open/.test(page), id);
|
||||||
assert.equal((page.match(/<details>/g) || []).length, (page.match(/<\/details>/g) || []).length, id);
|
assert.equal((page.match(/<details>/g) || []).length, (page.match(/<\/details>/g) || []).length, id);
|
||||||
assert.equal((narrative.match(/^## /gm) || []).length, 1, id);
|
assert.equal((narrative.match(/^## /gm) || []).length, id === 'measurement' ? 4 : id === 'roadmap' ? 5 : 1, id);
|
||||||
assert((narrative.match(/^- /gm) || []).length <= 3, id);
|
assert((narrative.match(/^- /gm) || []).length <= 3, id);
|
||||||
assert(narrative.includes('**Next:**'), id);
|
assert(narrative.includes('**Next:**'), id);
|
||||||
assert(!narrative.includes('| ---'), id);
|
assert(!narrative.includes('| ---'), id);
|
||||||
const words = narrative.replace(/<[^>]*>/g, '').trim().split(/\s+/).length;
|
const words = narrative.replace(/<[^>]*>/g, '').trim().split(/\s+/).length;
|
||||||
assert(words <= 200, `${id}: ${words} default narrative words`);
|
assert(words <= (id === 'measurement' ? 1100 : id === 'roadmap' ? 600 : 200), `${id}: ${words} default narrative words`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,19 +26,27 @@ test('mobile story diagrams preserve the same nodes and directed relationships',
|
|||||||
for (const {id} of model.pages) {
|
for (const {id} of model.pages) {
|
||||||
const page = read(`docs/strategy/${id}.mdx`);
|
const page = read(`docs/strategy/${id}.mdx`);
|
||||||
const name = /<ArchifyDiagram name="([^"]+)"[^>]+ story/.exec(page)[1];
|
const name = /<ArchifyDiagram name="([^"]+)"[^>]+ story/.exec(page)[1];
|
||||||
|
if (name === 'strategy-roadmap') {
|
||||||
|
const desktop = JSON.parse(read(`diagrams/${name}.workflow.json`));
|
||||||
|
const mobile = JSON.parse(read(`diagrams/${name}-mobile.workflow.json`));
|
||||||
|
const semantic = d => ({nodes:d.nodes.map(({col,yOffset,...n}) => n), edges:d.edges.map(({fromSide,toSide,...e}) => e)});
|
||||||
|
assert.deepEqual(semantic(desktop), semantic(mobile));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||||
assert(desktop.components.length >= 5 && desktop.components.length <= 7, name);
|
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 18 : 7), name);
|
||||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,...e})=>e)});
|
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.deepEqual(semantic(desktop), semantic(mobile), name);
|
||||||
assert(desktop.connections.every(e => e.label && e.from !== e.to), name);
|
assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-')) && e.from !== e.to), name);
|
||||||
assert.equal(desktop.meta.legend.mode, 'hidden');
|
assert.equal(desktop.meta.legend.mode, 'hidden');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('all sixteen exact KPI formulas remain reachable in the closed reference', () => {
|
test('all sixteen exact KPI formulas remain reachable in the linked reference', () => {
|
||||||
const page = read('docs/strategy/measurement.mdx');
|
assert(read('docs/strategy/measurement.mdx').includes('./reference/measurement-contracts.mdx'));
|
||||||
const contracts = page.split('<summary>All 16 KPI contracts')[1].split('</details>')[0];
|
const page = read('docs/strategy/reference/measurement-contracts.mdx');
|
||||||
|
const contracts = page.split('## Metric contracts')[1].split('## Run the synthetic')[0];
|
||||||
assert.equal((contracts.match(/^### /gm) || []).length, 16);
|
assert.equal((contracts.match(/^### /gm) || []).length, 16);
|
||||||
for (const metric of model.kpis) {
|
for (const metric of model.kpis) {
|
||||||
const section = contracts.split(`### ${metric.name}\n`)[1]?.split('\n### ')[0];
|
const section = contracts.split(`### ${metric.name}\n`)[1]?.split('\n### ')[0];
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import assert from 'node:assert/strict';
|
|||||||
import {readFileSync, readdirSync} from 'node:fs';
|
import {readFileSync, readdirSync} from 'node:fs';
|
||||||
const read = p => readFileSync(p, 'utf8');
|
const read = p => readFileSync(p, 'utf8');
|
||||||
const model = JSON.parse(read('research/ai-adoption/strategy-model.json'));
|
const model = JSON.parse(read('research/ai-adoption/strategy-model.json'));
|
||||||
test('eight fixed strategy pages preserve target boundary and separate evidence', () => {
|
test('seven canonical strategy pages preserve target boundary and separate evidence', () => {
|
||||||
assert.equal(model.pages.length, 8);
|
assert.equal(model.pages.length, 7);
|
||||||
assert.equal(readdirSync('docs/strategy').length, 8);
|
assert.equal(readdirSync('docs/strategy').filter(p => p.endsWith('.mdx')).length, 7);
|
||||||
for (const {id} of model.pages) {
|
for (const {id} of model.pages) {
|
||||||
const page = read(`docs/strategy/${id}.mdx`);
|
const page = read(`docs/strategy/${id}.mdx`);
|
||||||
assert(page.includes(`id: ${id}`));
|
assert(page.includes(`id: ${id}`));
|
||||||
@@ -19,7 +19,7 @@ test('eight fixed strategy pages preserve target boundary and separate evidence'
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
test('every metric has an actionable reader-facing contract', () => {
|
test('every metric has an actionable reader-facing contract', () => {
|
||||||
const page = read('docs/strategy/measurement.mdx');
|
const page = read('docs/strategy/reference/measurement-contracts.mdx');
|
||||||
assert.equal(model.kpis.length,16);
|
assert.equal(model.kpis.length,16);
|
||||||
for (const k of model.kpis) {
|
for (const k of model.kpis) {
|
||||||
const section=page.split(`### ${k.name}\n`)[1]?.split('\n### ')[0];
|
const section=page.split(`### ${k.name}\n`)[1]?.split('\n### ')[0];
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
import {createRouter} from '../vendor/archify/renderers/architecture/routing.mjs';
|
||||||
|
import {segmentIntersectsRect} from '../vendor/archify/renderers/shared/geometry.mjs';
|
||||||
|
|
||||||
|
for (const suffix of ['', '-mobile']) {
|
||||||
|
test(`Target${suffix} uses shared native midpoint ports without node crossings`, () => {
|
||||||
|
const diagram = JSON.parse(readFileSync(`diagrams/strategy-target${suffix}.architecture.json`, 'utf8'));
|
||||||
|
const boxes = new Map(diagram.components.map(({id, pos: [x, y], size: [width, height]}) =>
|
||||||
|
[id, {id, x, y, width, height, cx: x + width / 2, cy: y + height / 2}]));
|
||||||
|
const router = createRouter(boxes, diagram.connections, {distinctAutomaticPorts: true});
|
||||||
|
const agent = boxes.get('agent');
|
||||||
|
for (const edge of diagram.connections.filter(edge => edge.from === 'agent')) {
|
||||||
|
const side = suffix ? 'left' : ['scope', 'architecture', 'releases'].includes(edge.to) ? 'top'
|
||||||
|
: ['planning', 'delivery', 'uat'].includes(edge.to) ? 'bottom' : edge.to === 'security' ? 'left' : 'right';
|
||||||
|
assert.equal(edge.fromSide, side, edge.id);
|
||||||
|
assert(edge.via || (edge.route && edge.route !== 'auto'), `${edge.id} must opt out of automatic port spreading`);
|
||||||
|
const expected = {top: [agent.cx, agent.y], bottom: [agent.cx, agent.y + agent.height],
|
||||||
|
left: [agent.x, agent.cy], right: [agent.x + agent.width, agent.cy]}[side];
|
||||||
|
const points = router.pathFor(edge).points;
|
||||||
|
assert.deepEqual(points[0], expected, edge.id);
|
||||||
|
for (let i = 1; i < points.length; i++) {
|
||||||
|
for (const box of boxes.values()) {
|
||||||
|
if ([edge.from, edge.to].includes(box.id)) continue;
|
||||||
|
assert(!segmentIntersectsRect({start: points[i - 1], end: points[i]}, box), `${edge.id} crosses ${box.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Owned desktop-only integration, applied AFTER native artifact verification.
|
||||||
|
// Native architecture JSON has no per-edge attachment offset. Keep the central
|
||||||
|
// governance ports native; shift only these lifecycle channels on the same sides.
|
||||||
|
export const targetLifecycleOffsets = [
|
||||||
|
{id:'stage-2', from:'architecture', to:'ui-ux', x:870, shiftedX:850, y1:262.5, y2:457.5},
|
||||||
|
{id:'stage-3', from:'ui-ux', to:'planning', x:870, shiftedX:850, y1:532.5, y2:727.5},
|
||||||
|
{id:'stage-6', from:'uat', to:'security', x:330, shiftedX:350, y1:727.5, y2:532.5},
|
||||||
|
{id:'stage-7', from:'security', to:'releases', x:330, shiftedX:350, y1:457.5, y2:262.5},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function targetRouting(viewer) {
|
||||||
|
for (const {id, from, to, x, shiftedX, y1, y2} of targetLifecycleOffsets) {
|
||||||
|
const oldPath = `M ${x} ${y1} L ${x} ${y2}`;
|
||||||
|
const newPath = `M ${shiftedX} ${y1} L ${shiftedX} ${y2}`;
|
||||||
|
const oldPoints = `${x},${y1};${x},${y2}`;
|
||||||
|
const newPoints = `${shiftedX},${y1};${shiftedX},${y2}`;
|
||||||
|
// Match the whole native crossover group: its mask must move with the edge.
|
||||||
|
// Trace/export use this actual path; composition checks use its point list.
|
||||||
|
const groups = [...viewer.matchAll(/<g data-graph-role="automatic-crossover"[^>]*>\s*<path\b[^>]*\/>\s*<path\b[^>]*\/>\s*<\/g>/g)]
|
||||||
|
.filter(([group]) => group.includes(`data-edge-id="${id}"`));
|
||||||
|
if (groups.length !== 1) throw new Error(`Target routing drift: ${id} group`);
|
||||||
|
const group = groups[0][0];
|
||||||
|
for (const attribute of [`data-edge-from="${from}"`, `data-edge-to="${to}"`, `data-composition-points="${oldPoints}"`, 'data-graph-role="automatic-crossover-underlay"']) {
|
||||||
|
if (!group.includes(attribute)) throw new Error(`Target routing drift: ${id} ${attribute}`);
|
||||||
|
}
|
||||||
|
if (group.split(`d="${oldPath}"`).length !== 3) throw new Error(`Target routing drift: ${id} paths`);
|
||||||
|
const shifted = group.replaceAll(`d="${oldPath}"`, `d="${newPath}"`)
|
||||||
|
.replace(`data-composition-points="${oldPoints}"`, `data-composition-points="${newPoints}"`);
|
||||||
|
viewer = viewer.replace(group, shifted);
|
||||||
|
}
|
||||||
|
return viewer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {targetRouting, targetLifecycleOffsets} from './target-routing.mjs';
|
||||||
|
|
||||||
|
const fixture = targetLifecycleOffsets.map(({id, from, to, x, y1, y2}) => `<g data-graph-role="automatic-crossover" style="--step:9">
|
||||||
|
<path data-graph-role="automatic-crossover-underlay" d="M ${x} ${y1} L ${x} ${y2}"/>
|
||||||
|
<path data-edge-from="${from}" data-edge-to="${to}" data-edge-id="${id}" data-composition-points="${x},${y1};${x},${y2}" d="M ${x} ${y1} L ${x} ${y2}" marker-end="url(#arrowhead-emphasis)"/>
|
||||||
|
</g>`).join('\n');
|
||||||
|
|
||||||
|
test('Target adapter shifts precisely four paths, masks and composition points', () => {
|
||||||
|
const unrelated = '<path data-edge-id="govern-architecture" d="M 600 450 L 870 262.5"/><g data-node-id="agent"/>';
|
||||||
|
const output = targetRouting(fixture + unrelated);
|
||||||
|
assert(output.endsWith(unrelated));
|
||||||
|
let restored = output;
|
||||||
|
for (const {x, shiftedX, y1, y2} of targetLifecycleOffsets) {
|
||||||
|
assert.equal(output.split(`d="M ${shiftedX} ${y1} L ${shiftedX} ${y2}"`).length, 3);
|
||||||
|
assert(output.includes(`data-composition-points="${shiftedX},${y1};${shiftedX},${y2}"`));
|
||||||
|
restored = restored.replaceAll(`d="M ${shiftedX} ${y1} L ${shiftedX} ${y2}"`, `d="M ${x} ${y1} L ${x} ${y2}"`)
|
||||||
|
.replace(`data-composition-points="${shiftedX},${y1};${shiftedX},${y2}"`, `data-composition-points="${x},${y1};${x},${y2}"`);
|
||||||
|
}
|
||||||
|
assert.equal(restored, fixture + unrelated, 'all other bytes preserved');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Target adapter fails closed on missing, duplicate, moved or changed native edges', () => {
|
||||||
|
for (const changed of [fixture.replace('stage-2', 'missing'), fixture + fixture,
|
||||||
|
fixture.replace('870 262.5', '871 262.5'), fixture.replace('870,262.5', '871,262.5'),
|
||||||
|
fixture.replace('data-edge-from="architecture"', 'data-edge-from="scope"'),
|
||||||
|
fixture.replace('automatic-crossover-underlay', 'unknown')]) {
|
||||||
|
assert.throws(() => targetRouting(changed), /Target routing drift/);
|
||||||
|
}
|
||||||
|
assert.throws(() => targetRouting(targetRouting(fixture)), /Target routing drift/);
|
||||||
|
});
|
||||||
@@ -18,6 +18,9 @@ assert(workflow.includes("gitea.event_name == 'push' && gitea.ref == 'refs/heads
|
|||||||
if (process.argv.includes('--build')) {
|
if (process.argv.includes('--build')) {
|
||||||
const model = JSON.parse(read('research/ai-adoption/strategy-model.json'));
|
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 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) {
|
for (const {id, title} of model.pages) {
|
||||||
const route = `strategy/${id === 'index' ? '' : `${id}/`}index.html`;
|
const route = `strategy/${id === 'index' ? '' : `${id}/`}index.html`;
|
||||||
const page = read(join('build',route));
|
const page = read(join('build',route));
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||||
export default {strategySidebar: ['index', 'target-operating-model', 'skills-governance', 'skills-quality', 'skills-adoption', 'measurement', 'skills-maintenance', 'roadmap']} satisfies SidebarsConfig;
|
export default {strategySidebar: ['target-operating-model', 'roadmap', 'skills-governance', 'skills-quality', 'skills-adoption', 'measurement', 'skills-maintenance']} satisfies SidebarsConfig;
|
||||||
+11
-3
@@ -110,7 +110,10 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
|||||||
.archify-panel--story {max-width: 740px;}
|
.archify-panel--story {max-width: 740px;}
|
||||||
.archify-panel--story > iframe {height: auto; aspect-ratio: 480 / 408;}
|
.archify-panel--story > iframe {height: auto; aspect-ratio: 480 / 408;}
|
||||||
.archify-panel--story > .story-mobile {display: none;}
|
.archify-panel--story > .story-mobile {display: none;}
|
||||||
.archify-panel--story[data-diagram="strategy-target"] > iframe {aspect-ratio: 480 / 628;}
|
.theme-doc-markdown:has([data-diagram="strategy-roadmap"]) {max-width: none;}
|
||||||
|
.archify-panel--story[data-diagram="strategy-roadmap"] {max-width: none;}
|
||||||
|
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-desktop {aspect-ratio: auto; height: 360px;}
|
||||||
|
.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-adoption"] > iframe {aspect-ratio: 545 / 408;}
|
||||||
.archify-panel--story[data-diagram="strategy-maintenance"] > iframe {aspect-ratio: 524 / 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 > details {margin: .65rem 0; border: 1px solid var(--line); padding: .8rem 1rem; background: var(--surface);}
|
||||||
@@ -121,10 +124,15 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
|||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.archify-panel--story > .story-desktop {display: none;}
|
.archify-panel--story > .story-desktop {display: none;}
|
||||||
.archify-panel--story > .story-mobile {display: block; height: auto; aspect-ratio: 240 / 748;}
|
.archify-panel--story > .story-mobile {display: block; height: auto; aspect-ratio: 240 / 748;}
|
||||||
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {aspect-ratio: 240 / 628;}
|
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {aspect-ratio: 364 / 2600;}
|
||||||
.archify-panel--story[data-diagram="strategy-adoption"] > .story-mobile {aspect-ratio: 305 / 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-maintenance"] > .story-mobile {aspect-ratio: 284 / 748;}
|
||||||
.archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 301 / 868;}
|
.archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 680 / 2340;}
|
||||||
|
}
|
||||||
|
/* Five Roadmap cards need desktop space, including room for the sidebar. */
|
||||||
|
@media (width < 1360px) {
|
||||||
|
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-desktop {display: none;}
|
||||||
|
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {display: block; height: auto; aspect-ratio: 364 / 2600;}
|
||||||
}
|
}
|
||||||
.archify-expand {display: grid; place-items: center; width: 44px; height: 44px; border: 0; padding: 0; background: transparent; color: var(--ifm-link-color); cursor: pointer;}
|
.archify-expand {display: grid; place-items: center; width: 44px; height: 44px; border: 0; padding: 0; background: transparent; color: var(--ifm-link-color); cursor: pointer;}
|
||||||
.archify-expand:hover {background: var(--ifm-hover-overlay);}
|
.archify-expand:hover {background: var(--ifm-hover-overlay);}
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ export default function Home(): React.ReactElement {
|
|||||||
<div className="section-heading"><div><p className="eyebrow">START HERE</p><h2 id="start-title">A clear place to begin.</h2></div><span className="foundation-badge">Foundation · v1</span></div>
|
<div className="section-heading"><div><p className="eyebrow">START HERE</p><h2 id="start-title">A clear place to begin.</h2></div><span className="foundation-badge">Foundation · v1</span></div>
|
||||||
<div className="start-grid">
|
<div className="start-grid">
|
||||||
<Link className="start-card" to="/organization/structure/"><span className="card-number">01 / ORGANIZATION</span><h3>Organization</h3><p>Explore the structure workspace and its first interactive diagram.</p><span className="card-action">Explore structure <span aria-hidden="true">↗</span></span></Link>
|
<Link className="start-card" to="/organization/structure/"><span className="card-number">01 / ORGANIZATION</span><h3>Organization</h3><p>Explore the structure workspace and its first interactive diagram.</p><span className="card-action">Explore structure <span aria-hidden="true">↗</span></span></Link>
|
||||||
<Link className="start-card" to="/strategy/"><span className="card-number">02 / STRATEGY</span><h3>Strategy</h3><p>Shape the direction for an AI-powered Eldorado.</p><span className="card-action">View the direction <span aria-hidden="true">↗</span></span></Link>
|
<Link className="start-card" to="/strategy/target-operating-model/"><span className="card-number">02 / STRATEGY</span><h3>Strategy</h3><p>Shape the direction for an AI-powered Eldorado.</p><span className="card-action">View the direction <span aria-hidden="true">↗</span></span></Link>
|
||||||
<Link className="start-card" to="/contribution/"><span className="card-number">03 / CONTRIBUTION</span><h3>Contribution</h3><p>Contribute evidence, proposals, documentation, and diagrams.</p><span className="card-action">Start contributing <span aria-hidden="true">↗</span></span></Link>
|
<Link className="start-card" to="/contribution/"><span className="card-number">03 / CONTRIBUTION</span><h3>Contribution</h3><p>Contribute evidence, proposals, documentation, and diagrams.</p><span className="card-action">Start contributing <span aria-hidden="true">↗</span></span></Link>
|
||||||
</div>
|
</div>
|
||||||
<aside className="scope-note"><span className="scope-symbol" aria-hidden="true">✦</span><div><strong>Shaping the direction.</strong><p>This space is dedicated to AI strategy and direction for Eldorado. Specific initiatives, priorities, and the roadmap remain to be defined.</p></div></aside>
|
<aside className="scope-note"><span className="scope-symbol" aria-hidden="true">✦</span><div><strong>Shaping the direction.</strong><p>This space is dedicated to AI strategy and direction for Eldorado. Specific initiatives, priorities, and the roadmap remain to be defined.</p></div></aside>
|
||||||
|
|||||||
Reference in new issue
Block a user