diff --git a/diagrams/strategy-adoption-mobile.architecture.json b/diagrams/strategy-adoption-mobile.architecture.json index ef99828..78a9456 100644 --- a/diagrams/strategy-adoption-mobile.architecture.json +++ b/diagrams/strategy-adoption-mobile.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Learn before expanding", + "title": "TO-BE: Adoption", "output": "static/diagrams/strategy-adoption-mobile/index.html", "quality_profile": "showcase", "legend": { @@ -11,129 +11,136 @@ }, "components": [ { - "id": "choose", + "id": "delivery", "type": "backend", - "label": "Choose workflow", - "sublabel": "Department outcome", + "label": "Delivery first", + "tag": "Understand real work", + "sublabel": "Codebase + principles\nSystem logic and tests\nBuilds and packaging\nDeployments + baseline", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 + ] + }, + { + "id": "workflow", + "type": "security", + "label": "Choose bounded work", + "tag": "Team + Product owner", + "sublabel": "Define accepted outcome\nData & AI shapes the skill\nQuality sets acceptance\nSecurity clears data", + "pos": [ + 40, + 610 + ], + "size": [ + 560, + 440 ] }, { "id": "coach", - "type": "backend", - "label": "Coach people", - "sublabel": "Verify + recover", + "type": "frontend", + "label": "Practice together", + "tag": "Human + AI Agent", + "sublabel": "Platform qualifies client\nPeople supports learning\nSelect skill; inspect work\nKeep a manual path", "pos": [ - 20, - 140 + 40, + 1180 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "canary", - "type": "backend", - "label": "Run bounded canary", - "sublabel": "Qualified skill + client", + "id": "verify", + "type": "security", + "label": "Accept actual work", + "tag": "Independent reviewer", + "sublabel": "Check artifacts, not calls\nRecognize unsafe inputs\nUse incident routes\nExplain limits + recovery", "pos": [ - 20, - 260 + 40, + 1750 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "accept", - "type": "backend", - "label": "Accept real work", - "sublabel": "Independent review", + "id": "learn", + "type": "database", + "label": "Review the feedback", + "tag": "Owning team", + "sublabel": "Collect metrics + friction\nNon-use is useful feedback\nRevise through review\nNo quotas or rankings", "pos": [ - 20, - 380 + 40, + 2320 ], "size": [ - 180, - 60 - ] - }, - { - "id": "feedback", - "type": "backend", - "label": "Review feedback", - "sublabel": "Usefulness + non-use", - "pos": [ - 20, - 500 - ], - "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "expand", - "type": "backend", - "label": "Expand or revise", - "sublabel": "Keep a manual path", + "type": "cloud", + "label": "Expand with evidence", + "tag": "Other SDLC phases", + "sublabel": "Later order undecided\nRepeat baseline + topology\nDelivery keeps improving\nNot autonomous authority", "pos": [ - 20, - 620 + 40, + 2890 ], "size": [ - 180, - 60 + 560, + 440 ] } ], "connections": [ { "id": "flow-0", - "from": "choose", - "to": "coach", - "label": "Prepare" + "from": "delivery", + "to": "workflow", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-1", - "from": "coach", - "to": "canary", - "label": "support pilot" + "from": "workflow", + "to": "coach", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-2", - "from": "canary", - "to": "accept", - "label": "Verify" + "from": "coach", + "to": "verify", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", - "from": "accept", - "to": "feedback", - "label": "learn" + "from": "verify", + "to": "learn", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-4", - "from": "feedback", + "from": "learn", "to": "expand", - "label": "decide" - }, - { - "id": "feedback-loop", - "from": "expand", - "to": "coach", - "label": "Review revision", - "variant": "dashed", - "fromSide": "right", - "toSide": "right" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-adoption.architecture.json b/diagrams/strategy-adoption.architecture.json index c92665e..9fe33da 100644 --- a/diagrams/strategy-adoption.architecture.json +++ b/diagrams/strategy-adoption.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Learn before expanding", + "title": "TO-BE: Adoption", "output": "static/diagrams/strategy-adoption/index.html", "quality_profile": "showcase", "legend": { @@ -11,129 +11,136 @@ }, "components": [ { - "id": "choose", + "id": "delivery", "type": "backend", - "label": "Choose workflow", - "sublabel": "Department outcome", + "label": "Delivery first", + "tag": "Understand real work", + "sublabel": "Codebase + principles\nSystem logic and tests\nBuilds and packaging\nDeployments + baseline", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 + ] + }, + { + "id": "workflow", + "type": "security", + "label": "Choose bounded work", + "tag": "Team + Product owner", + "sublabel": "Define accepted outcome\nData & AI shapes the skill\nQuality sets acceptance\nSecurity clears data", + "pos": [ + 700, + 40 + ], + "size": [ + 560, + 440 ] }, { "id": "coach", - "type": "backend", - "label": "Coach people", - "sublabel": "Verify + recover", + "type": "frontend", + "label": "Practice together", + "tag": "Human + AI Agent", + "sublabel": "Platform qualifies client\nPeople supports learning\nSelect skill; inspect work\nKeep a manual path", "pos": [ - 260, - 20 + 1360, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "canary", - "type": "backend", - "label": "Run bounded canary", - "sublabel": "Qualified skill + client", + "id": "verify", + "type": "security", + "label": "Accept actual work", + "tag": "Independent reviewer", + "sublabel": "Check artifacts, not calls\nRecognize unsafe inputs\nUse incident routes\nExplain limits + recovery", "pos": [ - 260, - 150 + 1360, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "accept", - "type": "backend", - "label": "Accept real work", - "sublabel": "Independent review", + "id": "learn", + "type": "database", + "label": "Review the feedback", + "tag": "Owning team", + "sublabel": "Collect metrics + friction\nNon-use is useful feedback\nRevise through review\nNo quotas or rankings", "pos": [ - 20, - 150 + 700, + 610 ], "size": [ - 180, - 60 - ] - }, - { - "id": "feedback", - "type": "backend", - "label": "Review feedback", - "sublabel": "Usefulness + non-use", - "pos": [ - 20, - 280 - ], - "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "expand", - "type": "backend", - "label": "Expand or revise", - "sublabel": "Keep a manual path", + "type": "cloud", + "label": "Expand with evidence", + "tag": "Other SDLC phases", + "sublabel": "Later order undecided\nRepeat baseline + topology\nDelivery keeps improving\nNot autonomous authority", "pos": [ - 260, - 280 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] } ], "connections": [ { "id": "flow-0", - "from": "choose", - "to": "coach", - "label": "Prepare" + "from": "delivery", + "to": "workflow", + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-1", - "from": "coach", - "to": "canary", - "label": "support pilot" + "from": "workflow", + "to": "coach", + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-2", - "from": "canary", - "to": "accept", - "label": "Verify" + "from": "coach", + "to": "verify", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", - "from": "accept", - "to": "feedback", - "label": "learn" + "from": "verify", + "to": "learn", + "fromSide": "left", + "toSide": "right", + "route": "straight" }, { "id": "flow-4", - "from": "feedback", + "from": "learn", "to": "expand", - "label": "decide" - }, - { - "id": "feedback-loop", - "from": "expand", - "to": "coach", - "label": "Review revision", - "variant": "dashed", - "fromSide": "right", - "toSide": "right" + "fromSide": "left", + "toSide": "right", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-governance-mobile.architecture.json b/diagrams/strategy-governance-mobile.architecture.json index 3e05b06..a0bb588 100644 --- a/diagrams/strategy-governance-mobile.architecture.json +++ b/diagrams/strategy-governance-mobile.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Independent approval", + "title": "TO-BE: Governance", "output": "static/diagrams/strategy-governance-mobile/index.html", "quality_profile": "showcase", "legend": { @@ -12,86 +12,92 @@ "components": [ { "id": "owner", - "type": "security", - "label": "Owner proposes", - "sublabel": "Existing accountable team", + "type": "backend", + "label": "Propose a skill", + "tag": "Owning team", + "sublabel": "Problem + intended users\nData and authority scope\nSKILL.md + dependencies\nTestable outcome", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "review", "type": "security", "label": "Independent review", - "sublabel": "Quality + risk evidence", + "tag": "Quality + Security", + "sublabel": "Evaluate useful outcomes\nCheck risk boundaries\nAuthor is not sole approver\nRecord revision evidence", "pos": [ - 20, - 140 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "canary", - "type": "security", - "label": "Bounded canary", - "sublabel": "Qualified client path", + "type": "frontend", + "label": "Qualify real use", + "tag": "Human + AI Agent", + "sublabel": "Test exact client build\nRun a bounded canary\nInspect work independently\nNo failed mandatory gate", "pos": [ - 20, - 260 + 40, + 1180 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "approve", "type": "security", - "label": "Approve revision", - "sublabel": "Exact digest + scope", + "label": "Approve exact scope", + "tag": "Accountable approvers", + "sublabel": "Bind approval to digest\nPermitted uses + expiry\nExceptions cannot bypass\nprotected controls", "pos": [ - 20, - 380 + 40, + 1750 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "publish", - "type": "security", - "label": "Publish bundle", - "sublabel": "Data & AI catalog", + "type": "database", + "label": "Version the bundle", + "tag": "Data & AI catalog", + "sublabel": "Pin skill + adapter\nand dependency revisions\nRecord approval evidence\nKeep last-known-good", "pos": [ - 20, - 500 + 40, + 2320 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "distribute", - "type": "security", - "label": "Verify effective state", - "sublabel": "Platform & IT", + "type": "cloud", + "label": "Verify loaded state", + "tag": "Platform & IT", + "sublabel": "Stage and check digest\nRead effective revision\nTest open-session reload\nRevoke unsafe revisions", "pos": [ - 20, - 620 + 40, + 2890 ], "size": [ - 180, - 60 + 560, + 440 ] } ], @@ -100,31 +106,41 @@ "id": "flow-0", "from": "owner", "to": "review", - "label": "submit" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-1", "from": "review", "to": "canary", - "label": "qualify" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-2", "from": "canary", "to": "approve", - "label": "Accept" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", "from": "approve", "to": "publish", - "label": "record approval" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-4", "from": "publish", "to": "distribute", - "label": "Deliver" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-governance.architecture.json b/diagrams/strategy-governance.architecture.json index f413a5e..e3a777f 100644 --- a/diagrams/strategy-governance.architecture.json +++ b/diagrams/strategy-governance.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Independent approval", + "title": "TO-BE: Governance", "output": "static/diagrams/strategy-governance/index.html", "quality_profile": "showcase", "legend": { @@ -12,86 +12,92 @@ "components": [ { "id": "owner", - "type": "security", - "label": "Owner proposes", - "sublabel": "Existing accountable team", + "type": "backend", + "label": "Propose a skill", + "tag": "Owning team", + "sublabel": "Problem + intended users\nData and authority scope\nSKILL.md + dependencies\nTestable outcome", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "review", "type": "security", "label": "Independent review", - "sublabel": "Quality + risk evidence", + "tag": "Quality + Security", + "sublabel": "Evaluate useful outcomes\nCheck risk boundaries\nAuthor is not sole approver\nRecord revision evidence", "pos": [ - 260, - 20 + 700, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "canary", - "type": "security", - "label": "Bounded canary", - "sublabel": "Qualified client path", + "type": "frontend", + "label": "Qualify real use", + "tag": "Human + AI Agent", + "sublabel": "Test exact client build\nRun a bounded canary\nInspect work independently\nNo failed mandatory gate", "pos": [ - 260, - 150 + 1360, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "approve", "type": "security", - "label": "Approve revision", - "sublabel": "Exact digest + scope", + "label": "Approve exact scope", + "tag": "Accountable approvers", + "sublabel": "Bind approval to digest\nPermitted uses + expiry\nExceptions cannot bypass\nprotected controls", "pos": [ - 20, - 150 + 1360, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "publish", - "type": "security", - "label": "Publish bundle", - "sublabel": "Data & AI catalog", + "type": "database", + "label": "Version the bundle", + "tag": "Data & AI catalog", + "sublabel": "Pin skill + adapter\nand dependency revisions\nRecord approval evidence\nKeep last-known-good", "pos": [ - 20, - 280 + 700, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "distribute", - "type": "security", - "label": "Verify effective state", - "sublabel": "Platform & IT", + "type": "cloud", + "label": "Verify loaded state", + "tag": "Platform & IT", + "sublabel": "Stage and check digest\nRead effective revision\nTest open-session reload\nRevoke unsafe revisions", "pos": [ - 260, - 280 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] } ], @@ -100,31 +106,41 @@ "id": "flow-0", "from": "owner", "to": "review", - "label": "submit" + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-1", "from": "review", "to": "canary", - "label": "qualify" + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-2", "from": "canary", "to": "approve", - "label": "Accept" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", "from": "approve", "to": "publish", - "label": "record approval" + "fromSide": "left", + "toSide": "right", + "route": "straight" }, { "id": "flow-4", "from": "publish", "to": "distribute", - "label": "Deliver" + "fromSide": "left", + "toSide": "right", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-maintenance-mobile.architecture.json b/diagrams/strategy-maintenance-mobile.architecture.json index cfdb0d9..dc6535e 100644 --- a/diagrams/strategy-maintenance-mobile.architecture.json +++ b/diagrams/strategy-maintenance-mobile.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Maintain trust over time", + "title": "TO-BE: Maintenance", "output": "static/diagrams/strategy-maintenance-mobile/index.html", "quality_profile": "showcase", "legend": { @@ -12,86 +12,92 @@ "components": [ { "id": "monitor", - "type": "backend", - "label": "Monitor skills", - "sublabel": "Usefulness + safety", + "type": "database", + "label": "Watch for change", + "tag": "Data & AI + owners", + "sublabel": "Review usefulness + safety\nDue dates and feedback\nModel / client / tool drift\nFailures trigger early review", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "triage", "type": "backend", - "label": "Triage changes", - "sublabel": "Owner + risk review", + "label": "Triage the evidence", + "tag": "Owner + Quality", + "sublabel": "Reproduce the failure\nKeep artifact / client IDs\nAdd sanitized regressions\nAssess business effects", "pos": [ - 20, - 140 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 + ] + }, + { + "id": "contain", + "type": "security", + "label": "If unsafe: contain", + "tag": "Security + Platform", + "sublabel": "Revoke authority\nQuarantine revision\nRestore known-good bundle\nVerify effective state", + "pos": [ + 40, + 1180 + ], + "size": [ + 560, + 440 ] }, { "id": "correct", "type": "backend", - "label": "Correct and test", - "sublabel": "Independent regression", + "label": "Create a new revision", + "tag": "Owning team", + "sublabel": "Correct skill + dependencies\nIndependent tests / review\nRequalify client + model\nNo automatic reinstatement", "pos": [ - 20, - 260 + 40, + 1750 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "decision", - "type": "backend", - "label": "Approve or hold", - "sublabel": "Exact revised bundle", + "id": "approve", + "type": "security", + "label": "Authorize restart", + "tag": "Accountable human", + "sublabel": "Require regression evidence\nVerify rollback / revocation\nApprove exact new revision\nCanary before wider use", "pos": [ - 20, - 380 + 40, + 2320 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "promote", - "type": "backend", - "label": "Promote safely", - "sublabel": "Verify state + monitor", + "id": "retire", + "type": "cloud", + "label": "Retire when obsolete", + "tag": "Data & AI + Platform", + "sublabel": "Deprecate with migration\nRetire: no new invocation\nTrack revoked versions\nHistory stays in metrics", "pos": [ - 20, - 500 + 40, + 2890 ], "size": [ - 180, - 60 - ] - }, - { - "id": "observe", - "type": "backend", - "label": "Quarantine / rollback", - "sublabel": "Revoke unsafe revision", - "pos": [ - 20, - 620 - ], - "size": [ - 180, - 60 + 560, + 440 ] } ], @@ -100,42 +106,41 @@ "id": "flow-0", "from": "monitor", "to": "triage", - "label": "Detect" + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-1", "from": "triage", - "to": "correct", - "label": "scope correction" + "to": "contain", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-2", - "from": "correct", - "to": "decision", - "label": "Prove" + "from": "contain", + "to": "correct", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", - "from": "decision", - "to": "promote", - "label": "authorize" - }, - - { - "id": "unsafe", - "from": "triage", - "to": "observe", - "label": "Unsafe", - "variant": "security", - "fromSide": "right", - "toSide": "right" + "from": "correct", + "to": "approve", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { - "id": "review-loop", - "from": "observe", - "to": "correct", - "label": "Requalify", - "variant": "dashed" + "id": "flow-4", + "from": "approve", + "to": "retire", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-maintenance.architecture.json b/diagrams/strategy-maintenance.architecture.json index 06d3b9a..2ec14d3 100644 --- a/diagrams/strategy-maintenance.architecture.json +++ b/diagrams/strategy-maintenance.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Maintain trust over time", + "title": "TO-BE: Maintenance", "output": "static/diagrams/strategy-maintenance/index.html", "quality_profile": "showcase", "legend": { @@ -12,86 +12,92 @@ "components": [ { "id": "monitor", - "type": "backend", - "label": "Monitor skills", - "sublabel": "Usefulness + safety", + "type": "database", + "label": "Watch for change", + "tag": "Data & AI + owners", + "sublabel": "Review usefulness + safety\nDue dates and feedback\nModel / client / tool drift\nFailures trigger early review", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "triage", "type": "backend", - "label": "Triage changes", - "sublabel": "Owner + risk review", + "label": "Triage the evidence", + "tag": "Owner + Quality", + "sublabel": "Reproduce the failure\nKeep artifact / client IDs\nAdd sanitized regressions\nAssess business effects", "pos": [ - 260, - 20 + 700, + 40 ], "size": [ - 180, - 60 + 560, + 440 + ] + }, + { + "id": "contain", + "type": "security", + "label": "If unsafe: contain", + "tag": "Security + Platform", + "sublabel": "Revoke authority\nQuarantine revision\nRestore known-good bundle\nVerify effective state", + "pos": [ + 1360, + 40 + ], + "size": [ + 560, + 440 ] }, { "id": "correct", "type": "backend", - "label": "Correct and test", - "sublabel": "Independent regression", + "label": "Create a new revision", + "tag": "Owning team", + "sublabel": "Correct skill + dependencies\nIndependent tests / review\nRequalify client + model\nNo automatic reinstatement", "pos": [ - 260, - 150 + 1360, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "decision", - "type": "backend", - "label": "Approve or hold", - "sublabel": "Exact revised bundle", + "id": "approve", + "type": "security", + "label": "Authorize restart", + "tag": "Accountable human", + "sublabel": "Require regression evidence\nVerify rollback / revocation\nApprove exact new revision\nCanary before wider use", "pos": [ - 20, - 150 + 700, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "promote", - "type": "backend", - "label": "Promote safely", - "sublabel": "Verify state + monitor", + "id": "retire", + "type": "cloud", + "label": "Retire when obsolete", + "tag": "Data & AI + Platform", + "sublabel": "Deprecate with migration\nRetire: no new invocation\nTrack revoked versions\nHistory stays in metrics", "pos": [ - 20, - 280 + 40, + 610 ], "size": [ - 180, - 60 - ] - }, - { - "id": "observe", - "type": "backend", - "label": "Quarantine / rollback", - "sublabel": "Revoke unsafe revision", - "pos": [ - 260, - 280 - ], - "size": [ - 180, - 60 + 560, + 440 ] } ], @@ -100,42 +106,41 @@ "id": "flow-0", "from": "monitor", "to": "triage", - "label": "Detect" + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-1", "from": "triage", - "to": "correct", - "label": "scope correction" + "to": "contain", + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-2", - "from": "correct", - "to": "decision", - "label": "Prove" + "from": "contain", + "to": "correct", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", - "from": "decision", - "to": "promote", - "label": "authorize" - }, - - { - "id": "unsafe", - "from": "triage", - "to": "observe", - "label": "Unsafe", - "variant": "security", - "fromSide": "right", - "toSide": "right" + "from": "correct", + "to": "approve", + "fromSide": "left", + "toSide": "right", + "route": "straight" }, { - "id": "review-loop", - "from": "observe", - "to": "correct", - "label": "Requalify", - "variant": "dashed" + "id": "flow-4", + "from": "approve", + "to": "retire", + "fromSide": "left", + "toSide": "right", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-measurement-mobile.architecture.json b/diagrams/strategy-measurement-mobile.architecture.json index d8d498b..30c2372 100644 --- a/diagrams/strategy-measurement-mobile.architecture.json +++ b/diagrams/strategy-measurement-mobile.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Measure skills adoption", + "title": "Recommended collection plan: Measurement", "output": "static/diagrams/strategy-measurement-mobile/index.html", "quality_profile": "showcase", "legend": { @@ -11,120 +11,95 @@ }, "components": [ { - "id": "collect", + "id": "baseline", "type": "backend", "label": "Establish baseline", - "sublabel": "Current workflow", + "tag": "Comparable workflow", + "sublabel": "Join task / PR / CI IDs\nAgree acceptance + window\nRecord revision and client\nKeep unknowns visible", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "validate", - "type": "backend", - "label": "Join evidence", - "sublabel": "Tasks / versions / costs", + "id": "success", + "type": "frontend", + "label": "Accepted outcomes", + "tag": "Quality · weekly", + "sublabel": "Tracker: starts + acceptance\nAccepted / eligible starts\nwith ended window × 100\nKeep failed, stopped, unknown\nShow outcome coverage", "pos": [ - 20, - 140 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "calculate", - "type": "backend", - "label": "Measure the pilot", - "sublabel": "Outcomes / quality", + "id": "quality", + "type": "security", + "label": "Escaped quality", + "tag": "Quality · monthly", + "sublabel": "PRs + linked defect records\nAffected / followed-up\naccepted changes × 100\nCount each change once\nMissing links ≠ clean", "pos": [ - 20, - 260 + 40, + 1180 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "balance", - "type": "backend", - "label": "Report together", - "sublabel": "Cost / confidence / gaps", + "id": "cost", + "type": "cloud", + "label": "Cost per outcome", + "tag": "Finance · monthly", + "sublabel": "Invoices + review minutes\nAll costs / accepted tasks\nSame matured start cohort\nFailed work + licenses too\nMissing buckets = partial", "pos": [ - 20, - 380 + 40, + 1750 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "weigh", - "type": "backend", - "label": "Broader adoption", - "sublabel": "Availability + use", + "id": "confidence", + "type": "frontend", + "label": "Verification confidence", + "tag": "People · each cycle", + "sublabel": "Anonymous 1–5 survey\nSum of valid scores / replies\nShow spread; response rate:\nreplies / invitations × 100\nProtect small cohorts", "pos": [ - 20, - 500 + 40, + 2320 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "decide", - "type": "backend", + "id": "monitor", + "type": "database", "label": "Ongoing monitoring", - "sublabel": "Review + maintenance", + "tag": "Catalog + cohort report", + "sublabel": "Catalog + cohort records\nTrack support, use, reviews\nCompare with baseline\nShow counts + coverage\nZero denominator = N/A", "pos": [ - 20, - 620 + 40, + 2890 ], "size": [ - 180, - 60 + 560, + 440 ] } ], - "connections": [ - { - "id": "flow-0", - "from": "collect", - "to": "validate", - "label": "prepare" - }, - { - "id": "flow-1", - "from": "validate", - "to": "calculate", - "label": "collect" - }, - { - "id": "flow-2", - "from": "calculate", - "to": "balance", - "label": "calculate" - }, - { - "id": "flow-3", - "from": "balance", - "to": "weigh", - "label": "extend" - }, - { - "id": "flow-4", - "from": "weigh", - "to": "decide", - "label": "repeat" - } - ] + "connections": [] } \ No newline at end of file diff --git a/diagrams/strategy-measurement.architecture.json b/diagrams/strategy-measurement.architecture.json index 4ba86e2..004ad65 100644 --- a/diagrams/strategy-measurement.architecture.json +++ b/diagrams/strategy-measurement.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Measure skills adoption", + "title": "Recommended collection plan: Measurement", "output": "static/diagrams/strategy-measurement/index.html", "quality_profile": "showcase", "legend": { @@ -11,120 +11,95 @@ }, "components": [ { - "id": "collect", + "id": "baseline", "type": "backend", "label": "Establish baseline", - "sublabel": "Current workflow", + "tag": "Comparable workflow", + "sublabel": "Join task / PR / CI IDs\nAgree acceptance + window\nRecord revision and client\nKeep unknowns visible", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "validate", - "type": "backend", - "label": "Join evidence", - "sublabel": "Tasks / versions / costs", + "id": "success", + "type": "frontend", + "label": "Accepted outcomes", + "tag": "Quality · weekly", + "sublabel": "Tracker: starts + acceptance\nAccepted / eligible starts\nwith ended window × 100\nKeep failed, stopped, unknown\nShow outcome coverage", "pos": [ - 260, - 20 + 700, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "calculate", - "type": "backend", - "label": "Measure the pilot", - "sublabel": "Outcomes / quality", + "id": "quality", + "type": "security", + "label": "Escaped quality", + "tag": "Quality · monthly", + "sublabel": "PRs + linked defect records\nAffected / followed-up\naccepted changes × 100\nCount each change once\nMissing links ≠ clean", "pos": [ - 260, - 150 + 1360, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "balance", - "type": "backend", - "label": "Report together", - "sublabel": "Cost / confidence / gaps", + "id": "cost", + "type": "cloud", + "label": "Cost per outcome", + "tag": "Finance · monthly", + "sublabel": "Invoices + review minutes\nAll costs / accepted tasks\nSame matured start cohort\nFailed work + licenses too\nMissing buckets = partial", "pos": [ - 20, - 150 + 1360, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "weigh", - "type": "backend", - "label": "Broader adoption", - "sublabel": "Availability + use", + "id": "confidence", + "type": "frontend", + "label": "Verification confidence", + "tag": "People · each cycle", + "sublabel": "Anonymous 1–5 survey\nSum of valid scores / replies\nShow spread; response rate:\nreplies / invitations × 100\nProtect small cohorts", "pos": [ - 20, - 280 + 700, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "decide", - "type": "backend", + "id": "monitor", + "type": "database", "label": "Ongoing monitoring", - "sublabel": "Review + maintenance", + "tag": "Catalog + cohort report", + "sublabel": "Catalog + cohort records\nTrack support, use, reviews\nCompare with baseline\nShow counts + coverage\nZero denominator = N/A", "pos": [ - 260, - 280 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] } ], - "connections": [ - { - "id": "flow-0", - "from": "collect", - "to": "validate", - "label": "prepare" - }, - { - "id": "flow-1", - "from": "validate", - "to": "calculate", - "label": "collect" - }, - { - "id": "flow-2", - "from": "calculate", - "to": "balance", - "label": "calculate" - }, - { - "id": "flow-3", - "from": "balance", - "to": "weigh", - "label": "extend" - }, - { - "id": "flow-4", - "from": "weigh", - "to": "decide", - "label": "repeat" - } - ] + "connections": [] } \ No newline at end of file diff --git a/diagrams/strategy-quality-mobile.architecture.json b/diagrams/strategy-quality-mobile.architecture.json index 7ab18d8..d6c2cb9 100644 --- a/diagrams/strategy-quality-mobile.architecture.json +++ b/diagrams/strategy-quality-mobile.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Risk-tiered qualification", + "title": "TO-BE: Quality", "output": "static/diagrams/strategy-quality-mobile/index.html", "quality_profile": "showcase", "legend": { @@ -13,85 +13,91 @@ { "id": "risk", "type": "security", - "label": "Classify risk", - "sublabel": "R1 / R2 / R3", + "label": "Define the envelope", + "tag": "Owner + reviewers", + "sublabel": "R1: nonsensitive drafting\nR2: code / customer work\nR3: human-authorized\nAgree acceptance rules", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "plan", - "type": "security", - "label": "Agree evaluation", - "sublabel": "Task mix + thresholds", + "id": "compare", + "type": "backend", + "label": "Compare three paths", + "tag": "Same starting artifacts", + "sublabel": "No skill / approved / new\nPositive + negative cases\nRepeat trials per client\nKeep failed attempts", "pos": [ - 20, - 140 + 40, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "trial", - "type": "security", - "label": "Repeat trials", - "sublabel": "Candidate vs approved", + "id": "inspect", + "type": "frontend", + "label": "Inspect the outcome", + "tag": "Independent evidence", + "sublabel": "Right skill selected?\nProcedure safe?\nArtifact checks pass?\nUseful workflow result?", "pos": [ - 20, - 260 + 40, + 1180 ], "size": [ - 180, - 60 - ] - }, - { - "id": "verify", - "type": "security", - "label": "Verify artifacts", - "sublabel": "Independent checks", - "pos": [ - 20, - 380 - ], - "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "gate", "type": "security", - "label": "All gates pass?", - "sublabel": "Safety cannot be traded", + "label": "Every gate must pass", + "tag": "Quality Engineering", + "sublabel": "Safety + authorization\nSchema + provenance\nCritical effect blocks\nMissing coverage blocks", "pos": [ - 20, - 500 + 40, + 1750 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "promote", - "type": "security", - "label": "Promote or hold", - "sublabel": "Owner decision", + "id": "canary", + "type": "cloud", + "label": "Test bounded work", + "tag": "Human + AI Agent", + "sublabel": "Qualified client + model\nHuman judges usefulness\nShow counts / uncertainty\nEvidence before promotion", "pos": [ - 20, - 620 + 40, + 2320 ], "size": [ - 180, - 60 + 560, + 440 + ] + }, + { + "id": "improve", + "type": "database", + "label": "Feed the next revision", + "tag": "Owning team", + "sublabel": "Failures to regression set\nReview corrections\nMaterial client/model drift\ntriggers requalification", + "pos": [ + 40, + 2890 + ], + "size": [ + 560, + 440 ] } ], @@ -99,32 +105,42 @@ { "id": "flow-0", "from": "risk", - "to": "plan", - "label": "Scope" + "to": "compare", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-1", - "from": "plan", - "to": "trial", - "label": "freeze plan" + "from": "compare", + "to": "inspect", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-2", - "from": "trial", - "to": "verify", - "label": "Assess" + "from": "inspect", + "to": "gate", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", - "from": "verify", - "to": "gate", - "label": "review evidence" + "from": "gate", + "to": "canary", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-4", - "from": "gate", - "to": "promote", - "label": "decide" + "from": "canary", + "to": "improve", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" } ] } \ No newline at end of file diff --git a/diagrams/strategy-quality.architecture.json b/diagrams/strategy-quality.architecture.json index baf9879..7084adc 100644 --- a/diagrams/strategy-quality.architecture.json +++ b/diagrams/strategy-quality.architecture.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "TO-BE: Risk-tiered qualification", + "title": "TO-BE: Quality", "output": "static/diagrams/strategy-quality/index.html", "quality_profile": "showcase", "legend": { @@ -13,85 +13,91 @@ { "id": "risk", "type": "security", - "label": "Classify risk", - "sublabel": "R1 / R2 / R3", + "label": "Define the envelope", + "tag": "Owner + reviewers", + "sublabel": "R1: nonsensitive drafting\nR2: code / customer work\nR3: human-authorized\nAgree acceptance rules", "pos": [ - 20, - 20 + 40, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "plan", - "type": "security", - "label": "Agree evaluation", - "sublabel": "Task mix + thresholds", + "id": "compare", + "type": "backend", + "label": "Compare three paths", + "tag": "Same starting artifacts", + "sublabel": "No skill / approved / new\nPositive + negative cases\nRepeat trials per client\nKeep failed attempts", "pos": [ - 260, - 20 + 700, + 40 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "trial", - "type": "security", - "label": "Repeat trials", - "sublabel": "Candidate vs approved", + "id": "inspect", + "type": "frontend", + "label": "Inspect the outcome", + "tag": "Independent evidence", + "sublabel": "Right skill selected?\nProcedure safe?\nArtifact checks pass?\nUseful workflow result?", "pos": [ - 260, - 150 + 1360, + 40 ], "size": [ - 180, - 60 - ] - }, - { - "id": "verify", - "type": "security", - "label": "Verify artifacts", - "sublabel": "Independent checks", - "pos": [ - 20, - 150 - ], - "size": [ - 180, - 60 + 560, + 440 ] }, { "id": "gate", "type": "security", - "label": "All gates pass?", - "sublabel": "Safety cannot be traded", + "label": "Every gate must pass", + "tag": "Quality Engineering", + "sublabel": "Safety + authorization\nSchema + provenance\nCritical effect blocks\nMissing coverage blocks", "pos": [ - 20, - 280 + 1360, + 610 ], "size": [ - 180, - 60 + 560, + 440 ] }, { - "id": "promote", - "type": "security", - "label": "Promote or hold", - "sublabel": "Owner decision", + "id": "canary", + "type": "cloud", + "label": "Test bounded work", + "tag": "Human + AI Agent", + "sublabel": "Qualified client + model\nHuman judges usefulness\nShow counts / uncertainty\nEvidence before promotion", "pos": [ - 260, - 280 + 700, + 610 ], "size": [ - 180, - 60 + 560, + 440 + ] + }, + { + "id": "improve", + "type": "database", + "label": "Feed the next revision", + "tag": "Owning team", + "sublabel": "Failures to regression set\nReview corrections\nMaterial client/model drift\ntriggers requalification", + "pos": [ + 40, + 610 + ], + "size": [ + 560, + 440 ] } ], @@ -99,32 +105,42 @@ { "id": "flow-0", "from": "risk", - "to": "plan", - "label": "Scope" + "to": "compare", + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-1", - "from": "plan", - "to": "trial", - "label": "freeze plan" + "from": "compare", + "to": "inspect", + "fromSide": "right", + "toSide": "left", + "route": "straight" }, { "id": "flow-2", - "from": "trial", - "to": "verify", - "label": "Assess" + "from": "inspect", + "to": "gate", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" }, { "id": "flow-3", - "from": "verify", - "to": "gate", - "label": "review evidence" + "from": "gate", + "to": "canary", + "fromSide": "left", + "toSide": "right", + "route": "straight" }, { "id": "flow-4", - "from": "gate", - "to": "promote", - "label": "decide" + "from": "canary", + "to": "improve", + "fromSide": "left", + "toSide": "right", + "route": "straight" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 510cb82..d8447de 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "verify:build": "node scripts/verify.mjs --build", "diagrams:build": "node scripts/build-diagrams.mjs", "test": "node --test scripts/*.test.mjs", - "test:browser": "node scripts/browser/strategy-navigation.cjs && node scripts/browser/strategy-story.cjs && node scripts/browser/inline-controls.cjs && node scripts/browser/sdlc-cycle.cjs && node scripts/browser/target-midpoints.cjs && node scripts/browser/skills-measurement.cjs && node scripts/browser/roadmap.cjs && node scripts/browser/roadmap-timeline.cjs", + "test:browser": "node scripts/browser/skills-stories.cjs && node scripts/browser/strategy-navigation.cjs && node scripts/browser/strategy-story.cjs && node scripts/browser/inline-controls.cjs && node scripts/browser/sdlc-cycle.cjs && node scripts/browser/target-midpoints.cjs && node scripts/browser/skills-measurement.cjs && node scripts/browser/roadmap.cjs && node scripts/browser/roadmap-timeline.cjs", "kpi:example": "node scripts/ai-kpi.mjs" }, "dependencies": { diff --git a/scripts/browser/skills-stories.cjs b/scripts/browser/skills-stories.cjs new file mode 100644 index 0000000..43d8355 --- /dev/null +++ b/scripts/browser/skills-stories.cjs @@ -0,0 +1,45 @@ +const {chromium}=require('playwright'); +const fs=require('node:fs'); +const assert=require('node:assert/strict'); +(async()=>{ + const base=process.argv[2]||'http://127.0.0.1:4173'; + const out=`.cache/skills-${process.argv[3]||'candidate'}`;fs.mkdirSync(out,{recursive:true}); + const browser=await chromium.launch({executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,args:['--no-sandbox']}); const results=[]; + try { + for(const topic of ['governance','quality','adoption','measurement','maintenance']) for(const width of [1440,1024,390,320]) { + const page=await browser.newPage({viewport:{width,height:900},colorScheme:'light'});const errors=[];page.on('pageerror',e=>errors.push(e.message)); + const route=topic==='measurement'?topic:`skills-${topic}`; + assert.equal((await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}/`,{waitUntil:'networkidle'})).status(),200); + const panel=page.locator('.archify-panel--skills');assert.equal(await panel.count(),1); + const iframe=panel.locator(width>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('g[data-node-id]')];const issues=[];let min=Infinity; + for(const n of nodes){const rect=n.querySelector('rect').getBBox();for(const t of n.querySelectorAll('text')){ + const b=t.getBBox();min=Math.min(min,parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a); + if(b.xrect.x+rect.width||b.y+b.height>rect.y+rect.height)issues.push(n.id+':'+t.textContent); + }} + return {nodes:nodes.length,min,issues,width:innerWidth,svgWidth:svg.getBoundingClientRect().width}; + }); + assert.equal(geometry.nodes,6);assert.deepEqual(geometry.issues,[]);assert(geometry.min>=10,JSON.stringify(geometry)); + for(const theme of ['light','dark']){ + if(await frame.locator('html').getAttribute('data-theme')!==theme)await frame.locator('#btn-theme').click(); + await page.waitForFunction(t=>document.documentElement.dataset.theme===t,theme); + await panel.scrollIntoViewIfNeeded();await page.screenshot({path:`${out}/${topic}-${width}-${theme}-inline.png`}); + const open=panel.getByRole('button',{name:/^Expand /});await open.click();const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'}); + const expanded=await(await panel.locator('dialog iframe').elementHandle()).contentFrame();await expanded.locator('svg[role="img"]').waitFor(); + assert((await expanded.url()).includes(width<=700?`${topic}-mobile/`:`${topic}/`)); + await page.screenshot({path:`${out}/${topic}-${width}-${theme}-fullscreen.png`}); + if(width<=700){await page.setViewportSize({width,height:480});assert(await panel.locator('dialog').evaluate(n=>n.scrollHeight>n.clientHeight));await panel.locator('dialog').evaluate(n=>n.scrollTop=n.scrollHeight);await page.screenshot({path:`${out}/${topic}-${width}-${theme}-short-bottom.png`});await page.setViewportSize({width,height:900});} + await close.focus();await page.keyboard.press('Escape');assert(await open.evaluate(n=>n===document.activeElement)); + } + await frame.locator('#btn-preset').click();await frame.locator('[data-preset-value="blueprint"]').click(); + await frame.locator('#btn-preset').click();await frame.locator('[data-preset-value="classic"]').click(); + await frame.locator('#btn-export').click();const [download]=await Promise.all([page.waitForEvent('download'),frame.locator('[data-format="svg"]').click()]);assert.equal(await download.failure(),null); + await download.saveAs(`${out}/${topic}-${width}.svg`);assert(fs.readFileSync(`${out}/${topic}-${width}.svg`,'utf8').includes('eldorado-skills-typography')); + assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));assert.deepEqual(errors,[]); + results.push({topic,width,geometry,controls:true,export:true,themes:true,fullscreen:true});fs.writeFileSync(`${out}/results.json`,JSON.stringify(results,null,2));await page.close(); + } + console.log(`PASS ${results.length} Skills story contexts: geometry, themes, controls, exports, fullscreen and short-phone scrolling`); + }finally{await browser.close();} +})().catch(e=>{console.error(e);process.exitCode=1}); diff --git a/scripts/build-diagrams.mjs b/scripts/build-diagrams.mjs index d05871a..ac1e377 100644 --- a/scripts/build-diagrams.mjs +++ b/scripts/build-diagrams.mjs @@ -1,6 +1,7 @@ import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs'; import {join} from 'node:path'; import {execFileSync} from 'node:child_process'; +import {skillsViewer, isSkillsStory} from './skills-viewer.mjs'; import {roadmapViewer} from './roadmap-viewer.mjs'; import {targetRouting} from './target-routing.mjs'; import {createHash} from 'node:crypto'; @@ -27,11 +28,12 @@ for (const file of readdirSync('diagrams').sort()) { if (spec.diagram_type !== type || spec.meta.output !== output) throw new Error(`Diagram type/output mismatch: ${file}`); const run = args => execFileSync(process.execPath,[cli,...args],{stdio:'inherit',env:{...process.env,ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}); const roadmap = /^strategy-roadmap(?:-(?:adoption|loop))?(?:-mobile)?$/.test(name); - if (roadmap) { + const skills = isSkillsStory(name); + if (roadmap || skills) { // Native renderer validates schema and compiles layout. Check readability // after multiline typography, not upstream's single-line preview. - execFileSync(process.execPath, ['vendor/archify/renderers/workflow/render-workflow.mjs', input, output], {stdio:'inherit', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}); - writeFileSync(output, roadmapViewer(readFileSync(output, 'utf8'), spec)); + execFileSync(process.execPath, [`vendor/archify/renderers/${type}/render-${type}.mjs`, input, output], {stdio:'inherit', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}); + writeFileSync(output, (skills ? skillsViewer : roadmapViewer)(readFileSync(output, 'utf8'), spec)); run(['check',output,'--json']); } else { run(['validate',type,input,'--quality','showcase','--json']); @@ -61,13 +63,13 @@ for (const file of readdirSync('diagrams').sort()) { } writeFileSync(output, viewer.replace('', ``) .replace('', ``)); - if (roadmap) { + if (roadmap || skills) { // This is a portal receipt, not upstream deliver provenance: formatting // happens between the native schema/layout compiler and artifact checker. const hash = path => createHash('sha256').update(readFileSync(path)).digest('hex'); const checked = JSON.parse(execFileSync(process.execPath, [cli,'check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}})); - if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Roadmap final artifact failed'); - writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash('scripts/roadmap-viewer.mjs'), artifactSha256:hash(output), checker:checked}, null, 2)); + if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Story final artifact failed'); + writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash(skills ? 'scripts/skills-viewer.mjs' : 'scripts/roadmap-viewer.mjs'), artifactSha256:hash(output), checker:checked}, null, 2)); } else { renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`); if (name === 'strategy-target') { diff --git a/scripts/skills-stories.test.mjs b/scripts/skills-stories.test.mjs new file mode 100644 index 0000000..6b672c9 --- /dev/null +++ b/scripts/skills-stories.test.mjs @@ -0,0 +1,48 @@ +import {test} from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync, mkdtempSync, rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {execFileSync} from 'node:child_process'; +import {skillsViewer,isSkillsStory} from './skills-viewer.mjs'; +const read = p => readFileSync(p,'utf8'); +const topics = ['governance','quality','adoption','measurement','maintenance']; +test('all six Skills Adoption pages account for exactly five diagrams and ten sources',()=>{ + let count=0; + for(const route of ['stage-01-skills-adoption',...topics.map(t=>t==='measurement'?t:`skills-${t}`)]){ + const diagrams=[...read(`docs/strategy/${route}.mdx`).matchAll(/{ + const name=`strategy-${topic}`;const desktop=JSON.parse(read(`diagrams/${name}.architecture.json`)); + const mobile=JSON.parse(read(`diagrams/${name}-mobile.architecture.json`)); + const semantic = d=>d.components.map(({pos,size,...n})=>n); + assert.deepEqual(semantic(desktop),semantic(mobile)); + assert.equal(new Set(desktop.components.map(n=>n.pos[0])).size,3); + assert.equal(new Set(mobile.components.map(n=>n.pos[0])).size,1); + for(const n of desktop.components){assert(n.tag);assert(n.sublabel.split('\n').length>=4);} + const dir=mkdtempSync(join(tmpdir(),'skills-viewer-')); + try{ + const path=join(dir,'native.html'); + execFileSync(process.execPath,['vendor/archify/renderers/architecture/render-architecture.mjs',`diagrams/${name}.architecture.json`,path]); + const native=read(path);const html=skillsViewer(native,desktop); + assert.equal((html.match(/]*data-node-id=/g)||[]).length,6); + assert.equal((html.match(/skillsViewer(native.replace('data-node-id="'+desktop.components[0].id+'"','data-node-id="missing"'),desktop)); + const bad=structuredClone(desktop);bad.components[0].sublabel='not a full story';assert.throws(()=>skillsViewer(native,bad)); + }finally{rmSync(dir,{recursive:true,force:true});} +}); +test('domain contracts retain human authority, honest measures and Delivery-first rollout',()=>{ + const text=t=>read(`diagrams/strategy-${t}.architecture.json`); + assert(text('governance').includes('Author is not sole approver')); + assert(text('quality').includes('Critical effect blocks')); + assert(text('adoption').includes('Delivery first')&&text('adoption').includes('Later order undecided')); + assert(text('adoption').includes('No quotas or rankings')); + for(const marker of ['Tracker:','Invoices + review minutes','linked defect records','Anonymous 1–5 survey','Missing buckets = partial','Zero denominator = N/A'])assert(text('measurement').includes(marker),marker); + assert(text('maintenance').includes('No automatic reinstatement')); +}); diff --git a/scripts/skills-viewer.mjs b/scripts/skills-viewer.mjs new file mode 100644 index 0000000..4af5978 --- /dev/null +++ b/scripts/skills-viewer.mjs @@ -0,0 +1,35 @@ +// Keep native geometry, metadata, routing and exports; enrich only typography. +export const isSkillsStory = name => /^strategy-(governance|quality|adoption|measurement|maintenance)(-mobile)?$/.test(name); +const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"'); +export function skillsViewer(html, spec) { + if (spec.diagram_type !== 'architecture' || spec.components.length !== 6) throw new Error('Skills story contract drift'); + for (const node of spec.components) { + const pattern = new RegExp(`]*data-node-id="${node.id}"[^>]*>[\\s\\S]*?`); + const match = html.match(pattern); + if (!match) throw new Error(`Missing Skills node: ${node.id}`); + const original = match[0]; + const rectangles = [...original.matchAll(/]*class="c-[^"]+"[^>]*\/>/g)]; + if (rectangles.length !== 2) throw new Error('Skills shape contract drift'); + const [x,y] = node.pos; + const lines = node.sublabel.split('\n'); + if ((lines.length < 4 || lines.length > 5) || lines.some(line => line.length > 29) || node.label.length > 25) throw new Error(`Skills copy exceeds capacity: ${node.id}`); + const text = (value, top, size, attrs) => `${escape(value)}`; + const content = text(node.tag, 52, 36, 'class="t-muted" data-detail="context"') + + text(node.label, 118, 42, 'class="t-primary" data-node-label="" font-weight="700"') + + lines.map((line,i) => text(line, 190+i*46, 36, 'class="t-primary" data-detail="context"')).join(''); + const prefix = original.slice(0,rectangles[1].index+rectangles[1][0].length).replaceAll('rx="6"','rx="0"'); + html = html.replace(original,prefix+content+''); + } + const roots = [...html.matchAll(/]*role="img"[^>]*>/g)]; + if (roots.length !== 1) throw new Error('Skills SVG contract drift'); + const width = Math.max(...spec.components.map(n=>n.pos[0]+n.size[0]))+40; + const height = Math.max(...spec.components.map(n=>n.pos[1]+n.size[1]))+40; + const root = roots[0][0].replace(/viewBox="[^"]*"/,`viewBox="0 0 ${width} ${height}"`); + // Embedded in SVG so downloaded files retain typography without the viewer. + html = html.replace(roots[0][0],root+``); + return html; +} diff --git a/scripts/strategy-story.test.mjs b/scripts/strategy-story.test.mjs index f425cf0..17875f8 100644 --- a/scripts/strategy-story.test.mjs +++ b/scripts/strategy-story.test.mjs @@ -38,7 +38,7 @@ test('mobile story diagrams preserve the same nodes and directed relationships', 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,route,via,labelAt,...e})=>e)}); assert.deepEqual(semantic(desktop), semantic(mobile), name); - assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-')) && e.from !== e.to), name); + assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-') || /^strategy-(governance|quality|adoption|maintenance)$/.test(name)) && e.from !== e.to), name); assert.equal(desktop.meta.legend.mode, 'hidden'); } }); diff --git a/src/components/ArchifyDiagram/index.tsx b/src/components/ArchifyDiagram/index.tsx index 87d8eea..3ae27f1 100644 --- a/src/components/ArchifyDiagram/index.tsx +++ b/src/components/ArchifyDiagram/index.tsx @@ -12,14 +12,15 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa const headingId = useId(); const [expanded, setExpanded] = useState(false); const [narrowRoadmap, setNarrowRoadmap] = useState(false); + const skillsStory = /^strategy-(governance|quality|adoption|measurement|maintenance)$/.test(name); useEffect(() => { - if (!name.startsWith('strategy-roadmap')) return; - const media = window.matchMedia('(max-width: 999px)'); + if (!name.startsWith('strategy-roadmap') && !skillsStory) return; + const media = window.matchMedia(skillsStory ? '(max-width: 700px)' : '(max-width: 999px)'); const update = () => setNarrowRoadmap(media.matches); update(); media.addEventListener('change', update); return () => media.removeEventListener('change', update); - }, [name]); + }, [name, skillsStory]); const {colorMode, setColorMode} = useColorMode(); const initialTheme = useRef(colorMode); const desktop = useRef(null); @@ -66,7 +67,7 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa return () => {document.body.style.overflow = previousOverflow;}; }, [expanded]); const close = () => {dialog.current?.close();}; - return
+ return
{story ? <>