diff --git a/diagrams/strategy-target-mobile.architecture.json b/diagrams/strategy-target-mobile.architecture.json index f1a4580..c311370 100644 --- a/diagrams/strategy-target-mobile.architecture.json +++ b/diagrams/strategy-target-mobile.architecture.json @@ -3,25 +3,50 @@ "diagram_type": "architecture", "meta": {"title": "TO-BE: autonomous SDLC, human direction", "output": "static/diagrams/strategy-target-mobile/index.html", "quality_profile": "showcase", "legend": {"mode": "hidden"}}, "components": [ - {"id": "humans", "type": "backend", "label": "Humans", "sublabel": "Direct, guide and review", "pos": [20, 20], "size": [240, 80]}, - {"id": "agent", "type": "security", "label": "Autonomous agent", "sublabel": "Executes and governs SDLC", "pos": [20, 180], "size": [240, 80]}, - {"id": "scope", "type": "backend", "label": "Scope", "sublabel": "01", "pos": [20, 340], "size": [240, 80]}, - {"id": "architecture", "type": "backend", "label": "Architecture", "sublabel": "02", "pos": [20, 500], "size": [240, 80]}, - {"id": "ui-ux", "type": "backend", "label": "UI/UX", "sublabel": "03", "pos": [20, 660], "size": [240, 80]}, - {"id": "planning", "type": "backend", "label": "Planning", "sublabel": "04", "pos": [20, 820], "size": [240, 80]}, - {"id": "delivery", "type": "backend", "label": "Delivery", "sublabel": "05", "pos": [20, 980], "size": [240, 80]}, - {"id": "uat", "type": "backend", "label": "UAT", "sublabel": "06", "pos": [20, 1140], "size": [240, 80]}, - {"id": "releases", "type": "backend", "label": "Releases", "sublabel": "07", "pos": [20, 1300], "size": [240, 80]} + {"id":"humans","type":"backend","label":"Humans","sublabel":"Direct, guide and review","pos":[140,20],"size":[360,100]}, + {"id":"agent","type":"security","label":"Autonomous agent","sublabel":"Executes / governs SDLC","pos":[140,200],"size":[360,120]}, + {"id":"scope","type":"backend","label":"Scope","sublabel":"01","pos":[100,440],"size":[240,100]}, + {"id":"architecture","type":"backend","label":"Architecture","sublabel":"02","pos":[100,660],"size":[240,100]}, + {"id":"ui-ux","type":"backend","label":"UI/UX","sublabel":"03","pos":[100,880],"size":[240,100]}, + {"id":"planning","type":"backend","label":"Planning","sublabel":"04","pos":[100,1100],"size":[240,100]}, + {"id":"delivery","type":"backend","label":"Delivery","sublabel":"05","pos":[100,1320],"size":[240,100]}, + {"id":"uat","type":"backend","label":"UAT","sublabel":"06","pos":[100,1540],"size":[240,100]}, + {"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]}, + {"id":"code-scope","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,440],"size":[240,100]}, + {"id":"code-architecture","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,660],"size":[240,100]}, + {"id":"code-ui-ux","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,880],"size":[240,100]}, + {"id":"code-planning","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1100],"size":[240,100]}, + {"id":"code-delivery","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1320],"size":[240,100]}, + {"id":"code-uat","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1540],"size":[240,100]}, + {"id":"code-security","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1760],"size":[240,100]}, + {"id":"code-releases","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1980],"size":[240,100]} ], "connections": [ - {"id": "human-direction", "from": "humans", "to": "agent", "label": "Guide", "fromSide": "bottom", "toSide": "top"}, - {"id": "agent-lifecycle", "from": "agent", "to": "scope", "label": "Entire cycle", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-1", "from": "scope", "to": "architecture", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-2", "from": "architecture", "to": "ui-ux", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-3", "from": "ui-ux", "to": "planning", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-4", "from": "planning", "to": "delivery", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-5", "from": "delivery", "to": "uat", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-6", "from": "uat", "to": "releases", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-7", "from": "releases", "to": "scope", "label": "Repeat indefinitely", "fromSide": "right", "toSide": "right", "via": [[300, 1340], [300, 380]], "labelAt": [180, 1430]} + {"id":"human-direction","from":"humans","to":"agent","label":"Guide","fromSide":"bottom","toSide":"top"}, + {"id":"govern-scope","from":"agent","to":"scope","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,490]]}, + {"id":"govern-architecture","from":"agent","to":"architecture","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,710]]}, + {"id":"govern-ui-ux","from":"agent","to":"ui-ux","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,930]]}, + {"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":"govern-security","from":"agent","to":"security","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1810]]}, + {"id":"govern-releases","from":"agent","to":"releases","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,2030]]}, + {"id":"stage-1","from":"scope","to":"architecture","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"}, + {"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":"stage-5","from":"delivery","to":"uat","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"}, + {"id":"stage-6","from":"uat","to":"security","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"}, + {"id":"stage-7","from":"security","to":"releases","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"}, + {"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":"contribute-ui-ux","from":"code-ui-ux","to":"ui-ux","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"}, + {"id":"contribute-planning","from":"code-planning","to":"planning","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"}, + {"id":"contribute-delivery","from":"code-delivery","to":"delivery","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"}, + {"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"} ] } \ No newline at end of file diff --git a/diagrams/strategy-target.architecture.json b/diagrams/strategy-target.architecture.json index 5f12e77..a1ab490 100644 --- a/diagrams/strategy-target.architecture.json +++ b/diagrams/strategy-target.architecture.json @@ -3,25 +3,50 @@ "diagram_type": "architecture", "meta": {"title": "TO-BE: autonomous SDLC, human direction", "output": "static/diagrams/strategy-target/index.html", "quality_profile": "showcase", "legend": {"mode": "hidden"}}, "components": [ - {"id": "humans", "type": "backend", "label": "Humans", "sublabel": "Direct, guide and review", "pos": [40, 20], "size": [240, 80]}, - {"id": "agent", "type": "security", "label": "Autonomous agent", "sublabel": "Executes and governs SDLC", "pos": [360, 20], "size": [240, 80]}, - {"id": "scope", "type": "backend", "label": "Scope", "sublabel": "01", "pos": [40, 220], "size": [240, 80]}, - {"id": "architecture", "type": "backend", "label": "Architecture", "sublabel": "02", "pos": [360, 220], "size": [240, 80]}, - {"id": "ui-ux", "type": "backend", "label": "UI/UX", "sublabel": "03", "pos": [680, 220], "size": [240, 80]}, - {"id": "planning", "type": "backend", "label": "Planning", "sublabel": "04", "pos": [680, 380], "size": [240, 80]}, - {"id": "delivery", "type": "backend", "label": "Delivery", "sublabel": "05", "pos": [680, 540], "size": [240, 80]}, - {"id": "uat", "type": "backend", "label": "UAT", "sublabel": "06", "pos": [360, 540], "size": [240, 80]}, - {"id": "releases", "type": "backend", "label": "Releases", "sublabel": "07", "pos": [40, 540], "size": [240, 80]} + {"id":"humans","type":"backend","label":"Humans","sublabel":"Direct, guide and review","pos":[465,322.5],"size":[270,75]}, + {"id":"agent","type":"security","label":"Autonomous agent","sublabel":"Executes / governs SDLC","pos":[465,450],"size":[270,90]}, + {"id":"scope","type":"backend","label":"Scope","sublabel":"01","pos":[510,187.5],"size":[180,75]}, + {"id":"architecture","type":"backend","label":"Architecture","sublabel":"02","pos":[780,187.5],"size":[180,75]}, + {"id":"ui-ux","type":"backend","label":"UI/UX","sublabel":"03","pos":[780,457.5],"size":[180,75]}, + {"id":"planning","type":"backend","label":"Planning","sublabel":"04","pos":[780,727.5],"size":[180,75]}, + {"id":"delivery","type":"backend","label":"Delivery","sublabel":"05","pos":[510,727.5],"size":[180,75]}, + {"id":"uat","type":"backend","label":"UAT","sublabel":"06","pos":[240,727.5],"size":[180,75]}, + {"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]}, + {"id":"code-scope","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[510,37.5],"size":[180,75]}, + {"id":"code-architecture","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[780,37.5],"size":[180,75]}, + {"id":"code-ui-ux","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[1005,457.5],"size":[180,75]}, + {"id":"code-planning","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[780,877.5],"size":[180,75]}, + {"id":"code-delivery","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[510,877.5],"size":[180,75]}, + {"id":"code-uat","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[240,877.5],"size":[180,75]}, + {"id":"code-security","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[15,457.5],"size":[180,75]}, + {"id":"code-releases","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[240,37.5],"size":[180,75]} ], "connections": [ - {"id": "human-direction", "from": "humans", "to": "agent", "label": "Guide", "fromSide": "right", "toSide": "left"}, - {"id": "agent-lifecycle", "from": "agent", "to": "scope", "label": "Entire cycle", "fromSide": "bottom", "toSide": "top", "via": [[480, 160], [160, 160]], "labelAt": [320, 145]}, - {"id": "stage-1", "from": "scope", "to": "architecture", "label": "Then", "fromSide": "right", "toSide": "left"}, - {"id": "stage-2", "from": "architecture", "to": "ui-ux", "label": "Then", "fromSide": "right", "toSide": "left"}, - {"id": "stage-3", "from": "ui-ux", "to": "planning", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-4", "from": "planning", "to": "delivery", "label": "Then", "fromSide": "bottom", "toSide": "top"}, - {"id": "stage-5", "from": "delivery", "to": "uat", "label": "Then", "fromSide": "left", "toSide": "right"}, - {"id": "stage-6", "from": "uat", "to": "releases", "label": "Then", "fromSide": "left", "toSide": "right"}, - {"id": "stage-7", "from": "releases", "to": "scope", "label": "Repeat indefinitely", "fromSide": "top", "toSide": "bottom", "via": [[160, 460], [160, 380]], "labelAt": [260, 420]} + {"id":"human-direction","from":"humans","to":"agent","label":"Guide","fromSide":"bottom","toSide":"top"}, + {"id":"govern-scope","from":"agent","to":"scope","label":"","variant":"security","fromSide":"right","toSide":"bottom","via":[[750,495],[750,292.5],[600,292.5]]}, + {"id":"govern-architecture","from":"agent","to":"architecture","label":"","variant":"security","fromSide":"right","toSide":"bottom","via":[[765,495],[765,345],[870,345]]}, + {"id":"govern-ui-ux","from":"agent","to":"ui-ux","label":"","variant":"security","fromSide":"right","toSide":"left"}, + {"id":"govern-planning","from":"agent","to":"planning","label":"","variant":"security","fromSide":"right","toSide":"top","via":[[765,495],[765,690],[870,690]]}, + {"id":"govern-delivery","from":"agent","to":"delivery","label":"","variant":"security","fromSide":"bottom","toSide":"top"}, + {"id":"govern-uat","from":"agent","to":"uat","label":"","variant":"security","fromSide":"left","toSide":"top","via":[[435,495],[435,690],[330,690]]}, + {"id":"govern-security","from":"agent","to":"security","label":"","variant":"security","fromSide":"left","toSide":"right"}, + {"id":"govern-releases","from":"agent","to":"releases","label":"","variant":"security","fromSide":"left","toSide":"bottom","via":[[435,495],[435,345],[330,345]]}, + {"id":"stage-1","from":"scope","to":"architecture","label":"Then","variant":"emphasis","fromSide":"right","toSide":"left"}, + {"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":"stage-5","from":"delivery","to":"uat","label":"Then","variant":"emphasis","fromSide":"left","toSide":"right"}, + {"id":"stage-6","from":"uat","to":"security","label":"Then","variant":"emphasis","fromSide":"top","toSide":"bottom"}, + {"id":"stage-7","from":"security","to":"releases","label":"Then","variant":"emphasis","fromSide":"top","toSide":"bottom"}, + {"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":"contribute-ui-ux","from":"code-ui-ux","to":"ui-ux","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right","labelAt":[990,570]}, + {"id":"contribute-planning","from":"code-planning","to":"planning","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"}, + {"id":"contribute-delivery","from":"code-delivery","to":"delivery","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"}, + {"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"} ] } \ No newline at end of file diff --git a/docs/strategy/index.mdx b/docs/strategy/index.mdx index 96187fb..b0b19e7 100644 --- a/docs/strategy/index.mdx +++ b/docs/strategy/index.mdx @@ -16,9 +16,11 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram'; -An autonomous agent executes and governs the SDLC. Humans direct, guide and review the work throughout the cycle. +An autonomous agent executes and governs every phase. Humans direct, guide and review it; a separate human-controlled code agent contributes to each phase from outside the cycle. -**Scope → Architecture → UI/UX → Planning → Delivery → UAT → Releases → Scope** +**Solid arrows:** lifecycle · **Dotted spokes:** execution and governance · **Dashed arrows:** contributions. + +**Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope** Releases returns to Scope and the cycle repeats indefinitely. Governed skills and measured outcomes support the path toward this target; they are not a substitute for it. diff --git a/docs/strategy/target-operating-model.mdx b/docs/strategy/target-operating-model.mdx index b761e03..11836a9 100644 --- a/docs/strategy/target-operating-model.mdx +++ b/docs/strategy/target-operating-model.mdx @@ -1,6 +1,7 @@ --- id: target-operating-model title: Target operating model +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 --- @@ -15,9 +16,11 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram'; -An autonomous agent executes and governs the SDLC. Humans direct, guide and review the work throughout the cycle. +An autonomous agent executes and governs every phase. Humans direct, guide and review it; a separate human-controlled code agent contributes to each phase from outside the cycle. -**Scope → Architecture → UI/UX → Planning → Delivery → UAT → Releases → Scope** +**Solid arrows:** lifecycle · **Dotted spokes:** execution and governance · **Dashed arrows:** contributions. + +**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. @@ -44,7 +47,7 @@ The autonomous agent is more than a task router: it executes and governs the rep 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. -These controls support the sequence Scope → Architecture → UI/UX → Planning → Delivery → UAT → 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. +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 diff --git a/scripts/browser/inline-controls.cjs b/scripts/browser/inline-controls.cjs index 6b6aa5f..2a193fb 100644 --- a/scripts/browser/inline-controls.cjs +++ b/scripts/browser/inline-controls.cjs @@ -88,7 +88,7 @@ const fs = require('node:fs'); if (format.startsWith('svg')) { const valid = await page.evaluate(xml => { const doc = new DOMParser().parseFromString(xml, 'image/svg+xml'); - return !doc.querySelector('parsererror') && doc.documentElement.localName === 'svg' && doc.querySelectorAll('text').length > 0; + 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()); assert(valid, format); } else { @@ -117,10 +117,13 @@ const fs = require('node:fs'); await close.focus(); await page.keyboard.press('Escape'); assert(await opener.evaluate(n => n === document.activeElement)); 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 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/target-operating-model/"]').filter({visible:true}).focus(); + await page.keyboard.press('Enter'); 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'}`); diff --git a/scripts/browser/sdlc-cycle.cjs b/scripts/browser/sdlc-cycle.cjs index 6faf892..fa4a729 100644 --- a/scripts/browser/sdlc-cycle.cjs +++ b/scripts/browser/sdlc-cycle.cjs @@ -19,7 +19,7 @@ const fs = require('node:fs'); 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'}); - if (width === 1440) assert.equal(await page.locator('.theme-doc-sidebar-menu .menu__link').first().innerText(), 'Target operating model'); + if (width === 1440) assert.equal(await page.locator('.theme-doc-sidebar-menu .menu__link').first().innerText(), 'Target'); const iframe = page.locator(`.archify-panel--story > .story-${width > 700 ? 'desktop' : 'mobile'}`); await iframe.scrollIntoViewIfNeeded(); const frame = await (await iframe.elementHandle()).contentFrame(); @@ -32,15 +32,36 @@ const fs = require('node:fs'); const box = n.querySelector('rect').getBoundingClientRect(); return [...n.querySelectorAll('text')].filter(t => {const b=t.getBoundingClientRect();return b.leftbox.right||b.topbox.bottom;}).map(t=>t.textContent); }); - return {labels,edges,overflow}; + 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','releases']; + 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, 18); + 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.from==='humans' && e.to==='agent')); + 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, []); assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth)); - await page.evaluate(()=>scrollTo(0,0)); + 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)); diff --git a/scripts/browser/strategy-story.cjs b/scripts/browser/strategy-story.cjs index 3b64422..3d8fe7b 100644 --- a/scripts/browser/strategy-story.cjs +++ b/scripts/browser/strategy-story.cjs @@ -19,18 +19,21 @@ const fs=require('node:fs'); const nodes=[...svg.querySelectorAll('[data-node-id]')].map(n=>{const r=n.getBoundingClientRect();return {id:n.id,x:r.x,y:r.y,right:r.right,bottom:r.bottom};}); return {width:innerWidth,height:innerHeight,nodes,svg:svg.getBoundingClientRect().toJSON(),minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n=>parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))}; }); - assert(geometry.nodes.length>=5 && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?9: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}`); 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); - 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 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)); 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 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)); - const summary=page.locator('.strategy-content > details > summary').first();await summary.click();assert(await summary.evaluate(n=>n.parentElement.open));await summary.click(); + 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:true});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'}); diff --git a/scripts/build-diagrams.mjs b/scripts/build-diagrams.mjs index b27a332..72094cd 100644 --- a/scripts/build-diagrams.mjs +++ b/scripts/build-diagrams.mjs @@ -37,7 +37,15 @@ for (const file of readdirSync('diagrams').sort()) { 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); if (!viewer.includes('') || !viewer.includes('')) throw new Error(`Invalid viewer: ${output}`); - const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name) ? cycleStyles : ''; + 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 = /]*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}`); + } writeFileSync(output, viewer.replace('', ``) .replace('', ``)); renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`); diff --git a/scripts/cycle-viewer.css b/scripts/cycle-viewer.css index 6bff0ac..40d042e 100644 --- a/scripts/cycle-viewer.css +++ b/scripts/cycle-viewer.css @@ -2,13 +2,18 @@ .diagram-container > svg text { font-family: Arial, sans-serif; letter-spacing: 0; - font-size: 18px; + font-size: 30px; } .diagram-container > svg text[data-node-label] { - font-size: 24px; - transform: translateY(-3px); + font-size: var(--cycle-label); + transform: translateY(-6px); } .diagram-container > svg text[data-detail="context"] { - font-size: 18px; - transform: translateY(7px); + 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;} diff --git a/scripts/sdlc-cycle.test.mjs b/scripts/sdlc-cycle.test.mjs index 4b0703c..f4459e5 100644 --- a/scripts/sdlc-cycle.test.mjs +++ b/scripts/sdlc-cycle.test.mjs @@ -2,35 +2,59 @@ 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', 'Releases']; +const stages = ['Scope', 'Architecture', 'UI/UX', 'Planning', 'Delivery', 'UAT', 'Security', 'Releases']; test('Strategy opens at the autonomous lifecycle, with a matching direct landing story', () => { assert.match(read('sidebars/strategy.ts'), /strategySidebar: \['target-operating-model', 'index'/); + assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m); for (const id of ['index', 'target-operating-model']) { const page = read(`docs/strategy/${id}.mdx`); const primary = page.split('
')[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 → Releases → Scope')); + assert(primary.includes('Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope')); + assert(primary.includes('human-controlled code agent')); assert(primary.includes('TO-BE')); assert(!/only routes|People authorize|independent gates protect release/.test(primary)); } }); -test('both target diagrams have exactly seven sequential stages and a Releases to Scope return', () => { +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, 7); + 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(flow.at(-1).via.length >= 2, 'explicit outside return corridor'); + assert(d.components.some(n => n.label === 'Autonomous agent' && /Executes.*governs/.test(n.sublabel))); assert(d.components.some(n => n.label === 'Humans' && n.sublabel === 'Direct, guide and review')); assert(d.connections.some(e => e.from === 'humans' && e.to === 'agent')); - assert(d.connections.some(e => e.from === 'agent' && e.to === ids[0])); + 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 === 'Code 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, 18); + 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`); + } + } } }); diff --git a/scripts/strategy-story.test.mjs b/scripts/strategy-story.test.mjs index ad92e1b..28c764e 100644 --- a/scripts/strategy-story.test.mjs +++ b/scripts/strategy-story.test.mjs @@ -28,10 +28,10 @@ test('mobile story diagrams preserve the same nodes and directed relationships', const name = /]+ story/.exec(page)[1]; const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`)); const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`)); - assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 9 : 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,via,labelAt,...e})=>e)}); 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'); } }); diff --git a/src/css/custom.css b/src/css/custom.css index 3e3a781..7666d83 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -110,7 +110,7 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse .archify-panel--story {max-width: 740px;} .archify-panel--story > iframe {height: auto; aspect-ratio: 480 / 408;} .archify-panel--story > .story-mobile {display: none;} -.archify-panel--story[data-diagram="strategy-target"] > iframe {aspect-ratio: 960 / 708;} +.archify-panel--story[data-diagram="strategy-target"] > iframe {aspect-ratio: 1620 / 1430;} .archify-panel--story[data-diagram="strategy-adoption"] > iframe {aspect-ratio: 545 / 408;} .archify-panel--story[data-diagram="strategy-maintenance"] > iframe {aspect-ratio: 524 / 408;} .strategy-content > details {margin: .65rem 0; border: 1px solid var(--line); padding: .8rem 1rem; background: var(--surface);} @@ -124,7 +124,7 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse .archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {aspect-ratio: 240 / 628;} .archify-panel--story[data-diagram="strategy-adoption"] > .story-mobile {aspect-ratio: 305 / 748;} .archify-panel--story[data-diagram="strategy-maintenance"] > .story-mobile {aspect-ratio: 284 / 748;} - .archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 340 / 1520;} + .archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 680 / 2340;} } .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);}