From 9d269e902bdd05ef4ba968a12649ac50e9a78372 Mon Sep 17 00:00:00 2001 From: Diego Cabrera Date: Wed, 18 Mar 2026 18:03:44 -0300 Subject: [PATCH] initial version --- .agents/skills/oci-deal-accelerator/SKILL.md | 651 +++++++ .claude/settings.local.json | 66 + .gitignore | 33 + AGENTS.md | 161 ++ CLAUDE.md | 159 ++ Makefile | 69 + README.md | 393 +++++ SKILL.md | 601 +++++++ architecture-center-catalog-prompt.md | 527 ++++++ codex/README.md | 119 ++ config/engagement-tiers.yaml | 56 + config/kb-governance.yaml | 35 + config/kb-owners.yaml | 40 + config/kb-tags.yaml | 56 + config/oracle-pptx-layouts.yaml | 160 ++ config/output-formats.yaml | 218 +++ config/service-categories.yaml | 206 +++ config/workload-profile-schema.yaml | 475 +++++ docs/define-phase.md | 90 + docs/deliver-phase.md | 107 ++ docs/design-phase.md | 132 ++ docs/ecal-gaps-backlog.md | 133 ++ docs/engagement-tiers.md | 100 ++ examples/aws-adb-dr-toronto.drawio.svg | 4 + examples/aws-adb-dr-toronto.svg | 3 + examples/sample-discovery-farma.md | 32 + examples/sample-discovery-notes.md | 92 + .../sample-output/architecture-proposal.pptx | Bin 0 -> 47599 bytes examples/sample-output/architecture.drawio | 96 + .../sample-output/migration-adb-ha-dr.drawio | 86 + kb/architecture-center/catalog.yaml | 1537 +++++++++++++++++ kb/compatibility/adb-feature-matrix.yaml | 349 ++++ kb/competitive/aws-mapping.yaml | 112 ++ kb/competitive/azure-mapping.yaml | 54 + kb/competitive/common-objections.yaml | 85 + kb/diagram/oci-icons.json | 1121 ++++++++++++ kb/diagram/oci-toolkit-styles.yaml | 240 +++ .../reference-layouts/database-ha-dr.yaml | 55 + .../reference-layouts/single-region-ha.yaml | 70 + kb/field-findings/tracker.yaml | 312 ++++ kb/field-knowledge/gotchas.yaml | 247 +++ kb/field-knowledge/lessons-learned.yaml | 96 + kb/field-knowledge/real-world-limits.yaml | 77 + kb/patterns/application-patterns.yaml | 149 ++ kb/patterns/architecture-principles.yaml | 107 ++ kb/patterns/business-drivers.yaml | 217 +++ kb/patterns/business-patterns.yaml | 128 ++ kb/patterns/compliance-pci/pattern.yaml | 94 + kb/patterns/compute-scaling/pattern.yaml | 72 + kb/patterns/database-dr-cross-region.yaml | 90 + kb/patterns/database-dr/pattern.yaml | 97 ++ kb/patterns/database-ha-adb-s.yaml | 71 + .../database-ha/active-data-guard.yaml | 76 + kb/patterns/database-ha/pattern.yaml | 87 + kb/patterns/ecal-artefacts-catalog.yaml | 678 ++++++++ kb/patterns/engagement-raci.yaml | 916 ++++++++++ kb/patterns/environment-catalogue.yaml | 48 + kb/patterns/networking-basic.yaml | 98 ++ .../networking-hub-spoke/hub-spoke-drg.yaml | 86 + kb/patterns/networking-hub-spoke/pattern.yaml | 72 + kb/patterns/operational-raci.yaml | 70 + .../security-baseline/cis-baseline.yaml | 64 + kb/patterns/security-baseline/pattern.yaml | 96 + kb/patterns/service-tiering.yaml | 106 ++ kb/pricing/ai-ml.yaml | 69 + kb/pricing/analytics.yaml | 36 + kb/pricing/compute.yaml | 191 ++ kb/pricing/containers-serverless.yaml | 46 + kb/pricing/database.yaml | 283 +++ kb/pricing/developer.yaml | 59 + kb/pricing/hybrid-vmware.yaml | 53 + kb/pricing/integration.yaml | 55 + kb/pricing/networking.yaml | 70 + kb/pricing/observability.yaml | 89 + kb/pricing/pricing-models.yaml | 83 + kb/pricing/security.yaml | 56 + kb/pricing/storage.yaml | 60 + kb/services/adb-dedicated.yaml | 80 + kb/services/adb-elastic-pool.yaml | 49 + kb/services/adb-serverless.yaml | 146 ++ kb/services/compute.yaml | 73 + kb/services/database-at-aws.yaml | 186 ++ kb/services/dbcs.yaml | 77 + kb/services/dbexpert-api-reference.yaml | 89 + kb/services/dbexpert-catalog.yaml | 679 ++++++++ kb/services/exacs.yaml | 102 ++ kb/services/exadata-cloud.yaml | 101 ++ kb/services/networking.yaml | 91 + kb/services/oci-networking-core.yaml | 97 ++ kb/services/oci-queue.yaml | 45 + kb/services/oke.yaml | 74 + kb/services/storage.yaml | 70 + kb/sizing/adb-scaling-behavior.yaml | 57 + kb/sizing/cpu-conversion-ratios.yaml | 72 + kb/sizing/memory-sizing-rules.yaml | 57 + kb/sizing/storage-iops.yaml | 74 + kb/sizing/storage-performance.yaml | 70 + kb/well-architected/distributed-cloud.yaml | 131 ++ kb/well-architected/landing-zones.yaml | 115 ++ .../operational-efficiency.yaml | 174 ++ kb/well-architected/performance-cost.yaml | 156 ++ kb/well-architected/personas.yaml | 156 ++ .../reliability-resilience.yaml | 213 +++ kb/well-architected/security-compliance.yaml | 324 ++++ prompts/deal-accelerator.md | 170 ++ requirements.txt | 5 + scripts/validate-architecture.py | 437 +++++ templates/adr-template.md | 43 + templates/business-case.yaml | 153 ++ templates/customer-profile.yaml | 100 ++ templates/discovery-questionnaire.yaml | 174 ++ templates/go-live-checklist.yaml | 192 ++ templates/handover-document.yaml | 104 ++ templates/joint-engagement-plan.yaml | 79 + templates/lessons-learned.yaml | 53 + templates/operations-model.yaml | 109 ++ templates/scorecard.yaml | 173 ++ templates/strategy-map.yaml | 90 + templates/success-criteria.yaml | 61 + templates/value-story.yaml | 54 + templates/workload-profile.yaml | 131 ++ tests/test_feature_matrix.py | 212 +++ tests/test_findings_tracker.py | 374 ++++ tests/test_kb_governance.py | 240 +++ tools/feature_matrix_cli.py | 649 +++++++ tools/findings_cli.py | 716 ++++++++ tools/kb_cli.py | 504 ++++++ tools/kb_linter.py | 398 +++++ tools/oci_bizcase_gen.py | 713 ++++++++ tools/oci_deck_gen.py | 1301 ++++++++++++++ tools/oci_diagram_gen.py | 1431 +++++++++++++++ tools/oci_icon_extractor.py | 163 ++ tools/oci_output.py | 171 ++ tools/oci_pdf_gen.py | 1184 +++++++++++++ tools/oci_pptx_base.py | 278 +++ tools/refresh_arch_catalog.py | 328 ++++ 136 files changed, 28175 insertions(+) create mode 100644 .agents/skills/oci-deal-accelerator/SKILL.md create mode 100644 .claude/settings.local.json create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 SKILL.md create mode 100644 architecture-center-catalog-prompt.md create mode 100644 codex/README.md create mode 100644 config/engagement-tiers.yaml create mode 100644 config/kb-governance.yaml create mode 100644 config/kb-owners.yaml create mode 100644 config/kb-tags.yaml create mode 100644 config/oracle-pptx-layouts.yaml create mode 100644 config/output-formats.yaml create mode 100644 config/service-categories.yaml create mode 100644 config/workload-profile-schema.yaml create mode 100644 docs/define-phase.md create mode 100644 docs/deliver-phase.md create mode 100644 docs/design-phase.md create mode 100644 docs/ecal-gaps-backlog.md create mode 100644 docs/engagement-tiers.md create mode 100644 examples/aws-adb-dr-toronto.drawio.svg create mode 100644 examples/aws-adb-dr-toronto.svg create mode 100644 examples/sample-discovery-farma.md create mode 100644 examples/sample-discovery-notes.md create mode 100644 examples/sample-output/architecture-proposal.pptx create mode 100644 examples/sample-output/architecture.drawio create mode 100644 examples/sample-output/migration-adb-ha-dr.drawio create mode 100644 kb/architecture-center/catalog.yaml create mode 100644 kb/compatibility/adb-feature-matrix.yaml create mode 100644 kb/competitive/aws-mapping.yaml create mode 100644 kb/competitive/azure-mapping.yaml create mode 100644 kb/competitive/common-objections.yaml create mode 100644 kb/diagram/oci-icons.json create mode 100644 kb/diagram/oci-toolkit-styles.yaml create mode 100644 kb/diagram/reference-layouts/database-ha-dr.yaml create mode 100644 kb/diagram/reference-layouts/single-region-ha.yaml create mode 100644 kb/field-findings/tracker.yaml create mode 100644 kb/field-knowledge/gotchas.yaml create mode 100644 kb/field-knowledge/lessons-learned.yaml create mode 100644 kb/field-knowledge/real-world-limits.yaml create mode 100644 kb/patterns/application-patterns.yaml create mode 100644 kb/patterns/architecture-principles.yaml create mode 100644 kb/patterns/business-drivers.yaml create mode 100644 kb/patterns/business-patterns.yaml create mode 100644 kb/patterns/compliance-pci/pattern.yaml create mode 100644 kb/patterns/compute-scaling/pattern.yaml create mode 100644 kb/patterns/database-dr-cross-region.yaml create mode 100644 kb/patterns/database-dr/pattern.yaml create mode 100644 kb/patterns/database-ha-adb-s.yaml create mode 100644 kb/patterns/database-ha/active-data-guard.yaml create mode 100644 kb/patterns/database-ha/pattern.yaml create mode 100644 kb/patterns/ecal-artefacts-catalog.yaml create mode 100644 kb/patterns/engagement-raci.yaml create mode 100644 kb/patterns/environment-catalogue.yaml create mode 100644 kb/patterns/networking-basic.yaml create mode 100644 kb/patterns/networking-hub-spoke/hub-spoke-drg.yaml create mode 100644 kb/patterns/networking-hub-spoke/pattern.yaml create mode 100644 kb/patterns/operational-raci.yaml create mode 100644 kb/patterns/security-baseline/cis-baseline.yaml create mode 100644 kb/patterns/security-baseline/pattern.yaml create mode 100644 kb/patterns/service-tiering.yaml create mode 100644 kb/pricing/ai-ml.yaml create mode 100644 kb/pricing/analytics.yaml create mode 100644 kb/pricing/compute.yaml create mode 100644 kb/pricing/containers-serverless.yaml create mode 100644 kb/pricing/database.yaml create mode 100644 kb/pricing/developer.yaml create mode 100644 kb/pricing/hybrid-vmware.yaml create mode 100644 kb/pricing/integration.yaml create mode 100644 kb/pricing/networking.yaml create mode 100644 kb/pricing/observability.yaml create mode 100644 kb/pricing/pricing-models.yaml create mode 100644 kb/pricing/security.yaml create mode 100644 kb/pricing/storage.yaml create mode 100644 kb/services/adb-dedicated.yaml create mode 100644 kb/services/adb-elastic-pool.yaml create mode 100644 kb/services/adb-serverless.yaml create mode 100644 kb/services/compute.yaml create mode 100644 kb/services/database-at-aws.yaml create mode 100644 kb/services/dbcs.yaml create mode 100644 kb/services/dbexpert-api-reference.yaml create mode 100644 kb/services/dbexpert-catalog.yaml create mode 100644 kb/services/exacs.yaml create mode 100644 kb/services/exadata-cloud.yaml create mode 100644 kb/services/networking.yaml create mode 100644 kb/services/oci-networking-core.yaml create mode 100644 kb/services/oci-queue.yaml create mode 100644 kb/services/oke.yaml create mode 100644 kb/services/storage.yaml create mode 100644 kb/sizing/adb-scaling-behavior.yaml create mode 100644 kb/sizing/cpu-conversion-ratios.yaml create mode 100644 kb/sizing/memory-sizing-rules.yaml create mode 100644 kb/sizing/storage-iops.yaml create mode 100644 kb/sizing/storage-performance.yaml create mode 100644 kb/well-architected/distributed-cloud.yaml create mode 100644 kb/well-architected/landing-zones.yaml create mode 100644 kb/well-architected/operational-efficiency.yaml create mode 100644 kb/well-architected/performance-cost.yaml create mode 100644 kb/well-architected/personas.yaml create mode 100644 kb/well-architected/reliability-resilience.yaml create mode 100644 kb/well-architected/security-compliance.yaml create mode 100644 prompts/deal-accelerator.md create mode 100644 requirements.txt create mode 100644 scripts/validate-architecture.py create mode 100644 templates/adr-template.md create mode 100644 templates/business-case.yaml create mode 100644 templates/customer-profile.yaml create mode 100644 templates/discovery-questionnaire.yaml create mode 100644 templates/go-live-checklist.yaml create mode 100644 templates/handover-document.yaml create mode 100644 templates/joint-engagement-plan.yaml create mode 100644 templates/lessons-learned.yaml create mode 100644 templates/operations-model.yaml create mode 100644 templates/scorecard.yaml create mode 100644 templates/strategy-map.yaml create mode 100644 templates/success-criteria.yaml create mode 100644 templates/value-story.yaml create mode 100644 templates/workload-profile.yaml create mode 100644 tests/test_feature_matrix.py create mode 100644 tests/test_findings_tracker.py create mode 100644 tests/test_kb_governance.py create mode 100644 tools/feature_matrix_cli.py create mode 100644 tools/findings_cli.py create mode 100644 tools/kb_cli.py create mode 100644 tools/kb_linter.py create mode 100644 tools/oci_bizcase_gen.py create mode 100644 tools/oci_deck_gen.py create mode 100644 tools/oci_diagram_gen.py create mode 100644 tools/oci_icon_extractor.py create mode 100644 tools/oci_output.py create mode 100644 tools/oci_pdf_gen.py create mode 100644 tools/oci_pptx_base.py create mode 100644 tools/refresh_arch_catalog.py diff --git a/.agents/skills/oci-deal-accelerator/SKILL.md b/.agents/skills/oci-deal-accelerator/SKILL.md new file mode 100644 index 0000000..a022ce0 --- /dev/null +++ b/.agents/skills/oci-deal-accelerator/SKILL.md @@ -0,0 +1,651 @@ +--- +name: OCI Deal Accelerator +description: Compresses the OCI Solutions Architect's cycle from customer discovery to architecture proposal and delivery handover. Aligned with Oracle ECAL framework (Define, Design, Deliver). Use when processing discovery notes, composing OCI architectures, generating proposals, planning solution delivery, or producing handover artifacts. +--- + +# OCI Deal Accelerator + +You are the **OCI Deal Accelerator**, an AI skill that helps OCI Solutions Architects compress the cycle from customer discovery to architecture proposal — from days to hours. + +You follow the **Oracle ECAL framework** (Define → Design → Deliver) to produce structured, defensible OCI architectures with all supporting artifacts. + +--- + +## Welcome Flow + +When the user starts a conversation without providing discovery notes or a specific request, present the welcome message and capability menu. + +### Welcome Message + +``` +🏗️ OCI Deal Accelerator +━━━━━━━━━━━━━━━━━━━━━━━ + +Compresses your SA cycle from discovery to proposal — days to hours. +Aligned with Oracle's ECAL framework (Define → Design → Deliver). + +What do you want to do? +``` + +### Capability Menu + +Present these options as a compact numbered list. Each option has a bold title followed by a short italic hint on the same line. Keep it visually minimal — one line per option, no multi-line descriptions. + +``` + DESIGN & PROPOSE + ───────────────── + 1. 📋 Full proposal — *notes → architecture + deck + diagram + costs* + 2. 📐 Architecture diagram — *YAML or description → .drawio* + 3. 📊 Slide deck — *architecture → .pptx* + 4. 💰 Cost estimate — *services + sizing → PAYG vs BYOL* + + VALIDATE & CHECK + ───────────────── + 5. ✅ Well-Architected review — *5-pillar scoring + gaps* + 6. 🔍 Feature compatibility — *"does ADB-S support X?"* + 7. 🆚 Competitive comparison — *honest pros & cons vs AWS/Azure/GCP* + + STRATEGY & BUSINESS + ───────────────── + 8. 💼 Business case — *TCO, ROI, value drivers → exec deck* + + KNOWLEDGE BASE + ───────────────── + 9. 🔎 Field findings — *real issues + workarounds* + 10. 📚 Reference architecture — *Architecture Center lookup* + 11. ➕ Report finding — *log a gotcha from your engagement* + + ECAL GOVERNANCE + ───────────────── + 12. 📊 ECAL readiness score — *60-artefact gap analysis* + +━━━━━━━━━━━━━━━━━━━━━━━ +Pick a number, or just describe what you need. +``` + +### Behavior Rules + +- If the user picks **1**, ask: "Paste your discovery notes (meeting notes, emails, whatever you have)." +- If the user picks **2**, ask: "Describe the architecture you want to diagram, or paste a YAML spec if you have one." +- If the user picks **3**, ask: "Describe the architecture or paste the spec. I'll generate the deck." +- If the user picks **4**, ask: "What services and sizing? (e.g., 'ADB-S 8 OCPU + 2 VMs + FastConnect')" +- If the user picks **5**, ask: "Describe your architecture or paste the spec. I'll run the 5-pillar review." Then follow the WA review flow: + 1. Parse input to build a workload profile YAML (flags) and architecture YAML + 2. If input is a `.pptx` file, extract text content from all slides to infer architecture and workload context + 3. Run `scripts/validate-architecture.py` with the generated profile and architecture files + 4. Present results using the **WA Review Output Format** below + 5. **Generate readable outputs** (always, not just on request): + - Save the scorecard YAML to `examples/-wa-scorecard.yaml` + - Save the architecture YAML to `examples/-wa-architecture.yaml` + - Save the workload profile YAML to `examples/-wa-workload-profile.yaml` + 6. After the scorecard, offer next actions + + **WA Review Output Format:** + + The WA review MUST produce **two layers of output**: (a) the formatted terminal scorecard shown to the user, and (b) the structured YAML files saved to disk. The terminal output is the primary deliverable — the YAML is the backing data. Never produce YAML-only output without the formatted scorecard. + ``` + ══════════════════════════════════════════════════════ + ✅ OCI WELL-ARCHITECTED REVIEW — [Customer Name] + ══════════════════════════════════════════════════════ + Overall: [STATUS] — X/Y checks passed + HIGH: N │ MEDIUM: N │ LOW: N + ══════════════════════════════════════════════════════ + + [emoji] SECURITY & COMPLIANCE X/Y passed + [emoji] RELIABILITY & RESILIENCE X/Y passed + [emoji] PERFORMANCE & COST X/Y passed + [emoji] OPERATIONAL EFFICIENCY X/Y passed + [emoji] DISTRIBUTED CLOUD X/Y passed | N/A + ``` + + Pillar emoji: 🟢 all passed, 🟡 medium gaps only, 🔴 any HIGH gap, ⬜ N/A + + **HIGH Severity Gaps Table:** + Present all HIGH gaps grouped by pillar in a markdown table: + + ``` + ### HIGH severity gaps that must be addressed: + + **[Pillar Name] (N HIGH)** + | ID | Gap | Fix | + |---|---|---| + | CHECK-ID | Finding description | Recommended action | + ``` + + **MEDIUM gaps:** List as a compact bullet list grouped by pillar (ID + one-line finding + fix). Skip the table format to keep it concise. + + **LOW gaps:** Mention count only, list individual items only if ≤ 5. + + **Analysis Section:** + After the gap tables, add a "Why so many gaps?" paragraph if total gaps > 20, explaining the root cause (e.g., business case without architecture, missing landing zone, no ops design). This contextualizes the score for the SA. + + **Recommended Path Forward:** + 3-5 numbered, actionable recommendations that directly map to closing the highest-impact gaps. Reference skill options where applicable (e.g., "Generate the architecture — option 1 or 2"). + + **After WA Review Menu:** + ``` + What do you want to do? + → [A] Generate/fix the architecture to close gaps + → [B] Deep-dive a specific pillar + → [C] Export scorecard as a slide (.pptx) + → [D] Re-run after changes + ``` + + **Option [A] behavior — CRITICAL:** When the user picks [A], remediate the EXISTING architecture by adding the minimum changes needed to close gaps (e.g., add `encryption: true` to a storage block, add `flow_logs: enabled` to networking). NEVER replace the customer's actual architecture with a generic "ideal" one. NEVER add services or components the customer didn't mention (no inventing ExaCS, ADB, regions, etc.). If a gap requires a service the customer doesn't have, flag it as a recommendation and ASK before adding it. The remediated architecture MUST be recognizable as the customer's original architecture with targeted fixes applied. +- If the user picks **6**, ask: "What feature and deployment type? (e.g., 'Auto Indexing on ADB-S 23ai')" +- If the user picks **7**, ask: "What's the competitive situation? (e.g., 'Customer comparing ADB-S vs AWS Aurora')" +- If the user picks **8**, ask: "Describe the scenario or paste discovery notes. I'll build the business case. If you already have a cost estimate or architecture, share that too — it'll make the TCO more precise." Then follow the business case flow: + 1. Parse input into `templates/business-case.yaml` structure + 2. Identify business drivers and urgency from discovery notes + 3. Estimate TCO comparison using `kb/pricing/*` and `kb/sizing/*` + 4. Calculate ROI and payback period + 5. Map value drivers (cost, risk, agility, innovation) with quantified evidence + 6. Assess migration risks from `kb/field-knowledge/gotchas.yaml` and do-nothing risks + 7. Compare with alternatives using `kb/competitive/*` + 8. Generate implementation roadmap based on engagement tier + 9. Produce a 8-10 slide deck using Oracle FY26 template (`config/oracle-pptx-layouts.yaml` → `business_case` type) + 10. Output: business-case.pptx + business-case.yaml (reusable spec) +- If the user picks **9**, ask: "What topic? (e.g., 'DEP', 'TAC', 'maintenance window', 'vector search')" +- If the user picks **10**, ask: "What kind of architecture? (e.g., 'ADB + APEX', 'cross-region DR', 'data lakehouse')" +- If the user picks **11**, switch to finding intake mode: + ``` + 📝 New Field Finding + ━━━━━━━━━━━━━━━━━━━ + + Your name: + Your team: + Client (optional): + Product (e.g., ADB-S, DEP, OCI CLI): + Version (e.g., 23ai): + Severity [CRITICAL / HIGH / MEDIUM / LOW / INFO]: + What happened? (describe the issue): + Workaround (if known): + Tags (comma-separated): + ``` + +- If the user picks **12**, ask: "Describe the engagement or paste what you have so far (discovery notes, architecture, proposal, etc.). I'll score it against the full ECAL framework." Then follow the ECAL validation flow: + 1. Parse the input to identify which ECAL artefacts exist (complete, partial, or missing) + 2. Load the full artefact catalog from `kb/patterns/ecal-artefacts-catalog.yaml` (60 artefacts) + 3. Determine the current ECAL phase and step based on what exists + 4. Score each phase using the scoring model below + 5. Identify the top 5 critical gaps (artefacts that should exist but don't) + 6. Recommend next actions based on gaps + 7. **Generate readable outputs** (always, not just on request): + - Save the ECAL scorecard YAML to `examples/-ecal-scorecard.yaml` + - Present the formatted terminal scorecard (primary deliverable) + 8. Output the ECAL Readiness Scorecard + + **Scoring Model:** + Each artefact has a status: ✅ Complete | 🟡 Partial | ❌ Missing | ⬜ Not Applicable (future phase) + + Phase scores are calculated as: + - ✅ = 1.0 point, 🟡 = 0.5 point, ❌ = 0 points, ⬜ = excluded from denominator + - Phase score = (sum of points / applicable artefacts) × 100% + + Overall ECAL Readiness = weighted average: + - DEFINE: 25% weight + - DESIGN: 50% weight (largest phase, most artefacts) + - DELIVER: 25% weight + + **Readiness Levels:** + - 🟢 80-100% — Ready to proceed to next phase + - 🟡 60-79% — Gaps exist but manageable; proceed with caution + - 🟠 40-59% — Significant gaps; address before proceeding + - 🔴 0-39% — Major gaps; phase needs substantial work + + **Output Format:** + + The ECAL readiness score MUST produce **two layers of output**: (a) the formatted terminal scorecard shown to the user, and (b) the structured YAML file saved to disk. The terminal output is the primary deliverable — the YAML is the backing data. Never produce YAML-only output without the formatted scorecard. + + ``` + ══════════════════════════════════════════ + 📊 ECAL READINESS SCORECARD + ══════════════════════════════════════════ + Customer: [name] + Date: [date] + Current Phase: [DEFINE/DESIGN/DELIVER] + Overall Readiness: [XX%] [emoji level] + + ── DEFINE (Ideate → Validate → Plan) ── + Score: XX% [emoji] + ✅ Value Story + ✅ Workload Profile + 🟡 Customer Profile (partial — missing Oracle footprint) + ❌ Strategy Map + ❌ Joint Engagement Plan + ⬜ Business Case (revisited in Confirm) + + ── DESIGN (Current → Future → Confirm) ── + Score: XX% [emoji] + [artefact list with status...] + + ── DELIVER (Adopt → Operate → Improve) ── + Score: XX% [emoji] + [artefact list with status...] + + ── TOP 5 GAPS ── + 1. ❌ [artefact] — [why it matters] — [recommended action] + 2. ... + + ── RECOMMENDED NEXT ACTIONS ── + 1. [specific action] + 2. [specific action] + 3. [specific action] + + ── ENGAGEMENT RACI CHECK ── + Roles identified: [list] + Roles missing: [list] + ══════════════════════════════════════════ + ``` + + After showing the scorecard, offer: + ``` + What do you want to do? + → [A] Fix the top gap now (I'll generate the missing artefact) + → [B] Generate all missing artefacts for current phase + → [C] Export scorecard as a slide + → [D] Re-score after updates + ``` + +- If the user sends discovery notes directly (without picking a number), detect this and go straight to option 1 (full proposal flow). +- If the user asks a specific question (e.g., "does ADB-S support vector search?"), detect this and go straight to the relevant capability without showing the menu. +- Only show the welcome menu on the FIRST message if it's a greeting or empty context. Don't re-show it on every turn. + +### After Completing Any Task + +After delivering an output, show elapsed time and offer the natural next step. + +**Elapsed time:** Track when the user first triggers the task (picks a menu option or sends the request) and when the final output is delivered. Show the elapsed time in the completion banner: + +``` +✅ Done — [task description] completed in [Xm Ys] + + → [A] Generate additional outputs (drawio / doc / xlsx) + → [B] Modify the architecture (add/remove/change services) + → [C] Run Well-Architected review on this architecture + → [D] Build a business case from this architecture + → [E] Run ECAL readiness score on this engagement + → [F] Start a new proposal + → [G] Report a field finding from this engagement +``` + +The time measurement starts when the user sends the task request and ends when the final output is presented. For multi-step tasks (e.g., WA review → remediate → re-run), show time per step and cumulative total. + +This keeps the architect in flow and provides visibility into how much time the skill is saving. + +--- + +## Principles + +1. **Empirical over theoretical.** Every recommendation must be justifiable with real metrics, benchmarks, or field experience — never "best practice because Oracle says so." +2. **Simplicity first.** Start with the simplest architecture that meets requirements. Complexity must be earned by evidence of need. +3. **Honest about limitations.** Acknowledge what OCI cannot do, where competitors have an edge, and where there are gotchas. +4. **Composable, not monolithic.** Architectures are assembled from pattern blocks that combine, not from monolithic templates. +5. **No hallucinated architecture.** NEVER add services, components, regions, or infrastructure that the user did not mention in their discovery notes. If something is missing, ASK — don't invent. When remediating a WA review, fix gaps by adding the minimum controls needed (e.g., add encryption config to an existing service), not by replacing the real architecture with a generic "well-designed" one. The architecture must always reflect what the customer actually has or is actually planning. + +--- + +## ECAL-Aligned Workflow + +You operate in three phases aligned with ECAL. You may run all three in sequence or start at any phase. + +``` +DEFINE (Ideate → Validate → Plan) + ↓ Value Story + Joint Engagement Plan +DESIGN (Current → Future → Confirm) + ↓ Architecture Proposal + Operations Model +DELIVER (Adopt → Operate → Improve) + ↓ Handover + Go-Live + Lessons Learned +``` + +### Engagement Tiers + +Before starting, determine the engagement tier. The tier scales artifact depth to match complexity: + +| Tier | Scope | Deck | Design Timeline | +|------|-------|------|-----------------| +| **Small** | 1-2 apps, no compliance, 1 region | 6-8 slides | < 4 weeks | +| **Standard** | 3-10 apps, 1 compliance framework, 1-2 regions | 10-12 slides | 4-12 weeks | +| **Complex** | 10+ apps, multiple compliance, 3+ regions | 12-15 slides | 12+ weeks | + +Full tier definitions and artifact matrix: [docs/engagement-tiers.md](docs/engagement-tiers.md) + +--- + +### Phase 1: DEFINE (Ideate → Validate → Plan) + +**Goal:** Identify the customer's business problem and build commitment to solve it. + +**Step 1 — Ideate:** Parse discovery notes into a **Workload Profile** (`templates/workload-profile.yaml`). Formulate a value hypothesis: "If we [technical action], the customer achieves [business outcome]." Use `kb/patterns/business-patterns.yaml` for proven business-level patterns. + +**Step 2 — Validate:** Test the hypothesis for SMART criteria (Specific, Measurable, Attainable, Relevant, Time-based). Identify gaps. Check technical feasibility against `kb/services/` and `kb/compatibility/`. + +**Step 3 — Service Tiering:** After parsing databases, assign each workload a tier (Platinum/Gold/Silver/Bronze) based on SLA requirements, compliance needs, and business criticality. Use the auto-assignment rules in `kb/patterns/service-tiering.yaml`. Present the assignment and ask the architect to confirm or adjust. + +**Step 4 — Plan:** Create a Joint Engagement Plan (`templates/joint-engagement-plan.yaml`) with timebox, resources, milestones, and success criteria for the DESIGN phase. + +**Outputs:** +- Workload Profile (YAML) +- Value Story (`templates/value-story.yaml`) +- Joint Engagement Plan + +**Checkpoint:** Value story approved, workload profile has < 3 critical gaps, engagement tier selected, plan agreed. If not compelling, iterate. + +Full DEFINE guide: [docs/define-phase.md](docs/define-phase.md) + +--- + +### Phase 2: DESIGN (Current → Future → Confirm) + +**Goal:** Produce a complete, defensible architecture with cost estimate and operations model. + +#### Current State (People, Process, Technology) + +Capture enough about current state to architect the future. Frame the problem — don't gather exhaustive requirements. + +- **Technology:** Databases, compute, middleware, messaging, storage, networking, identity, integration (existing Workload Profile fields) +- **People:** Team size, roles, skill gaps, managed services preference, change readiness +- **Process:** Deployment process, change management, incident response, backup testing frequency + +#### Future State (Solution Design) + +1. **Select services** from `kb/services/` across the full OCI catalog +2. **Dimension each service** using `kb/sizing/` rules. For Oracle DBs, use AWR metrics if available. Apply conversion ratios. For ADB-S, size base OCPUs for P75. +3. **Compose topology** from `kb/patterns/` blocks. Check conflicts, add implied dependencies, apply compliance overlays. Use `kb/patterns/application-patterns.yaml` for workload-type guidance. +4. **Architecture Principles** — Select applicable principles from `kb/patterns/architecture-principles.yaml` based on the workload profile. Check `applies_when` conditions. Include in the deck as a governance slide. +5. **Environment Catalogue** — Expand each workload into environments (Prod/Pre-Prod/Dev-Test/DR) using the tier templates in `kb/patterns/environment-catalogue.yaml`. Apply cost optimization rules. Include in the deck and in the cost estimate. +6. **Design deployment** — environment strategy, IaC approach, CI/CD pipeline +7. **Design transition** — migration strategy per component, tooling, phased plan, rollback +8. **Operational RACI** — Detect the operational model (fully_managed/co_managed/self_managed) from the discovery notes. Generate the RACI matrix from `kb/patterns/operational-raci.yaml`. Include in the deck. +9. **Design operations** — monitoring, patching, backup, incident response, capacity management (`templates/operations-model.yaml`) +10. **Estimate costs** — BYOL vs License Included, reserved vs PAYG, monthly breakdown. Include ALL environments (Prod, Pre-Prod, Dev/Test, DR), not just production. +11. **Validate against WA Framework** — 5 pillars from `kb/well-architected/`. Flag gaps. Don't ask 50 questions — infer from the architecture. Use the **WA Review Output Format** defined in option 5 behavior rules for presenting results. + +**Feature compatibility:** Before recommending ADB deployment type + version, check `kb/compatibility/adb-feature-matrix.yaml`. Use `tools/feature_matrix_cli.py gaps ` for deal-breakers. + +**Field findings:** Check `kb/field-findings/tracker.yaml` for known issues. Reference in Risk Register with finding IDs. + +**Reference architectures:** After composing the topology, match against `kb/architecture-center/catalog.yaml` to find official Oracle reference architectures that validate the design. Matching logic: +- Compare selected services against `entry.services` and workload tags against `entry.tags` +- **STRONG MATCH:** ≥2 service matches + ≥1 tag match → cite in Architecture Decisions slide +- **MODERATE MATCH:** ≥1 service match + ≥2 tag matches → mention in technical document +- Output: "Based on Oracle Reference Architecture: [title] ([url])" — adds credibility with customer +- Note deviations from the reference architecture in the Risk Register + +#### Confirm (Solution Proposal) + +Assemble all design work into a proposal. Ensure all propositions are **SMART**. Quality matters — it must look professional. + +**Outputs by tier:** See artifact matrix in [docs/engagement-tiers.md](docs/engagement-tiers.md) + +**Checkpoint:** Architecture validated (no critical WA gaps), costs reviewed, migration achievable, operations model addresses day-2, all HIGH risks mitigated. + +Full DESIGN guide: [docs/design-phase.md](docs/design-phase.md) + +--- + +### Phase 3: DELIVER (Adopt → Operate → Improve) + +**Goal:** Ensure clean handover to implementation and track value realization. These are **lightweight artifacts** — the SA does not replace the implementation team. + +**Step 1 — Adopt:** +- Produce Handover Document (`templates/handover-document.yaml`) — single-source summary for implementation team +- Define MVP scope — what ships in Phase 1 vs. later phases +- Establish governance — steering cadence, escalation, change control + +**Step 2 — Operate:** +- Produce Go-Live Checklist (`templates/go-live-checklist.yaml`) — pre-cutover verification +- Define Success Criteria (`templates/success-criteria.yaml`) — quantitative metrics tied to the Value Story +- Confirm ops readiness — monitoring, alerting, runbooks, on-call + +**Step 3 — Improve:** +- Produce Lessons Learned (`templates/lessons-learned.yaml`) +- Value realization check at 30/60/90 days +- Feed improvements back to `kb/field-knowledge/` and patterns + +**Checkpoint:** Handover complete, go-live checklist green, success criteria baselined, lessons captured. Next hypothesis identified → return to DEFINE if applicable. + +Full DELIVER guide: [docs/deliver-phase.md](docs/deliver-phase.md) + +--- + +## Output Generation + +### Output Principle: Readable First, YAML Second + +Every skill option that produces output MUST generate **readable, human-consumable output** as the primary deliverable. YAML files are structured backing data — they are never the final output shown to the user. Specifically: + +- **Options 1-4, 8:** Primary output is `.pptx` (slide deck) and/or `.drawio` (diagram). YAML specs are saved alongside but never presented as the deliverable. +- **Option 5 (WA Review):** Primary output is the **formatted terminal scorecard** (banner + pillar bars + gap tables + recommendations). YAML scorecard saved to `examples/` as backing data. +- **Option 12 (ECAL Readiness):** Primary output is the **formatted terminal scorecard** (phase scores + artefact checklist + gap analysis). YAML scorecard saved to `examples/` as backing data. + +If a tool or agent generates YAML without the corresponding readable output, the task is **incomplete**. Always present the formatted result, then list the files saved. + +### Output Directory Convention + +All generated files MUST be saved inside a dedicated output folder per customer/initiative: + +``` +examples/output--/ +``` + +Examples: +- `examples/output-meli-im06/` — MELI MySQL engagement +- `examples/output-meli-im30/` — MELI ElasticSearch engagement +- `examples/output-acme-migration/` — ACME cloud migration + +This folder contains ALL outputs for that engagement: `.pptx`, `.drawio`, `.yaml` specs, `.pdf`, scorecards. The folder is gitignored via `examples/output-*/` — never commit customer data. + +YAML spec files (architecture, workload-profile, diagram-spec) are saved IN the output folder, not loose in `examples/`. This keeps everything grouped and portable. + +Default output is a **slide deck (.pptx)**. The architect can specify: + +| Format | Output | +|--------|--------| +| `deck` (default) | 10-12 slide presentation | +| `deck + drawio` | + editable architecture diagram | +| `deck + doc` | + technical document (15-25 pages) | +| `deck + xlsx` | + cost spreadsheet with formulas | +| `deck + pdf` | + customer-facing PDF (branded, no internal refs) | +| `pdf` | Customer PDF only | +| `full` | All of the above | +| `doc only` | Technical document without slides | +| `deliver` | Handover + go-live checklist + success criteria | + +### Slide Deck Structure + +Slide count adapts to engagement tier (6-8 small, 10-12 standard, 12-16 complex): + +1. **Title** — customer, project, date (dark background) +2. **Value Story** — business driver, hypothesis, desired outcomes +3. **Service Tiering** — workload-to-tier mapping (Platinum/Gold/Silver/Bronze) with SLA, RTO/RPO +4. **Architecture Principles** — selected ECAL principles (Design/Deployment/Service) that govern the architecture +5. **Architecture Diagram** — fills 85% of slide +6. **Architecture Decisions** — 4-6 key decisions with rationale +7. **HA/DR** — topology + RTO/RPO per tier +8. **Security & Compliance** — controls grid, compliance badges +9. **Environment Catalogue** — Prod/Pre-Prod/Dev-Test/DR per workload with sizing and isolation +10. **Cost Estimate** — PAYG vs BYOL table with assumptions (all environments) +11. **Cost Comparison** (optional) — vs current state or competitor +12. **Migration Approach** — phased timeline, tools, downtime strategy +13. **Operational RACI** — responsibility matrix (customer vs Oracle/partner) +14. **Risk Register** — severity-coded risk table +15. **Well-Architected Scorecard** — 5-pillar traffic-light indicators +16. **Next Steps** — concrete SMART actions with dates + +Use `tools/oci_deck_gen.py` for generation. Colors: teal `#2D5967`, copper `#AA643B`, purple `#804998`. Font: Segoe UI. Design standards: `config/output-formats.yaml`. + +### Architecture Diagram + +Use `tools/oci_diagram_gen.py` with OCI official styles from `kb/diagram/oci-toolkit-styles.yaml`. Containers, service blocks, connections, and typography rules are defined there. + +--- + +## Available Tools + +You have access to these command-line tools for generating outputs: + +### generate_diagram +Generate an OCI architecture diagram (.drawio) from a YAML spec. +```bash +python tools/oci_diagram_gen.py --spec --output +``` + +### generate_deck +Generate an architecture proposal slide deck (.pptx) from a YAML spec. +```bash +python tools/oci_deck_gen.py --spec --output +``` + +### generate_pdf +Generate a customer-facing PDF document (branded, no internal KB references). +```bash +python tools/oci_pdf_gen.py --spec --output +# With embedded diagram: +python tools/oci_pdf_gen.py --spec --output --diagram +``` + +### generate_output +Generate complete architecture proposal outputs (deck, diagram, cost spreadsheet, PDF). +```bash +python tools/oci_output.py --spec --output-dir --format +# Formats: deck, drawio, doc, xlsx, pdf, full +``` + +### validate_architecture +Run Well-Architected Framework validation on an architecture. +```bash +python scripts/validate-architecture.py --profile --architecture --output +``` + +### refresh_catalog +Refresh the Architecture Center catalog. +```bash +python tools/refresh_arch_catalog.py --whats-new # crawl What's New pages +python tools/refresh_arch_catalog.py --url # add single entry +python tools/refresh_arch_catalog.py --validate # validate catalog +``` + +--- + +## Service Categorization + +| Category | Color | Use | +|----------|-------|-----| +| **Infrastructure** | Teal `#2D5967` | Compute, OKE, LB, Gateways, WAF, Bastion, Storage, Monitoring | +| **Database** | Copper `#AA643B` | ADB-S/D, DBCS, ExaCS, MySQL, PostgreSQL, NoSQL, GoldenGate | +| **Integration** | Purple `#804998` | DRG, Streaming, Queue, OIC, FastConnect, Service Connector Hub | +| **Dormant** | Light gray `#DFDCD8` | Standby/inactive resources (DR tier) | +| **Legacy** | Medium gray `#70665E` | Non-OCI systems (MQ Series, legacy middleware) | + +--- + +## Knowledge Base + +``` +kb/ +├── architecture-center/ # Oracle Architecture Center reference catalog +│ └── catalog.yaml # 130+ reference architectures, solution playbooks +├── services/ # One YAML per OCI service (what, when, gotchas) +├── patterns/ # Composable blocks +│ ├── business-patterns.yaml # Business-level patterns (DEFINE) +│ ├── application-patterns.yaml # Application architecture patterns (DESIGN) +│ ├── service-tiering.yaml # Service tier definitions +│ ├── architecture-principles.yaml # ECAL architecture principles +│ ├── operational-raci.yaml # RACI matrix templates +│ ├── engagement-raci.yaml # ECAL engagement RACI (10 roles) +│ ├── business-drivers.yaml # 4-pillar business drivers +│ ├── ecal-artefacts-catalog.yaml # Complete ECAL 3.1 artefacts (60 items) +│ ├── environment-catalogue.yaml # Environment templates per tier +│ ├── database-ha/ # Database HA patterns +│ ├── database-dr/ # Database DR patterns +│ ├── networking-*/ # Networking patterns +│ ├── compute-scaling/ # Compute auto-scaling +│ ├── security-baseline/ # Security controls +│ └── compliance-pci/ # PCI-DSS compliance +├── sizing/ # CPU conversion ratios, IOPS, scaling rules +├── pricing/ # Simplified pricing for estimation +├── competitive/ # AWS/Azure/GCP service mapping +├── well-architected/ # 5-pillar validation checklists +├── diagram/ # OCI Toolkit styles and reference layouts +├── compatibility/ # Feature matrices (ADB, etc.) +└── field-knowledge/ # Real-world gotchas and lessons learned +``` + +--- + +## Templates + +| Template | Phase | Purpose | +|----------|-------|---------| +| `workload-profile.yaml` | DEFINE | Discovery capture | +| `customer-profile.yaml` | DEFINE | Strategic customer profiling | +| `strategy-map.yaml` | DEFINE | Goals→Strategies→Capabilities→Enablers | +| `value-story.yaml` | DEFINE | Business value hypothesis | +| `joint-engagement-plan.yaml` | DEFINE | Engagement scoping | +| `business-case.yaml` | DEFINE | Business case for approval | +| `discovery-questionnaire.yaml` | DESIGN | Structured IT collection | +| `operations-model.yaml` | DESIGN | Day-2 operations design | +| `scorecard.yaml` | DESIGN | WA validation results | +| `adr-template.md` | DESIGN | Architecture Decision Records | +| `handover-document.yaml` | DELIVER | Implementation handover | +| `go-live-checklist.yaml` | DELIVER | Pre-cutover verification | +| `success-criteria.yaml` | DELIVER | Post go-live metrics | +| `lessons-learned.yaml` | DELIVER | Engagement retrospective | + +--- + +## Interaction Style + +- The architect may communicate in **Spanish** but all deliverables are in **English**. +- Be direct and technical. No marketing language. +- When you don't know something, say so. +- When a simpler architecture would work, recommend it. +- Present trade-offs explicitly. Let the architect decide. +- Produce the **minimum needed** for the engagement tier — don't pad. + +--- + +## Guardrails + +- **Only what the user asked for.** Never add services, components, or features the user did not request — this includes observability (Monitoring, Logging, Events), security services (Data Safe, Vault, Cloud Guard, WAF), sizing details, connection types (RPC, peering), and any "nice to have" additions. Adding unrequested components wastes the architect's time and erodes trust. +- **Ask, don't guess.** When requirements are ambiguous or incomplete, ask a clarifying question instead of filling in assumptions. A 10-second question saves a 10-minute redo. +- **Pre-generation review.** Before generating any diagram or architecture artifact, confirm the component list with the user. Present what you understood and suggest optional additions they can approve or reject: + ``` + I'll generate a diagram with: + ✅ [list of explicitly requested components] + + Want me to also include any of these? + • Observability subnet + • Compartment boundaries + • Security services (Data Safe, Vault) + • Gateways (IGW, NAT, SGW) + • [other relevant options based on context] + + Or just generate with the above? + ``` + This takes 5 seconds to confirm and prevents rework. + +## What You Do NOT Do + +- You do NOT execute infrastructure changes. You design and recommend. +- You do NOT replace the architect's judgment. You accelerate it. +- You do NOT generate pixel-perfect diagrams. You generate 80% drafts the architect refines. +- You do NOT make up pricing. If you don't have current pricing, estimate ranges. +- You do NOT claim features exist if you're unsure. Check the KB first. +- You do NOT do detailed project management. DELIVER artifacts are lightweight handover aids. +- You do NOT add services or components the user did not request. + +--- + +## Multi-Agent Mode (Codex) + +When running in Codex with multiple agents, this skill can be split: + +- **Agent 1 (Architect)**: Runs Phase 1 (DEFINE — discovery capture) and Phase 2 (DESIGN — composition) +- **Agent 2 (Validator)**: Runs WA validation on the composed architecture +- **Agent 3 (Renderer)**: Generates diagram + deck + PDF + any other outputs + +The Architect agent produces the structured YAML spec. The Validator annotates it with WA findings. The Renderer consumes the annotated spec and produces files. + +Each agent reads the same KB but focuses on its phase. The orchestrating agent (or the user) coordinates handoffs. diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..5229293 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,66 @@ +{ + "permissions": { + "allow": [ + "Bash(python:*)", + "Bash(which python3:*)", + "Read(//mnt/c/Users/Diego/Downloads/gol-ecommerce-oci-1-oracle/gol-ecommerce-on-oci-1-oracle/**)", + "Bash(python3:*)", + "Bash(pip search:*)", + "WebFetch(domain:pypi.org)", + "WebSearch", + "Bash(python3.12 -c \"\nfrom drawpyo.diagram.edges import line_ends_db\nprint\\('open:', line_ends_db['open']\\)\nprint\\('none:', line_ends_db['none']\\)\nprint\\('classic:', line_ends_db['classic']\\)\n\" 2>&1)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/simple-hybrid-adb-gg.yaml --output /tmp/test-hybrid.drawio 2>&1)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output /tmp/test-ecommerce.drawio 2>&1)", + "Bash(python3.12 -c \"from tools.oci_diagram_gen import OCIDiagramGenerator; print\\('Diagram Generator OK'\\)\" 2>&1)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/migration-adb-ha-dr.yaml --output /tmp/test-migration.drawio 2>&1)", + "Bash(git:*)", + "Bash(python3.12 tools/oci_deck_gen.py --spec examples/farma-sa-proposal-spec.yaml --output examples/farma-sa-proposal-v2.pptx --template '/mnt/c/Users/Diego/Downloads/Oracle Database Icon Library [July, 2024].pptx' 2>&1)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/simple-hybrid-adb-gg.yaml --output examples/simple-hybrid-adb-gg.drawio 2>&1)", + "Bash(python3.12 tools/oci_deck_gen.py --spec examples/farma-sa-proposal-spec.yaml --output examples/farma-sa-proposal-v3.pptx --template '/mnt/c/Users/Diego/Downloads/Oracle Database Icon Library [July, 2024].pptx' 2>&1)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output examples/ecommerce-architecture.drawio 2>&1)", + "Bash(chmod:*)", + "Bash(pip install:*)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec-select-ai-apex.yaml --output examples/select-ai-apex-architecture.drawio)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output examples/e-commerce-architecture.drawio)", + "Bash(SVG=\"/mnt/c/Users/Diego/Downloads/select-ai-apex-architecture-oracle/select-ai-apex-architecture-oracle/select-ai-apex-architecture-skill.svg\" grep -oP 'viewBox=\"\"\"\"[^\"\"\"\"]*\"\"\"\"' \"$SVG\" grep -oP 'width=\"\"\"\"[^\"\"\"\"]*\"\"\"\"' \"$SVG\")", + "Bash(SVG=\"/mnt/c/Users/Diego/Downloads/select-ai-apex-architecture-oracle/select-ai-apex-architecture-oracle/select-ai-apex-architecture-skill.svg\")", + "Read(//mnt/c/Users/Diego/Documents/oci-deal-accelerator/oci-deal-accelerator/**)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec-aws-oci-exacs.yaml --output examples/aws-oci-exacs-architecture.drawio)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec-adb-mysql-prod-test.yaml --output examples/adb-mysql-prod-test-architecture.drawio)", + "WebFetch(domain:www.oracle.com)", + "WebFetch(domain:aws.amazon.com)", + "WebFetch(domain:blogs.oracle.com)", + "WebFetch(domain:www.ateam-oracle.com)", + "Bash(python3.12 tools/oci_deck_gen.py --spec examples/proposal-spec-multicloud-dr.yaml --output examples/proposal-multicloud-dr.pptx 2>&1)", + "WebFetch(domain:apexadb.oracle.com)", + "Bash(curl -v --max-time 10 \"https://apexadb.oracle.com/ords/dbexpert/\")", + "Bash(curl -s --http1.1 --max-time 15 -w \"\\\\n---HTTP_CODE:%{http_code}---\" \"https://apexadb.oracle.com/ords/dbexpert/\")", + "Bash(curl -s --http1.1 --max-time 15 -H \"Accept: application/json\" -w \"\\\\n---HTTP_CODE:%{http_code}---\" \"https://apexadb.oracle.com/ords/dbexpert/metadata-catalog/\")", + "WebFetch(domain:www.thatjeffsmith.com)", + "WebFetch(domain:oracle-base.com)", + "Bash(curl -v -s -X GET 'https://apexadb.oracle.com/ords/dbexpert/db-services/v1/all' -H 'accept: application/json' -H 'User-Agent: Mozilla/5.0' --http1.1 --max-time 30 -o /tmp/dbexpert.json -w \"\\\\nHTTP_CODE:%{http_code}\\\\n\")", + "Bash(python3.12 tools/oci_bizcase_gen.py --spec examples/business-case-acme-retail.yaml --output examples/business-case-acme-retail.pptx 2>&1)", + "Bash(python3.12 tools/oci_bizcase_gen.py --spec examples/business-case-acme-retail.yaml --output examples/business-case-acme-retail-v2.pptx 2>&1)", + "Bash(python3.12 tools/oci_deck_gen.py --spec examples/proposal-spec.yaml --output examples/proposal-acme-retail.pptx 2>&1)", + "Read(//usr/local/bin/**)", + "Read(//usr/bin/**)", + "Bash(/usr/bin/python3.12 tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output examples/proposal-customer.pdf 2>&1)", + "Bash(apt-get install:*)", + "Bash(python3.12 -c \"\nfrom pptx import Presentation\nprs = Presentation\\('templates/Oracle_PPT-template_FY26.pptx'\\)\nprint\\(f'Total layouts: {len\\(prs.slide_layouts\\)}'\\)\nfor i, layout in enumerate\\(prs.slide_layouts\\):\n print\\(f' {i:3d}: {layout.name}'\\)\n\" 2>&1)", + "Bash(pip3 show:*)", + "Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec-aws-adb-dr-toronto.yaml --output examples/aws-adb-dr-toronto.drawio 2>&1)", + "Bash(dpkg -l)", + "Bash(npm list:*)", + "Bash(pip list:*)", + "Bash(pip3 install:*)", + "Read(//usr/local/lib/**)", + "Bash(find / -name python3* -type f -executable)", + "Bash(/usr/bin/python3 -m pip install python-pptx)", + "Bash(PYTHONPATH=/tmp/pptx_lib python3 -c \"from pptx import Presentation; print\\(''''OK''''\\)\")", + "Bash(PYTHONPATH=/tmp/pptx_ok python3 -c \"from pptx import Presentation; print\\(''''OK''''\\)\")", + "Bash(PYTHONPATH=/tmp/pptx_ok python3 /tmp/migrate_to_oracle.py)", + "Bash(PYTHONPATH=/tmp/pptx_ok python3 -c \"\nfrom pptx import Presentation\nprs = Presentation\\('/mnt/c/Users/Diego/Documents/oci-deal-accelerator/oci-deal-accelerator/templates/Oracle_PPT-template_FY26.pptx'\\)\nprint\\('Template has {} existing slides'.format\\(len\\(prs.slides\\)\\)\\)\n\")", + "Bash(grep:*)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4abf7b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +__pycache__/ +*.pyc +*.pyo +output/ +*.pptx +!examples/sample-output/*.pptx + +# OCI Toolkit binary (download from Oracle, don't commit) +kb/diagram/OCI Library.xml + +# Build prompt (internal) +COMPLETE-BUILD-PROMPT.md + +# Client-specific examples (never commit customer data) +examples/farma-sa-* + +# Skill-generated output folders (customer-specific, never commit) +examples/output-*/ + +# Exported screenshots (temporary) +examples/*.png + +# Exported ppts +examples/*.ppts + +# Exported diagrams +examples/*.drawio + +# Exported YAML files +examples/*.yaml + +# Exported PDFfiles +examples/*.pdf diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..583f268 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,161 @@ +# OCI Deal Accelerator + +AI skill aligned with Oracle ECAL framework (Define → Design → Deliver) that compresses the OCI SA's cycle from customer discovery to architecture proposal and delivery handover — from days to hours. + +## Project Structure + +``` +├── SKILL.md # LLM system prompt (Claude Code version) +├── AGENTS.md # This file (Codex agent instructions) +├── .agents/skills/oci-deal-accelerator/ +│ └── SKILL.md # Codex skill definition +├── README.md # Project overview and quick start +├── CLAUDE.md # Claude Code dev guide +├── Makefile # Build automation (make help for commands) +├── docs/ # ECAL phase guides (progressive disclosure) +│ ├── define-phase.md # DEFINE phase detailed guide +│ ├── design-phase.md # DESIGN phase detailed guide +│ ├── deliver-phase.md # DELIVER phase detailed guide +│ ├── engagement-tiers.md # Tier definitions and artifact matrix +│ └── ecal-gaps-backlog.md # Remaining ECAL gaps to implement +├── kb/ # Knowledge Base +│ ├── architecture-center/ # Oracle Architecture Center reference catalog +│ │ └── catalog.yaml # 123 curated reference architectures +│ ├── services/ # One YAML per OCI service (what, when, gotchas) +│ ├── patterns/ # Composable architecture blocks +│ │ ├── business-patterns.yaml # Business-level patterns (DEFINE) +│ │ ├── application-patterns.yaml # Application architecture patterns (DESIGN) +│ │ ├── service-tiering.yaml # Service tier model (Platinum/Gold/Silver/Bronze) +│ │ ├── architecture-principles.yaml # ECAL principles +│ │ ├── operational-raci.yaml # RACI matrix templates +│ │ ├── engagement-raci.yaml # ECAL engagement RACI (10 roles) +│ │ ├── business-drivers.yaml # 4-pillar business drivers +│ │ ├── ecal-artefacts-catalog.yaml # Complete ECAL 3.1 artefacts (60 items) +│ │ ├── environment-catalogue.yaml # Environment templates per tier +│ │ └── (database-ha/, database-dr/, networking-*, etc.) +│ ├── sizing/ # CPU conversion ratios, IOPS, scaling rules +│ ├── pricing/ # Simplified pricing for estimation +│ ├── competitive/ # AWS/Azure/GCP service mapping +│ ├── well-architected/ # 5-pillar WA Framework checklists +│ ├── compatibility/ # Feature matrices (ADB, etc.) +│ ├── diagram/ # Diagram styles (OCI Toolkit v24.2) +│ └── field-knowledge/ # Real-world gotchas and lessons learned +├── tools/ # Python tooling +│ ├── oci_deck_gen.py # .pptx slide deck generator (DEFAULT output) +│ ├── oci_pdf_gen.py # .pdf customer-facing document +│ ├── oci_diagram_gen.py # .drawio diagram generator +│ ├── oci_output.py # Output orchestrator +│ └── refresh_arch_catalog.py # Architecture Center catalog refresh +├── scripts/ +│ └── validate-architecture.py # WA validation engine +├── config/ +│ ├── service-categories.yaml # Service → color/category mapping +│ ├── output-formats.yaml # Output format specs and design standards +│ ├── engagement-tiers.yaml # Tier definitions +│ ├── workload-profile-schema.yaml # Workload profile field definitions +│ └── oracle-pptx-layouts.yaml # Oracle FY26 POTX layout mapping +├── templates/ # ECAL phase templates +│ ├── Oracle_PPT-template_FY26.pptx # Official Oracle FY26 template +│ ├── workload-profile.yaml # DEFINE: Discovery capture +│ ├── customer-profile.yaml # DEFINE: Strategic customer profiling +│ ├── strategy-map.yaml # DEFINE: Goals→Strategies→Capabilities→Enablers +│ ├── discovery-questionnaire.yaml # DESIGN: Structured IT collection +│ ├── business-case.yaml # DEFINE: Business case +│ ├── value-story.yaml # DEFINE: Business value hypothesis +│ ├── joint-engagement-plan.yaml # DEFINE: Engagement scoping +│ ├── scorecard.yaml # DESIGN: WA validation results +│ ├── adr-template.md # DESIGN: Architecture Decision Records +│ ├── operations-model.yaml # DESIGN: Day-2 operations design +│ ├── handover-document.yaml # DELIVER: Implementation handover +│ ├── go-live-checklist.yaml # DELIVER: Pre-cutover verification +│ ├── success-criteria.yaml # DELIVER: Post go-live metrics +│ └── lessons-learned.yaml # DELIVER: Engagement retrospective +└── examples/ # Example specs and generated outputs +``` + +## ECAL-Aligned Workflow + +``` +DEFINE (Ideate → Validate → Plan) → DESIGN (Current → Future → Confirm) → DELIVER (Adopt → Operate → Improve) +``` + +1. **DEFINE**: Discovery notes → Workload Profile + Value Story + Joint Engagement Plan +2. **DESIGN**: Current state → Architecture + Operations Model → Solution Proposal +3. **DELIVER**: Handover Document → Go-Live Checklist + Success Criteria → Lessons Learned + +Phase details in `docs/` — skill references them via progressive disclosure. + +## Running Tools + +```bash +# Generate slide deck (default output) +python tools/oci_deck_gen.py --spec examples/proposal-spec.yaml --output proposal.pptx + +# Generate customer-facing PDF (branded, no internal KB refs) +python tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output proposal.pdf + +# Generate diagram +python tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output arch.drawio + +# Run WA validation +python scripts/validate-architecture.py \ + --profile examples/sample-workload-profile.yaml \ + --architecture examples/sample-architecture.yaml \ + --output scorecard.yaml + +# Output orchestrator (multiple formats at once) +python tools/oci_output.py --spec examples/proposal-spec.yaml --format full --output-dir output/ + +# Build automation +make help # show all commands +make full # generate all outputs +make validate # run WA validation +make lint # check YAML syntax +``` + +## Output Formats + +Default output is a **slide deck (.pptx)** — adapts to engagement tier (6-8 / 10-12 / 12-16 slides). + +``` +deck ← default +deck + drawio ← + editable diagram +deck + doc ← + technical document +deck + xlsx ← + cost spreadsheet +deck + pdf ← + customer-facing PDF (branded, no internal refs) +pdf ← customer PDF only +full ← everything (pptx + drawio + docx + xlsx + pdf) +doc only ← technical doc without slides +deliver ← handover + go-live checklist + success criteria +``` + +## Output Principle: Readable First, YAML Second + +Every skill option that produces output MUST generate **readable, human-consumable output** as the primary deliverable. YAML files are structured backing data — they are never the final output shown to the user. + +- **Options 1-4, 8:** Primary output is `.pptx` and/or `.drawio`. YAML specs saved alongside. +- **Option 5 (WA Review):** Primary output is the **formatted terminal scorecard** (banner + pillar bars + gap tables + recommendations). YAML saved to `examples/` as backing data. +- **Option 12 (ECAL Readiness):** Primary output is the **formatted terminal scorecard** (phase scores + artefact checklist + gap analysis). YAML saved to `examples/` as backing data. + +If a task generates YAML without the corresponding readable output, the task is **incomplete**. Always present the formatted result first, then list files saved. + +## Key Principles + +- **Empirical over theoretical** — cite metrics, not marketing +- **Simplicity first** — complexity must be earned +- **Honest about limitations** — acknowledge OCI gaps +- **Composable** — patterns combine, not monolithic templates +- **KB is the moat** — field experience, not documentation regurgitation +- **ECAL-aligned** — Define → Design → Deliver with iterative checkpoints +- **Readable outputs** — YAML is backing data, never the deliverable +- **No hallucinated architecture** — NEVER add services/components/regions the user didn't mention. When remediating WA gaps, apply minimum targeted fixes to the existing architecture; don't replace it with a generic "ideal" one. ASK before adding new services. + +## Welcome Flow + +When the user starts a conversation without providing discovery notes or a specific request (e.g., a greeting or empty context), present the welcome message and capability menu defined in the skill's SKILL.md § Welcome Flow. Specifically: + +1. Show the welcome banner and numbered capability menu +2. Follow the behavior rules for each option (1-12) +3. After completing any task, offer the next-step menu (A-G) +4. If the user sends discovery notes directly, skip the menu and go straight to full proposal flow +5. If the user asks a specific question, skip the menu and answer directly diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ebcdb46 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,159 @@ +# OCI Deal Accelerator + +AI skill aligned with Oracle ECAL framework (Define → Design → Deliver) that compresses the OCI SA's cycle from customer discovery to architecture proposal and delivery handover — from days to hours. + +## Project Structure + +``` +├── SKILL.md # LLM system prompt (the skill itself) +├── AGENTS.md # Codex agent instructions (mirrors CLAUDE.md) +├── .agents/skills/oci-deal-accelerator/ +│ └── SKILL.md # Codex skill definition (full skill, Codex format) +├── README.md # Project overview and quick start +├── CLAUDE.md # This file (dev guide) +├── Makefile # Build automation (make help for commands) +├── docs/ # ECAL phase guides (progressive disclosure from SKILL.md) +│ ├── define-phase.md # DEFINE phase detailed guide +│ ├── design-phase.md # DESIGN phase detailed guide +│ ├── deliver-phase.md # DELIVER phase detailed guide +│ ├── engagement-tiers.md # Tier definitions and artifact matrix +│ └── ecal-gaps-backlog.md # Remaining ECAL gaps to implement (20 items) +├── kb/ # Knowledge Base +│ ├── architecture-center/ # Oracle Architecture Center reference catalog +│ │ └── catalog.yaml # 123 curated reference architectures +│ ├── services/ # One YAML per OCI service (what, when, gotchas) +│ ├── patterns/ # Composable architecture blocks +│ │ ├── business-patterns.yaml # Business-level patterns (DEFINE) +│ │ ├── application-patterns.yaml # Application architecture patterns (DESIGN) +│ │ ├── service-tiering.yaml # Service tier model (Platinum/Gold/Silver/Bronze) +│ │ ├── architecture-principles.yaml # ECAL principles (Design/Deployment/Service) +│ │ ├── operational-raci.yaml # RACI matrix templates (3 operational models) +│ │ ├── engagement-raci.yaml # ECAL engagement RACI (10 roles, all 9 steps) +│ │ ├── business-drivers.yaml # 4-pillar business drivers + hypothesis families +│ │ ├── ecal-artefacts-catalog.yaml # Complete ECAL 3.1 artefacts catalog (60 items) +│ │ ├── environment-catalogue.yaml # Environment templates per tier +│ │ ├── database-ha-adb-s.yaml +│ │ ├── database-dr-cross-region.yaml +│ │ ├── networking-basic.yaml +│ │ └── (dirs: database-ha/, database-dr/, networking-hub-spoke/, etc.) +│ ├── sizing/ # CPU conversion ratios, IOPS, scaling rules +│ ├── pricing/ # Simplified pricing for estimation +│ ├── competitive/ # AWS/Azure/GCP service mapping +│ ├── well-architected/ # 5-pillar WA Framework checklists +│ ├── compatibility/ # Feature matrices (ADB, etc.) +│ ├── diagram/ # Diagram styles (OCI Toolkit v24.2) +│ └── field-knowledge/ # Real-world gotchas and lessons learned +├── tools/ # Python tooling +│ ├── oci_deck_gen.py # .pptx slide deck generator (DEFAULT output) +│ ├── oci_pdf_gen.py # .pdf customer-facing document (branded, no internal refs) +│ ├── oci_diagram_gen.py # .drawio diagram generator +│ ├── oci_output.py # Output orchestrator +│ └── refresh_arch_catalog.py # Architecture Center catalog refresh tool +├── scripts/ # Validation and utilities +│ └── validate-architecture.py # WA validation engine +├── config/ +│ ├── service-categories.yaml # Service → color/category mapping +│ ├── output-formats.yaml # Output format specs and design standards +│ ├── engagement-tiers.yaml # Tier definitions (small/standard/complex) +│ ├── workload-profile-schema.yaml # Workload profile field definitions +│ └── oracle-pptx-layouts.yaml # Oracle FY26 POTX layout mapping for deck generation +├── templates/ # ECAL phase templates +│ ├── Oracle_PPT-template_FY26.pptx # Official Oracle FY26 PowerPoint template +│ ├── workload-profile.yaml # DEFINE: Discovery capture +│ ├── customer-profile.yaml # DEFINE: Strategic customer profiling (internal) +│ ├── strategy-map.yaml # DEFINE: Goals→Strategies→Capabilities→Enablers +│ ├── discovery-questionnaire.yaml # DESIGN: Structured customer IT collection +│ ├── business-case.yaml # DEFINE: Business case for customer approval +│ ├── value-story.yaml # DEFINE: Business value hypothesis +│ ├── joint-engagement-plan.yaml # DEFINE: Engagement scoping +│ ├── scorecard.yaml # DESIGN: WA validation results +│ ├── adr-template.md # DESIGN: Architecture Decision Records +│ ├── operations-model.yaml # DESIGN: Day-2 operations design +│ ├── handover-document.yaml # DELIVER: Implementation handover +│ ├── go-live-checklist.yaml # DELIVER: Pre-cutover verification +│ ├── success-criteria.yaml # DELIVER: Post go-live metrics +│ └── lessons-learned.yaml # DELIVER: Engagement retrospective +├── codex/ # Codex setup guide (README only) +└── examples/ # Example specs and generated outputs +``` + +## ECAL-Aligned Workflow + +``` +DEFINE (Ideate → Validate → Plan) → DESIGN (Current → Future → Confirm) → DELIVER (Adopt → Operate → Improve) +``` + +1. **DEFINE**: Discovery notes → Workload Profile + Value Story + Joint Engagement Plan +2. **DESIGN**: Current state (people/process/tech) → Architecture + Operations Model → Solution Proposal +3. **DELIVER**: Handover Document → Go-Live Checklist + Success Criteria → Lessons Learned + +Phase details in `docs/` — SKILL.md references them via progressive disclosure. + +## Output Formats + +Default output is a **slide deck (.pptx)** — adapts to engagement tier (6-8 / 10-12 / 12-16 slides). + +``` +deck ← default +deck + drawio ← + editable diagram +deck + doc ← + technical document +deck + xlsx ← + cost spreadsheet +deck + pdf ← + customer-facing PDF (branded, no internal refs) +pdf ← customer PDF only +full ← everything (pptx + drawio + docx + xlsx + pdf) +doc only ← technical doc without slides +deliver ← handover + go-live checklist + success criteria +``` + +## Running Tools + +```bash +# Generate slide deck (default output) +python tools/oci_deck_gen.py --spec examples/proposal-spec.yaml --output proposal.pptx + +# Generate customer-facing PDF (branded, no internal KB refs) +python tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output proposal.pdf +python tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output proposal.pdf --diagram arch.png + +# Generate diagram +python tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output arch.drawio + +# Run WA validation +python scripts/validate-architecture.py \ + --profile examples/sample-workload-profile.yaml \ + --architecture examples/sample-architecture.yaml \ + --output scorecard.yaml + +# Output orchestrator (multiple formats at once) +python tools/oci_output.py --spec examples/proposal-spec.yaml --format full --output-dir output/ + +# Refresh Architecture Center catalog +python tools/refresh_arch_catalog.py --whats-new # crawl What's New pages +python tools/refresh_arch_catalog.py --url # add single entry +python tools/refresh_arch_catalog.py --validate # validate catalog + +# Build automation +make help # show all commands +make full # generate all outputs +make validate # run WA validation +make lint # check YAML syntax +``` + +## Key Principles + +- **Empirical over theoretical** — cite metrics, not marketing +- **Simplicity first** — complexity must be earned +- **Honest about limitations** — acknowledge OCI gaps +- **Composable** — patterns combine, not monolithic templates +- **KB is the moat** — field experience, not documentation regurgitation +- **ECAL-aligned** — Define → Design → Deliver with iterative checkpoints + +## Welcome Flow + +When the user starts a conversation without providing discovery notes or a specific request (e.g., a greeting like "hola", "hey", or empty context), present the welcome message and capability menu defined in `SKILL.md` § Welcome Flow. Specifically: + +1. Show the welcome banner and numbered capability menu from SKILL.md +2. Follow the behavior rules for each option (1-10) +3. After completing any task, offer the next-step menu (A-E) +4. If the user sends discovery notes directly, skip the menu and go straight to full proposal flow +5. If the user asks a specific question, skip the menu and answer directly diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f073bf2 --- /dev/null +++ b/Makefile @@ -0,0 +1,69 @@ +# OCI Deal Accelerator — Build Automation + +.PHONY: help install test validate example diagram deck full clean lint codex-package update-icons + +PYTHON ?= python3.12 +SPEC_DIR = examples +OUTPUT_DIR = examples/sample-output + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +install: ## Install Python dependencies + pip install -r requirements.txt + +validate: ## Validate imports and configs + $(PYTHON) -c "from tools.oci_diagram_gen import OCIDiagramGenerator; print('Diagram Generator OK')" + $(PYTHON) -c "from tools.oci_deck_gen import OCIDeckGenerator; print('Deck Generator OK')" + $(PYTHON) -c "import yaml; yaml.safe_load(open('config/workload-profile-schema.yaml')); print('Schema OK')" + $(PYTHON) -c "import yaml; yaml.safe_load(open('config/service-categories.yaml')); print('Categories OK')" + @echo "All validations passed" + +example: ## Generate sample architecture diagram (.drawio) + @mkdir -p $(OUTPUT_DIR) + $(PYTHON) tools/oci_diagram_gen.py \ + --spec $(SPEC_DIR)/migration-adb-ha-dr.yaml \ + --output $(OUTPUT_DIR)/architecture.drawio + @echo "Generated $(OUTPUT_DIR)/architecture.drawio" + +diagram: example ## Alias for example + +deck: ## Generate sample proposal deck (.pptx) + @mkdir -p $(OUTPUT_DIR) + $(PYTHON) tools/oci_deck_gen.py \ + --spec $(SPEC_DIR)/migration-adb-ha-dr.yaml \ + --output $(OUTPUT_DIR)/architecture-proposal.pptx + @echo "Generated $(OUTPUT_DIR)/architecture-proposal.pptx" + +full: ## Generate all outputs via orchestrator + $(PYTHON) tools/oci_output.py \ + --spec $(SPEC_DIR)/migration-adb-ha-dr.yaml \ + --output-dir $(OUTPUT_DIR) \ + --format full + @echo "Generated all outputs in $(OUTPUT_DIR)/" + +codex-package: ## Package as Codex skill + @mkdir -p codex/tools + cp tools/oci_diagram_gen.py codex/tools/ + cp tools/oci_deck_gen.py codex/tools/ + cp tools/oci_output.py codex/tools/ + cp -r kb codex/ + cp -r config codex/ + @echo "Codex skill package ready in codex/" + +update-icons: ## Re-extract OCI icons after updating OCI Library.xml + $(PYTHON) tools/oci_icon_extractor.py + @echo "Updated kb/diagram/oci-icons.json from OCI Library.xml" + +clean: ## Remove generated output files + rm -f examples/sample-output/*.drawio + rm -f examples/sample-output/*.pptx + rm -f examples/sample-output/*.xlsx + rm -f examples/sample-output/*.docx + +lint: ## Check YAML files for syntax errors + @echo "Checking YAML files..." + @find kb/ config/ examples/ -name '*.yaml' -exec $(PYTHON) -c \ + "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" {} \; \ + -print 2>&1 | grep -v "^$$" || echo "All YAML files valid." diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f70cfb --- /dev/null +++ b/README.md @@ -0,0 +1,393 @@ +# OCI Deal Accelerator + +An AI-powered skill that acts as a **force multiplier for OCI Solutions Architects**. Feed it raw discovery notes from a customer call and get back a complete, defensible architecture proposal — ready to present. + +What normally takes an SA days of work (structuring notes, designing architecture, building decks, estimating costs, validating against Well-Architected) gets compressed into a single conversation. The skill doesn't just generate documents — it applies field-tested patterns, real pricing data, and lessons learned from actual OCI engagements to produce artifacts you can confidently put in front of a customer. + +**Fully aligned with Oracle's ECAL 3.1 framework** — covers all 9 steps (Ideate → Validate → Plan → Current → Future → Confirm → Adopt → Operate → Improve) with a catalog of 60 artefacts, engagement RACIs for 10 roles, and an ECAL Readiness Scorecard to track engagement completeness. + +### Key differentiators + +- **ECAL 3.1 native** — engagement RACI, artefact catalog, readiness scoring, and lessons learned per step baked into the workflow +- **Field knowledge, beyond the docs** — built-in KB with real gotchas, workarounds, and sizing lessons from production OCI deployments +- **Honest about trade-offs** — flags OCI limitations and competitive gaps instead of overselling +- **Multi-cloud aware** — supports hybrid/multi-cloud diagrams (AWS, Azure, GCP icons) and considers options like ADB Multicloud before recommending full migration +- **End-to-end coverage** — from discovery notes to go-live checklist, not just the architecture slide + +## What It Produces + +From unstructured input (meeting notes, emails, Slack threads), the skill generates: + +| Artifact | Format | ECAL Phase | ECAL Step | +|---|---|---|---| +| **Customer Profile** — strategic goals, Oracle footprint, industry analysis | YAML | Define | Ideate | +| **Strategy Map** — goals → strategies → capabilities → enablers | YAML | Define | Ideate | +| **Workload Profile** — structured discovery capture | YAML | Define | Ideate | +| **Value Story** — business hypothesis linked to OCI outcomes | YAML | Define | Ideate | +| **Business Case** — TCO, ROI, value drivers, risk assessment | .pptx | Define | Ideate | +| **Joint Engagement Plan** — scope, resources, timeline | YAML | Define | Plan | +| **Discovery Questionnaire** — structured IT landscape collection | YAML | Design | Current | +| **Architecture Diagram** — official Oracle visual style, multi-cloud support | .drawio | Design | Future | +| **Slide Deck** — 6-15 slides scaled to engagement complexity | .pptx | Design | Confirm | +| **Customer PDF** — branded, no internal KB references | .pdf | Design | Confirm | +| **Cost Estimate** — BYOL vs PAYG breakdown with assumptions | YAML | Design | Future | +| **Well-Architected Scorecard** — 5-pillar automated validation | YAML | Design | Future | +| **Operations Model** — day-2 monitoring, patching, incident response | YAML | Design | Future | +| **ECAL Readiness Scorecard** — 60-artefact gap analysis per phase | Text | All | All | +| **Delivery Artifacts** — handover, go-live checklist, success criteria | YAML | Deliver | Adopt | + +## Quick Start + +Feed `SKILL.md` as a system prompt to any LLM (Claude, GPT-4o, Gemini Pro). Then give it your discovery notes: + +``` +Here are my notes from the discovery call with Acme Corp: + +- 3 Oracle 19c databases on Exadata X8M on-prem, largest is 4TB OLTP +- Using GoldenGate for replication to reporting DB +- Need 99.95% availability, PCI compliance +- Seasonal peaks 3x normal during Black Friday +- Want to reduce costs, current Oracle licensing is $2M/year +- Team has 2 Oracle DBAs, no cloud experience +- CTO wants to move to cloud in 6 months +- Comparing with AWS +``` + +The skill follows the ECAL workflow automatically: DEFINE (value story) → DESIGN (architecture) → DELIVER (handover). + +## Output Formats + +``` +deck ← default (.pptx) +deck + drawio ← + editable diagram +deck + doc ← + technical document +deck + xlsx ← + cost spreadsheet +deck + pdf ← + customer-facing PDF (branded, no internal refs) +pdf ← customer PDF only +bizcase ← business case deck for customer approval +full ← everything (pptx + drawio + docx + xlsx + pdf) +deliver ← handover + go-live checklist + success criteria +``` + +## Knowledge Base + +The KB is the moat — field experience, not documentation regurgitation. + +### Contributing to the KB + +Any SA can contribute knowledge to the skill. The KB lives in `kb/` as editable YAML files. Here's where each type of contribution goes: + +| What you want to contribute | Where it goes | How | +|---|---|---| +| **Field caveat or workaround** | `kb/field-findings/tracker.yaml` | Menu option 11, or `python tools/findings_cli.py add` | +| **Lesson learned** | `kb/field-knowledge/lessons-learned.yaml` | Edit YAML directly | +| **Undocumented real-world limit** | `kb/field-knowledge/real-world-limits.yaml` | Edit YAML directly | +| **Service caveat** | `kb/field-knowledge/gotchas.yaml` | Edit YAML directly | +| **OCI service info** | `kb/services/.yaml` | Create or edit the service YAML | +| **Architecture pattern** | `kb/patterns/` | Add YAML following existing format | +| **Architecture Center reference** | `kb/architecture-center/catalog.yaml` | `python tools/refresh_arch_catalog.py --url ` | +| **Updated pricing** | `kb/pricing/.yaml` | Edit with data from [OCI Price List](https://www.oracle.com/cloud/price-list/) | +| **Feature compatibility** | `kb/compatibility/adb-feature-matrix.yaml` | Edit the matrix, mark `verified_in_field: true` | +| **Competitive comparison** | `kb/competitive/` | Add or edit YAML with real pros AND cons | + +**Fastest path**: if you hit something in an engagement that another SA should know about, use menu option 11 (Report a field finding) — the skill walks you through the format and adds it to the tracker automatically. + +### OCI Pricing (13 categories) + +Comprehensive pricing from the [OCI Price List](https://www.oracle.com/cloud/price-list/) covering compute, database, storage, networking, AI/ML, containers, integration, observability, developer, analytics, hybrid/VMware, and security services. Source date: 2025-09-11. + +``` +kb/pricing/ +├── compute.yaml # VMs, bare metal, GPU (B200/B300/H200/H100/A100/L40S) +├── database.yaml # ADB (ECPU), DBCS, ExaCS, MySQL, PostgreSQL, NoSQL, Redis +├── storage.yaml # Block, object, file, data transfer +├── networking.yaml # LB, NLB, FastConnect, DNS, egress +├── ai-ml.yaml # GenAI, Data Science, Vision, Speech, Digital Assistant +├── containers-serverless.yaml # OKE, Functions, Container Instances +├── integration.yaml # OIC, API Mgmt, Streaming, Queue, GoldenGate +├── observability.yaml # Monitoring, Logging, APM, DR, Email +├── developer.yaml # APEX, Visual Builder, DevOps, Blockchain +├── analytics.yaml # OAC, Essbase, Big Data, Data Flow +├── hybrid-vmware.yaml # OCVS, Compute C@C, Managed Mac, Roving Edge +├── security.yaml # Access Governance, Vault, Cloud Guard +└── pricing-models.yaml # PAYG, UCM, Reserved, BYOL +``` + +### DBExpert Database Services Catalog + +35 Oracle Database services with full capabilities, multicloud availability (Azure/GCP/AWS locations), SLAs, MAA medals, compliance certifications, and certified Oracle applications. Sourced from the [Oracle DBExpert API](https://oracle-dbexpert.github.io/swagger/). + +``` +kb/services/dbexpert-catalog.yaml # 35 services, queryable by capability +kb/services/dbexpert-api-reference.yaml # API endpoints and refresh procedure +``` + +### Architecture Center Catalog + +**123 Oracle Architecture Center reference architectures** (`kb/architecture-center/catalog.yaml`) covering Database@Azure/AWS/Google Cloud, networking, security, AI/ML, migration, HA/DR, and more. + +During **Phase 2 (DESIGN)**, the skill automatically matches the proposed architecture against the catalog: + +- **STRONG MATCH** (≥2 service + ≥1 tag) — cited in the Architecture Decisions slide +- **MODERATE MATCH** (≥1 service + ≥2 tag) — referenced in the technical document + +```bash +python tools/refresh_arch_catalog.py --whats-new # crawl What's New pages +python tools/refresh_arch_catalog.py --url # add a single entry +python tools/refresh_arch_catalog.py --validate # validate catalog integrity +``` + +### Feature Compatibility Matrix + +Before recommending a deployment type, the skill checks `kb/compatibility/adb-feature-matrix.yaml` — a field-verified matrix of what works, what doesn't, and what has caveats the docs don't mention. + +```bash +python tools/feature_matrix_cli.py check "Auto Scaling" adb_s 23ai +python tools/feature_matrix_cli.py compare adb_s exacs 23ai +python tools/feature_matrix_cli.py gaps dbcs_ee 23ai +``` + +### Field Findings Tracker + +Real issues, limitations, and workarounds encountered during customer engagements. + +```bash +python tools/findings_cli.py search "maintenance window" +python tools/findings_cli.py add +python tools/findings_cli.py stats +``` + +### Competitive Positioning + +Honest AWS/Azure/GCP comparisons (`kb/competitive/`) that cover genuine advantages AND genuine gaps. No marketing — only field-verified facts. + +## ECAL Readiness Score + +Option 12 in the menu scores an engagement against the complete ECAL 3.1 framework: + +``` +══════════════════════════════════════════ +📊 ECAL READINESS SCORECARD +══════════════════════════════════════════ +Customer: Acme Corp +Current Phase: DESIGN +Overall Readiness: 62% 🟡 + +── DEFINE ──────────────────── 85% 🟢 +✅ Value Story +✅ Workload Profile +🟡 Customer Profile (missing Oracle footprint) +❌ Strategy Map +✅ Joint Engagement Plan + +── DESIGN ──────────────────── 55% 🟠 +✅ Future State Architecture +✅ Cost Estimate +🟡 Discovery Questionnaire (partial) +❌ Operational RACI +❌ Recovery Model +... + +── TOP 5 GAPS ── +1. ❌ Strategy Map — links solution to business goals +2. ❌ Operational RACI — who runs what post go-live +... +══════════════════════════════════════════ +``` + +The scorecard evaluates each of the **60 ECAL artefacts** from `kb/patterns/ecal-artefacts-catalog.yaml`, weighted by phase (DEFINE 25%, DESIGN 50%, DELIVER 25%). Readiness levels: 🟢 80%+ | 🟡 60-79% | 🟠 40-59% | 🔴 <40%. + +After scoring, the skill offers to generate missing artefacts, fix the top gap, or export the scorecard as a slide. + +## ECAL 3.1 Coverage + +The KB includes comprehensive ECAL 3.1 process knowledge: + +| KB File | What it covers | +|---|---| +| `kb/patterns/ecal-artefacts-catalog.yaml` | All 60 ECAL artefacts with description, purpose, and skill support level | +| `kb/patterns/engagement-raci.yaml` | RACI matrices for 10 roles across all 9 ECAL steps + lessons learned | +| `kb/patterns/business-drivers.yaml` | 4-pillar framework (Strategic/Financial/BizOps/ITOps) + hypothesis families | +| `kb/patterns/architecture-principles.yaml` | Design, Deployment, and Service principles from ECAL | +| `kb/patterns/operational-raci.yaml` | 3 operational models (fully managed, co-managed, self-managed) | +| `kb/patterns/service-tiering.yaml` | Platinum/Gold/Silver/Bronze service tier definitions | +| `kb/patterns/environment-catalogue.yaml` | Environment templates per tier (prod, pre-prod, dev, DR) | +| `templates/customer-profile.yaml` | Strategic customer profiling (goals, footprint, industry) | +| `templates/strategy-map.yaml` | Goals → Strategies → Capabilities → Enablers mapping | +| `templates/discovery-questionnaire.yaml` | Structured IT landscape collection with prioritization matrix | + +## Business Case Builder + +Option 8 in the menu generates a business case deck for customer internal approval: + +| Slide | Layout | Content | +|-------|--------|---------| +| Cover | Dark - Title_Pillar | Customer name + subtitle | +| Executive Summary | Impact Statement | Bold 1-sentence opportunity | +| Business Drivers | Multi Statement | 3 key drivers: Why now | +| TCO Comparison | Blank + Table | Current vs OCI (3-5 year) | +| ROI Headline | Blank + Metric | Big number (e.g., "2080% ROI") | +| Value Drivers | Blank + Cards | 4 categories: Cost, Risk, Agility, Innovation | +| Risk Assessment | Blank + 2-Column | Migration risks vs Do-nothing risks | +| Roadmap | Blank + Timeline | Implementation phases | +| Recommendation | Dark Impact | Clear ask with next steps | + +Uses the **Oracle FY26 official PowerPoint template** with Redwood design system. + +```bash +python tools/oci_bizcase_gen.py --spec business-case.yaml --output business-case.pptx +``` + +## Welcome Flow + +When you start a conversation without discovery notes, the skill presents an interactive menu: + +``` +🏗️ OCI Deal Accelerator +━━━━━━━━━━━━━━━━━━━━━━━ + +Compresses your SA cycle from discovery to proposal — days to hours. +Aligned with Oracle's ECAL framework (Define → Design → Deliver). + +What do you want to do? + + DESIGN & PROPOSE + ───────────────── + 1. 📋 Full proposal — notes → architecture + deck + diagram + costs + 2. 📐 Architecture diagram — YAML or description → .drawio + 3. 📊 Slide deck — architecture → .pptx + 4. 💰 Cost estimate — services + sizing → PAYG vs BYOL + + VALIDATE & CHECK + ───────────────── + 5. ✅ Well-Architected review — 5-pillar scoring + gaps + 6. 🔍 Feature compatibility — "does ADB-S support X?" + 7. 🆚 Competitive comparison — honest pros & cons vs AWS/Azure/GCP + + STRATEGY & BUSINESS + ───────────────── + 8. 💼 Business case — TCO, ROI, value drivers → exec deck + + KNOWLEDGE BASE + ───────────────── + 9. 🔎 Field findings — real issues + workarounds + 10. 📚 Reference architecture — Architecture Center lookup + 11. ➕ Report finding — log a gotcha from your engagement + + ECAL GOVERNANCE + ───────────────── + 12. 📊 ECAL readiness score — 60-artefact gap analysis + +━━━━━━━━━━━━━━━━━━━━━━━ +Pick a number, or just describe what you need. +``` + +If you paste discovery notes directly, the skill skips the menu and goes straight to the full proposal flow. + +## Tools + +```bash +# Slide deck generation (technical proposal) +python tools/oci_deck_gen.py --spec examples/proposal-spec.yaml --output proposal.pptx + +# Customer-facing PDF (branded, no internal KB refs) +python tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output proposal.pdf +python tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output proposal.pdf --diagram arch.png + +# Business case deck +python tools/oci_bizcase_gen.py --spec business-case.yaml --output business-case.pptx + +# Architecture diagram +python tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output arch.drawio + +# Output orchestrator (multiple formats at once) +python tools/oci_output.py --spec examples/proposal-spec.yaml --format full --output-dir output/ + +# Architecture Center catalog +python tools/refresh_arch_catalog.py --validate +python tools/refresh_arch_catalog.py --whats-new + +# Feature compatibility +python tools/feature_matrix_cli.py check "Auto Scaling" adb_s 23ai +python tools/feature_matrix_cli.py compare adb_s exacs 23ai + +# Field findings +python tools/findings_cli.py search "maintenance window" +python tools/findings_cli.py stats + +# KB governance +python tools/kb_cli.py health + +# WA validation +python scripts/validate-architecture.py \ + --profile examples/sample-workload-profile.yaml \ + --architecture examples/sample-architecture.yaml \ + --output scorecard.yaml + +# Build automation +make help +``` + +## Multi-LLM Support + +The skill is LLM-agnostic. The same KB and templates work across platforms: + +| Platform | How to use | +|----------|-----------| +| **Claude Code** | Uses `SKILL.md` + `CLAUDE.md` natively | +| **OpenAI Codex** | Uses `AGENTS.md` + `.agents/skills/` (see below) | +| **ChatGPT / GPT-4o** | Paste SKILL.md as system prompt | +| **Gemini Pro** | Paste SKILL.md as system instruction | + +### OpenAI Codex Setup + +The repo is 100% compatible with [Codex CLI](https://github.com/openai/codex). Codex auto-discovers the skill on startup: + +``` +├── AGENTS.md # Project instructions (Codex reads automatically) +├── .agents/skills/oci-deal-accelerator/ +│ └── SKILL.md # Full skill definition (YAML frontmatter + instructions) +└── codex/ + └── README.md # Detailed setup guide +``` + +```bash +# Just run Codex from the project root — auto-discovers everything +cd oci-deal-accelerator +codex + +# Or load the skill explicitly +codex --skill oci-deal-accelerator +``` + +For temporary overrides (e.g., focusing on a specific customer), create `AGENTS.override.md` at the project root — it takes highest priority. + +Full setup details: [`codex/README.md`](codex/README.md) + +## Roadmap + +### ECAL Completeness (see `docs/ecal-gaps-backlog.md` for full list) +- Integration Catalog template — detailed integration mapping with data flows +- Cloud Operating Framework — 52-week operational plan (9 capability areas) +- OCI Operationalization Framework — 5-milestone deployment methodology +- POD (Pool of Databases) pattern — large-scale DB consolidation +- Banking/Financial compliance pattern — EBA/FCA/PRA mapped to OCI services +- ExaCC managed service pattern — complete ExaCC + ZDLRA/ZFS/OEM/OKV + +### Platform +- Interactive what-if cost simulator (adjust ECPU/storage/commitment live) +- Automated migration complexity scoring from discovery notes +- Multi-region DR cost optimizer +- Engagement timeline generator (Gantt-style from Joint Engagement Plan) +- DBExpert API auto-refresh for database service catalog +- KB vectorizada en base de datos (RAG) — almacenar knowledge base en OCI 23ai con embeddings para busqueda semantica en lugar de lookup estatico por YAML + +## Requirements + +- Python 3.8+ +- `pip install pyyaml python-pptx drawpyo requests beautifulsoup4 lxml reportlab` +- No OCI CLI or SDK needed (the skill designs, it doesn't deploy) + +## License + +Internal use. Not for distribution. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..ddc0323 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,601 @@ +--- +name: OCI Deal Accelerator +description: Compresses the OCI Solutions Architect's cycle from customer discovery to architecture proposal and delivery handover. Aligned with Oracle ECAL framework (Define, Design, Deliver). Use when processing discovery notes, composing OCI architectures, generating proposals, planning solution delivery, or producing handover artifacts. +--- + +# OCI Deal Accelerator + +You are the **OCI Deal Accelerator**, an AI skill that helps OCI Solutions Architects compress the cycle from customer discovery to architecture proposal — from days to hours. + +You follow the **Oracle ECAL framework** (Define → Design → Deliver) to produce structured, defensible OCI architectures with all supporting artifacts. + +--- + +## Welcome Flow + +When the user starts a conversation without providing discovery notes or a specific request, present the welcome message and capability menu. + +### Welcome Message + +``` +🏗️ OCI Deal Accelerator +━━━━━━━━━━━━━━━━━━━━━━━ + +Compresses your SA cycle from discovery to proposal — days to hours. +Aligned with Oracle's ECAL framework (Define → Design → Deliver). + +What do you want to do? +``` + +### Capability Menu + +Present these options as a compact numbered list. Each option has a bold title followed by a short italic hint on the same line. Keep it visually minimal — one line per option, no multi-line descriptions. + +``` + DESIGN & PROPOSE + ───────────────── + 1. 📋 Full proposal — *notes → architecture + deck + diagram + costs* + 2. 📐 Architecture diagram — *YAML or description → .drawio* + 3. 📊 Slide deck — *architecture → .pptx* + 4. 💰 Cost estimate — *services + sizing → PAYG vs BYOL* + + VALIDATE & CHECK + ───────────────── + 5. ✅ Well-Architected review — *5-pillar scoring + gaps* + 6. 🔍 Feature compatibility — *"does ADB-S support X?"* + 7. 🆚 Competitive comparison — *honest pros & cons vs AWS/Azure/GCP* + + STRATEGY & BUSINESS + ───────────────── + 8. 💼 Business case — *TCO, ROI, value drivers → exec deck* + + KNOWLEDGE BASE + ───────────────── + 9. 🔎 Field findings — *real issues + workarounds* + 10. 📚 Reference architecture — *Architecture Center lookup* + 11. ➕ Report finding — *log a gotcha from your engagement* + + ECAL GOVERNANCE + ───────────────── + 12. 📊 ECAL readiness score — *60-artefact gap analysis* + +━━━━━━━━━━━━━━━━━━━━━━━ +Pick a number, or just describe what you need. +``` + +### Behavior Rules + +- If the user picks **1**, ask: "Paste your discovery notes (meeting notes, emails, whatever you have)." +- If the user picks **2**, ask: "Describe the architecture you want to diagram, or paste a YAML spec if you have one." +- If the user picks **3**, ask: "Describe the architecture or paste the spec. I'll generate the deck." +- If the user picks **4**, ask: "What services and sizing? (e.g., 'ADB-S 8 OCPU + 2 VMs + FastConnect')" +- If the user picks **5**, ask: "Describe your architecture or paste the spec. I'll run the 5-pillar review." Then follow the WA review flow: + 1. Parse input to build a workload profile YAML (flags) and architecture YAML + 2. If input is a `.pptx` file, extract text content from all slides to infer architecture and workload context + 3. Run `scripts/validate-architecture.py` with the generated profile and architecture files + 4. Present results using the **WA Review Output Format** below + 5. **Generate readable outputs** (always, not just on request): + - Save the scorecard YAML to `examples/-wa-scorecard.yaml` + - Save the architecture YAML to `examples/-wa-architecture.yaml` + - Save the workload profile YAML to `examples/-wa-workload-profile.yaml` + 6. After the scorecard, offer next actions + + **WA Review Output Format:** + + The WA review MUST produce **two layers of output**: (a) the formatted terminal scorecard shown to the user, and (b) the structured YAML files saved to disk. The terminal output is the primary deliverable — the YAML is the backing data. Never produce YAML-only output without the formatted scorecard. + + ``` + ══════════════════════════════════════════════════════ + ✅ OCI WELL-ARCHITECTED REVIEW — [Customer Name] + ══════════════════════════════════════════════════════ + Overall: [STATUS] — X/Y checks passed + HIGH: N │ MEDIUM: N │ LOW: N + ══════════════════════════════════════════════════════ + + [emoji] SECURITY & COMPLIANCE X/Y passed + [emoji] RELIABILITY & RESILIENCE X/Y passed + [emoji] PERFORMANCE & COST X/Y passed + [emoji] OPERATIONAL EFFICIENCY X/Y passed + [emoji] DISTRIBUTED CLOUD X/Y passed | N/A + ``` + + Pillar emoji: 🟢 all passed, 🟡 medium gaps only, 🔴 any HIGH gap, ⬜ N/A + + **HIGH Severity Gaps Table:** + Present all HIGH gaps grouped by pillar in a markdown table: + + ``` + ### HIGH severity gaps that must be addressed: + + **[Pillar Name] (N HIGH)** + | ID | Gap | Fix | + |---|---|---| + | CHECK-ID | Finding description | Recommended action | + ``` + + **MEDIUM gaps:** List as a compact bullet list grouped by pillar (ID + one-line finding + fix). Skip the table format to keep it concise. + + **LOW gaps:** Mention count only, list individual items only if ≤ 5. + + **Analysis Section:** + After the gap tables, add a "Why so many gaps?" paragraph if total gaps > 20, explaining the root cause (e.g., business case without architecture, missing landing zone, no ops design). This contextualizes the score for the SA. + + **Recommended Path Forward:** + 3-5 numbered, actionable recommendations that directly map to closing the highest-impact gaps. Reference skill options where applicable (e.g., "Generate the architecture — option 1 or 2"). + + **Files Generated:** + Always list the files saved at the end of the review: + ``` + 📁 Files saved: + - examples/-wa-scorecard.yaml + - examples/-wa-architecture.yaml + - examples/-wa-workload-profile.yaml + ``` + + **After WA Review Menu:** + ``` + What do you want to do? + → [A] Generate/fix the architecture to close gaps + → [B] Deep-dive a specific pillar + → [C] Export scorecard as a slide (.pptx) + → [D] Re-run after changes + ``` + + **Option [A] behavior — CRITICAL:** When the user picks [A], remediate the EXISTING architecture by adding the minimum changes needed to close gaps (e.g., add `encryption: true` to a storage block, add `flow_logs: enabled` to networking). NEVER replace the customer's actual architecture with a generic "ideal" one. NEVER add services or components the customer didn't mention (no inventing ExaCS, ADB, regions, etc.). If a gap requires a service the customer doesn't have, flag it as a recommendation and ASK before adding it. The remediated architecture MUST be recognizable as the customer's original architecture with targeted fixes applied. +- If the user picks **6**, ask: "What feature and deployment type? (e.g., 'Auto Indexing on ADB-S 23ai')" +- If the user picks **7**, ask: "What's the competitive situation? (e.g., 'Customer comparing ADB-S vs AWS Aurora')" +- If the user picks **8**, ask: "Describe the scenario or paste discovery notes. I'll build the business case. If you already have a cost estimate or architecture, share that too — it'll make the TCO more precise." Then follow the business case flow: + 1. Parse input into `templates/business-case.yaml` structure + 2. Identify business drivers and urgency from discovery notes + 3. Estimate TCO comparison using `kb/pricing/*` and `kb/sizing/*` + 4. Calculate ROI and payback period + 5. Map value drivers (cost, risk, agility, innovation) with quantified evidence + 6. Assess migration risks from `kb/field-knowledge/gotchas.yaml` and do-nothing risks + 7. Compare with alternatives using `kb/competitive/*` + 8. Generate implementation roadmap based on engagement tier + 9. Produce a 8-10 slide deck using Oracle FY26 template (`config/oracle-pptx-layouts.yaml` → `business_case` type) + 10. Output: business-case.pptx + business-case.yaml (reusable spec) +- If the user picks **9**, ask: "What topic? (e.g., 'DEP', 'TAC', 'maintenance window', 'vector search')" +- If the user picks **10**, ask: "What kind of architecture? (e.g., 'ADB + APEX', 'cross-region DR', 'data lakehouse')" +- If the user picks **11**, switch to finding intake mode: + ``` + 📝 New Field Finding + ━━━━━━━━━━━━━━━━━━━ + + Your name: + Your team: + Client (optional): + Product (e.g., ADB-S, DEP, OCI CLI): + Version (e.g., 23ai): + Severity [CRITICAL / HIGH / MEDIUM / LOW / INFO]: + What happened? (describe the issue): + Workaround (if known): + Tags (comma-separated): + ``` + +- If the user picks **12**, ask: "Describe the engagement or paste what you have so far (discovery notes, architecture, proposal, etc.). I'll score it against the full ECAL framework." Then follow the ECAL validation flow: + 1. Parse the input to identify which ECAL artefacts exist (complete, partial, or missing) + 2. Load the full artefact catalog from `kb/patterns/ecal-artefacts-catalog.yaml` (60 artefacts) + 3. Determine the current ECAL phase and step based on what exists + 4. Score each phase using the scoring model below + 5. Identify the top 5 critical gaps (artefacts that should exist but don't) + 6. Recommend next actions based on gaps + 7. **Generate readable outputs** (always, not just on request): + - Save the ECAL scorecard YAML to `examples/-ecal-scorecard.yaml` + - Present the formatted terminal scorecard (primary deliverable) + 8. Output the ECAL Readiness Scorecard + + **Scoring Model:** + Each artefact has a status: ✅ Complete | 🟡 Partial | ❌ Missing | ⬜ Not Applicable (future phase) + + Phase scores are calculated as: + - ✅ = 1.0 point, 🟡 = 0.5 point, ❌ = 0 points, ⬜ = excluded from denominator + - Phase score = (sum of points / applicable artefacts) × 100% + + Overall ECAL Readiness = weighted average: + - DEFINE: 25% weight + - DESIGN: 50% weight (largest phase, most artefacts) + - DELIVER: 25% weight + + **Readiness Levels:** + - 🟢 80-100% — Ready to proceed to next phase + - 🟡 60-79% — Gaps exist but manageable; proceed with caution + - 🟠 40-59% — Significant gaps; address before proceeding + - 🔴 0-39% — Major gaps; phase needs substantial work + + **Output Format:** + + The ECAL readiness score MUST produce **two layers of output**: (a) the formatted terminal scorecard shown to the user, and (b) the structured YAML file saved to disk. The terminal output is the primary deliverable — the YAML is the backing data. Never produce YAML-only output without the formatted scorecard. + + ``` + ══════════════════════════════════════════ + 📊 ECAL READINESS SCORECARD + ══════════════════════════════════════════ + Customer: [name] + Date: [date] + Current Phase: [DEFINE/DESIGN/DELIVER] + Overall Readiness: [XX%] [emoji level] + + ── DEFINE (Ideate → Validate → Plan) ── + Score: XX% [emoji] + ✅ Value Story + ✅ Workload Profile + 🟡 Customer Profile (partial — missing Oracle footprint) + ❌ Strategy Map + ❌ Joint Engagement Plan + ⬜ Business Case (revisited in Confirm) + + ── DESIGN (Current → Future → Confirm) ── + Score: XX% [emoji] + [artefact list with status...] + + ── DELIVER (Adopt → Operate → Improve) ── + Score: XX% [emoji] + [artefact list with status...] + + ── TOP 5 GAPS ── + 1. ❌ [artefact] — [why it matters] — [recommended action] + 2. ... + + ── RECOMMENDED NEXT ACTIONS ── + 1. [specific action] + 2. [specific action] + 3. [specific action] + + ── ENGAGEMENT RACI CHECK ── + Roles identified: [list] + Roles missing: [list] + ══════════════════════════════════════════ + ``` + + **Files Generated:** + Always list the files saved at the end of the scorecard: + ``` + 📁 Files saved: + - examples/-ecal-scorecard.yaml + ``` + + After showing the scorecard, offer: + ``` + What do you want to do? + → [A] Fix the top gap now (I'll generate the missing artefact) + → [B] Generate all missing artefacts for current phase + → [C] Export scorecard as a slide (.pptx) + → [D] Re-score after updates + ``` + +- If the user sends discovery notes directly (without picking a number), detect this and go straight to option 1 (full proposal flow). +- If the user asks a specific question (e.g., "does ADB-S support vector search?"), detect this and go straight to the relevant capability without showing the menu. +- Only show the welcome menu on the FIRST message if it's a greeting or empty context. Don't re-show it on every turn. + +### After Completing Any Task + +After delivering an output, show elapsed time and offer the natural next step. + +**Elapsed time:** Track when the user first triggers the task (picks a menu option or sends the request) and when the final output is delivered. Show the elapsed time in the completion banner: + +``` +✅ Done — [task description] completed in [Xm Ys] + + → [A] Generate additional outputs (drawio / doc / xlsx) + → [B] Modify the architecture (add/remove/change services) + → [C] Run Well-Architected review on this architecture + → [D] Build a business case from this architecture + → [E] Run ECAL readiness score on this engagement + → [F] Start a new proposal + → [G] Report a field finding from this engagement +``` + +The time measurement starts when the user sends the task request and ends when the final output is presented. For multi-step tasks (e.g., WA review → remediate → re-run), show time per step and cumulative total. + +This keeps the architect in flow and provides visibility into how much time the skill is saving. + +--- + +## Principles + +1. **Empirical over theoretical.** Every recommendation must be justifiable with real metrics, benchmarks, or field experience — never "best practice because Oracle says so." +2. **Simplicity first.** Start with the simplest architecture that meets requirements. Complexity must be earned by evidence of need. +3. **Honest about limitations.** Acknowledge what OCI cannot do, where competitors have an edge, and where there are gotchas. +4. **Composable, not monolithic.** Architectures are assembled from pattern blocks that combine, not from monolithic templates. +5. **No hallucinated architecture.** NEVER add services, components, regions, or infrastructure that the user did not mention in their discovery notes. If something is missing, ASK — don't invent. When remediating a WA review, fix gaps by adding the minimum controls needed (e.g., add encryption config to an existing service), not by replacing the real architecture with a generic "well-designed" one. The architecture must always reflect what the customer actually has or is actually planning. + +--- + +## ECAL-Aligned Workflow + +You operate in three phases aligned with ECAL. You may run all three in sequence or start at any phase. + +``` +DEFINE (Ideate → Validate → Plan) + ↓ Value Story + Joint Engagement Plan +DESIGN (Current → Future → Confirm) + ↓ Architecture Proposal + Operations Model +DELIVER (Adopt → Operate → Improve) + ↓ Handover + Go-Live + Lessons Learned +``` + +### Engagement Tiers + +Before starting, determine the engagement tier. The tier scales artifact depth to match complexity: + +| Tier | Scope | Deck | Design Timeline | +|------|-------|------|-----------------| +| **Small** | 1-2 apps, no compliance, 1 region | 6-8 slides | < 4 weeks | +| **Standard** | 3-10 apps, 1 compliance framework, 1-2 regions | 10-12 slides | 4-12 weeks | +| **Complex** | 10+ apps, multiple compliance, 3+ regions | 12-15 slides | 12+ weeks | + +Full tier definitions and artifact matrix: [docs/engagement-tiers.md](docs/engagement-tiers.md) + +--- + +### Phase 1: DEFINE (Ideate → Validate → Plan) + +**Goal:** Identify the customer's business problem and build commitment to solve it. + +**Step 1 — Ideate:** Parse discovery notes into a **Workload Profile** (`templates/workload-profile.yaml`). Formulate a value hypothesis: "If we [technical action], the customer achieves [business outcome]." Use `kb/patterns/business-patterns.yaml` for proven business-level patterns. + +**Step 2 — Validate:** Test the hypothesis for SMART criteria (Specific, Measurable, Attainable, Relevant, Time-based). Identify gaps. Check technical feasibility against `kb/services/` and `kb/compatibility/`. + +**Step 3 — Service Tiering:** After parsing databases, assign each workload a tier (Platinum/Gold/Silver/Bronze) based on SLA requirements, compliance needs, and business criticality. Use the auto-assignment rules in `kb/patterns/service-tiering.yaml`. Present the assignment and ask the architect to confirm or adjust. + +**Step 4 — Plan:** Create a Joint Engagement Plan (`templates/joint-engagement-plan.yaml`) with timebox, resources, milestones, and success criteria for the DESIGN phase. + +**Outputs:** +- Workload Profile (YAML) +- Value Story (`templates/value-story.yaml`) +- Joint Engagement Plan + +**Checkpoint:** Value story approved, workload profile has < 3 critical gaps, engagement tier selected, plan agreed. If not compelling, iterate. + +Full DEFINE guide: [docs/define-phase.md](docs/define-phase.md) + +--- + +### Phase 2: DESIGN (Current → Future → Confirm) + +**Goal:** Produce a complete, defensible architecture with cost estimate and operations model. + +#### Current State (People, Process, Technology) + +Capture enough about current state to architect the future. Frame the problem — don't gather exhaustive requirements. + +- **Technology:** Databases, compute, middleware, messaging, storage, networking, identity, integration (existing Workload Profile fields) +- **People:** Team size, roles, skill gaps, managed services preference, change readiness +- **Process:** Deployment process, change management, incident response, backup testing frequency + +#### Future State (Solution Design) + +1. **Select services** from `kb/services/` across the full OCI catalog +2. **Dimension each service** using `kb/sizing/` rules. For Oracle DBs, use AWR metrics if available. Apply conversion ratios. For ADB-S, size base OCPUs for P75. +3. **Compose topology** from `kb/patterns/` blocks. Check conflicts, add implied dependencies, apply compliance overlays. Use `kb/patterns/application-patterns.yaml` for workload-type guidance. +4. **Architecture Principles** — Select applicable principles from `kb/patterns/architecture-principles.yaml` based on the workload profile. Check `applies_when` conditions. Include in the deck as a governance slide. +5. **Environment Catalogue** — Expand each workload into environments (Prod/Pre-Prod/Dev-Test/DR) using the tier templates in `kb/patterns/environment-catalogue.yaml`. Apply cost optimization rules. Include in the deck and in the cost estimate. +6. **Design deployment** — environment strategy, IaC approach, CI/CD pipeline +7. **Design transition** — migration strategy per component, tooling, phased plan, rollback +8. **Operational RACI** — Detect the operational model (fully_managed/co_managed/self_managed) from the discovery notes. Generate the RACI matrix from `kb/patterns/operational-raci.yaml`. Include in the deck. +9. **Design operations** — monitoring, patching, backup, incident response, capacity management (`templates/operations-model.yaml`) +10. **Estimate costs** — BYOL vs License Included, reserved vs PAYG, monthly breakdown. Include ALL environments (Prod, Pre-Prod, Dev/Test, DR), not just production. +11. **Validate against WA Framework** — 5 pillars from `kb/well-architected/`. Flag gaps. Don't ask 50 questions — infer from the architecture. Use the **WA Review Output Format** defined in option 5 behavior rules for presenting results. + +**Feature compatibility:** Before recommending ADB deployment type + version, check `kb/compatibility/adb-feature-matrix.yaml`. Use `tools/feature_matrix_cli.py gaps ` for deal-breakers. + +**Field findings:** Check `kb/field-findings/tracker.yaml` for known issues. Reference in Risk Register with finding IDs. + +**Reference architectures:** After composing the topology, match against `kb/architecture-center/catalog.yaml` to find official Oracle reference architectures that validate the design. Matching logic: +- Compare selected services against `entry.services` and workload tags against `entry.tags` +- **STRONG MATCH:** ≥2 service matches + ≥1 tag match → cite in Architecture Decisions slide +- **MODERATE MATCH:** ≥1 service match + ≥2 tag matches → mention in technical document +- Output: "Based on Oracle Reference Architecture: [title] ([url])" — adds credibility with customer +- Note deviations from the reference architecture in the Risk Register + +#### Confirm (Solution Proposal) + +Assemble all design work into a proposal. Ensure all propositions are **SMART**. Quality matters — it must look professional. + +**Outputs by tier:** See artifact matrix in [docs/engagement-tiers.md](docs/engagement-tiers.md) + +**Checkpoint:** Architecture validated (no critical WA gaps), costs reviewed, migration achievable, operations model addresses day-2, all HIGH risks mitigated. + +Full DESIGN guide: [docs/design-phase.md](docs/design-phase.md) + +--- + +### Phase 3: DELIVER (Adopt → Operate → Improve) + +**Goal:** Ensure clean handover to implementation and track value realization. These are **lightweight artifacts** — the SA does not replace the implementation team. + +**Step 1 — Adopt:** +- Produce Handover Document (`templates/handover-document.yaml`) — single-source summary for implementation team +- Define MVP scope — what ships in Phase 1 vs. later phases +- Establish governance — steering cadence, escalation, change control + +**Step 2 — Operate:** +- Produce Go-Live Checklist (`templates/go-live-checklist.yaml`) — pre-cutover verification +- Define Success Criteria (`templates/success-criteria.yaml`) — quantitative metrics tied to the Value Story +- Confirm ops readiness — monitoring, alerting, runbooks, on-call + +**Step 3 — Improve:** +- Produce Lessons Learned (`templates/lessons-learned.yaml`) +- Value realization check at 30/60/90 days +- Feed improvements back to `kb/field-knowledge/` and patterns + +**Checkpoint:** Handover complete, go-live checklist green, success criteria baselined, lessons captured. Next hypothesis identified → return to DEFINE if applicable. + +Full DELIVER guide: [docs/deliver-phase.md](docs/deliver-phase.md) + +--- + +## Output Generation + +### Output Principle: Readable First, YAML Second + +Every skill option that produces output MUST generate **readable, human-consumable output** as the primary deliverable. YAML files are structured backing data — they are never the final output shown to the user. Specifically: + +- **Options 1-4, 8:** Primary output is `.pptx` (slide deck) and/or `.drawio` (diagram). YAML specs are saved alongside but never presented as the deliverable. +- **Option 5 (WA Review):** Primary output is the **formatted terminal scorecard** (banner + pillar bars + gap tables + recommendations). YAML scorecard saved to `examples/` as backing data. +- **Option 12 (ECAL Readiness):** Primary output is the **formatted terminal scorecard** (phase scores + artefact checklist + gap analysis). YAML scorecard saved to `examples/` as backing data. +- **After any review/score:** When the user picks [C] "Export as slide", generate a 1-2 slide `.pptx` with the scorecard visualization using `tools/oci_deck_gen.py`. + +If a tool or agent generates YAML without the corresponding readable output, the task is **incomplete**. Always present the formatted result, then list the files saved. + +### Output Directory Convention + +All generated files MUST be saved inside a dedicated output folder per customer/initiative: + +``` +examples/output--/ +``` + +Examples: +- `examples/output-meli-im06/` — MELI MySQL engagement +- `examples/output-meli-im30/` — MELI ElasticSearch engagement +- `examples/output-acme-migration/` — ACME cloud migration + +This folder contains ALL outputs for that engagement: `.pptx`, `.drawio`, `.yaml` specs, `.pdf`, scorecards. The folder is gitignored via `examples/output-*/` — never commit customer data. + +YAML spec files (architecture, workload-profile, diagram-spec) are saved IN the output folder, not loose in `examples/`. This keeps everything grouped and portable. + +Default output is a **slide deck (.pptx)**. The architect can specify: + +| Format | Output | +|--------|--------| +| `deck` (default) | 10-12 slide presentation | +| `deck + drawio` | + editable architecture diagram | +| `deck + doc` | + technical document (15-25 pages) | +| `deck + xlsx` | + cost spreadsheet with formulas | +| `deck + pdf` | + customer-facing PDF (branded, no internal refs) | +| `pdf` | Customer PDF only | +| `full` | Everything (pptx + drawio + docx + xlsx + pdf) | +| `doc only` | Technical document without slides | +| `deliver` | Handover + go-live checklist + success criteria | + +### Slide Deck Structure + +Slide count adapts to engagement tier (6-8 small, 10-12 standard, 12-16 complex): + +1. **Title** — customer, project, date (dark background) +2. **Value Story** — business driver, hypothesis, desired outcomes +3. **Service Tiering** — workload-to-tier mapping (Platinum/Gold/Silver/Bronze) with SLA, RTO/RPO +4. **Architecture Principles** — selected ECAL principles (Design/Deployment/Service) that govern the architecture +5. **Architecture Diagram** — fills 85% of slide +6. **Architecture Decisions** — 4-6 key decisions with rationale +7. **HA/DR** — topology + RTO/RPO per tier +8. **Security & Compliance** — controls grid, compliance badges +9. **Environment Catalogue** — Prod/Pre-Prod/Dev-Test/DR per workload with sizing and isolation +10. **Cost Estimate** — PAYG vs BYOL table with assumptions (all environments) +11. **Cost Comparison** (optional) — vs current state or competitor +12. **Migration Approach** — phased timeline, tools, downtime strategy +13. **Operational RACI** — responsibility matrix (customer vs Oracle/partner) +14. **Risk Register** — severity-coded risk table +15. **Well-Architected Scorecard** — 5-pillar traffic-light indicators +16. **Next Steps** — concrete SMART actions with dates + +Use `tools/oci_deck_gen.py` for generation. Colors: teal `#2D5967`, copper `#AA643B`, purple `#804998`. Font: Segoe UI. Design standards: `config/output-formats.yaml`. + +### Architecture Diagram + +Use `tools/oci_diagram_gen.py` with OCI official styles from `kb/diagram/oci-toolkit-styles.yaml`. Containers, service blocks, connections, and typography rules are defined there. + +--- + +## Service Categorization + +| Category | Color | Use | +|----------|-------|-----| +| **Infrastructure** | Teal `#2D5967` | Compute, OKE, LB, Gateways, WAF, Bastion, Storage, Monitoring | +| **Database** | Copper `#AA643B` | ADB-S/D, DBCS, ExaCS, MySQL, PostgreSQL, NoSQL, GoldenGate | +| **Integration** | Purple `#804998` | DRG, Streaming, Queue, OIC, FastConnect, Service Connector Hub | +| **Dormant** | Light gray `#DFDCD8` | Standby/inactive resources (DR tier) | +| **Legacy** | Medium gray `#70665E` | Non-OCI systems (MQ Series, legacy middleware) | + +--- + +## Knowledge Base + +``` +kb/ +├── architecture-center/ # Oracle Architecture Center reference catalog +│ └── catalog.yaml # 130+ reference architectures, solution playbooks +├── services/ # One YAML per OCI service (what, when, gotchas) +├── patterns/ # Composable blocks +│ ├── business-patterns.yaml # Business-level patterns (DEFINE) +│ ├── application-patterns.yaml # Application architecture patterns (DESIGN) +│ ├── service-tiering.yaml # Service tier definitions (Platinum/Gold/Silver/Bronze) +│ ├── architecture-principles.yaml # ECAL architecture principles (Design/Deployment/Service) +│ ├── operational-raci.yaml # RACI matrix templates (fully/co/self-managed) +│ ├── environment-catalogue.yaml # Environment templates per tier +│ ├── database-ha/ # Database HA patterns +│ ├── database-dr/ # Database DR patterns +│ ├── networking-*/ # Networking patterns +│ ├── compute-scaling/ # Compute auto-scaling +│ ├── security-baseline/ # Security controls +│ └── compliance-pci/ # PCI-DSS compliance +├── sizing/ # CPU conversion ratios, IOPS, scaling rules +├── pricing/ # Simplified pricing for estimation +├── competitive/ # AWS/Azure/GCP service mapping +├── well-architected/ # 5-pillar validation checklists +├── diagram/ # OCI Toolkit styles and reference layouts +├── compatibility/ # Feature matrices (ADB, etc.) +└── field-knowledge/ # Real-world gotchas and lessons learned +``` + +--- + +## Templates + +| Template | Phase | Purpose | +|----------|-------|---------| +| `workload-profile.yaml` | DEFINE | Discovery capture | +| `value-story.yaml` | DEFINE | Business value hypothesis | +| `joint-engagement-plan.yaml` | DEFINE | Engagement scoping | +| `operations-model.yaml` | DESIGN | Day-2 operations design | +| `scorecard.yaml` | DESIGN | WA validation results | +| `adr-template.md` | DESIGN | Architecture Decision Records | +| `handover-document.yaml` | DELIVER | Implementation handover | +| `go-live-checklist.yaml` | DELIVER | Pre-cutover verification | +| `success-criteria.yaml` | DELIVER | Post go-live metrics | +| `lessons-learned.yaml` | DELIVER | Engagement retrospective | + +--- + +## Interaction Style + +- The architect may communicate in **Spanish** but all deliverables are in **English**. +- Be direct and technical. No marketing language. +- When you don't know something, say so. +- When a simpler architecture would work, recommend it. +- Present trade-offs explicitly. Let the architect decide. +- Produce the **minimum needed** for the engagement tier — don't pad. + +--- + +## Guardrails + +- **Only what the user asked for.** Never add services, components, or features the user did not request — this includes observability (Monitoring, Logging, Events), security services (Data Safe, Vault, Cloud Guard, WAF), sizing details, connection types (RPC, peering), and any "nice to have" additions. Adding unrequested components wastes the architect's time and erodes trust. +- **Ask, don't guess.** When requirements are ambiguous or incomplete, ask a clarifying question instead of filling in assumptions. A 10-second question saves a 10-minute redo. +- **Pre-generation review.** Before generating any diagram or architecture artifact, confirm the component list with the user. Present what you understood and suggest optional additions they can approve or reject: + ``` + I'll generate a diagram with: + ✅ [list of explicitly requested components] + + Want me to also include any of these? + • Observability subnet + • Compartment boundaries + • Security services (Data Safe, Vault) + • Gateways (IGW, NAT, SGW) + • [other relevant options based on context] + + Or just generate with the above? + ``` + This takes 5 seconds to confirm and prevents rework. + +## What You Do NOT Do + +- You do NOT execute infrastructure changes. You design and recommend. +- You do NOT replace the architect's judgment. You accelerate it. +- You do NOT generate pixel-perfect diagrams. You generate 80% drafts the architect refines. +- You do NOT make up pricing. If you don't have current pricing, estimate ranges. +- You do NOT claim features exist if you're unsure. Check the KB first. +- You do NOT do detailed project management. DELIVER artifacts are lightweight handover aids. +- You do NOT add services or components the user did not request. diff --git a/architecture-center-catalog-prompt.md b/architecture-center-catalog-prompt.md new file mode 100644 index 0000000..b285c4e --- /dev/null +++ b/architecture-center-catalog-prompt.md @@ -0,0 +1,527 @@ +# OCI DEAL ACCELERATOR — Architecture Center Catalog Builder + +## CRITICAL: Git Branching Rule + +```bash +git checkout main && git pull origin main && git checkout -b feature/architecture-center-catalog +``` + +--- + +## Objective + +Build a comprehensive catalog of Oracle Architecture Center content (reference architectures, solution playbooks, built & deployed) from the last 2 years. Each entry has ONLY: title, URL, type, date, tags, and a 2-3 sentence summary focused on Architecture Recommendations and Considerations. + +This catalog enables the Deal Accelerator to say: "Your architecture matches this Oracle reference architecture: [title] — [url]" when composing a proposal. + +--- + +## Step 1: Fetch the What's New Pages + +These pages list ALL published/updated content by date: + +``` +https://docs.oracle.com/en/solutions/oracle-architecture-center/whats-new-20251.html +https://docs.oracle.com/en/solutions/oracle-architecture-center/whats-new-2024.html +``` + +From each page, extract every entry title and its link. These are the master lists. + +Also fetch the filtered views to catch anything missed: + +``` +https://docs.oracle.com/solutions/?q=&cType=reference-architectures&sort=date-desc&lang=en +https://docs.oracle.com/solutions/?q=&cType=built-deployed&sort=date-desc&lang=en +https://docs.oracle.com/solutions/?q=&cType=solution-playbook&sort=date-desc&lang=en +``` + +If the filtered views are JavaScript-rendered and don't return content via fetch, rely on the What's New pages as the master source. + +--- + +## Step 2: For Each Entry, Fetch the Detail Page + +Each reference architecture has a URL like: +``` +https://docs.oracle.com/en/solutions/{slug}/index.html +``` + +Fetch each page and extract: +- **Title**: the H1 +- **Summary**: the first 2-3 sentences of the description (before the diagram) +- **Architecture Recommendations**: from the "Recommendations" or "Considerations" section — summarize in 2-3 sentences MAX +- **Services used**: list the OCI services mentioned (ADB-S, ExaCS, OKE, VCN, etc.) +- **Tags**: auto-generate from services and topic (database, networking, security, migration, ha-dr, multicloud, etc.) +- **Date**: from the What's New page or the page footer copyright +- **Terraform/GitHub link**: if present + +--- + +## Step 3: Write the Catalog File + +### Output format: `kb/architecture-center/catalog.yaml` + +```yaml +# ============================================================================= +# ORACLE ARCHITECTURE CENTER — REFERENCE CATALOG +# ============================================================================= +# +# Auto-generated index of Oracle Architecture Center content. +# Used by the Deal Accelerator to match customer architectures with +# official Oracle reference architectures and solution playbooks. +# +# Entry format is intentionally minimal — title, summary, tags, URL. +# The skill references the URL; the architect reads the full doc. +# +# To refresh: python tools/refresh_arch_catalog.py +# ============================================================================= + +--- +last_refreshed: "2026-03-16" +source: "https://docs.oracle.com/en/solutions/oracle-architecture-center/" +entry_count: 0 # UPDATE with actual count +--- + +entries: + + # ===== DATABASE ===== + + - title: "Deploy Oracle Autonomous Database on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/deploy-autonomous-database-db-at-azure/index.html" + type: reference-architecture + date: "2024-10" + services: [adb-s, adg, azure] + tags: [database, multicloud, azure, ha-dr, autonomous] + summary: > + Multi-AZ deployment of ADB-S on Database@Azure with Autonomous Data Guard. + Recommends VNet peering between app and DB VNets, TAC for availability, + and ADG standby in a different AZ for automatic failover. + terraform: null + + - title: "Oracle MAA for Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/oracle-maa-db-at-azure/index.html" + type: reference-architecture + date: "2025-05" + services: [exacs, adg, vault, azure] + tags: [database, multicloud, azure, ha-dr, maa, exadata] + summary: > + Cross-AZ Data Guard on ExaCS in Database@Azure. Active Data Guard + recommended for cross-AZ replication (block repair, app continuity, + read offload). Backups to Autonomous Recovery Service. + terraform: null + + - title: "Deploy Oracle Autonomous Database on Oracle Database@Google Cloud" + url: "https://docs.oracle.com/en/solutions/deploy-adb-db-at-google-cloud/index.html" + type: reference-architecture + date: "2025-03" + services: [adb-s, adg, google-cloud] + tags: [database, multicloud, google, ha-dr, autonomous] + summary: > + ADB-S on Database@Google Cloud with cross-region ADG. Non-overlapping + CIDR between VPC and VCN required. App tier should span 2+ AZs with + Google Global Load Balancer for failover. + terraform: null + + - title: "Deploy Oracle Database@Google Cloud" + url: "https://docs.oracle.com/en/solutions/deploy-oracle-database-at-google-cloud/index.html" + type: reference-architecture + date: "2025-10" + services: [exacs, adb-d, google-cloud] + tags: [database, multicloud, google, exadata, autonomous-dedicated] + summary: > + ExaCS and ADB-D on Database@Google Cloud. RAC for active-active HA, + ASM for storage redundancy. Backups to OCI Object Storage via + Autonomous Recovery Service. + terraform: null + + - title: "Deploy Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/deploy-oracle-db-aws/index.html" + type: reference-architecture + date: "2025-07" + services: [exacs, adb-d, aws] + tags: [database, multicloud, aws, exadata, autonomous-dedicated] + summary: > + ExaCS and ADB-D colocated in AWS data centers. ODB peering between + VPC and ODB network in same AZ. Default limit of 2 DB servers and + 3 storage servers — request increase early. + terraform: null + + - title: "Migrate on-premises Oracle Database to Autonomous Database" + url: "https://docs.oracle.com/en/solutions/migrate-to-atp/index.html" + type: reference-architecture + date: "2024-01" + services: [adb-s, object-storage, compute, vpn] + tags: [database, migration, autonomous, data-pump] + summary: > + MV2ADB tool for on-prem EE to ADB-S migration using Data Pump. + Requires HTTP to Object Storage + SQL*Net to ADB. Terraform code + available on GitHub for networking + compute + ADB provisioning. + terraform: "https://github.com/oracle-quickstart/oci-arch-atp" + + - title: "Migrate Oracle RAC Databases to OCI" + url: "https://docs.oracle.com/en/solutions/ensure-ha-migrate-vmware-workloads-to-oci/migrate-oracle-rac-databases1.html" + type: reference-architecture + date: "2025-01" + services: [exacs, exascale, adb-d, base-db] + tags: [database, migration, rac, exadata, ha-dr] + summary: > + Compares 4 RAC migration targets: ExaCS Dedicated, Exascale, ADB-D, + and 2-node RAC on Base DB. ExaCS recommended for full RAC feature + parity. Exascale for 23ai-only without dedicated infra commitment. + terraform: null + + - title: "Deploy ORDS with High Availability on OCI" + url: "https://docs.oracle.com/en/solutions/deploy-ords-ha-oci/index.html" + type: reference-architecture + date: "2025-06" + services: [compute, load-balancer, adb-s, bastion] + tags: [database, application, ords, ha-dr, rest-api] + summary: > + Multi-instance ORDS behind OCI Load Balancer for HA REST access to + Oracle DB. Works with ADB-S, DBCS, or ExaCS. Recommends DB in + private subnet, ORDS in public with granular NSG rules. + terraform: null + + - title: "Deploy secure ADB and APEX application" + url: "https://docs.oracle.com/en/solutions/deploy-autonomous-database-and-app/index.html" + type: reference-architecture + date: "2024-06" + services: [adb-s, apex, load-balancer, bastion, cloud-guard] + tags: [database, application, apex, security, autonomous] + summary: > + APEX on ADB-S with private endpoint behind Load Balancer. OCI + Landing Zone via Terraform provisions in minutes. Recommends NSGs + over Security Lists, Cloud Guard with custom detector recipes. + terraform: "https://github.com/oracle-quickstart/oci-arch-apex-atp" + + # ===== NETWORKING & INTEGRATION ===== + + - title: "Design network architecture for data and application integration" + url: "https://docs.oracle.com/en/solutions/data-application-integration-workloads/index.html" + type: reference-architecture + date: "2025-11" + services: [vcn, drg, fastconnect, oic, data-integration, adb] + tags: [networking, integration, multicloud, data-integration] + summary: > + 4 integration patterns: single VCN, cross-VCN, cross-region, and + multicloud. FastConnect+DRG for on-prem, RPC for cross-region. + Multicloud via FastConnect+ExpressRoute/DirectConnect/PartnerInterconnect. + terraform: null + + # ===== DATA PLATFORM ===== + + - title: "Data platform - decentralized data platform" + url: "https://docs.oracle.com/en/solutions/data-platform-decentralized/index.html" + type: reference-architecture + date: "2025-03" + services: [adw, data-catalog, data-integration, object-storage] + tags: [data-platform, data-lake, data-sharing, autonomous] + summary: > + Decentralized data lakehouse with domain-level ADB-S instances + sharing data via Cloud Links or Delta Sharing. Centralized catalog, + IaC onboarding per domain. Hub-spoke model with OCI backbone routing. + terraform: null + + - title: "Cloud data lake house - process enterprise and streaming data" + url: "https://docs.oracle.com/en/solutions/oci-curated-analysis/index.html" + type: reference-architecture + date: "2024-02" + services: [adw, streaming, goldengate-stream-analytics, data-integration, object-storage] + tags: [data-platform, data-lake, streaming, analytics, machine-learning] + summary: > + Full data lakehouse with batch and streaming ingestion. ADW with + auto-scaling for curated layer. Hybrid partitioned tables to move + cold data to Object Storage transparently. GoldenGate Stream Analytics + for real-time event processing. + terraform: null + + - title: "Multicloud data lake integration" + url: "https://docs.oracle.com/en/solutions/oci-multicloud-datalake/index.html" + type: reference-architecture + date: "2024-03" + services: [data-integration, oic, object-storage, adw, streaming] + tags: [data-platform, multicloud, integration, data-lake] + summary: > + Bring data from AWS/Azure/on-prem into OCI data lake. OCI Data + Integration for batch ETL, OIC for app integration with pre-built + adapters. Read-only credentials for source systems recommended. + terraform: null + + # =================================================================== + # CONTINUE ADDING ALL REMAINING ENTRIES FROM THE WHAT'S NEW PAGES + # Follow the same format: title, url, type, date, services, tags, summary + # + # Fetch and process ALL entries from: + # - whats-new-20251.html (2025 entries) + # - whats-new-2024.html (2024 entries) + # + # For each entry: + # 1. Fetch the detail page + # 2. Extract the summary (first 2-3 sentences) + # 3. Extract Architecture Recommendations (summarize in 2-3 sentences) + # 4. Combine into the summary field + # 5. Auto-tag based on services and topic + # =================================================================== +``` + +--- + +## Step 4: Build the Refresh Script + +### `tools/refresh_arch_catalog.py` + +```python +#!/usr/bin/env python3 +""" +Refresh the Architecture Center catalog from Oracle docs. + +Usage: + python tools/refresh_arch_catalog.py + python tools/refresh_arch_catalog.py --url https://docs.oracle.com/en/solutions/{slug}/index.html + python tools/refresh_arch_catalog.py --whats-new + +This script: +1. Fetches the What's New pages for 2024 and 2025 +2. Extracts all reference architecture links +3. For each link, fetches the detail page +4. Extracts title, summary, services, recommendations +5. Writes/updates kb/architecture-center/catalog.yaml + +IMPORTANT: This is a semi-automated tool. It generates DRAFT entries +that should be reviewed by a human before committing. The auto-generated +summaries may miss nuance or include irrelevant detail. +""" + +import requests +import yaml +import re +import argparse +from bs4 import BeautifulSoup +from datetime import datetime +import os +import time + +# Service name detection patterns +SERVICE_PATTERNS = { + "adb-s": r"Autonomous\s+(Database|Transaction|Data\s+Warehouse)(?!.*Dedicated)", + "adb-d": r"Autonomous\s+Database\s+on\s+Dedicated|ADB-D", + "exacs": r"Exadata\s+(Cloud|Database)\s+Service|ExaCS|ExaDB", + "exascale": r"Exascale", + "base-db": r"Base\s+Database|VM\s+DB|DB\s+System", + "oke": r"Kubernetes\s+Engine|OKE|Container\s+Engine", + "compute": r"Compute\s+(Instance|VM)|Virtual\s+Machine", + "functions": r"OCI\s+Functions|Functions\s+Service", + "vcn": r"Virtual\s+Cloud\s+Network|VCN", + "load-balancer": r"Load\s+Balanc", + "waf": r"Web\s+Application\s+Firewall|WAF", + "fastconnect": r"FastConnect", + "drg": r"Dynamic\s+Routing\s+Gateway|DRG", + "object-storage": r"Object\s+Storage", + "data-safe": r"Data\s+Safe", + "vault": r"OCI\s+Vault|Key\s+Management", + "cloud-guard": r"Cloud\s+Guard", + "goldengate": r"GoldenGate", + "oic": r"Oracle\s+Integration|OIC", + "data-integration": r"Data\s+Integration", + "streaming": r"OCI\s+Streaming|Streaming\s+Service", + "monitoring": r"OCI\s+Monitoring", + "logging": r"Logging\s+Analytics", + "bastion": r"Bastion", + "apex": r"Oracle\s+APEX|APEX\s+application", + "mysql": r"MySQL\s+(Database\s+)?Service|MySQL\s+HeatWave", + "nosql": r"NoSQL\s+Database", + "adw": r"Autonomous\s+Data\s+Warehouse|ADW", + "azure": r"Database@Azure|Azure", + "aws": r"Database@AWS|AWS", + "google-cloud": r"Database@Google|Google\s+Cloud", +} + +TAG_PATTERNS = { + "database": r"database|autonomous|exadata|rac|mysql|nosql", + "migration": r"migrat|move\s+to|zero\s+downtime", + "ha-dr": r"high\s+availability|disaster\s+recovery|data\s+guard|failover|MAA", + "security": r"secur|encrypt|vault|data\s+safe|cloud\s+guard|compliance", + "networking": r"network|vcn|subnet|fastconnect|vpn|drg|load\s+balanc", + "multicloud": r"multicloud|multi-cloud|azure|aws|google\s+cloud|database@", + "integration": r"integrat|oic|streaming|goldengate|data\s+flow", + "data-platform": r"data\s+lake|data\s+warehouse|analytics|data\s+platform", + "application": r"apex|ords|kubernetes|container|weblogic|tomcat", + "ai-ml": r"machine\s+learning|ai\s+|generative|vector|select\s+ai", + "autonomous": r"autonomous\s+database|adb|auto-scal", +} + + +def detect_services(text): + """Auto-detect OCI services mentioned in text.""" + found = [] + for svc, pattern in SERVICE_PATTERNS.items(): + if re.search(pattern, text, re.IGNORECASE): + found.append(svc) + return sorted(set(found)) + + +def detect_tags(text): + """Auto-detect topic tags from text.""" + found = [] + for tag, pattern in TAG_PATTERNS.items(): + if re.search(pattern, text, re.IGNORECASE): + found.append(tag) + return sorted(set(found)) + + +def fetch_page(url, delay=1.0): + """Fetch a page with rate limiting.""" + time.sleep(delay) + headers = {"User-Agent": "OCI-Deal-Accelerator-CatalogBuilder/1.0"} + resp = requests.get(url, headers=headers, timeout=30) + resp.raise_for_status() + return BeautifulSoup(resp.text, "html.parser") + + +def extract_entry(url): + """Extract catalog entry from a reference architecture page.""" + try: + soup = fetch_page(url) + + title = soup.find("h1") + title_text = title.get_text(strip=True) if title else "Unknown" + + # Get all text for service/tag detection + body = soup.get_text(" ", strip=True) + + # Extract first 2-3 sentences as summary + paragraphs = soup.find_all("p") + summary_parts = [] + for p in paragraphs[:5]: + text = p.get_text(strip=True) + if len(text) > 50 and "Copyright" not in text: + summary_parts.append(text) + if len(" ".join(summary_parts)) > 300: + break + + summary = " ".join(summary_parts)[:500] + + # Detect services and tags + services = detect_services(body) + tags = detect_tags(body) + + # Detect type + entry_type = "reference-architecture" + if "playbook" in body.lower(): + entry_type = "solution-playbook" + elif "built and deployed" in body.lower() or "customer" in title_text.lower(): + entry_type = "built-deployed" + + # Find terraform/github links + terraform = None + for a in soup.find_all("a", href=True): + if "github" in a["href"].lower(): + terraform = a["href"] + break + + return { + "title": title_text, + "url": url, + "type": entry_type, + "services": services, + "tags": tags, + "summary": summary, + "terraform": terraform, + } + except Exception as e: + print(f" ERROR fetching {url}: {e}") + return None + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--url", help="Fetch a single URL") + parser.add_argument("--whats-new", action="store_true", help="Fetch from What's New pages") + parser.add_argument("--output", default="kb/architecture-center/catalog.yaml") + args = parser.parse_args() + + if args.url: + entry = extract_entry(args.url) + if entry: + print(yaml.dump([entry], default_flow_style=False)) + elif args.whats_new: + # Fetch What's New pages and extract all links + # ... implement the full crawl + pass + else: + print("Usage: --url or --whats-new") + + +if __name__ == "__main__": + main() +``` + +**Requirements:** `pip install requests beautifulsoup4` + +--- + +## Step 5: Integration with the Skill + +### Update `kb/INDEX.yaml` + +Add: + +```yaml +architecture_center: + catalog: + path: "kb/architecture-center/catalog.yaml" + keywords: ["reference architecture", "oracle architecture center", "solution playbook", "built deployed"] + load_when: "composing architecture or generating ADRs — match customer workload against known Oracle patterns" +``` + +### How the Skill Uses It + +During Phase 2 (Architecture Composition), after selecting services and patterns, the skill scans the catalog for matching entries: + +``` +Matching logic: +1. Compare selected services against entry.services +2. Compare workload tags against entry.tags +3. If ≥2 service matches + ≥1 tag match → STRONG MATCH +4. If ≥1 service match + ≥2 tag matches → MODERATE MATCH + +Output in the deck: +- Architecture Decisions slide: "Based on Oracle Reference Architecture: [title] ([url])" +- Technical document: Full reference with link +- Risk register: Note any deviations from the reference architecture +``` + +Example match for the Laboratorios Farma case: +``` +Customer: ADB-S + ADG + VPN + private subnet + APEX +Matches: + ✅ STRONG: "Deploy secure ADB and APEX application" (adb-s, apex, load-balancer) + ✅ MODERATE: "Deploy ORDS with High Availability on OCI" (adb-s, load-balancer) + ✅ MODERATE: "Migrate on-premises Oracle Database to ADB" (adb-s, migration) +``` + +--- + +## Build Order + +1. Create `kb/architecture-center/` directory +2. Run the refresh script to generate `catalog.yaml` (or build manually from What's New pages) +3. Review and curate the auto-generated entries (fix summaries, verify tags) +4. Update `kb/INDEX.yaml` with the architecture-center reference +5. Update `SKILL.md` / `SKILL_COMPACT.md` with matching logic instructions +6. Add `tools/refresh_arch_catalog.py` to the repo +7. Add tests: verify catalog loads, entries have required fields, no broken URLs + +## Refresh Cadence + +- **Monthly**: Run `python tools/refresh_arch_catalog.py --whats-new` to pick up new entries +- **On Oracle release**: Check for new reference architectures related to new features +- **Manual add**: For entries the scraper misses, add manually following the format + +## When Done + +```bash +git add . +git commit -m "feat: Architecture Center catalog — reference architectures index" +git push -u origin feature/architecture-center-catalog +``` diff --git a/codex/README.md b/codex/README.md new file mode 100644 index 0000000..0f21535 --- /dev/null +++ b/codex/README.md @@ -0,0 +1,119 @@ +# OCI Deal Accelerator — Codex Setup + +## Quick Start + +### Option 1: Using Codex CLI (recommended) + +```bash +# Navigate to the project root +cd oci-deal-accelerator + +# Run Codex — it auto-discovers AGENTS.md and .agents/skills/ +codex + +# Or explicitly load the skill +codex --skill oci-deal-accelerator +``` + +Codex automatically reads: +- `AGENTS.md` at project root (project-level instructions) +- `.agents/skills/oci-deal-accelerator/SKILL.md` (the skill definition) + +### Option 2: Using Codex App + +1. Open the Codex app +2. Point it to this repository +3. The skill is auto-discovered from `.agents/skills/` + +## File Structure (Codex-Specific) + +``` +├── AGENTS.md # Project instructions (Codex reads this) +├── .agents/ +│ └── skills/ +│ └── oci-deal-accelerator/ +│ └── SKILL.md # Full skill definition (Codex skill format) +├── codex/ +│ └── README.md # This file (setup guide) +├── kb/ # Knowledge Base (shared with all LLM targets) +├── tools/ # Python tooling (shared) +├── templates/ # ECAL phase templates (shared) +├── config/ # Configuration (shared) +└── examples/ # Example specs and outputs +``` + +## How It Works + +### AGENTS.md vs SKILL.md + +- **AGENTS.md** (project root): Codex reads this automatically when you open the project. It contains project structure, build commands, and conventions — equivalent to Claude Code's `CLAUDE.md`. +- **SKILL.md** (`.agents/skills/oci-deal-accelerator/`): The full skill definition with the welcome flow, ECAL workflow, output generation, guardrails, and multi-agent mode. Codex discovers this automatically from the `.agents/skills/` directory. + +### AGENTS.override.md + +If you need temporary overrides (e.g., focusing on a specific customer engagement), create an `AGENTS.override.md` at project root. It takes highest priority: + +```markdown +# Override: Acme Corp Engagement + +Focus on the Acme Corp engagement. The customer is migrating 5 Oracle 19c +databases from on-prem Exadata to OCI. Key constraints: +- Must use BYOL (ULA in place) +- PCI-DSS compliance required +- 4-hour RTO, 1-hour RPO +- Timeline: 12 weeks +``` + +## What the Skill Does + +Takes unstructured customer discovery notes and produces: +- Architecture slide deck (.pptx) with Oracle FY26 branding +- Editable architecture diagram (.drawio) with official OCI styles +- Customer-facing PDF (branded, no internal KB refs) +- Cost estimates (PAYG vs BYOL breakdown) +- Well-Architected Framework scorecard +- Business case with TCO/ROI analysis +- ECAL readiness score (60 artefacts) +- Handover documents, go-live checklists, lessons learned + +## 12 Capabilities + +| # | Capability | Category | +|---|-----------|----------| +| 1 | Full proposal from discovery notes | Design & Propose | +| 2 | Generate architecture diagram | Design & Propose | +| 3 | Generate slide deck | Design & Propose | +| 4 | Cost estimate | Design & Propose | +| 5 | Well-Architected review | Validate & Check | +| 6 | Feature compatibility check | Validate & Check | +| 7 | Competitive comparison | Validate & Check | +| 8 | Business case builder | Strategy & Business | +| 9 | Search field findings | Knowledge Base | +| 10 | Find reference architecture | Knowledge Base | +| 11 | Report a field finding | Knowledge Base | +| 12 | ECAL readiness score | ECAL Governance | + +## Multi-Agent Mode + +When running in Codex with multiple agents enabled, the skill splits into: + +- **Agent 1 (Architect)**: DEFINE + DESIGN phases — parses discovery, composes architecture +- **Agent 2 (Validator)**: WA validation — scores against 5 pillars +- **Agent 3 (Renderer)**: Output generation — deck, diagram, PDF, cost spreadsheet + +Each agent reads the same KB but focuses on its phase. + +## Requirements + +- Python 3.8+ +- Install dependencies: `pip install -r requirements.txt` +- Key packages: `pyyaml`, `python-pptx`, `reportlab` (for PDF) + +## Also Works With + +This skill is LLM-agnostic. The same KB and templates work with: +- **Claude Code** (Anthropic) — uses `SKILL.md` + `CLAUDE.md` +- **Codex** (OpenAI) — uses `AGENTS.md` + `.agents/skills/` (this setup) +- **ChatGPT** — paste SKILL.md as system prompt +- **Gemini Pro** — paste SKILL.md as system instruction +- Any LLM with tool/function calling support diff --git a/config/engagement-tiers.yaml b/config/engagement-tiers.yaml new file mode 100644 index 0000000..d245748 --- /dev/null +++ b/config/engagement-tiers.yaml @@ -0,0 +1,56 @@ +# Engagement Tier Configuration +# Defines how artifact depth scales with engagement complexity + +tiers: + small: + name: "Small (Rapid Innovation)" + description: "Single workload, single region, minimal compliance" + signals: + applications: "1-2" + compliance_frameworks: 0 + regions: 1 + integration_points: "0-2" + budget_monthly: "< $10K" + design_timeline: "< 4 weeks" + deck_slides: "6-8" + examples: + - "ADB-S + Analytics Cloud dashboard" + - "Single app migration to OKE" + - "PoC or pilot deployment" + + standard: + name: "Standard (Extend Capabilities)" + description: "Multi-component architecture, moderate complexity" + signals: + applications: "3-10" + compliance_frameworks: 1 + regions: "1-2" + integration_points: "3-5" + budget_monthly: "$10K-100K" + design_timeline: "4-12 weeks" + deck_slides: "10-12" + examples: + - "Oracle DB migration with HA/DR to ADB-S" + - "Multi-tier application modernization" + - "Hybrid cloud with FastConnect" + + complex: + name: "Complex (Business Transformation)" + description: "Enterprise-wide transformation, multi-region, multiple compliance" + signals: + applications: "10+" + compliance_frameworks: "2+" + regions: "3+" + integration_points: "5+" + budget_monthly: "> $100K" + design_timeline: "12+ weeks" + deck_slides: "12-15" + examples: + - "Full data center migration (50+ workloads)" + - "Multi-cloud architecture with OCI primary" + - "Regulated industry platform (banking, healthcare)" + +# Tier selection logic: +# If any signal is "complex" AND 2+ signals are "complex" → Complex +# If any signal is "complex" OR 3+ signals are "standard" → Standard +# Else → Small diff --git a/config/kb-governance.yaml b/config/kb-governance.yaml new file mode 100644 index 0000000..195050d --- /dev/null +++ b/config/kb-governance.yaml @@ -0,0 +1,35 @@ +# ============================================================================= +# KB GOVERNANCE CONFIGURATION +# ============================================================================= +# +# Controls freshness tracking, confidence decay, review cadence, and +# contribution requirements for all KB files. +# +# Referenced by: tools/kb_linter.py, tools/kb_cli.py +# ============================================================================= + +--- +contribution: + required_fields: [name, team, date, confidence] + optional_fields: [client, context] + confidence_levels: [validated, observed, reported, inferred] + +freshness: + warning_days: 180 + stale_days: 365 + +confidence_decay: + validated: { fresh: 180, stale: 365, expired: 730 } + observed: { fresh: 120, stale: 270, expired: 540 } + reported: { fresh: 90, stale: 180, expired: 365 } + inferred: { fresh: 90, stale: 180, expired: 365 } + +review_triggers: + - event: "Oracle database version release" + action: "Review all feature matrix entries. Mark untested. Validate within 14 days." + - event: "Oracle pricing change" + action: "Update kb/pricing/ files. Verify cost calculator accuracy." + - event: "After customer engagement" + action: "Run After-Engagement Review (AER). Add findings and KB updates." + - event: "Quarterly" + action: "Run kb_linter.py. Refresh stale files. Review open findings." diff --git a/config/kb-owners.yaml b/config/kb-owners.yaml new file mode 100644 index 0000000..1162350 --- /dev/null +++ b/config/kb-owners.yaml @@ -0,0 +1,40 @@ +# ============================================================================= +# KB DOMAIN OWNERSHIP +# ============================================================================= +# +# Each KB area has an owner responsible for quality and freshness. +# The owner doesn't write everything — they ensure content is reviewed +# and up to date on the specified cadence. +# +# Referenced by: tools/kb_linter.py, tools/kb_cli.py +# ============================================================================= + +--- +domains: + - area: "kb/services/" + owner: { name: "Diego Cabrera", team: "Field Architecture" } + review_cadence: "quarterly" + + - area: "kb/patterns/" + owner: { name: "Diego Cabrera", team: "Field Architecture" } + review_cadence: "quarterly" + + - area: "kb/compatibility/" + owner: { name: "TBD", team: "Database Specialists" } + review_cadence: "on Oracle version release + quarterly" + + - area: "kb/pricing/" + owner: { name: "TBD", team: "Solution Engineering" } + review_cadence: "monthly (pricing changes frequently)" + + - area: "kb/competitive/" + owner: { name: "TBD", team: "Competitive Intelligence" } + review_cadence: "quarterly + on competitor announcements" + + - area: "kb/field-findings/" + owner: { name: "All", team: "Everyone contributes" } + review_cadence: "continuous" + + - area: "kb/well-architected/" + owner: { name: "TBD", team: "Cloud Architecture" } + review_cadence: "on Oracle WA Framework updates" diff --git a/config/kb-tags.yaml b/config/kb-tags.yaml new file mode 100644 index 0000000..5138862 --- /dev/null +++ b/config/kb-tags.yaml @@ -0,0 +1,56 @@ +# ============================================================================= +# KB TAG TAXONOMY +# ============================================================================= +# +# Standardized tags for consistent search and categorization across all KB files. +# The CLI validates tags against this taxonomy and suggests corrections for +# unknown tags using fuzzy matching. +# +# Referenced by: tools/findings_cli.py, tools/kb_linter.py +# ============================================================================= + +--- +taxonomy: + products: + - adb-s + - adb-d + - dep + - exacs + - dbcs + - oke + - functions + - vcn + - drg + - fastconnect + - dms + - goldengate + - data-safe + - vault + + versions: + - 19c + - 21c + - 23ai + - 26ai + + categories: + - ha + - dr + - security + - networking + - performance + - migration + - cost + - sizing + - compliance + + severity_tags: + - blocker + - workaround-available + - cosmetic + + lifecycle: + - design + - migration + - day2 + - decommission diff --git a/config/oracle-pptx-layouts.yaml b/config/oracle-pptx-layouts.yaml new file mode 100644 index 0000000..702224e --- /dev/null +++ b/config/oracle-pptx-layouts.yaml @@ -0,0 +1,160 @@ +# Oracle FY26 POTX Template — Layout Mapping +# Source: templates/Oracle_PPT-template_FY26.potx (104 layouts) +# Slide size: 13.333 x 7.500 inches (16:9 widescreen) +# +# NOTE: .potx requires content-type patch to load with python-pptx. +# The deck generator handles this automatically. +# +# Each layout has Dark/Light variants. Convention: +# - Cover slides: Dark +# - Content slides: Light +# - Section dividers: Dark +# - Closing/CTA slides: Dark + +template_file: templates/Oracle_PPT-template_FY26.pptx +slide_size: + width_inches: 13.333 + height_inches: 7.500 + +# ── Layout Index Reference ───────────────────────────────────────── + +layouts: + # --- Cover / Title --- + cover_pillar_dark: { index: 0, name: "Dark - Title_Pillar" } + cover_pillar_light: { index: 1, name: "Light - Title_Pillar" } + cover_2_dark: { index: 2, name: "Dark - Title_2" } + cover_2_light: { index: 3, name: "Light - Title_2" } + cover_full_image: { index: 4, name: "Title-full-image" } + + # --- Section Dividers --- + divider_image_light: { index: 14, name: "Light - Divider_Image" } + divider_image_dark: { index: 15, name: "Dark- Divider_Image" } + divider_light: { index: 16, name: "Light - Divider" } + divider_dark: { index: 17, name: "Dark - Divider" } + + # --- Impact / Statement --- + impact_dark: { index: 26, name: "Dark - Impact Statement" } + impact_light: { index: 27, name: "Light - Impact Statement" } + + # --- Quote --- + quote_data_dark: { index: 28, name: "Dark - Quote with Data Texture Background" } + quote_data_light: { index: 29, name: "Light - Quote with Data Texture Background" } + + # --- Single Metric --- + metric_dark: { index: 32, name: "Dark - Single metric" } + metric_light: { index: 33, name: "Light - Single metric" } + + # --- Agenda / Table --- + agenda_dark: { index: 45, name: "Dark - Agenda Table" } + agenda_light: { index: 46, name: "Light - Agenda Table" } + + # --- Multi Statement (3 blocks) --- + multi_statement_dark: { index: 47, name: "Multi Statement – Dark" } + multi_statement_light: { index: 48, name: "Multi Statement – Light" } + + # --- 2 Column --- + two_col_light: { index: 66, name: "Light - Title/Subtitle 2 Column" } + two_col_dark: { index: 67, name: "Dark - Title/Subtitle 2 Column" } + + # --- Content + Illustration (half image) --- + content_illust_light: { index: 78, name: "Content + Illustration_Light" } + content_illust_dark: { index: 79, name: "Content + Illustration_Dark" } + + # --- Icons with text (3 or 4) --- + three_icons_light: { index: 80, name: "Light - 3 Icons, Subhead and text" } + three_icons_dark: { index: 81, name: "Dark - 3 Icons, Subhead and text" } + four_icons_light: { index: 82, name: "Light - 4 Icons, Subhead and text" } + four_icons_dark: { index: 83, name: "Dark - 4 Icons, Subhead and text" } + + # --- Blank --- + blank_dark: { index: 84, name: "Dark - Blank" } + blank_light: { index: 85, name: "Light - Blank" } + + # --- Split Content --- + split_dark: { index: 86, name: "Dark - Split content with line" } + split_light: { index: 87, name: "Light - Split content with line" } + + # --- Content + Photo --- + content_photo_light: { index: 72, name: "Light - Title, Content and 1/3 Photo" } + content_photo_dark: { index: 73, name: "Dark - Title, Content and 1/3 Photo" } + + # --- Safe Harbor --- + safe_harbor_dark: { index: 95, name: "Dark - Safe harbor - short" } + safe_harbor_light: { index: 96, name: "Light - Safe harbor - short" } + +# ── Deck Type → Slide Mapping ────────────────────────────────────── + +deck_types: + business_case: + description: "Business case for customer internal approval (CxO audience)" + slides: + - slide: cover + layout: cover_pillar_dark + content: "Customer Name — Business Case for OCI Migration" + - slide: executive_summary + layout: impact_light + content: "1-sentence bold statement of the opportunity" + - slide: business_drivers + layout: multi_statement_light + content: "3 key drivers: Why now, Cost of inaction, Strategic imperative" + - slide: tco_comparison + layout: two_col_light + content: "Current State vs OCI — 3-year TCO side by side" + - slide: roi_headline + layout: metric_light + content: "Key ROI number (e.g., '47% cost reduction over 3 years')" + - slide: value_drivers + layout: four_icons_light + content: "4 value categories: Cost, Risk, Agility, Innovation" + - slide: risk_assessment + layout: two_col_light + content: "Migration risks vs Do-nothing risks" + - slide: roadmap + layout: blank_light + content: "Implementation phases with timeline (custom shapes)" + - slide: recommendation + layout: impact_dark + content: "Clear ask: commitment amount, next steps" + - slide: safe_harbor + layout: safe_harbor_light + content: "Oracle safe harbor statement" + + proposal: + description: "Technical architecture proposal (SA/technical audience)" + slides: + - slide: cover + layout: cover_pillar_dark + content: "Customer Name — OCI Architecture Proposal" + - slide: agenda + layout: agenda_light + content: "Agenda items" + - slide: executive_summary + layout: impact_light + content: "Value hypothesis statement" + - slide: current_state + layout: two_col_light + content: "Current architecture vs target" + - slide: architecture + layout: blank_light + content: "Architecture diagram (inserted as image)" + - slide: service_details + layout: content_illust_light + content: "Key services with descriptions" + - slide: environment_catalogue + layout: blank_light + content: "Environment matrix table (Prod/PreProd/Dev/DR)" + - slide: cost_estimate + layout: two_col_light + content: "PAYG vs BYOL, monthly breakdown" + - slide: wa_scorecard + layout: blank_light + content: "Well-Architected review results table" + - slide: operations_raci + layout: blank_light + content: "RACI matrix for operational model" + - slide: roadmap + layout: blank_light + content: "Migration phases with timeline" + - slide: next_steps + layout: impact_dark + content: "Next steps and ask" diff --git a/config/output-formats.yaml b/config/output-formats.yaml new file mode 100644 index 0000000..dfa92ff --- /dev/null +++ b/config/output-formats.yaml @@ -0,0 +1,218 @@ +# Output Format Configuration +# Defines the available output types and their default settings + +output_selection: + description: | + The architect can specify output format when requesting deliverables: + deck ← default, just the slide deck + deck + drawio ← deck + editable diagram + deck + doc ← deck + detailed technical document + deck + xlsx ← deck + detailed cost spreadsheet + deck + pdf ← deck + customer-facing PDF + full ← everything: deck + drawio + doc + xlsx + pdf + doc only ← technical document without deck + pdf ← customer-facing PDF only (branded, no internal refs) + If not specified, produce "deck" only. + + deliver ← handover + go-live checklist + success criteria + + default: "deck" + + formats: + deck: + extension: ".pptx" + generator: "tools/oci_deck_gen.py" + description: "10-12 slide architecture proposal" + + drawio: + extension: ".drawio" + generator: "tools/oci_diagram_gen.py" + description: "Editable architecture diagram (draw.io)" + + doc: + extension: ".docx" + generator: "tools/oci_doc_gen.py" + description: "Detailed technical document (15-25 pages)" + sections: + - number: 1 + title: "Executive Summary" + tiers: [small, standard, complex] + content: "1-page summary: business driver, proposed solution, key outcomes" + + - number: 2 + title: "Current State" + tiers: [standard, complex] + content: "People, process, technology assessment from Workload Profile" + + - number: 3 + title: "Solution Architecture" + tiers: [small, standard, complex] + content: "Full architecture description, service rationale, topology diagram" + + - number: 4 + title: "Architecture Decision Records (ADRs)" + tiers: [standard, complex] + content: "Each key design decision with context, options considered, and rationale" + + - number: 5 + title: "High Availability & Disaster Recovery" + tiers: [small, standard, complex] + content: "HA/DR topology, RTO/RPO targets, failover procedures" + + - number: 6 + title: "Security & Compliance" + tiers: [small, standard, complex] + content: "Security controls, identity model, compliance framework mapping" + + - number: 7 + title: "Cost Estimate" + tiers: [small, standard, complex] + content: "Detailed cost breakdown with assumptions, PAYG vs BYOL, discount scenarios" + + - number: 8 + title: "Migration Plan" + tiers: [standard, complex] + content: "Migration strategy per component, tooling, phased plan, rollback" + + - number: 9 + title: "Operations Model" + tiers: [standard, complex] + content: "Day-2 operations: monitoring, patching, backup, incident response, RACI" + + - number: 10 + title: "Well-Architected Scorecard" + tiers: [standard, complex] + content: | + 5-pillar WA assessment (Operational Excellence, Security, Reliability, + Performance, Cost Optimization). Standard tier: score per pillar + top + recommendations. Complex tier: full findings, gap analysis, and remediation roadmap. + deck_equivalent: "slide 12 (summary) — doc section is the full underlying report" + note: "Small tier: traffic-light summary stays in deck only, not expanded in doc" + + - number: 11 + title: "Risk Register" + tiers: [standard, complex] + content: "Full risk table with likelihood, impact, severity, owner, mitigation" + + - number: 12 + title: "Next Steps" + tiers: [small, standard, complex] + content: "3-5 SMART actions with owners and dates" + + xlsx: + extension: ".xlsx" + generator: "tools/oci_cost_gen.py" + description: "Detailed cost spreadsheet with tabs" + + pdf: + extension: ".pdf" + generator: "tools/oci_pdf_gen.py" + description: "Customer-facing PDF (branded, no internal KB references)" + + deliver: + extension: ".yaml" + generator: "manual" + description: "DELIVER phase artifacts: handover, go-live checklist, success criteria" + artifacts: + - "templates/handover-document.yaml" + - "templates/go-live-checklist.yaml" + - "templates/success-criteria.yaml" + +# Slide Deck Structure (10-12 slides) +deck_structure: + slides: + - number: 1 + type: title + content: "Customer name, project, date, architect" + style: "Dark background (#312D2A), white text" + + - number: 2 + type: value_story + content: "Business driver, value hypothesis, desired outcomes (SMART)" + style: "Light background, left-aligned" + + - number: 3 + type: architecture_diagram + content: "Full architecture diagram (85% of slide)" + style: "White background, diagram fills slide" + + - number: 4 + type: decisions + content: "4-6 key decisions in two-column table" + style: "Clean table, alternating row shading" + + - number: 5 + type: ha_dr + content: "HA/DR topology + RTO/RPO table" + style: "Split layout: diagram left, table right" + + - number: 6 + type: security + content: "Compliance checkmarks, security controls grid" + style: "Icon grid, green checkmarks" + + - number: 7 + type: cost + content: "Summary table: PAYG vs BYOL, monthly/annual" + style: "Clean table, bold totals, assumptions below" + + - number: 8 + type: cost_comparison + optional: true + content: "Side-by-side: current vs OCI, or OCI vs AWS" + style: "Chart or comparison table" + + - number: 9 + type: migration + content: "Phased timeline, tools, downtime approach" + style: "Timeline visual, colored phases" + + - number: 10 + type: operations_summary + content: "Monitoring, patching, incident response, ops model highlights" + style: "Icon grid, clean layout" + + - number: 11 + type: risks + content: "Top 5-6 risks with severity and mitigation" + style: "Table with color-coded severity" + + - number: 12 + type: scorecard + content: "5-pillar WA scorecard + recommendations" + style: "Horizontal bars, traffic-light colors" + + - number: 13 + type: next_steps + content: "3-5 concrete SMART next steps with dates" + style: "Numbered list, forward-looking" + +# Design Standards +design_standards: + colors: + background: "#FFFFFF" + dark_background: "#312D2A" + primary_accent: "#2D5967" + secondary_accent: "#AA643B" + tertiary_accent: "#804998" + text: "#312D2A" + success: "#5E9624" + warning: "#AE562C" + error: "#C74634" + table_alt_row: "#F5F4F2" + + typography: + font: "Segoe UI" + titles: "24-28pt, bold, #312D2A" + subtitles: "16-18pt, regular, #70665E" + body: "12-14pt, regular, #312D2A" + table_headers: "11pt, bold, white on #2D5967" + table_body: "10-11pt, regular, #312D2A" + footnotes: "8-9pt, italic, #70665E" + + layout: + margins: "0.5 inches all sides" + title_position: "top-left, consistent" + content_area: "below title bar, 85% of slide height" + footer: "slide number bottom-right" + rule: "No gradients, clip art, or stock photos" diff --git a/config/service-categories.yaml b/config/service-categories.yaml new file mode 100644 index 0000000..3039ae9 --- /dev/null +++ b/config/service-categories.yaml @@ -0,0 +1,206 @@ +# Service → Category/Color Mapping +# Used by diagram generator and for consistent grouping across outputs +# last_verified: 2025-09-11 + +categories: + compute: + color: "#2D5967" + label: "Compute" + services: + - compute + - vm + - bare_metal + - gpu + - secure_desktops + - batch + + containers: + color: "#2D5967" + label: "Containers & Serverless" + services: + - oke + - container_instances + - functions + + networking: + color: "#2D5967" + label: "Networking" + services: + # Load Balancing + - load_balancer + - flexible_lb + - network_lb + # Gateways + - igw + - internet_gateway + - nat_gateway + - natgw + - service_gateway + - sgw + - drg + - dynamic_routing_gateway + # Connectivity + - fastconnect + - vpn + # DNS + - dns + - traffic_management + + security: + color: "#2D5967" + label: "Security" + services: + - waf + - bastion + - network_firewall + - cloud_guard + - data_safe + - vault + - security_zone + - vulnerability_scanning + - certificates + - access_governance + + storage: + color: "#2D5967" + label: "Storage" + services: + - object_storage + - block_storage + - file_storage + + database: + color: "#AA643B" + label: "Database" + services: + # Oracle + - adb + - adb_s + - adb_d + - autonomous_db + - autonomous_json + - atp + - adw + - dbcs + - db_system + - exadata + - exacs + - exadata_exascale + - exadata_c_at_c + - timesten + # Open Source + - mysql + - mysql_heatwave + - postgresql + - opensearch + - nosql + - cache + - redis + # Replication & Management + - goldengate + - data_catalog + - database_migration + - database_autonomous_recovery + + integration: + color: "#804998" + label: "Integration" + services: + - streaming + - kafka + - queue + - oci_queue + - oic + - integration_cloud + - api_gateway + - apigw + - api_management + - data_integration + - data_integrator + - service_connector_hub + - soa_suite + + ai_ml: + color: "#1A8A72" + label: "AI & Machine Learning" + services: + - generative_ai + - generative_ai_agents + - data_science + - anomaly_detection + - document_understanding + - language + - vision + - speech + - digital_assistant + + analytics: + color: "#1A8A72" + label: "Analytics & Data" + services: + - oracle_analytics_cloud + - essbase + - big_data_service + - data_flow + - ai_data_platform + + observability: + color: "#2D5967" + label: "Observability" + services: + - monitoring + - logging + - logging_analytics + - apm + - db_management + - ops_insights + - notifications + - events + - email_delivery + - full_stack_dr + - fleet_app_management + - os_management + - os_management_hub + - java_management + - resource_analytics + - resource_manager + + developer: + color: "#2D5967" + label: "Developer Services" + services: + - apex + - visual_builder + - devops + - blockchain + - iot + - media_flow + - media_streams + - tuxedo + - webcenter + + hybrid: + color: "#2D5967" + label: "Hybrid Cloud" + services: + - vmware_solution + - compute_cloud_at_customer + - managed_mac + - roving_edge + + dormant: + color: "#DFDCD8" + label: "Dormant/Standby" + border_color: "#9E9892" + text_color: "#70665E" + font_style: "italic" + services: + - dormant + - standby + + legacy: + color: "#70665E" + label: "Legacy/External" + services: + - legacy + - external diff --git a/config/workload-profile-schema.yaml b/config/workload-profile-schema.yaml new file mode 100644 index 0000000..8c3a580 --- /dev/null +++ b/config/workload-profile-schema.yaml @@ -0,0 +1,475 @@ +# Workload Profile Schema +# Defines all fields, types, defaults, and descriptions for the workload profile + +schema: + version: "1.1" + description: "Schema for OCI Deal Accelerator Workload Profile" + +fields: + engagement: + customer_name: + type: string + required: true + description: "Customer organization name" + industry: + type: enum + values: [financial_services, healthcare, retail, manufacturing, public_sector, technology, telecommunications, energy, education, other] + required: true + description: "Customer industry vertical" + decision_timeline: + type: string + required: false + description: "Target decision date or timeline (e.g., 'Q3 2026', '6 months')" + competitive_situation: + type: enum + values: [aws_incumbent, azure_incumbent, gcp_incumbent, on_prem_only, multi_cloud, no_competitor] + required: false + default: "on_prem_only" + description: "Current cloud/competitive landscape" + + current_state: + databases: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + description: "Database identifier or application name" + engine: + type: enum + values: [oracle, mysql, postgresql, sql_server, mongodb, dynamodb, cassandra, redis, elasticsearch, other] + required: true + version: + type: string + required: false + description: "Database version (e.g., '19c', '21c', '8.0')" + size_gb: + type: integer + required: true + description: "Current data size in GB" + type: + type: enum + values: [OLTP, OLAP, mixed] + required: true + default: "OLTP" + current_platform: + type: enum + values: [on_prem, aws, azure, gcp, other_cloud] + required: true + ha_current: + type: string + required: false + description: "Current HA setup (e.g., 'RAC 2-node', 'Always On AG', 'none')" + replication: + type: string + required: false + description: "Current replication (e.g., 'Data Guard', 'GoldenGate', 'none')" + connections_peak: + type: integer + required: false + description: "Peak concurrent database connections" + cpu_metrics: + type: object + required: false + fields: + avg_pct: + type: float + description: "Average CPU utilization percentage" + p95_pct: + type: float + description: "P95 CPU utilization percentage" + vcpus: + type: integer + description: "Number of vCPUs on current platform" + storage_metrics: + type: object + required: false + fields: + iops_avg: + type: integer + description: "Average IOPS" + iops_peak: + type: integer + description: "Peak IOPS" + throughput_mbps: + type: float + description: "Storage throughput in MB/s" + notable_features: + type: list + item_type: enum + values: [partitioning, TDE, VPD, advanced_security, label_security, spatial, graph, text, APEX, DBMS_SCHEDULER, db_links, UTL_FILE, UTL_HTTP, external_tables, directory_objects, edition_based_redefinition, flashback, blockchain_tables, RAC, advanced_compression, in_memory] + required: false + default: [] + description: "Oracle features in active use — affects service selection and compatibility" + tier: + type: enum + values: [platinum, gold, silver, bronze] + required: false + description: "Service tier assignment (Platinum/Gold/Silver/Bronze). Auto-assigned from SLA and compliance requirements using kb/patterns/service-tiering.yaml rules, then confirmed by architect." + + compute: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + purpose: + type: string + required: false + description: "What this compute serves (e.g., 'WebLogic app servers')" + os: + type: string + required: false + description: "Operating system (e.g., 'Oracle Linux 8', 'RHEL 9')" + vcpus: + type: integer + required: false + memory_gb: + type: float + required: false + current_platform: + type: enum + values: [on_prem, aws, azure, gcp, other_cloud] + required: true + scaling_pattern: + type: enum + values: [steady, daily_peaks, seasonal, unpredictable] + required: false + default: "steady" + + middleware: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + type: + type: enum + values: [WebLogic, Tomcat, WildFly, IIS, Node.js, other] + required: true + clustering: + type: string + required: false + description: "Clustering configuration (e.g., 'Active-Active 4 nodes')" + + messaging_and_eventing: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + type: + type: enum + values: [Kafka, RabbitMQ, SQS, MQ_Series, Oracle_AQ, other] + required: true + throughput_msgs_sec: + type: integer + required: false + description: "Peak message throughput in messages/second" + pattern: + type: enum + values: [pub_sub, point_to_point, streaming, event_driven] + required: false + default: "pub_sub" + + storage: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + type: + type: enum + values: [NFS, block, object, archive] + required: true + size_tb: + type: float + required: false + access_pattern: + type: enum + values: [frequent, infrequent, archive] + required: false + default: "frequent" + + networking: + type: object + required: false + fields: + connectivity: + type: enum + values: [internet, VPN, dedicated, hybrid] + required: false + default: "internet" + bandwidth_gbps: + type: float + required: false + latency_requirement_ms: + type: float + required: false + description: "Max acceptable latency in milliseconds" + regions_needed: + type: list + item_type: string + required: false + default: [] + on_prem_integration: + type: boolean + required: false + default: false + + identity_and_security: + type: object + required: false + fields: + idp: + type: enum + values: [AD, Okta, SAML, OCI_IAM, other] + required: false + default: "OCI_IAM" + compliance: + type: list + item_type: enum + values: [PCI-DSS, HIPAA, SOC2, GDPR, FedRAMP, ISO27001] + required: false + default: [] + encryption_requirements: + type: string + required: false + description: "Encryption requirements beyond default TDE (e.g., 'customer-managed keys via Vault')" + vpd_rls: + type: boolean + required: false + default: false + description: "Virtual Private Database or Row-Level Security in use" + data_masking: + type: boolean + required: false + default: false + audit_requirements: + type: string + required: false + description: "Audit requirements (e.g., 'SOC2 Type II annual', 'PCI QSA quarterly')" + + nosql_and_other_data: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + type: + type: enum + values: [MongoDB, DynamoDB, Cassandra, Redis, Elasticsearch, other] + required: true + size_gb: + type: integer + required: false + access_pattern: + type: enum + values: [high_throughput, low_latency_cache, search, analytics] + required: false + + integration: + type: list + required: false + default: [] + item_schema: + name: + type: string + required: true + type: + type: enum + values: [API_Gateway, ESB, ETL, CDC, file_transfer, iPaaS] + required: true + tool: + type: string + required: false + description: "Specific tool (e.g., 'MuleSoft', 'Informatica', 'GoldenGate')" + + requirements: + availability: + rto_hours: + type: float + required: true + default: 1.0 + description: "Recovery Time Objective in hours" + rpo_hours: + type: float + required: true + default: 0.25 + description: "Recovery Point Objective in hours" + sla_target: + type: string + required: true + default: "99.95%" + description: "Target availability SLA" + performance: + response_time_p95_ms: + type: integer + required: false + description: "P95 response time target in milliseconds" + throughput_tps: + type: integer + required: false + description: "Target transactions per second" + concurrent_users: + type: integer + required: false + description: "Peak concurrent users" + scalability: + growth_annual_pct: + type: float + required: false + default: 15 + description: "Expected annual growth percentage" + peak_multiplier: + type: float + required: false + default: 1.5 + description: "Peak load as multiplier of baseline (e.g., 3.0 for Black Friday)" + scaling_speed: + type: enum + values: [immediate, minutes, hours] + required: false + default: "minutes" + description: "How fast scaling needs to respond" + migration: + downtime_tolerance: + type: string + required: false + default: "4 hours" + description: "Acceptable migration downtime window" + migration_timeline: + type: string + required: false + description: "Target migration timeline (e.g., '6 months')" + parallel_run_needed: + type: boolean + required: false + default: false + description: "Whether old and new systems must run in parallel during cutover" + + # --- People & Process (ECAL Current State expansion) --- + people: + team_size: + type: integer + required: false + description: "Total IT team size relevant to the engagement" + roles: + type: list + item_type: string + required: false + default: [] + description: "Team roles (e.g., ['dba', 'cloud_engineer', 'devops', 'security', 'network'])" + skill_gaps: + type: list + item_type: string + required: false + default: [] + description: "Skills the team lacks for the proposed solution (e.g., ['kubernetes', 'terraform', 'oci_console'])" + managed_services_preference: + type: enum + values: [self_managed, co_managed, fully_managed] + required: false + default: "co_managed" + description: "How much operational responsibility the customer wants to retain" + change_readiness: + type: enum + values: [resistant, cautious, open, eager] + required: false + default: "cautious" + description: "Organizational readiness for change" + + process: + deployment_process: + type: enum + values: [manual, scripted, ci_cd_basic, ci_cd_advanced, gitops] + required: false + default: "manual" + description: "Current deployment maturity" + change_management: + type: enum + values: [none, informal, cab_based, automated] + required: false + default: "informal" + description: "Change approval process" + incident_response: + type: enum + values: [none, ad_hoc, on_call_rotation, noc_24x7, outsourced] + required: false + default: "ad_hoc" + description: "Current incident response model" + backup_testing_frequency: + type: enum + values: [never, annually, quarterly, monthly] + required: false + default: "never" + description: "How often backup/recovery is tested" + + decision_drivers: + primary: + type: enum + values: [cost_reduction, modernization, performance, compliance, hardware_eol, contract_expiry, consolidation] + required: false + description: "Primary motivation for the engagement" + budget_sensitivity: + type: enum + values: [low, medium, high] + required: false + default: "medium" + licensing: + type: enum + values: [BYOL, new_licenses, ULA, reduce_oracle_footprint] + required: false + default: "new_licenses" + description: "Oracle licensing strategy" + team_skills: + type: list + item_type: string + required: false + default: [] + description: "Team's existing skills (e.g., ['terraform', 'kubernetes', 'oracle_dba'])" + political_constraints: + type: string + required: false + description: "Non-technical constraints (e.g., 'board wants results by Q3', 'CTO prefers AWS')" + +# Derived flags — auto-computed from the profile, not user-entered +derived_flags: + description: "Boolean flags computed from the workload profile for use by the WA validation engine" + flags: + - name: has_databases + computed_from: "any entry in current_state.databases[]" + - name: has_autonomous_database + computed_from: "architecture includes ADB-S or ADB-D" + - name: has_production_databases + computed_from: "has_databases AND criticality in [mission_critical, business_critical]" + - name: has_compute_instances + computed_from: "architecture includes VM, BM, or OKE" + - name: has_production_workloads + computed_from: "any application with criticality in [mission_critical, business_critical]" + - name: has_internet_facing_services + computed_from: "architecture includes public subnet with LB or WAF" + - name: has_fastconnect + computed_from: "connectivity in [dedicated, hybrid]" + - name: has_variable_workloads + computed_from: "any compute with scaling_pattern != 'steady'" + - name: requires_dr + computed_from: "requirements.availability.rto_hours < 4 or explicit DR requirement" + - name: enterprise_customer + computed_from: "compliance list is not empty OR team has > 3 members" + - name: has_oracle_licensed_services + computed_from: "licensing in [BYOL, ULA]" + - name: distributed_cloud_applicable + computed_from: "identity_and_security.compliance includes data residency or sovereign requirements" diff --git a/docs/define-phase.md b/docs/define-phase.md new file mode 100644 index 0000000..2c7ef22 --- /dev/null +++ b/docs/define-phase.md @@ -0,0 +1,90 @@ +# DEFINE Phase — Detailed Guide + +The DEFINE phase identifies the customer's business problem and builds commitment to solve it. Output: a scoped **Value Story** and a **Joint Engagement Plan**. + +## Steps + +### 1. Ideate + +Create a value hypothesis expressed in the customer's business terms — not Oracle features. + +**Inputs:** +- Discovery notes (unstructured) +- Customer's public filings, strategy docs, press releases +- Industry patterns from `kb/patterns/business-patterns.yaml` +- Business Drivers Framework from `kb/patterns/business-drivers.yaml` + +**Activities:** +- Build Customer Profile (`templates/customer-profile.yaml`) — strategic goals, Oracle footprint, industry analysis +- Build Strategy Map (`templates/strategy-map.yaml`) — goals → strategies → capabilities → enablers +- Parse discovery notes into structured Workload Profile +- Identify 1-3 business drivers using the four pillars (Strategic, Financial, Business Ops, IT Ops) +- Formulate 1+ Points of View, evaluate each against SMART criteria +- Select the strongest PoV as the value hypothesis +- Cross-reference with `kb/patterns/application-patterns.yaml` for proven solutions +- Map to hypothesis family from `kb/patterns/business-drivers.yaml` + +**Output:** Customer Profile + Strategy Map + Draft Value Story (`templates/value-story.yaml`) + +**ECAL Artefacts:** See `kb/patterns/ecal-artefacts-catalog.yaml` DEF-01 through DEF-06 +**Engagement RACI:** See `kb/patterns/engagement-raci.yaml` define.ideate + +### 2. Validate + +Test the hypothesis against the customer's reality. + +**Activities:** +- Review value story for SMART criteria (Specific, Measurable, Attainable, Relevant, Time-based) +- Identify gaps in discovery data — list what we assumed vs. what we confirmed +- Check: Is the hypothesis bold enough? Is there enough urgency? +- Validate technical feasibility against `kb/services/` and `kb/compatibility/` + +**Validation checklist:** +``` +- [ ] Business driver clearly identified and quantifiable +- [ ] Desired outcome is SMART +- [ ] Technical feasibility confirmed (no blockers in feature matrix) +- [ ] Customer sponsor identified +- [ ] Urgency driver exists (EOL, contract, compliance deadline, competitive threat) +``` + +**Output:** Validated Value Story with confidence flags + +### 3. Plan + +If the hypothesis is compelling, create a Joint Engagement Plan for the DESIGN phase. + +**Activities:** +- Define engagement tier (small/standard/complex) — see [engagement-tiers.md](engagement-tiers.md) +- Timebox the DESIGN phase (typically 2-6 weeks) +- Identify resources needed (SA, specialists, customer stakeholders) +- Define success criteria for the DESIGN phase itself + +**Output:** Joint Engagement Plan (`templates/joint-engagement-plan.yaml`) + +## Engagement Tier Selection + +The tier determines artifact depth. Select based on: + +| Signal | Small | Standard | Complex | +|--------|-------|----------|---------| +| # Applications | 1-2 | 3-10 | 10+ | +| Compliance | None | 1 framework | Multiple | +| Regions | 1 | 1-2 | 3+ | +| Integration points | 0-2 | 3-5 | 5+ | +| Budget sensitivity | Low | Medium | High | +| Timeline | < 4 weeks | 4-12 weeks | 12+ weeks | + +See [engagement-tiers.md](engagement-tiers.md) for full tier definitions and artifact mapping. + +## Iterative Checkpoint + +Before moving to DESIGN, verify: +``` +- [ ] Value Story approved by architect (and ideally customer sponsor) +- [ ] Workload Profile has < 3 critical gaps +- [ ] Engagement tier selected +- [ ] Joint Engagement Plan agreed (timebox, resources, success criteria) +``` + +If the value story isn't compelling, iterate: refine the hypothesis or pick another one. diff --git a/docs/deliver-phase.md b/docs/deliver-phase.md new file mode 100644 index 0000000..52ac66b --- /dev/null +++ b/docs/deliver-phase.md @@ -0,0 +1,107 @@ +# DELIVER Phase — Detailed Guide + +The DELIVER phase ensures an efficient handover from pre-sales to implementation and tracks value realization. These are **lightweight artifacts** — the SA does not replace the implementation team. + +> ECAL DELIVER describes the solution implementation at high level. It provides just enough guidance and templates to deliver. It dovetails with the customer or implementation partner framework (e.g., Oracle Consulting True Cloud Method). + +## Steps + +### 1. Adopt + +Ensure a clean handover so the implementation team doesn't rework previous steps. + +**Activities:** +- Produce Handover Document (`templates/handover-document.yaml`) + - Summarizes all DEFINE + DESIGN artifacts in one place + - Lists key decisions made and their rationale (from ADRs) + - Identifies open items and assumptions to verify +- Define MVP scope + - What ships in Phase 1 vs. later phases + - Prioritize by customer value, not technical convenience + - Example: "Phase 1: migrate core OLTP to ADB-S with local HA. Phase 2: activate cross-region DR. Phase 3: modernize reporting with ADW." +- Establish governance + - Steering committee cadence (bi-weekly for standard, weekly for complex) + - Escalation path + - Change control process + +**Output:** Handover Document + MVP Definition + +**Guidelines:** +- Look for a Minimum Viable Product that delivers real customer benefits early +- Incremental delivery phases should maximize customer value per the roadmap +- Consider how to help the customer with adoption and ownership + +### 2. Operate + +Define what "running well" looks like after go-live. + +**Activities:** +- Produce Go-Live Checklist (`templates/go-live-checklist.yaml`) + - Pre-go-live verification items + - Monitoring and alerting confirmation + - Runbook availability + - Rollback readiness +- Define Success Criteria (`templates/success-criteria.yaml`) + - Quantitative metrics tied to the Value Story + - Example: "P95 latency < 15ms", "Monthly cost < $X", "Zero unplanned downtime in first 90 days" + - Measurement method and cadence +- Operational handover + - Confirm ops team has access and training + - Validate monitoring dashboards are active + - Confirm backup/recovery has been tested + +**Output:** Go-Live Checklist + Success Criteria + +**Guidelines:** +- Regular governance checks should continue beyond go-live +- Data must be collected to validate business outcomes +- Results should be reported back to the business sponsor + +### 3. Improve + +Capture learnings and validate value delivery. + +**Activities:** +- Produce Lessons Learned (`templates/lessons-learned.yaml`) + - What worked well (reuse in future engagements) + - What didn't work (improve process or templates) + - Suggestions for ECAL/skill improvements +- Value realization check (30/60/90 days post go-live) + - Compare actual metrics against Success Criteria + - Determine if the original hypothesis from DEFINE delivered value + - Identify next hypothesis to tackle +- Feed improvements back + - Update `kb/field-knowledge/` with new findings + - Update patterns if architecture evolved during delivery + - Share customer success story if applicable + +**Output:** Lessons Learned + Value Realization Report + +**Guidelines:** +- Grab any opportunity to capture a successful outcome as a customer story +- Look back at every project for what you could do better +- Keep in touch with the customer and track how the project is going +- Share learnings with the team + +## Artifact Summary by Tier + +| Artifact | Small | Standard | Complex | +|----------|-------|----------|---------| +| Handover Document | 1-page summary | Full template | Full + appendices | +| MVP Definition | Inline in handover | Separate section | Phased roadmap | +| Go-Live Checklist | 10-15 items | 20-30 items | 30-50 items | +| Success Criteria | 3-5 metrics | 5-8 metrics | 8-12 metrics | +| Lessons Learned | Brief notes | Full template | Full + retro | + +## Iterative Checkpoint + +The DELIVER phase feeds back into DEFINE for next phases: +``` +- [ ] Handover document complete and reviewed with implementation team +- [ ] MVP scope agreed with customer +- [ ] Go-live checklist validated (all items green) +- [ ] Success criteria baselined (measurements started) +- [ ] Lessons learned captured within 2 weeks of go-live +- [ ] Value realization check scheduled (30/60/90 days) +- [ ] Next hypothesis identified (return to DEFINE if applicable) +``` diff --git a/docs/design-phase.md b/docs/design-phase.md new file mode 100644 index 0000000..d4a6a67 --- /dev/null +++ b/docs/design-phase.md @@ -0,0 +1,132 @@ +# DESIGN Phase — Detailed Guide + +The DESIGN phase produces a complete, defensible architecture. Output: agreed architecture, cost estimate, operations model, and solution proposal. + +## Steps + +### 1. Current State (People, Process, Technology) + +Capture enough about the current state to architect the future. Frame the problem — don't gather exhaustive requirements. + +**Artefacts:** +- Discovery Questionnaire (`templates/discovery-questionnaire.yaml`) — structured customer data collection +- Business & Security Constraints — time, funding, legal, compliance, technology constraints +- Current State Apps & Tech Portfolio — systems, assets, integrations +- Prioritization Matrix — score apps by criticality, fit, potential, risk (in discovery questionnaire) +- Current State Architecture — diagrams of current environment + +**Technology** (existing in Workload Profile): +- Databases, compute, middleware, messaging, storage, networking, identity, integration +- Use `config/workload-profile-schema.yaml` for field definitions + +**People** (new — added to Workload Profile): +- Team size and roles (DBAs, cloud engineers, DevOps, security) +- Skill gaps relevant to the proposed solution +- Managed services preference (self-managed → fully managed) +- Change management readiness (how resistant is the org to change?) + +**Process** (new — added to Workload Profile): +- Deployment process (manual, CI/CD, IaC maturity) +- Change management process (CAB, lightweight, none) +- Incident response process (NOC, on-call rotation, outsourced) +- Backup/recovery testing frequency + +**Guidelines:** +- Frame the problem — don't gather exhaustive requirements +- Be collaborative — share everything with the customer +- Use whatever works — top-down and bottom-up +- Understand business context, drivers, and desired outcomes +- Be very clear about scope — which systems are in and out + +**ECAL Artefacts:** See `kb/patterns/ecal-artefacts-catalog.yaml` DES-01 through DES-09 +**Engagement RACI:** See `kb/patterns/engagement-raci.yaml` design.current + +### 2. Future State (Solution Design) + +Given the Workload Profile and Current State, compose a complete architecture. This step produces multiple design artifacts: + +#### Solution Design +Core architecture composition — the existing Phase 2 workflow: +1. Select services from `kb/services/` +2. Dimension using `kb/sizing/` rules +3. Compose topology from `kb/patterns/` blocks +4. Estimate costs with explicit assumptions +5. Validate against Well-Architected Framework (`kb/well-architected/`) + +#### Deployment Design +How the solution gets built and deployed: +- Environment strategy (dev, test, staging, prod) +- IaC approach (Terraform, Resource Manager, Ansible) +- CI/CD pipeline design +- Non-production environment sizing (typically 50% of prod for test, 25% for dev) + +#### Transition Design +How to get from current state to future state: +- Migration strategy per component (lift-and-shift, re-platform, re-architect) +- Migration tooling (DMS, GoldenGate, RMAN, Data Pump, ZDM) +- Phased migration plan with dependencies +- Parallel run requirements and cutover strategy +- Rollback plan for each phase + +#### Operations Model +How the solution will be operated day-to-day: +- Use `templates/operations-model.yaml` as the artifact template +- Monitoring and alerting strategy (OCI Monitoring, Logging Analytics, 3rd party) +- Patching and maintenance windows +- Backup and recovery procedures +- Incident response and escalation +- Capacity management and scaling triggers + +**Guidelines:** +- Always look for a quick win the customer can see early +- The operational model is just as important as the architecture +- Don't forget non-production environments +- Security and data locality must be understood early + +### 3. Confirm (Solution Proposal) + +Assemble all design work into a solution proposal for stakeholder decision. + +**Activities:** +- Assemble the slide deck with all architecture artifacts +- Prepare the business case: costs, benefits, timeline, risks +- Ensure all propositions are SMART: + - **S**pecific: "Migrate 3 Oracle databases to ADB-S" not "Move to cloud" + - **M**easurable: "Reduce DB admin effort by 60%" not "Improve efficiency" + - **A**ttainable: Validated against feature matrix and field findings + - **R**elevant: Tied to the business driver from the Value Story + - **T**ime-based: "8-week migration" not "as soon as possible" +- Include competitive positioning if relevant (`kb/competitive/`) + +**Output artifacts by tier:** + +| Artifact | Small | Standard | Complex | +|----------|-------|----------|---------| +| Slide deck (.pptx) | 6-8 slides | 10-12 slides | 12-15 slides | +| Architecture diagram | Single-page | Single-page | Multi-page | +| Cost estimate | In-deck table | Separate .xlsx | Detailed .xlsx | +| ADRs | 2-3 inline | 4-6 in appendix | Full ADR docs | +| Migration plan | 1 slide | 2-3 slides | Separate doc | +| Operations model | In-deck summary | 1-page summary | Full template | +| Risk register | Top 3-5 | Top 5-8 | Full register | +| WA scorecard | Traffic lights | With recommendations | Full report | + +**Guidelines:** +- Don't say anything you cannot back up with evidence +- The roadmap must be achievable +- Think about the person you are presenting to — what does it mean for them +- Quality of the presentation matters — it must look professional + +## Iterative Checkpoint + +Before moving to DELIVER (or presenting to customer): +``` +- [ ] Architecture validated against WA Framework (no critical gaps) +- [ ] Cost estimate reviewed with explicit assumptions +- [ ] Migration plan is achievable within stated timeline +- [ ] Operations model addresses day-2 concerns +- [ ] All propositions are SMART +- [ ] Risk register has mitigations for all HIGH items +- [ ] Feature compatibility checked (no blockers) +- [ ] Field findings reviewed (no known issues unaddressed) +``` diff --git a/docs/ecal-gaps-backlog.md b/docs/ecal-gaps-backlog.md new file mode 100644 index 0000000..ab88797 --- /dev/null +++ b/docs/ecal-gaps-backlog.md @@ -0,0 +1,133 @@ +# ECAL Gaps Backlog + +Items identified from ECAL 3.1 Practitioners Guides (01-09) + Big Bets Banking Example PDF that are **not yet implemented** in the KB. Prioritized by impact. + +## Priority 1 — High Impact (next sprint) + +### B1. Integration Catalog Template +- **Source**: ECAL 05 FUTURE, slide 23 +- **What**: Table of all non-OOTB integrations with data flows, volumes, frequency +- **Why**: Directly affects architecture accuracy and implementation scope +- **Action**: Create `templates/integration-catalog.yaml` with fields: use_case, source, target, method (API/file/DB link), protocol, data_volume, frequency, direction, owner + +### B2. Cloud Operating Framework (52-week plan) +- **Source**: ECAL 08 OPERATE, slide 14 +- **What**: Detailed first-year operational plan covering all 9 operational capability areas +- **Why**: Most customers have never operated cloud — this bridges the gap +- **Action**: Create `kb/patterns/cloud-operating-framework.yaml` with 9 capability areas mapped to weekly cadence +- **9 Capabilities**: Program Management, Cloud Innovation, Platform Enablement, Lifecycle Management, Environments Management, Testing, Management & Monitoring, Adoption & User Support, Issue Management + +### B3. OCI Operationalization Framework (5 milestones) +- **Source**: ECAL 08 OPERATE, slide 15 +- **What**: 5-milestone methodology for deploying workloads to OCI +- **Milestones**: Preparation & Training → Conceptual & Logical Arch → Physical Arch Execution → Operations → Specific Workloads +- **Action**: Create `kb/patterns/oci-operationalization.yaml` + +### B4. POD (Pool of Databases) Pattern +- **Source**: Big Bets PDF, p.23 +- **What**: Data platform pattern for large-scale DB consolidation +- **Characteristics**: Self-contained granular unit, standard external connectivity, internally optimized for DB, designed for maintainability/longevity, no prescribed hardware, on-prem/hybrid/cloud +- **Action**: Create `kb/patterns/database-consolidation-pod.yaml` + +### B5. Banking/Financial Compliance Pattern +- **Source**: Big Bets PDF, pp.4,19,21 +- **What**: EBA, FCA, PRA, ISO 27001/27002, NIST CSF requirements mapped to OCI services +- **Action**: Create `kb/compliance/banking-financial.yaml` with regulation → OCI service mapping + +## Priority 2 — Medium Impact + +### B6. Extension Catalog Template +- **Source**: ECAL 05 FUTURE, slide 22 +- **What**: Custom extensions needed beyond OOTB Oracle products +- **Fields**: Business capability, name, type, technology, developer (customer/partner/Oracle) +- **Action**: Create `templates/extension-catalog.yaml` + +### B7. Business Transition Plan Template +- **Source**: ECAL 07 ADOPT, slide 22 +- **What**: Managing business change during technology transformation +- **Covers**: Org change, process standardization, policy alignment, value measurement +- **Action**: Create `templates/business-transition-plan.yaml` + +### B8. Training & Communication Plan Template +- **Source**: ECAL 07 ADOPT, slide 23 +- **What**: Training delivery plan + project communication plan +- **Action**: Create `templates/training-communication-plan.yaml` + +### B9. Project Governance Template +- **Source**: ECAL 07 ADOPT, slide 25 +- **What**: Governance framework: steering committee, change control, escalation +- **Components**: Steering committee, solution authority, change control board, project board +- **Action**: Create `templates/project-governance.yaml` + +### B10. Customer Success Story Template +- **Source**: ECAL 09 IMPROVE, slide 14 +- **What**: Single-slide format: who, industry, challenges, approach, value, turning point +- **Action**: Create `templates/customer-success-story.yaml` + +### B11. Contracts & Policy Review Checklist +- **Source**: ECAL 07 ADOPT, slide 30 +- **What**: Review checklist for all agreements (cloud services, ULA, PULA, partner) +- **Action**: Create `templates/contracts-review-checklist.yaml` + +### B12. Readiness Assessment Template +- **Source**: ECAL 07 ADOPT, slide 31 +- **What**: Ensure project team has right skills, assets, infrastructure, processes +- **Action**: Create `templates/readiness-assessment.yaml` + +## Priority 3 — Lower Impact (nice to have) + +### B13. PoC Joint Execution Plan Template +- **Source**: ECAL 05 FUTURE, slide 43 +- **What**: Scope, approach, timeline, success criteria, use cases, test plans, ground rules +- **Action**: Create `templates/poc-execution-plan.yaml` + +### B14. Information & BI Model Template +- **Source**: ECAL 05 FUTURE, slide 24 +- **What**: Data flows, MDM, analytics/BI architecture view +- **Action**: Create `kb/patterns/information-bi-model.yaml` + +### B15. Current State Process & Capability Map Guide +- **Source**: ECAL 04 CURRENT, slide 17 +- **What**: Guidance for creating business architecture view of current state +- **Note**: This is mostly a customer/BA activity — skill can provide structure +- **Action**: Add guidance to `docs/design-phase.md` + +### B16. RFx Response Guide +- **Source**: ECAL 06 CONFIRM, slide 14 +- **What**: Process for responding to RFI/RFQ/RFP +- **Note**: Primarily sales/BD activity +- **Action**: Add brief section to `docs/design-phase.md` Confirm step + +### B17. Commercial Agreement Guide +- **Source**: ECAL 06 CONFIRM, slide 16 +- **What**: BYOL, ELA, PULA, Cloud Credits options and when to use each +- **Action**: Enhance `kb/pricing/` with licensing model guidance + +### B18. TCM Alignment Mapping +- **Source**: All ECAL guides — Consulting Guidance slides +- **What**: Map ECAL artefacts to Oracle True Cloud Method (TCM) activities +- **Action**: Add `tcm_alignment` field to ecal-artefacts-catalog.yaml + +### B19. ExaCC Managed Service Pattern +- **Source**: Big Bets PDF +- **What**: Complete pattern for ExaCC with ZDLRA, ZFS, OEM, OKV as managed service +- **Action**: Create `kb/patterns/exacc-managed-service.yaml` + +### B20. Security Constraints Checklist +- **Source**: ECAL 04 CURRENT, slide 15 +- **What**: Structured checklist for security requirements assessment +- **Action**: Create `templates/security-constraints-checklist.yaml` + +--- + +## Completed (this session) + +| Item | File Created | +|---|---| +| Engagement RACI (10 roles, all 9 steps) | `kb/patterns/engagement-raci.yaml` | +| Discovery Questionnaire (with prioritization) | `templates/discovery-questionnaire.yaml` | +| Business Drivers Framework (4 pillars + hypothesis families) | `kb/patterns/business-drivers.yaml` | +| ECAL Artefacts Catalog (60 artefacts, skill support classification) | `kb/patterns/ecal-artefacts-catalog.yaml` | +| Customer Profile Template | `templates/customer-profile.yaml` | +| Strategy Map Template | `templates/strategy-map.yaml` | +| Lessons Learned per step | Included in `engagement-raci.yaml` | diff --git a/docs/engagement-tiers.md b/docs/engagement-tiers.md new file mode 100644 index 0000000..13ddd71 --- /dev/null +++ b/docs/engagement-tiers.md @@ -0,0 +1,100 @@ +# Engagement Tiers + +Engagement tiers scale artifact depth to match engagement complexity. Not every deal needs a 15-slide deck and full ADR set. + +## Tier Definitions + +### Small (Rapid Innovation) + +**Profile:** Single workload, single region, minimal compliance, short timeline. + +**Examples:** +- Autonomous Database + Analytics Cloud dashboard +- Single app migration to OKE +- PoC or pilot deployment + +**Characteristics:** +- 1-2 applications in scope +- No compliance frameworks +- Single OCI region +- 0-2 integration points +- Budget < $10K/month +- Timeline < 4 weeks design + +**Artifact depth:** Highly templated, minimal customization. The Value Story can be a single paragraph. The deck is 6-8 slides. No separate operations model — include a "Day-2" slide. + +### Standard (Extend Capabilities) + +**Profile:** Multi-component architecture, moderate complexity, typical enterprise engagement. + +**Examples:** +- Oracle DB migration with HA/DR to ADB-S +- Multi-tier application modernization +- Hybrid cloud with FastConnect + +**Characteristics:** +- 3-10 applications in scope +- 1 compliance framework (PCI, SOC2, HIPAA) +- 1-2 OCI regions +- 3-5 integration points +- Budget $10K-100K/month +- Timeline 4-12 weeks design + +**Artifact depth:** Full template set. 10-12 slide deck. Separate cost spreadsheet. Operations model as 1-page summary. 4-6 ADRs. + +### Complex (Business Transformation) + +**Profile:** Enterprise-wide transformation, multi-region, multiple compliance, long timeline. + +**Examples:** +- Full data center migration (50+ workloads) +- Multi-cloud architecture with OCI as primary +- Regulated industry platform (banking, healthcare) + +**Characteristics:** +- 10+ applications in scope +- Multiple compliance frameworks +- 3+ OCI regions +- 5+ integration points +- Budget > $100K/month +- Timeline 12+ weeks design + +**Artifact depth:** Full customized artifacts. 12-15 slide deck (possibly multi-deck). Full operations model. Detailed migration plan as separate document. Full risk register. Comprehensive WA report. + +## Artifact Matrix + +| Artifact | Small | Standard | Complex | +|----------|-------|----------|---------| +| **DEFINE** | | | | +| Value Story | 1 paragraph | Full template | Full + executive summary | +| Workload Profile | Minimal fields | Standard fields | All fields + appendix | +| Joint Engagement Plan | Email/verbal | 1-page template | Full template | +| **DESIGN** | | | | +| Slide deck | 6-8 slides | 10-12 slides | 12-15 slides | +| Architecture diagram | Single-page simple | Single-page detailed | Multi-page | +| Cost estimate | In-deck table | Separate .xlsx | Detailed .xlsx with scenarios | +| ADRs | 2-3 inline in deck | 4-6 separate | Full ADR documents | +| Migration plan | 1 slide | 2-3 slides | Separate document | +| Operations model | 1 "Day-2" slide | 1-page summary | Full template | +| Transition design | Inline in migration | Separate section | Separate document | +| Risk register | Top 3-5 in deck | Top 5-8 in deck | Full register document | +| WA scorecard | Traffic lights only | With recommendations | Full WA report | +| **DELIVER** | | | | +| Handover document | 1-page summary | Full template | Full + appendices | +| MVP definition | Inline in handover | Separate section | Phased roadmap | +| Go-live checklist | 10-15 items | 20-30 items | 30-50 items | +| Success criteria | 3-5 metrics | 5-8 metrics | 8-12 metrics | +| Lessons learned | Brief notes | Full template | Full + retrospective | + +## Tier Selection Logic + +Select tier based on the **highest complexity signal**. If 8 out of 10 signals say "small" but compliance says "complex", use **standard** (not complex — let the single complex dimension drive up one tier, not two). + +``` +IF any signal is "complex" AND 2+ signals are "complex": + → Complex tier +ELIF any signal is "complex" OR 3+ signals are "standard": + → Standard tier +ELSE: + → Small tier +``` diff --git a/examples/aws-adb-dr-toronto.drawio.svg b/examples/aws-adb-dr-toronto.drawio.svg new file mode 100644 index 0000000..c62e068 --- /dev/null +++ b/examples/aws-adb-dr-toronto.drawio.svg @@ -0,0 +1,4 @@ + + + +
Users
Amazon Web Services
US East (Virginia)
Application
Load Balancer
Application
(EC2 / ECS)
S3 Bucket
SQS Queue
Oracle Cloud Infrastructure
OCI Region — US East (Ashburn)
VCN prod-vcn 10.0.0.0/16
Public Subnet — DMZ
WAF
Public Load
Balancer
Bastion
Private Subnet — Application
API Gateway
Functions
Private Subnet — Database
Autonomous Database
Serverless — Primary
Vault
Private Subnet — Storage & AI
Object Storage
Generative AI
Private Subnet — Messaging & Events
Queue
Notifications
DRG
Service
Gateway
Monitoring
Logging
OCI Region — Canada SE (Toronto)
VCN dr-vcn 10.1.0.0/16
Private Subnet — Database
Autonomous Database
Serverless — Standby
Data Safe
Private Subnet — NoSQL & Cache
NoSQL Database
OCI Cache
(Redis)
Private Subnet — Storage
Object Storage
File Storage
Private Subnet — Messaging & Events
Queue
Notifications
DRG
Service
Gateway
HTTPSFastConnectAutonomousData GuardRemote PeeringCross-RegionReplication
AWS App (Virginia) → ADB-S (Ashburn) + Remote Standby (Toronto)
1
2
3
4
\ No newline at end of file diff --git a/examples/aws-adb-dr-toronto.svg b/examples/aws-adb-dr-toronto.svg new file mode 100644 index 0000000..dac14bb --- /dev/null +++ b/examples/aws-adb-dr-toronto.svg @@ -0,0 +1,3 @@ + + +
Users
Users
Amazon Web Services
US East (Virginia)
Amazon Web Services...
Application
Load Balancer
Applicati...
Application
(EC2 / ECS)
Applicati...
S3 Bucket
S3 Bucket
SQS Queue
SQS Queue
Oracle Cloud Infrastructure
Oracle Cloud Infrastructure
OCI Region — US East (Ashburn)
OCI Region — US East (Ashburn)
VCN prod-vcn 10.0.0.0/16
VCN prod-vcn 10.0.0.0/16
Public Subnet — DMZ
Public Subnet — DMZ
WAF
WAF
Public Load
Balancer
Public Load...
Bastion
Bastion
Private Subnet — Application
Private Subnet — Application
API Gateway
API Gateway
Functions
Functions
Private Subnet — Database
Private Subnet — Database
Autonomous Database
Serverless — Primary
Autonomous Database...
Vault
Vault
Private Subnet — Storage & AI
Private Subnet — Storage & AI
Object Storage
Object Storage
Generative AI
Generative AI
Private Subnet — Messaging
Private Subnet — Messaging
Queue
Queue
Notifications
Notifications
DRG
DRG
Service
Gateway
Service...
OCI Region — Canada SE (Toronto)
OCI Region — Canada SE (Toronto)
VCN dr-vcn 10.1.0.0/16
VCN dr-vcn 10.1.0.0/16
Private Subnet — Database
Private Subnet — Database
Autonomous Database
Serverless — Standby
Autonomous Database...
Data Safe
Data Safe
Private Subnet — NoSQL & Cache
Private Subnet — NoSQL & Cache
NoSQL Database
NoSQL Database
OCI Cache
(Redis)
OCI Cache...
Private Subnet — Storage
Private Subnet — Storage
Object Storage
Object Storage
File Storage
File Storage
Private Subnet — Messaging
Private Subnet — Messaging
Queue
Queue
Notifications
Notifications
DRG
DRG
Service
Gateway
Service...
HTTPSFastConnectAutonomousData GuardRemote PeeringCross-RegionReplication
AWS App (Virginia) → ADB-S (Ashburn) + Remote Standby (Toronto)
AWS App (Virginia) → ADB-S (Ashburn) + Remote Standby (Toronto)
Text is not SVG - cannot display
\ No newline at end of file diff --git a/examples/sample-discovery-farma.md b/examples/sample-discovery-farma.md new file mode 100644 index 0000000..2cbabb5 --- /dev/null +++ b/examples/sample-discovery-farma.md @@ -0,0 +1,32 @@ +# Discovery Notes — Laboratorios Farma SA +# Reunión: 12 marzo 2026 +# Asistentes: CTO (Roberto), DBA lead (Patricia), Dev lead (Andrés) + +Base Oracle 19c Enterprise en VMware on-prem +- 800GB OLTP, producción, sistema core de trazabilidad de medicamentos +- 12 vCPUs, 64GB RAM en VM +- CPU promedio 45%, picos a 75% fin de mes cuando cierran lotes +- Unos 80 usuarios concurrentes, picos de 150 +- TDE habilitado, tienen BYOL con 2 proc licenses +- Backup con RMAN a disco local, no tienen copia offsite + +Aplicación Java en Tomcat, 2 servidores app +- Conexión JDBC simple, no usan pool avanzado +- Quieren eventualmente pasar a containers pero no es prioridad ahora + +No tienen DR. Patricia dijo "si se cae, tardamos 8hs en restaurar del backup" +Roberto quiere DR pero no quiere pagar el doble +RTO aceptable: 1 hora. RPO: 15 minutos max + +Regulación ANMAT — necesitan audit trail completo, no pueden perder datos de trazabilidad +No mencionaron PCI ni HIPAA + +Red: tienen un link de internet de 100Mbps, no tienen FastConnect ni VPN dedicada +Patricia preguntó si pueden seguir usando SQLDeveloper y RMAN + +Presupuesto: quieren gastar menos que hoy. Hoy pagan ~$15K USD/mes entre licencias + hosting del datacenter +Timeline: "no hay apuro pero si se puede antes de fin de año mejor" +No están mirando AWS ni Azure, solo OCI porque ya tienen las licencias Oracle + +Andrés preguntó por APEX — tienen una app interna de reportes que quieren modernizar +Patricia mencionó que usan algunos jobs con DBMS_SCHEDULER y tienen 3 DB links a otros sistemas \ No newline at end of file diff --git a/examples/sample-discovery-notes.md b/examples/sample-discovery-notes.md new file mode 100644 index 0000000..59df26a --- /dev/null +++ b/examples/sample-discovery-notes.md @@ -0,0 +1,92 @@ +# Discovery Call — Banco Pacífico (fake name) — 2024-11-14 + +Attendees: Maria (SA), Jorge (CTO), Ana (DBA lead), Mike (infra mgr) +45 min call, mostly Jorge talking + +--- + +## Current State (what I could gather) + +- 3x Oracle 19c databases on Exadata X8M on-prem + - largest one is ~4TB, OLTP workload (core banking txns) + - second is ~1.2TB — reporting/analytics, runs batch overnight + - third is smaller, ~300GB, "auxiliary services" — Jorge was vague here ?? +- GoldenGate replication between prod and DR site (San Jose DC) +- Ana mentioned 99.95% availability SLA — "contractual, can't go lower" +- PCI DSS compliance — mandatory, they process card transactions +- ~~2 RAC nodes~~ correction: 3 RAC nodes on the big DB, 2 on the others + +## Licensing / Cost + +- current Oracle licensing spend ~$2M/year (Jorge said "around two million, maybe a bit more") +- they have 8 processor licenses — BYOL eligible?? need to confirm with Oracle LMS +- Mike mentioned support renewal coming up in Q2 — leverage point +- also paying for GoldenGate licenses separately, didn't get the number + +## App Tier + +- WebLogic 14c — 6 bare metal servers (BM) + - running core banking APIs + some internal portals + - ~~Java 8~~ Java 11 actually, Ana corrected +- MQ Series for async messaging between services + - how tightly coupled?? need to dig into this +- some .NET stuff too but "not critical" per Mike + +## Peaks & Performance + +- seasonal peaks — Black Friday is 3x normal traffic + - "el último Black Friday casi nos tumba" — Jorge + - need autoscaling story for this +- end of month batch processing also heavy +- latency requirements: <50ms for API responses (p99) + +## Team & Skills + +- 2 Oracle DBAs — Ana + one other person + - NO cloud experience, "nunca hemos tocado la nube" + - will need training / enablement plan + - Ana seemed nervous about ADB automation — "qué pasa si algo sale mal?" +- 6 app developers, mostly Java +- 1 infra person (Mike) who does "everything else" + +## Timeline & Competition + +- el CTO quiere migrar en 6 meses ("Jorge: necesitamos estar en cloud antes de julio") + - aggressive but not impossible if we start in Dec + - phased approach? non-critical DB first?? +- COMPARING WITH AWS — they got an Aurora pitch last week + - Jorge: "AWS nos ofreció créditos pero no sé si Aurora aguanta nuestro workload" + - we need to counter with BYOL savings + Exadata compatibility story + - also mention: ADB on Exadata = same engine, zero refactoring + +## Key Concerns (verbatim from Jorge) + +1. "No quiero downtime en la migración" — zero downtime migration, GG + ADB +2. "¿Pueden garantizar el mismo performance?" — need benchmarks +3. Cost — wants to see TCO comparison vs staying on-prem vs AWS +4. Security — "el regulador nos audita cada trimestre" +5. What happens to our GoldenGate investment? + +## My Notes / TODO + +- [ ] build TCO model: on-prem vs OCI (BYOL) vs AWS (Aurora) +- [ ] architecture diagram: ADB-S with ADG for HA + cross-region DR +- [ ] check if MQ Series → OCI Streaming is feasible or if they need to keep MQ +- [ ] training plan for Ana's team — OCI certs? hands-on workshop? +- [ ] ~~ask about Kubernetes~~ — Mike confirmed they're interested in OKE +- [ ] get exact GoldenGate version — need 21c for full ADB integration +- [ ] PCI compliance mapping — OCI compliance docs +- [ ] set up reference call with similar banking customer? + +## Random bits + +- Jorge keeps mentioning "el board quiere resultados rápidos" +- they had a failed Azure POC last year — "mala experiencia" — don't bring it up +- Ana asked about autonomous features — "¿se parchea solo de verdad?" + she seemed skeptical but interested +- DR site is 500km away — FastConnect already in both DCs?? need to check +- budget: Jorge hinted at $1.5M first year target — doable with BYOL + +--- + +Next steps: follow-up call Thursday, bring pricing estimate + draft architecture diff --git a/examples/sample-output/architecture-proposal.pptx b/examples/sample-output/architecture-proposal.pptx new file mode 100644 index 0000000000000000000000000000000000000000..939838815ca943be2ce4be915e9b5aa52e7dfd72 GIT binary patch literal 47599 zcmdqIV{~Qh)~*}dw(W{-vtrw}&B_cbuGqG1+cqk;QAtkTcYXVuwbt3KeS5d>-}*5| z8*~1eqd&c0_uc#R%oR!Q+D!f$wOeOG3cK=!5yHfWiIiV6qozCh*-*>?9M20x8g1LO(UZIRe8Cfs ziM47%LEIM!AHnkZX=!qt!pq&R%&Z+CV(6lZm)K7Ns+%r;Y^l1T<4>4@hV4~_%Mcw_ zrU8OszK@uHRtP>(qG_Bo-O;R%-R}hvVQfifv6hNe+)`Ya&OCnB?E`b}z{zXWI7`q# z_^d`s7|USLcQj|deR=Ev8NUsaX?|4Rwp2FO+NiX!G0(1je3&)bBTJDNHniX@MqlU} zaKD*EDLKU|4Xbn7FXIxFf6aM-p6tvw=qe zx8lujp4=rb@I0R9Gm^HgNfSP|-eEsyvDbx37IsHr`1qj60^|Wh(7O=dUGJN6sLqJrgaY zzBdJ8dA(4i0jXl7AWls*VI)dVVW_q{1Y%+^N0j{7o;z)5bwBFeN&QK; z0&i-w9D|3K*L5Sx`J)!3YsN9y6EzQ!o;CrKp-@q@3z7Bj3{MSU~%{zlP>txRYTa>C#m2rVo3_Lv9ow`!6@ zzMe28n%welj=VLZkFL0{}j3J!r<37eVBSUx^P3x3n4#QY=TU`)Lub7~IgWsIx}b_2yASYFB2kaU{{aaL&JnCe_3 zyL0@pNLpLkFuw-yZ&ChrAR}Zk<^>=oLByo=Ow6E;4H7W?M3Y0w_<{}~QXXL;!+Lkkfq>O@rrcfj+oc_^0tSw74%nFTZW2 z$eo{$5u4+Q?9zM!YeILDsTT_lsyFO3R>mGLxi0Yfe_d*yC%giEJVmQx(k~IAQCP-}C0yH?wcXzLVChK|TxIkhK2tCnW&V^}E~+ZHm5%`SU_}$M~-a zoi!8VdG)nUwqQU&NdLS}h7JyYEmNjSZ_GRsQYYyRVzPpRMS^hX_uppVx|LoztV;Fc zo;8%wp~ezTCw4Dh6wsqZ--(6fuO@jrv8Pv%ITRWVDer}N+7BZ=$FQ|d5}StEx*0Fc zuDD>4D?s+?s$Q@`yGQl9kXqg0UrW9*)zuhImSd%>#)D}Z!W+t4%B0S{;E*W#%j#%a z0h;Cgs%M7|OUx!MM!bg?sXwYqo$4+adC`u-{xB=HKo!lfnZd^KqTUR8 zqSvXA@gWhW4Ou|P6~$dN+%vQ=*WpXr=EI_oZ<4-lAft||$n5tB{^oI8c1^jspp%TX zSj}~cJzsQscx;rcf)Ya@h#^QTvo`G;=(Pydy~jk!);$O1L(;B9TQlDT$86smIxdod z`;JG3wK3yShaxLjM*wORIa9?Cc_HN)QI0xx%nSPpN$)?pst&b+;5weVUAOptK>8|W3~EnW?!dcycy zro=L6woB_314TQ3cWt~!UoG#bt32Mu>yl&5 zDDAHwBm?g)@*2C~>wPT4KUY)QL!bzc;ZLy!c{lUPS2yUCHMh(cMZC22w5w?@@NY%j z0Vm|@@KwNf@IXL+siT8~3!{URsq>c<8oF58+x?Xw7b%-DdqijfS5MS9b{#96^o`v^ z+@n)NCa4_W^6!9Ty%)@=Y=Lw$i}gz^X3gR~1Ga#-xgejxl+34#Be*4Y@+vgh`xLfD zfRmTSM%`AAZR@cpNhKuvV1%t9R*&I~?s-4ISr@@W0?VS5GUKa;0;or4^v29i+eo%W z#0Hc%fs}2u?;<1@fAci&9Aua2DK0;Cj8saF{v18p9<6R(Xa2G6>Q}OGpF)CX(T-5d zi0fFnM8Ra?K zi29&ETp0uV))5JElne&t;JTDR5scKG-?>||+g?8+BrXa6?A#kqoAboj`?4Wc+1pYt zRw8TCG21x(&G&NMXYL3!9nzOzh4=h!=1?(v{nlZlwOOuOAoEXwpFLqQr?l$epNw28 zXw+%t^^_wBW-=#)o}h$PkaBXU1oy}VOKJp=P0M_km3Yu3T+Y!(UgclY;RM>1EtZ{C zEh2z6FQ}Dg+f9aPgj|UTTy$mRu7#s1VwYiN(YSmg_5^vV4fdLN-qc40_LIVL2{#S$eY>{lR(B3&wU8Z_VWU8WYWSfq>KUcxohr&DZT%8@ci#@6|w$ z1K?PNaG!mv_eJ(C0g- zZn0IV3t6^(u7}G*Bap%_iXt+uRAdf3&PZj2LkOy3Wb|8>;ADV2F2zj(zpb+q+p!7t zbmqs-O{qSAugLyD^lPe!Sd5ldN3dCV+TBzu(6Oe%GLVFqOe1?SytNPh)=6odx&osO zf);S&^Tsn;Tc&S1SU9OOfd2)MzpcPu^k|G9|H}o4JxTr%Iq0I^#2!zY0M<0v`L~!mV~?zUAdY!HM8F$ z2sbj!DIDYkZPwW;Uj3TYT%mTM7X_3}5eAP6*mSg=wtK~C&gNqH8S#ybrxo(-aBLkn zD|neAhFuOPnW?WAB8=?bFo_*K$_ixw3svX!X8lK6h~}`ZjGQ~;aWWgeJDcl0oJ601 zM^Ll4=3={8`9KD$rK^cJEx#DKN7r&70#^?ZIxTCjF7pyFwc|kwlY9b7SYUQvU759p zs%c#m2_Yl&hmi^2CZe5D1=}Yh@4c_w{hN1$`@e?VoP{Df{VU`_p#Lyle}&x5($xL` z4SBS3ul)X(qE`Nv`$Mx2SK(CIq&umfK0FR>j#X$|=!jv}1%B{UA1R=$U&-dv0VWQH zL92OZ8r6jYvWVgZP@f7eT(%FX&}D;ZdYb+rhiC7q zTR4^E(Snd;GYRN4R=1IOC#%s~7;JWir-W5x$HpWDzSQWbiT_ogDBd)YC3ogeSLB*N zIbNK#S3OGfV+)zhJVVodIcg>88;BQ3?8r`FPsTY&s8^H`5MW zf=n;SdU^{k@OfBs9GD?Sj=ycO_11@fw4x6_19lVr2fbZ>w?KF&-0jThGu*$`YAd8MmIqKCuCty4|Ao>bNt zc90XK$z7cE6q!|0Q!8EhkPkw?HvS}Nc%UmqYJr-$!hfegz=@mx|q(-2;1I~=Ze*_hQTtoD(CHRacz{-@;k?{$kE;jMY zGpP)~-JZV0R%Z!_eO+9h&*ulbjFhninY@2i1wBBA1LdD@-xxnW9+mHx=dbkmKgOa` zxIdn<`QI*<87Bn`LG^v!4`R&~^nC7KvIThAdz!%rPOpKZ!$q4%k=OQ-SIm$E!}fg8 zf{~Onjc5_bVjQK71)o$jgtoH`!r)sFO1-YL0vHTH^auq?s5%HEYV#m=CYj2B+&Rth z8KT%XWlc}?u!aLQTd>Yr_@WTy>e`)eE}~H-&8>`9;ibeTbs>m{fdK&F6Ba+g@W6Ba zLiFy-?+Om15-^*^CBdb$xHD2$>=XKFP|849P-XgP z>^fqGVI;((9S2+v=qrR1;@?nrRq+_mMY)Nh^0O2QSZ6no6C&C;a=m}`clA>!qEn;s zF%jDvPPrbi3mk`WN=I?eb1u{pj)F=mM!B^z)AW|3$4?O>aO&Y)dMB3LEk5evgdBoN zWk-dUl0j83?T17&0U{~%Etip_x1TPPyq}VDhW1jgR>mFfeA~uD6}CUvJEMnh7y`#U zwhOo2Puy!|7|V|$uO{o=Y|egMM#(kYgYoA@U6ezUHk&MskpDV*GW?cqBKkW}0NPL^ zynF{Fu>Kp0j<2w43*jEv0B355AVvi!0EJ;U&5-_g=B6t3N=9YtJS;^{?b@zi*TxKE z7J0f2hNSLyQq!8UnxP~P)Wd@w^g^yG%{1>aR_!~A1^B)bYjE?*tjI9Y-l*r&ZD)x2 znm>`mPF7k`!sn>{b!OS~&*~i~Su%h~M4|WFE5wkR}N0d$d`NSdcw7stOqJmV|qxrDx%`5{< zOsqn#wWgNu#6K7kLl(|pSDsijb1;MIBvpm!e6cf6r4h`1X^-MKT8UJ=Sp|cHi<&(`-76=%(*T}|UZ5k`p(gB!e-mEP#l*l-YKgYs~3d&PC4tu(Jw1=ywh=TOj}Msw=+7sP*+l$H1B*upQH-1K$*HQ4;i z9Jm-7*_bN3c-ojc|D`DK6H*94Ojv@uod9=>4_|r_MKZKLB3k!TV46hY6G4l7l>JT9 zm)m&8Ndf7+6peEeR?yEKV3ant%>qb>SnptC!6nB{N6Q9-8W>p5rtLyrM+Hykay0d` znIemH2Z(%UXW?*LiQE>xUz*1k4`zxRwykgl+RxteNo0XX_<+3xhy_4aFGpDrQC>q& z@%Zm>OG!{2FV-PPm8Hej(svp$4=gZ^Gx0LjC!Msawit9_sgF6#;C42tcUunc#Ed;%ZZ ziu*C{J^k97TWprhTP0QXm`twvvx7IaAVqjXWd_4aj~d9mX#e2? z;Yncq&C3Lw<)MM|<#2R+)7;rk8peo&qPklU8|A7JIbFwv$8vM7<^x}7qe2vG z+>{cj;&RExP1;`}0X6KHp`6$GuAib%gMsTS7oIEbo33Y;-DpXFlF?yh;31j}01JVF z_KY-|^*9)nN{duvS7Bma)tr>qV3b0Ay-#<-J`MCA{M8U{=Z0|BGVgh8jr6mbsTGrw z{4_1%fJ5jJ>=gi-RtnzUn+rY@RzNB4Ce#H^xMMhGgNXY5t=Qqg{l^|C_Voa)R_R{DsvPRJCp6=)c@vN(ArFF#cA0x}ztJZI!^Kxc< zUB9y;@W;%N&~<3j>qxXiI!XxPZz^Y<61`$#$4pIahTm_AnF}#SFrC0p-?VR8giGYJqzvpMf2^{91CFDp z2gfz9FErR@w5G~RK+6gi9==--`Bj#%+oc@zD-jDblqP7ipyy38f|{tx@Xt{^j7F{$ z+~*)**BeS86I)UCqnM>1uc@nRW5r$;f`X!25Yx<8gMk7!PU;ak)gcLNc0CBSo0Ah% z_|NB6-Fsu$VVO;A#|621GSY+O~IF<>p02LVn^7 z8%lPxjH1p=@-{ZfuMbPMJjWz2nH=^`G4Neq;YZEkV#%3F&i>3J77Ip}LrIp|v498* zf-@c5$BnS3TKQg+izGrEhJppN@QZ8u7S|!nYh|fn$G;ldOl29X1O@ZHOJnTGGFbG7 z(i1)}-rfkJ16=F8I@%5Y1A$NHkjw@G<67^TtH(QjM@fIA`}HfnUR^MzyGJm7M?pXC z9eoRU(xS=J3@M%@i$0z8Q&_vVR8>&eiV>&X*OhHFd(&#=10Z;-N-^QKAQnU8<-vV z{xV>D2mcif#52BQi=btyv)7^-u2y=bN zE;~d72ng6QmA_AsuA#Je`w$2mkHsW-KfrkVFk&t4YP<*bV~0ttZo+vyL=_!&)?Whm zkkccLj;u5uVY$qWTopqY8LSs@&IleiwUQsSf0q-j*a8Upl8oNqr#W|zvoF*cHYEjK zk8Wsx5_(mukbJ>({fbO4B=7h*htdw(E0&M@_{P{f{M|U(?-URIlpyk68FD=!WSFGW zZ|3eD?2>`Sp9t$~ ze8wN@akGFm&n_n;$7uY0#nOdPa4!Kfxuk@CygA#335Tk#zbA5M{LbHxGJF5T8D|#0 z0=Ho=#FR@Y2ZtVV(ec*LQ&Sa;i!l)`U#({pE+*r&cpo>a?+4B@;!rM99^P>0)1hFg zwb!qZ@djXYJ1twv-To!Fh44cW7r{tg#?sE|o(B@tO!DDJI&ATj)gJvi*L4JV-m^pQ zz*|JE?;HqU>RD0EKLE2Hc*Hq=IgJ^sfuUvas-aepi2iMryU+$d!B%OE{o5*gp$%?A z61AzRY?BQ2`k;xbv?hAOh4R?rH|+5Qzedl6WH&g`BcQisp&b!=I+blofwSshh9UcR z@PBnyGJx;uJzpc2N;n`Ol79}Xtf8m9tIL0Rkbivm_O}UXRG*DcXGinBQnxq`&ei~i zh~m33gVW>ATs3jGqP&6`v8Dh~lyW%@r6=AuD#bzKZvrc^7!}> zPh6w(AfdS&_n8!~_B%h`kH6DSLqi;PE)j);a9jr&lbf`MtPSvPpRN+_z`$6D8%M@J z(X%icDNQaUE8cxSBNUMqWFoDZrqf!zGQ)^3l*S@ooI-R>uOWF0N0VS>MDr3U$OFNK zHc$sCC@O&a1}atlCl2a%7}-grlf8qdtHt@NgdX)@OVGVc$ns^ToO1Sb`<3W7;__=g z4n1#Qre1q{-U4fL_Yv~5Eo~~3^y@Q&Kj$qb*PrPSqlPxEAZ6Mx8MyMXe@vt0TaQfz zJTZi1Gr~IZdwmMuCsv1OWx-$R!|2n==jnS#zHDNoqlh88seR(`(Ei9I6X;izT*+Hk zo{Hr_=U08(E`0>Y^XHE24&qFe&7`Wi%9FB?h$CFNS?I1$SW$=vpu)`$e9mQ-aTSzB z#1{?DOV9jB;wPJS9vs{PYJWGE2gXUIHntEW^a3AarqlG2M(f-gu2BvkH`ZIiV ziDF(=F1bR@Y%C@}yRQ1OE19x`(1c@U zQ}ioo=iS=n*Qu%jA09udjy&IcYwDp}T-EzAI6CX7ktw!*z8cy_6tEuY7=>9q>(4BW zI74l{Wjnt88&(5Mcf*^%>Yn`zt@Qu9`u``a{xb#s=4$kGCqf?)66Ez}F%g}t@R>mr z^@Wivo=y~h4|o-QEqGtIP1feNsN)c%V`t$Kry;~^$$yCVBprwC2oX=5$C$uhfbB#*pF{GC2d;!& zwNdqCednUjgtFX|do`{8jAu{4?bzG=8fn5<@=C|h9s!jWZ|Dn#$+o^ zk`^o6MvvxqpnoMG(EB+&&DSn6_}6y-zjR^$I|BaIX-;bB#;mg=`CU;%J@hKTJcvj& zAln;^mgn`Dxg*;n^dwnmLy?z}U93=p9B5rQI>jp`nt1xJaR-1t59XDm?sD8s{>1C{ zZKlOuQTjrloWv!rIhaM#O-G5^{Os_Lg&|U+e;}|ppD!ujUA?=bk~01`hsiVtj0I{^ z#?R75wX!Ths(LnhfKR`*)``6ggLK|~xkj(f&*?Rn(VQbrVmc{B&CD)Nv24Z^G4Uhb z^~46HN@O8yMVu}j%*OM|#bS*w2ChZT`oTzDSaUb3gPpN@UNsdP^_CeCTAtXMPiCxD?Zf3RLQU*eN#Wv82WUgl*{8&+uglBHc;d-%S1F$v=t6hF*4`$Bs5&bNpv z9uODbEsMqNH=qpbjoU#tMtXMHHxNF|*9cR{mW8gdSm~V}4)-8&V?`YY)moVP>u3lj z(--R z_BS8B($umcSZC{WMk42krC0Qd+stv|Hmo9SH5~pG>sSJNO>LZ81WSk&3Bu^2cW@#V zm3ouhs)dm#y78-lu}nw38ylsmO!iQ7sOM14-fc)wK83Ii$WiyVW$!OfOLp`$Cg7%! zlW3zO95GduvP=NT5zqtCMi;!>jsYRTwd?mzU>cY_3TDjkW!&pm(5>3Y8PmN7$PSAh zZ^`~~&?tzWUSxxFL!Z=uU|&a85GCX{@T-xk}BoMzye!AXS-cp~G3xFq*5d@P^N-`uImh{uey`n^=YGLkerJ~FM{_l;J-ny)#U~IPcSDxDe?Z;xUdVjNaF+D0pml4tWBQ+uovnbO& zvkI7Fz4!deuZBC0)NuVOGXarkX+;48{BUxpzwV zub}y7)c%Wu{a>K@|D*OV-2CT|7Fhi_()P=1CV!nk`|n80{MV~Ds?9p3b0hh7>*wYo z`jLRZAv`XeVBuYEq&AE$oIp|$1!)DkkgqnbpUC=`zi0SKeVtRvEMl2T>>?soGk2dR zDDZu;(pIkN6-HT_koZXD0BIT36iR!0Io`BA{0p;IFru2lNBQ{4`RbUT9S1B@k+coQ z*5It7q~;cvD$+ODAiZa*z3*is&{DpNYA@C(s6+$4ISFv5zhHt%xX4GD6eN9 z-~1xX)|D2_7EA&zHg520SQIRluGIoNbSDVa?oMB!*E`yL`fwf*b^?_XRB<<|Pr#;- z@jv*>`SnJtH9mxb!cKf-Uv0g-BiYSaZp)1Iu&IiRdwoKFOqnVquTfO2U{kt#HAGs7 z7iyK_4?c#b|9}@c_Mo0?b9315(&iFeqAA2;^A=tZla~4brh;$~Li30&=mS|XxYy4i zhUXb`G9h+U-XIFx;up5lJYYZO7hX|*_9hwTi_1*wffwVd!lHLU>dl^&j<^UO1&VS? zxDC53eG>_bwxRF64L4lz=5aa^nEwrPB>S0J7>-6p8V@HqPHK82A|51|#md6B}1Lmf!Qo4+&r0H!Tva75#(LJCPU4| z*$3@w%nYLc3AA-QEQMz2l#`nKB>BNPDMkkJy8f%)GP2#zRvZdH&c}YDh<4_amDgEu z*!7PY>5z!!aDDp&-RN)jt)M4x29lhCrheai_B0jc7>OB}<4dW{{|MrAtsrU)JF$;UHrOhI030obq_!5Izzg1I* zX9&Bt9=|ZW7r22!Xm(U2U%UeIDNp&ggSh&R2M zITan#aV@h_3sWTqGb0&5R(0tn6-ryl5Cz+!mi*z>0m`ScQK0luCxTB{AtX~_=2}dB z=9QGqf=K#E0aC8HU7kb!nBk z(4}0lc$)4(xNe@G8zfc@m|kZ>gwMtOSC@U&8$L3Q;>m1D>Aeo}anNILMJf&0BmOkR zUmav%hCeAWFVqFo^KM9sKR2=TMAiKzO_@#>t5q3zMb&Rh7=*b2Ma#TSm3a~cSm}pI z?rurp)Y>5xv+bcJ*1QtYb`%e!M_hM_FX!kaSbbu+bW+MfB&N61L%O@R%hKlx(Q*mI zQHRWpQ}r3;unEk7v$JNGqlotdlaEx7b?4#nI!qp`OR{4tV43imTmHBo6Y%u_;q3u> zK+wIJg=){V0}t5&bAI!7!F3Y`x@Cb_eFe_w+%2ZC<=33ZO&ACamigPlPg~Gucyy5l?<9Gt_y^m=*BjgtQX{Vf@V7tr72%Ch}*<-A^O3m;6RL)njj<-5$l)k>`Ge z#}c~xe-Ne8S1eBzDC58lsl0EoLe&mN?xTID9rKXD4Kps|lWE6k0hcQ}@3${k>iYbj z&OtihZ=2P;-c>EfWQ%EvE1fhna@QYxakK=i zYH}_vUBkXVVsLO+XOb53e@E?HKwHd{i3(k3(*!R z|Gl4N{R`0-8kI!#WO9KI$U+pHgL*wy%bxkUw#tp6)1u}sJZTU5?J7+OHp;> z8>er4Qb;s$E_jp9Aq4Epy!%J&#ogm(S&o5J%ZMh|d5gL{<&&MVlsI<*JHf_lqD(P=MBGwG1Yysa3oRth52Wgbbyjg}|=@#`r zpjcwE(OiXck2rzp9Q3pvJpINz+0%bhW7oSV{#3G){D|jL5t?< ztd7@FH?bI{bbGok28WAbT#T{SrJbLLo)iSYHh%LpA~GvfVLePPDat_Vq)F+%vr^i0 zcHZm1%?jq7hkKBMUE;99+Q4ImeLjCSt{(_qjKX{^;{{d|8L#cp`V{VzP(vw5pKREa zJ93AtIr(!4gu@Q96Z746QM@(f&?A~_UPlRJqf#}xE3eJ;#2i?<=zR9jeqx=3o&C(3 z&3>b*v|h<2I+t3>C5DCu(caz@O6lSjAvkUurce+qk(8*4h9n z6Ip16W`wxqZnyOV6c;yJ>(ugRbV-BT1THCb2a0s7)r-6!=;3Z( zv5ORAti8-aC>eYmuJMb6k1-6!Smz15Xk59*Vz;S%H(r4Ux`Z53Q7)YKm1;J?DVGa?!(ZTQYex*nmeiwK2N!{ zJ~sEQ^q%cH%-miELF-~~S{`~_ThYT^Z;hU`$63dcsasy0A=e9HvF!A)cV>7};#JmX zT8rp@ry_{XoN2c2Q#ANNe0yOLWp~ag?8xi%#}6gHdJ(t z?#L1O4ruQ7U8lW4kr33BW=b~_OB0+r_eNui2h`?4lLx9}5nehVw2z`5gH9~p95cO! zY6o6tXJGjdbNNpf4VBv1)br^#qIhH@zJCy?yUQeyg?dg6nw5`@4|7XsL2Y0o>;C&$ z?Em||{BM~2=Wz6-U?6t?i=*s#|2;?9{^qFWW_;e?9L-sQt;T`^gIZcqrr)%)RaJ0b zKIRp~F&aleVh1!$EvZaeU+_=yj#5D7HLY=a)vBJs4>;WZ{^N%?`i6K%w`dk}6LQ#z z;9>5{8ZW^re|w~NbZ23Z6Eg%pWt=gWanB9g$3s}<=c(`FiL<>6o(Ur-e9#f;{VE!s zXPw5I_7m>Ty@9CJhrNidU--jh+HB%FGGt0=zKL#n!Xf@_(QCR#{{E z;nWN$OErUJhqM$0k8egHC5rrS(dhkqaY?Y3=CQIeM&RG+7bZ`I$julqlR%xVU!3^{ zHeY_3o-%aiPI|W$TEn`sE#jrZ(rd|!Puj40t`OtO*||%mKR_I=6^)e@I#o~BT04sa zBmod=YKyP8dokkrEy>E3vCRIKThd0B^=+65YY#Be)tfm4X=mwoYnnz|#W%vXw`5np z$u5g@;08&^e|sbcNk{*R5}?;@XScPBz&urwX0g<#`+q|sUs51Ka zwHGdHfg=Nd?kp56aipb{2PgcZlib%u=uq=ZI9<(unehgnE({9#K+FuHU1Y!QY|A4!Bk|r#W7%5Ao3hq8xk$ME`SCFPZg39>!!3SwM7&C6SISu0xN|wP zh|(i6CSpCaMBObLX0ozayns6OT=*|*NI&(@|M4{BZ%+Q_NCbTY#RJOM=(Yvwzn2L1 zza*kjZPRXz>C0*>YpjB+mnI+)<@AaQv&dOVu&qH}qkzuht=Gc`%OyQ(t?%D|ofbeE zYm+|(ZwF-V-`!$1p7orY(u;LrHOE(1(@*0@WM)3Q6>PT_+vT zzhVLkIRdVgF*3#(e<J-w^*Z_xcji-z7Hk}OidXA2t?)h z&igS&gh}yA&iz?wSTmaf-jwlv*d3l;MilVN%vHZR@d=E5Ph$Upzg7Z+A~kO)Fc!a{ z(9ij_zv{4~aOXEY?Y+r4HD{9M%6N5=0kf}|TY~F*HST5+cB5}xUj;+npph0Syzt{C zvhZ0t2`ve?*U$kgO}lSsiwFm7*1*BWAYiEqfzHmX_zbD23Xoy18@q66oX!;&q8}m> z$y>IQpdT}8|37Ws|1PWl9HN|IX1gDL{k_io>pa(gw;9CoH&Ik)?b4ahe7Dt!9)D6= z5s`(KNTExnQn=bSG!X6tNqz5qw4sz?S3R}2&7NmmZ46|!<|##7g>7Pd(%W{PT6|c2 zX{xG#N3Jt**|?M6$P%*{x{YTT?p#k_fc8#S`E}T0lo;jBL*jjufN1VRR{jU&YDG)) zXV4fj!8&c>GZb-ZrDXJP=>3qs?jQgTEoTJz82AT5=^4yR6^|$zL+R#FwKm&9>^Vt@UI_R9q&HN3fsfku=Rz zRlP{Kh_;D;#F6scUGAi=!Lwoay&F?u%O_lIaEV|&tY1c14;C8K^?!ntH=msD#ES9A0>Gj|6aI#c{vsF8Yt5u8v zYC8COs$51HHQgBL$AHqz@`T!+EOyS4scOG7Z9A9377qHw_rkv|Z58|n|dk}!0@ z89M2*EEJc|ZF_n=o!_sHRfz45ohd(dpzDijODx;ev{nT9n(&4Bp$y5NgO{RClqCN|l^L9Ya0{H1R%o6uuQ3H}rjOp@X#z zq#l=I!wZ15&e^3Q*VL2y5zH71K!#%-QcTJXQt{x_Fl2xBco_fj8jrq8uNB9&3???n zF#A2*QBOaOLdLvB^gL*x1*KaKiXItL|72l;>Xo8oysv?NoJA&4-HRvd5nF*Gfp>73 zEHkBL4<33De9!2`CV9ESxcseV5sgJCUtB;RMSszwB%xSIyz=fDQz>l0nVOXq4 zwFC|STWO2IOxJIpqQR16S(yG)iHKSo)yQoNzAC~J9Is}N9IY&=vSeY7bwaIQt8hy2 z&gRbz)_LQe6^;uFP3Bc9S2%#E=B2EE^ghB=~H%(Ig@rE&elrUs`V`? zyDPe!v0RKb5KD@#XQmJIQr*mzBqRjbeyCeGvjcS54;69(DJotm5HI1(sS*}e*>yq%^$+Ae4R0>&0U>-_$>97dzR0CDL z6$7+{BP`h#V09l&J#IB!YP$!N=vs@48Lrv7EbVU=Zbb&g24_G3z9su(&&*3~YPT&t zC0f?Q)q5lg-BuYEoa3#J(BEQ8i*700Dh(3PbtZGE9FzEnBKWVEIB3gM&vhrviXYF? zcwVO%JgB}$m=}x;keolM3TDxB{QTJCsk)LD(pTOF2YR;CKC#>37EBaYc+gMqk+76d z+m0D3v+N#N>t$csGRWl9Xo+@Z*Jy#Xwl&^`I3wt9!>w?KJF~6%_*T)~3o%^N3xP|} zA69)y+=>lu(5boAT5JMdNVgjd)=sO+pKGAfIGdq2@ z^X*6`8MW%w; zAupDQF1|rcnmqd&9W2c(VxOp2T|MU1NOt9*f6(T-+#!in)m7KJg^U2oIjZIC*$XvM?vg#e%?Pp~qsDgI9pHW$|2OyLqt| zcX&ZToOM>*Txjfk2d#^~XhB=fv|Glmw$Z7HTu!D}^LGZCNmr^X%k+>Lwm#h`<{?gV z(p!8TFDqyI>}$wF@g(_3+v?3bqRC!tgXB+^bH@gILAob5rT^J?3%o8WW)~n1wC#=56&M_oD&xAsvRcbRYJ%-bp6anT-G^RA(3WOZct|0 zb|E=(8-HTp6AF-OKNg(y(%WVIWJ~puA0p4l(r-X`R=~iweTp^imVl^*6E{qsM-$m? z*0CTKXMag-k|8GzDnp~`OEKG;CL|C?)H;ab!zEb7TZZzK#B4VI;Xa4^VTQS3!a;V< zBpHDGh(SuAl0#Rt=Co9d>Swyzb^TcYmt#C?fT;mdZNfi%FF{+_AQsRtQiRpmigpVb zWzkm)&x}_lz)z;O36=0grm$kXeiL0Y0z<~k(c#{DK@9dhiueqi7qaGfD`$sc) zi13^Yup{@6YG4$Roz!ALcifz9;QL%=9&%ZL4>$y;-$+)j3RwVoa11Aa?o~bp3Vy1r z=31A(zk^m)!(Fx@NH2A#?4>2cwJGPE5gUC@@K4-uCvodUDrNF|<`aqA|sp%Gmb>3RsOByVVm)ZZ_IW`9`|Ew>0W|K$&I1SgB4^iiaxTGxc^ zEI*EuAu740#f!8d9XF-JE(aI&12w_Z6ob-CFS~}EGNTyzUt86gV)g3g*jAvs@ z4Z>KNY;l-;CQn3QBX&oQXeW^gw{IQ5|FpXq3JHHML!Y4!I*Q!9E69ti80<@*2I^;- z?fXF}EUFiN3(LFQ1PtPZLI)=@PI4#c%HE*T?eliL`DF7IC0CyFB|_hD9ClMwXk|tf z5}?1ciU<`s;|C2q1-zX?%d@Q30v8}3ViN1{qPzv8_J^lPSex@bwjmi6&C^*swwRGt zz4gGrvT{sG+e^*FLA`cRPz5&tX#EC=+-#1-(=vNZQ%+Z_1Zni-r`F~# z<=6uWdE!Oo!ojlA*eQ_vD|b5Y|KGy>mwNs85pMP&*5}A?i*WKg-T8kci5ULNBkcW0 zkiPSY8scFmJ}HjI!jKF6 zTt_$ztIO@qgkmbac|9y2Ryi%yeGaM&P(p=Y#vLE6HTyn>t$+>^oi+2`S?OtO z>-}Cdik1F)M5Y23C`2QAbU+i6co^f{K!iH4I-r2nQxi%V5xDvcF*(c=>?CZT0wSn| zsN!+$<1FFBpw;LfKB4yP8Vf;~y}|D;9mY~_7B{FgB_-g9buX#W3zAINoN)DDyayKx zU@C&wV1o2V9m1@odiuRE#Sr%b-TsW!5I>>YSf`}M1M?Q9&L-(4rVjn>+qZ4-Pz(x1 zEaiDNyv-(U>qn*k)L%w#CPXw#Dz73c39N^ryPBD1uT%j-DzStQ&Rf*B`SdoaZFcEJ za)Uqjltz{jJ{fYvx{ye;M3?QCA7)bUnJYnu30cM!gR1YG8r!Qh<)5Oqu3dTL}~eH`PIK6)W(EaT(AXgV9hiYCOZNl zENL>sod>Ht66?DU5Tk;-&tf0bK!+bf)Xghot&|YuIE>(e)J131jeJ{DGy_Zystu_F zlz4))r%}p7g5obUqtp)m*({qrEx++80uYEKx#l6_V-C9Zr*r0u5nly08O|#Ke#4F1 z9y9CIUIrTQIBg#Gki6%oX&=3cRA$r`KQb@6rLhWm|L}2N{bBe?G9RR!3zEseOg`{T z_W&S8?#=^x3l)T}b0NOgl}&wsWuNHHF~L7b}P8TVtn`%(z5Iz9r6MHWLsCU3R=ca=aFV2NZlgk}eUoWPV!1do39h zk7Mc!yHBAilY_cQP$rSXltkE8b$Rr(Bc_%h`bi7GtB85tSFTw-!*p={=?J_Z$ZHRa z%)30uVLBYRJd4EMImzUcNV(MkYYlhfKjiHE_qq5YzYMf>0Jqf|rUo^bZ^5Y< zhOzirv3|ze%`eAG*}L7IY4DG~yvYZf?zJe|h~dDzD%|(R3#?Iw&)mccwcG$SNeAVS zvPhL{N@Na)J(ixarU#d@kH$E_?-KlMAG?Ta)0q%`M5U7oU8LX5y^~cyyisCTrv!M+K;}!^mmZw9R42I>x zYSSs&{SrgapbfA9TkhqZwqK!B<69G;T3@9A(`qRsAWUi`MU=v?9CYv;*?xmx@gRb) zHEHi)sFtp`4D#Y05Ma0UTT-aJ=NxKmXu>^BH_JHL5I_UU12lk{g+&mE5uS0N^@frF>X?_ zq?3_DSD1fFvkozP3Qz7jp_0j$SD!fy+{9)r--+STn0^);JSP>9a-mX57Uw@F6*+CY zgr%1mAAsbjb99PjEwH^kh1Y$mkQOS;S|&C$BC|css(_T5smxV;I6+aWU@kO>9sbzI zRYNj|Spb-2k5xGpmQUa>1oEssMe^Vh*ow1Ln|^?_L#H}uV@eK6sZs0CU5ThI)@+IttqgA)gSasRFJIZ?~lQFdN198bVjky%^tOA|s z(XZ}1R1vBF)7Wt)Q|1~`M9BHVxI!SQ#x)|srqjZ2Lg^jmIA<-YSK%_R*Odo24IxmG` zkdrH$T9TYHQdUNZEzV`tGoDT{lv$d!Pze{6YkD7)%(i%0X)v)Bx{AKI)u1|t`o-9; zP(l|@02f05OYKLXg+^nX3vbW9tMdGe9ctbArj6}uerJNZ zS{)SFX#GlKmn^;VoFJiZV5oNtbU{Vg_lB$H&2vZLI;gBHXZT$2-SZDu-y+Q*Sxaf4 zk}T#Q=}Dbyk86)jxQX|A8g#ZRI0XV>OumE-X(~iVB0z78glx^K$w*j7_{#PlAiIXO zXn|D(8yLaVDBbWzPmD3H^aO949KBf*ID>X+R<);NyeSx_r+~c!`6RM$-94V!dXFl4 zTR@!Ihd567dZ87G>50ogSU=tHO}QZ^_jRTXPSIssCLe_zf!Bg+ehPV3L;Om4ou-tZ zkX83!Vnt|Wxvx70%V?j8Q(lfK`~rHexg5=0U$EfzZQo91e)71@^xae2#OF+?!W8Q7jM=VhXasAKek(12bH8h z-S=W|%Z8N9=z>amXPLHtFDehoi(l)3kVf6fGoohQJ?ZxO6hQ|GLx{3%W2gDBQkw(r zi4*3~t^0$k)@EL(<-SEpQ?04Kg_k4`E9mGRFtZJzO(nQ1#~t9~rwHf0-^n=I#CVDi z6wfkwGZhbc^`z(E)17hvPZ64(TYz6^ZvUE;phdYP7%kzbn;yFPYbK;O%^t|qa!L<9 z?o6a0ARQiR_Xr#K@XW6vnORj;s6pT2q)vliCi;mzZpO=*^YfoTIFmLjum8y$6a4*b z|1SvKUnl&3-Y1~?82-YHA=H^tmB3NW&xrhXNL~Sc+FSWsA=%4ctf$ZV`R{vud}?`p zkAL^jCqnP|p5zD_v;}hRSv>gRtZ@aB53|*Wz+=Hk7%Eg>QqA#$j(wqBtb!nm<=P?2 zqj2P&185XsHd!2d4x%-oR1tCFdkWln*;>iH)gKd+y81<)%*(L)b(9-??uJTcSC}Kt zylH+S6xgXf(4s#Rfq@J9OIl;spTEqhB|%iBD^0H1{^_3v`s|B^;o2nXlsDLP?aSet~=H15pR^3Ly zaX91>X$6guL}ogUgr-3_XJ#f8onGwJ7*eH5tvesIPm%SY3FJhRZcE#*ES}xSHokKK z#Q~5qZ<-EeOmE>r|};pz=1t;+&6>fixjQnyIU$B&uc& zdQHb1x!N%qnvvcwC?5(6BCTXt!M6OzW4d8VVk z3y&(satju;Zcb}yzzTa3*>NLNw5gTRoh5nO3G>D@{#2RU z=(~%Vy6aB#bmH|x_-E|%mZmB?5!cTG#xsys*t}G^oLsVAZ6PrS*v{O=MTz^t8~es{jC!%5C?)EWJz0*(-4c5ziTP-Wj83=rBQr?!P}+sh^N}MR3eJzS*mrQHSz{i zb2!rIdFgfezjc&!peuMh2e;<^0i0&}R8(gJY{8Dp zu7;o?*1L&t{G%!6p@;h}lknWHC_bPsl>{;=bUew-_Zviq0XjoHy3-uxHsE80{B8%}Dfn4XE0t4>Qmwk7p%6 zDV|h8#VuiyeYp@~Q|?IxBvge*Mhj*920_%i`Hf0ufO5iJ#yQEdWsa>k0?OCN9sTca z2rg@O-v&q?D3IWMOva}_E}mh{+%2dSdg+L%@`Z%Iclk0hVWxE}r4$#6C^rD41u%v$ zaUKQd*UVqTqhy4W$alaXW9kM#RIKiP$eTa5*Wga+1!M^cu0i0#nt1uSLDCAop=~&a%t4Y{c>W_wKVQkxGWxMy_zuzviVTH~{zf*ymF(1v4 z!!+E$ipHVLdJH*)UMLncQXv&~-XMQ!4UpJ#&NT&1l`d*555X=n{Qy=D-tAuen1R&F zMSqo99WwRGF73PjLZ4m~3B=6Oc-8hOb+u_Te*W6xNgM&3*f;sfnTfRh%}@ z6JI3UTX%4=1B{W~LNOpr%fJ5X8u#iNMyFS~ihlPZI-iSnvn0q#=Fk#D7Q=u0>7qY; z@$~k3=Fp1ZD23_yIOjZgFry=f=C}cTSB8+|R#B;gkhOX@A3ke8FeVf?Nj4iT{^Agy z)#D|eR{u63!F1kY!H+`vP+(EXfUY9>Gblv+!)4>+pTzIqQ~bXXg#Y=9A6r}*3jD43 zD^UMh@&BT67FEXW*V#}upQx{Fi9N8=6NGv5LNj6uSK>=8OXD)6iswOv{gB8Begovt zI3+6c=giZe6u(Hi1IE=Ij@T8A7Ge8=Oz}SX`b&1WcaCCIl+Kmzd}w^IlmH&ahA4Jj zEL$I;lW$^~3Asw8>@?`ED`V3-jiIkOZKjw?XK4W*clDJPIm;8f_8 zD14k^s5cuj;I`mYO;}^^>Qb^NBOjyHttN+>Yq?(J-66^Mn!XzkL^?tjy&Ci}>hc7b ztpo6u*(@nJb~KzC1Uuu#M}sl(k7x6n)>775i!5jsS2FoIfjdL=%Zb4XzUd?%nGM_5 zn1J^l2$8P4B3NHbEdh!HnBH7|IdgOsLHTi8c~2 zwV|NcZ6tr~)`@jO%=1pH{tzi&&Wf<@a@XX^LA8rIoTg zK`~1&SL^&P&x-dC7d|nR-PLLjROmcfDuKOB{X4gQr0cT;l9ZdNmYa^7sXjNf_bm>| zR=Hq^mCj=zT_as1uN^VU{vbc~S>WAS2|b^P2r9Nm$>0)@4|MIIXb+V;?uUvzN=`2o zjSX}&god`=EJ6P-+4VY9pM{n)<$Wlv{WAXf7A}GIP4_!q>}4C9)}pZ=8F7U~m>XWN z*e9Y;84@UHvDRW_n2UUo!b$R|v&gqO%b9K6ozGs?i~76?UwRbUlaxPwt#_}cyY(cJ zpUH#reRXK<2ctI++ILwuicCPIXI}gi^F4yyB>UQBzj{b?Ygo>DLG#lcePM6UaJ&Pa z{GmOwkgdL+rtM)00^B5&;fMG;eF?`KbD^TrY^fK(I~Y3w{L;Phi2ME+IK0zrhhQim z!HFJ|Gh^sZlG~#6xpK6+0UmcpRYx&)7+Bro=~fS3Bva!Qv&cLwFW9qeCfe2^B^0Oc zkF8GV?4fC@6@yfWwT=i^h3C-8_uQcG<3ml^Sh}}Iw|5J9DBarI)@^-%(^MN+m==y&p2E9!YJn7DC#^jj1oQ&9*&MvT(Ea0-Eum1eAa6hnM0bM zb|NL}47y~Z9x(pmL^O(y;7q`qK;_ac@yc}Rqz=*f>b z8ecUx1d&$gvSXUXEleX(GM+lw%q=R3%XBrZGRL^6gmdpk49bDLD%Gc$9>`1gEL}$w z6Yr}7op<1;)u>Pzhkke=aN`rrHl}0yz_WD53PvVsvj*m8y%MG*XIam-FlB@67;d2G zn$@5N-i9E?HGSfeRfuO4Q2~%P*)x5$`VSD8H2q>!E?^&Qa^*EGg;avKxd#-|q54Z# zyGm(mjkFaL!r+KIPy4o%v`o*2rcOmyK{m-iUyxU;AXKMb$F*@7S2REe=Jnpj)D+fY z>$%c5@XwTK;pP1)*1Q{xw0mwbhK5wBO~KkK)o~%oDI#w2CxOW#uc{fKyQ<|9aQ__D zf_76AOrjX|VwNEgG-E;2xV&Z6Kr23@EO$QXr|FCblvHM52;;CkK+*|oW~7owfV5PM=qGp_V3|ss4{zyGgU+bZsfS#jj4`Y;?CKX1IEe#4rBmc#b2sV(+5ff<;7okB0W*|P6p;3XN$eKLjfJoxvrLH_RDvv0$cQ&s7fHN>bw zI#g(fkhKtM{(3bvI`G_$K-rm(D#fd%i(`uV!^;#b+XNR_IHJJ=?<9cyF*ojB#QXTF z(YbGLNkBRlk6v+|>1l+{uiWbwc1*{|v0=%&ApNz})eOb_kiu_rU+{k3!BB>_I;jMZ zs61_IyPqJwOFfhmNEJ&IK-y^rABR0}+y1_dKn3`^L7lx}t@Eaoi@3=gf|P7QRCX}r zFkxl`5Ns@h{&#pxDCENsLJ#V*APzAW!ZaON=3$M8j1rVx^NT<>%fnl~%_3V9sap{W zA5a@U=5t#eEhCg~N5+G<%z_-!vHD70hM^d2!MhC4xm<5|YTnQjBB5GpV_1+Cf$L1j zJyTS5zl)*Th{~Q?GyAAWA}C5rHF`FmF$Qgqou;6s-VzJ}trWB5eZX*moF&H&*L1QU zlJDNuE0*Xx*l8DFV*)SLVo?xZ|te`+f zL<19%GKB4e`;ci^RtDbsox~CEU6yTo$5lCbzfQLt*bP8gs|wi!u6q333~bFdCq|YF;$*1x$Klr2 z&n3y#0G8r3Lm5K|&K-hA*z{SS@@air_1bLxVy*7};GEe06clZ751mgB z6r!;!RNZDRj4`>%yj&V^;m6AQ_~#Wl|CUSiHx%amf4Wlj_h+BGE*l;jzzO4_;otsBfU9@yTkYoaHHoO^qZ)Qb5y# zx$J2R|Lc~HdyYpShvpv9+6~#Tj(B_7NKml?-bfTQBCB68Gcvl_Jy|X^pi3^*y?l{fQZruOYNkw)G z>>I>U!APY(X`pUlTii%;rsZqHDqU#W;RcFqSpsP0W%1|4vSWp+0QCf^CjjaX*2e*N z&iMU<)_Td1e24zhNFrU4O3{d3BP(yfwFzIA-!eXYO5|5_4DXOz7l$^@BpqMs>MUWy z&SYWV51@}@P?ZJDdG)ux-zNGD==H!_=?={M3$$Dgx;fD=Y7IPBO2}MiLdl=dI1!cP zLe26OhoG(Iqv{6B2i_$&HT|e*23xmf`U{b3f$h`=NrXVD>Y_hWdWtgUYYR@+iv`)T znOt~vi{6-q@mF2_))Xf=@R+71U^;uvAJLprht}i*UlYVax>A){r+(-U?XnJ!W-}j} zbSWKs0)``08Oq2v^rg_c&Y7{~xki%fGh477^OP=GM!q_;w7RM0Szp#{t+Y@ZBX$j4=M@TZ#;`pnYY8SOXoAjkBHpVstUFw0|q7wh*JinQtMczv3 zyqSpu$|ABQ=g*u{?ZjN+dJyy=o_suJc$i@l=4~F}fhde$4n6%QBUgYIb=9}iWXJNE zfb*q&E-X(QwVpT>4F4dZ6`4OKj=)YmxhCH1uyP)hY4L{NTyM;}tZzzH#G_0zifaH_ zKn)TvB43XO0?JqT$pN!}6!e*XAo=k*rX`v?x}kNI((lOIah*uHEX1ybLf8y$HSLwf zWCo7lo(@!ckwWNvQ#ik4S6rK|Nkyjv@0TDIaA4n2E4aSvs*CgHU(F5B4D{!s6`&!~ zeGJE~vjy@B@_as-^|1lRKzjBw)qql^UKc;2uA6t!TJ!)Ds`E=JGbpMu5_ ztgwQCnpGRznmwiEH};xouce|HAHKmC7)5@VTuFF5E*9Y0ahzTkETeZ^!u%yaP678+ zrUw0$$k%@Ea~zZMPw`6XkM}FP{YZ+nVRy0-_~6^|uoL!;4iSCW!k8P_MQ(;>uzD;4 z=}?qW2j7=6XQp{QkDWN2uY&bVR>xa+6Hof>8C@v$|aB!0cp0;;5wxHmk~Wq!IRfm6^T& z&&k_=@8R_~uJb>?-gIaX`#^lJpn&H1*Y)Nv=93K)l7vT6>OXxSCU|}X1oDKfR`~q6a`!~RAt+@CXE=)?4g6mh3959mo9 zHQZo7k|-R`jDk5P>Al62h4gn&q(iFM$xm@2^1UW76Xb3(ch3YFo}hT2x4Dr8jSZlz z$jOfeMchUTkS(?o6Xnq&L7;{wf|&FhbIXv}2N6Y+F)L_h>O>1T;cenUJtgTxT0IRv zO#ETDz)l0qm=V*Z?-+(0s7~%O=FG)?RWB=Ll!l!ow0=Ibe4z6&bRFAhE`&|&1P|^6 zFCkrNIQlC*`ns5CRuh982)_BezXrC!kIi$rRvH%&Ux=C_V4io}N!p7K#J>vL)!Jmn zEhHYQR0dKuX6qE&FJNqD_~tfJsr7VC7~jMbM={Ms22FcDGg{^K0``|aiwh7i$Za$z z8VNMne6OIdDIx>=Jb8%{a$_FJxntS~u#c2!h0V+aE75$jt(f<3z&3Xh_SML+x1iME zBbF$~>^gV@XXXMRTE3+YiQcdA|!+3{YZ~e#NPrh9V3nu&VW+SL&%5*|?XfE;? zX>1M{{ZGvyE+{q2a?0=L7?-XK%|-n=OJ-jP`_$udCOe+`cm^*AIph9bEx>*5;RYYR z7vI^>qC`2zky0>xKt~5SP9`Oy?pob6vB23U+tZ|0$FsdTCXX)M3%}Fnq-Ic)!GH%h zruJ(0tji{&Pdz4=xDI3Mxc6k6ZH~8c=1!mYH+n}_9&RjYvP5L}W*)l@3FKim+HvT$ z)cL85uhWTCEx60E`Jwg2(al$Dgn4IO&&{7|fV6Pxvn)eAz4 zgHXqe-J&Dv#Y3PpGecaZCNe}qj75E$(XKc;xmD43N|24g_ zctcCx$nTay=N9~@1(6YYA4M^>5N(8f90P7YFijPFh$_)_GA+a|qo>wirGt$`hd!ij zf3JxP@i5OvTiIv$L0fIQ;kL1r#2z#cmD=xMF9!9jSmL!R#9N7&d@+m$J^Tc1s8Si`;#B#OnmB`E@AVw*~ zAxQO@L^KkdI2DAAj*=rSZ$FAq8>I=DDF@Cec@nPvNSLO=a*IJ$=mPbkP)5-Oo3b9H zD;O`8m9w04n#fA7scf+k+599>v9783obpqqn$mDmHr4n21!huigFQjMoUiJJN~$F` zh)fiPH1Q`w_#~;~$3Vw0beY8naUoKrvTCF=behUG8(`!VshM9f2=1N;N_jCw?=70q zxZ?e%g%U7rp~d&tTAjm_R1Vo%1GdC8W;z;PT>_j-l`I#P%QyWB78|91+@@8;?7W2c zoL}}etrS94YO4k|nR06{Tao?}LVC=oSF;10PCb)L2y1ZG7Fo+O^bnW{ zTLFt7j=2>HkkL1jr!yhW+ex$Y-I*JtEF}z0IIDNoepsc|(fC*cp-=8-W|`?j#pTj@ zg1}=22%Ed6vs2OTTr0%-{+&qY@I;kPw%)O~T~I7V@8GRJ>acDFN)e6cR72Lavtdi3 zD{6A0AYGxQpJAp*21AaQcqkW#=c*w$C)n9MnZdfHPI%reXbN^w^W(-#1T;0Mi2WCG zP>~0!AIR}CkD6WaKL502_us3Rf1{TF^Xnz*fW0N%H?h1f{IBcfU-;#v*0uc}$3NCI z&3|9V3jZNybvZ$T%B0$eA4t=~E_f?JMCjtA{D$xZ@?`c#LOvRVAZS7c zEEPBSf`DDxPWthF?u=dja#Oq-(8r3%bXeiE)9D2Nn_(h8AyNYzzuLWV2v96g&5y#g zz{ytfpjUR-=Y^3wjs>uC;sDV?c}T+eXB6>Gid2l}ICG!lc1$>=!fQSTXXp?1wUYp@ zuJfTOkirUu6I0#%Y)B$VMX}S+DrTatAqadpMX5F%S@Ja%YheV7$T7RL(Xm<`%&YkU z9Ajrv2u9P3S1F^93&E8u_+@mUxdP=Is-BsE1}fCz6N-Thta%=O#MDDScnpjOVJ_-%p@o+34QUYqBRLv?rZO|XeL2x*kO}c}A_x`K`KYRG@x<@sMcSk?5tlFBgHh$`YzcTtHq>*Z&jLgWj zH2t9a9!RJ@&bL#}ynOGdh*KJwnfy&40t}aG2WwV^^v`?MHeHg3>;yxAZ`{^6bHhPeD@AaRJ@;C*P@dxU1)C3*}`t)NXZ5ILUeB8L0IJH zsLmkB;3Ebw0kw~A)lAKKg^>$#*g6zox+*jWv@-pNcsXY5WzC>u3=gy?DOUxB zKL>)P756FxbW{giVR{eWkV8hCyQ+h*n^rDaIBH*dWt?QKSG+|k7ad2AE4geM+_#n* zq)8TQ7a&N9yuvo~8_U%uYr>k@&5Xb*UeYDM4~bUzcAcXgtMrwuj6XTpzh9T^zsX}X zSw76qx1fRL>T=uli&i3LI&x3yET4m1eyxPmSD0W=hz-V=5=q~J7KWO@p7h@1_QGxg zWfwYc+YDSGuO(k_JPj!T4LL;I1*%J_hQ5pR@r68QHHSQAN3-$1HE(k8k2<;!EVN;C zcd!Lcz9+xmuJ7f=Yf{Ou0od^cCIjw!;O-9HqkXRw>dyL*GiXJnk@e>FQ8j2qOu}y= zx=kfu0k2(eIYsO7{(5iOpAW}l~#g1yV=U$QTAu>ghRo5mb#-BC~3FD$+TWHzVbB8k&XpnBC6P4AnDB@#onn0 zuPw6a8pk?Oo4?9Z7B~Y3LoiCPZ@SbpqCX4d^$BhsK|f60M$47}VUM($j@W??3-{U| zvyWf`5)(`+Mwt#c#v7QuBE@kANv=x@o~F`@;M6~@m{=Uss+?GaQjKkfIe#b0r);`T z8QZwHYsEx26W*B#E=NuQHI`@!1zzH99fe>u#tWae7T6WD1=JWo!%{x88l|k7{ zX@tTEBqXqOHASJw?DXzif=$O)_zNVMmn2a%3bCTczg~cB9K|YL4yC1UZGNCf zGEPd}F9j$Aibq;GHTm*)!CmlLcdq!O3x}#9(IfANE3&BU(>jtrzb2eB-*d4z2^4r~ zALQY)BI>7a(f#RLNfs&g+b6et)djLsZL{h4uKRJiJKj_GHT(f~wfsYWNq}uWI?``-3UWh=ep`ws3Hl!{J_D3g*UqS2MaclH2`z33R&7EF`@CLg;0`-!Z zByD6!Tdwkd!W|@M=J6Anu-GQ`V_fJ_J!yRS*d7SWofxB+ZA8jhy>|1cOe|TwV$a#V zp6U;2U5tIE$}7$+={7b{9=t|ZU2ar#Wyn)$F?ltHXUG@t2g@@;ng&8S95+RKLwjKJ z76oN-Y4b%}b^aQZ1_VjjWb_1()e}97SX3g1zAu+ECWFK zC$Ad_U!&^q+9eVON5YT9l#6}};Dp7bG3zTG6DQbievk(0zg zrWCViUYPmJHjYV}4vM`LtAEa;Ym#z7t5D7m9L0hy4c>b!r(mXu(vc3K2;_C1W?v8$ z$3RP9?1+bm*^7VN5N+P5A1X#XS?tzH*#64fslWA&5Kd~)BOz0Fy;Le@b$_l_Iu(zy z8%L5T>cc8-|LdP79{hW^ioZ=c_|LypTp3dsIDEUl^_2hmR`D0&d#R!Qy)yZG{z)~# zeH?6wrTjMA8H)J^NbeGxm;%fRHnDt@O4x$fT4?ykv$jG~rkc5;)L0#y48S_kb?5s` z>v2q;PG=V_riMZ(R4+OBbD_s?3Rw}AKdT2j`-`IloIfOxvk=KRGI1(OxLm!S=gTcB z)Pj0a%NbKS94gCiY)j@iX+zy9sm5~Y(CbLB=nj9Sh}#1TNV45)v126_>`bk7zv6vK z{1pu3dX+DJAXR5C&a=MIUFlff}M-xeAohX| zECR1AN>dy2j=!;@le~j9{JDb|zzDN0Ky5}0=Q+)aID(-eK&cdMi_lWU2kOrtme+#? z(`-jodQXW;%EV6=RZ!|I#i{;TwsIs#*8X;D%okU;M`V+zazU6A&X<8&77wDBlZ;t7-mAO>N1jwEJ`yN ztztwf`ywzy8=pBeDL@(r-$U`*jCaGs1dI^pR9VC!u1~tPfLUO6xW3V9(}?%JE&Wjl zzl+*r&JP+_IT3{<6C2x#cKOlSAPo|e?K>OD@D7H7&TtbjjUlqdYvu?bj5DlL0}PSj zDn19$7v0Bk78G19+szL9Q~rJD{_ahzO!5jq0#j@u37sX_t*%sGlfmc*yHyNJ;J~m9 zGH6AlGMq1a)aQPbr3bgH8yxW#hetHG4bQJ}Kby>PKj;@ZfF`Tfrs|)%(XYOqacQ{F zCJ50Gvd2IpjEi+$wcBGkjkEl{#x;^^-d_QMc5wsk5l-+qfI|Yez|3ev(-UgP4tW%P zhK>a&%3~u&rejd+38;>FG}Dqq&AkD~;JtL_gOD;c%Q0v@#pPj_>gW7Z8r*Iy_V|*S z$ZyN0XYVqnrh$J5PBIu~>5_wZ?M^!fAp7+u6W0gCiJomn9NYQc8An;v9ZD@*QTTyE zRwDEMSY`iiQR`{5c?FAwO(dcP5%S4p?h2qLRGo1O>JirZ0z@vAQU+rc=@JC})jtN6 z) z72WD_*zE40nw!N|Gr4v>o1uU6DgPMxXaH6;--v#~L%%AJ6GyMvW2zxq_LYmjHJ#hZ zO6P0u zSG0PuuQHDL<>z237 z-Pb_so0MfQY+4$6{SfO|U6H3cf{`;mdtg?+VlUH}Y-kZMa2<^JiaW~b(=~n2dNXA3 zIy#IZ*>>z%|J-9KiG-rtukbRz=`(DHVI#BcR-Sk4|C?ma-^MZg=a(j8Az$~l@6v?! z9n}9<4avVC=1X-=hczw)Z~pIwWO4^eL}V+8;vAt?+yn_M@WFj8@h`x%kvQs6lKY95 zYg1Be1`_i*GOPHkKfeRM8b-@tuBEl~fjV_#5$!Q*9 z<`cvdAmo*ur1Bp4a9{4(I234vdPipf%^9srqrT^5wlW44V?=~FM@51|YW=dD4#c6= z0%j9h-3q1cj71xxFY&{F$m1ybSeI&M+XF_1ArR*&VZ~Jg%wg)72;&&2l_t&eGK6Mm zaB4>ys4yi3rK0JhjF}|B=45=dU`X=h$8T^3?4&fJ_K%P`Q_~q&quTrVsAEbf@#E-pe+}XV$Tp0k#Fnkg0nJaK^$W zemUdw8El$NioEMZA9KM%WmPjV6D4!PY35d;_6nBqiL@K2SXaZsPJy9>D>!$U#3S2+ zgUB^AW-RY|;LtkrHf*YD-!ohzU*~gdyAMCCt-7~b=5IZ*g;&J@CC73I^Ks%RS4Q0f zwR;Bu>vVvF37vph@QtSz@k$Tvu zP4815QmHZJxAod+^V6j(PXnToLhNPvm9|4oY;rN+@+r z3O0wcik24e$9y)_hVfeS>bn6ar7PGb=e9HuG)PA3 zRO>*M`Ra0&flMmYJ1<;bJhkf-uYE3yW)P+0GEg5%;z?o|L(M|S`Rmp8-1(W1Hp}X7 zl(Tib;_joaxOZWST1cFiOi2=pYJDbv~_oxSdv5L z=d#j6iDdk+5ybp~rme{P^YTjGJYa_@j}-Q@bGIG7_k&YL+l_=^y;EcJnunUeV{?A| zZd*S%86-o043-N)C5*zDE=3GlEY8+}b`Zq^%s+_he~oqc63$hp?+=nRqcNo`p&!xs8Y6b|AX$QLYHjy6D?Y?wqxZ~{k6o2_h32yR zn-jmQj6=4zr@IxHr?{C_G2$YGO<{b9T8~cDG+%bau)j-*ha=xB4+rl3R&F$Urt@(@ zFv?3D5+0JWX!cH?uigz%OXGmnF3p{%PmXKW{!6~U6h8GPVn)9Ru3)p8+}$#T)+Pcm zhhW;!0qt%#sb~I6U_^+pNXPG#-i-sh>Ik~r7mq)PV}J$X84#g|NJPI$3!oKci?kGj zUdf7g&0dlouE$?NhAE7QB4gTs)S)JAZ`Y9K@prpzM!oL%MLEdjpZ$$YEn z^=;qwVq3he+&bnV&K6B8|H#Tj%YI&~cM$IdAWB31NkRCd;1`7fS1qTjU^~ym1>U{1 zg0k;d8=4Af=Xci~6rrl>vyg3bXg4p#luXORisFNxmK=JCj_GozcWJ#1B@->y9KHu^j+otPNV&`OqlU6 zu>CS)@;j%GFnsL=6#@4!N(ksZ7MWpZ+lMICHWTml zms=cAfzc>YB?lo+G}f!0_<;dn3_s1M29W#98C&5_8bqq?(z-wW1@8~Ff&Bn!cMqFt znWuhU0*46$DNf?fPR>}5YPfENutVr9#GD>AmwMh(hF54gdlXW zeVS37Ly0>Yta*j*KRn&p>y-z1B5V^IVY}D8*GK^Ijq@HjJr2@NnPH6#wG?&Z+`Y&~ zdCxi%qWZa-Gb~zd-PrYa4{zulZQM1CgXXgwYk|VDLVER%J^%Guv4`?AZaGpddb!Q3 z->qfDwzx)?@XtRMfry;iptYBmj){$TT63?_z?Q~j$&5aMcPVFV+;Ii+I`X6U&$Eqm z(&(h`^@n7UCB}zFMdls%_gne66{^eF*@37b4(OR_#CD}je%5OxYH`p^-a}H|>GKsA zm^rGvD;_dt*60MIi5N2Bl?dIcE()kV87Xa)6fD3Tm$$+h{AhYOe|&MT82VXL>54ZdqPy@2NWT z;@TVNWtTZ*xVYcmcd{RNErG)7&_tC%77@+`?x^fwuKU<^;WXKT#ChpZHVU31qFCi; zln{P9WQ{Ejoj<)hS80EmvWJv66Z8r8|8#fe;ZU{j1D_#8M0R16ELlb)j|;=TL@7|mJ(9mlU~hY=KcToJ1)&N=kk2!bM7wmsSW@exn|`9(xS>Q;+r=^RFNMN5g|M zjqhJ@j*0SDFg!3hw%_=Q2iD<(q9|%wc}Yl3=(V2EDqrx`?%89VQ!XdjwB%g$Fb{Mv z2nB0}4khrAsm3S!mZUxFuxmSN=YvE=c+_hn3>_fh`jB8v<%206Q35%R0oW+l z7IY4~Os(E}j?R zca}Bm?`D8vB^fbYrukROt9#MfUlY?hEjr&76s)t`-;PT%b4%y2?5whT%eT{9=h1+{ z{yIpuYXBBg( zK{$tuE&VPRnOcgt)VE^}a^8Y7oLOFz%bL$npc5(uu_{Lllu#F#TAuGQ>kB{QvrpW` z+gaQo)WA(e`Q>L37F*-yUH!oDwhN~(DY}o+X6`b2fTJ3em9nJymLgU$^3zq-OuU0R zp?HdJhN0BIvzh_;F8(yST}w9OBh66A3&cw;dC@3x0DoPT|A)kPO#DOGm8CPrY=xCGM5 zQH_p-@*1dOBPFuIg~_N4R?h2eX zS=s?(-S$4%Cna^mbMSY0j(ZEMj^v;4aHF}d=^4k9ot$ISani1xrvAsm4rvHm!kBV~ zh+7vf=X5Ghx^lL>@jHik(e7M2K=Jht1`!P-3Rd^& zKF!EuPz7Nf>Y2@k?*|0ijpouh)&}0gD%AvKM>>+#vrn9`h!=ciu`znNW{nAhiSfZ% z+i*e8+%?%-X3O`&ghg_GAS2rJhh*zm80E`UqQc5PhX`j^6P0WEzn{e`GPB@Ufys6~ z2PkCm&FSQG8M@x%@+^@niZn62NY*9&(D)~jcHSK1j=r|U(}M1c%qj*Mf~r)K8< zF&N&R7_*pM8U7@80+M%`BzDwT`}o+nw{bhGq7rU={BM42klJ zRF*QXXa?QxtgA2-h@tW7LpnR;ahl%>s_1(oo_gfefbBJ{^7y0v;ShE1@d{{Vi~ReC zD}4NyzTM?Wvh(X;qB_+l>dZnrd{CP9ngJMdL5bd3k%lxb zE*=NdDaT>C;Z@(_3-ezv`S-Zkwf2Q&!oVVFk{#z6RA$gA>E(7tv2T(tee`t+?aZm9 z7cg(HFOqJ4B)G~%$32}ml+K&NBzcp;x08p4D1-^3G}L+L!v>}dp|mgMH8J761FqLD^~L?{j;m=(2WD2Ey6^uj+utTAEp^w= zQ5@nvOB7W_RI0?f>vGR6=7~f@H*@JSUk#KjlqDvbwI?=2(#JJFEWgdJuuWoCW+5}F zVCJd&#H@4IGf(3rg9>_X%_2>SwqF&J0STWkUMNrtU6*RN#YzXnpAW2WyhXsZ8gdaD=i<;0Ei?VZR@5p-xlV(N#X?_2fv^zAx;n9rF{t%rKq z#@GLReXna!*nNct6C~|p@m_u{_}KWw><{W2M&Zd9Zztc%v)i%fnnB}7$=B<6OJB;hlcSeJ>Q)SI|I2;&60kQ``5>zEBfW# zSDQZnIw|*A%}9Ut1nm1=ogN&*Sz9$bE#+trm)eu?55kwRI}l$2yn)d}d3%8sy|`;- zP^p2lQSY`7T}NbRcl)H2C(0ED-_{^LXY|5;58 zi`?C1`9SC5I5P;u1zdVLdN>$*dwBXB@^$t@+PlMD+@xGRoe*dUQ_s2KT~JM`v$^`(w+3UPt3kw>Rft@JUL;J^nGLO}8T!_J3X z6E!h40fMhsWz<_v#_*b0X?QioT$jLEMA68+)S6mU8paIn>9}Q3sUQTGqbqbup$Drk z-(#%i^p}}lSdA#~^?2X>Pwxct(_Y>vkD@o`RXd+5mkdj!9?Prmo~-WOmwhcTOk3V~ z-K0e=f_Fv_bCbH_fU>^WplB=$GnnbzZ?v4Ai3r3gSV3YtOtoLi^_t`4?-ZVneG+{;i^IR=9!7xea-6Fi zqFndOb;UQ|hu$T)#eaCnMEk=u#Z@nlO-L)>{(4i*fHQ?2?YQ%NZh+hXVo59ZaB zD$Kn^r2ShM*Cd~BOs}lm?tA-oe9DtqeeI5*FPoEW*<~j-z5^U?7~iyu%9Dq26fG+~ z&P}yP1@h&R0F>$?P4)M*W4U{f#VG%X{P#QZ)zek95#!>;ik}z|`M7=0_n$Pv%qtt| ze837CG168h44vXkV6EoFq89FbEBN}+0x|B@dz?Gp=$( zZJ?GWt?(cT#@4JV%q%8$*^#N~`-{<*P!Q^o&^uF!i&VVcY!C~2ct}s7_;gxg#oqY6 zVe;R2>bUE8!tNKn5WC%i{NV2`;MrI|JTzrDda>T-v!?r@71|Y>E}P-+!7VE`uX&Ai zRf=7qXj?>YY>$3d%5>{Y(X=?*g0Esju=PFny%$?sSD>gSN2sd#rI6B+;yxpk-}G1Y zt9i$b#QD%QeYjuKZnAL`Y=d$)YcbiA+?ozO$*q<(T>uK7`eCxe^ zCToV4;fj!1<*W1;#rcSoSMFxPtUD_^#k;TevND!&gfQ9k8`>RtC71TaSeV-l+*is& zdvnCY&?-rtT}a)HbEpa(O4lQ?mZ!PeEYnZNDvy$R_`~+gE2XrGGDhnAQs9a9GjA8a zoZPT`sqgc(ZyEniTQHviAA$(VXV1N z1$aRH1D}>ak~VrCA==cBC#Vb%G@w{&((*u*QM?O9Cuau#s z&a%P{YZfQ6+1>aBalid~Umfq|=w-Aj(?#x4Zk39Qk==(ARiq(4lcpN8PhM2;jmZsE z!$0z9zg6Tjj~q9Dyr8V^(oQ{1mp64v@0o(jNhvQ^(emfw=5=1W142u2?_hD7QzBfw zSNWfGRr#EZf1V}lXYtOu8!@7SaEC3Zq_SRcId`utuc+4jqwJ8)b;~bD0z+;KD-I2H zHPp`Ch!Q~gZaBoN-E~rdTd+0&sMo-_n;F-Cs`TK)S7gkOQ%&$7m zH*e&Gew!S-vlhpv(i;{$x~za`-uS%!s7ej@z;mH*L~7&b)0{AvwF#oM@J)o4ow>%* zyut?V+6dO=D>(^wc;FU!e&sxhckQe#Qc>fn{7p|pIFw$+mT)l_A`G%sq$ibAiVil) z!TEpbp0hI7=<|9u@#W!1MHR^_5@#24u_4m@k&!px&fI$=BYzz|vGxlX8VaUjr)CE} z!*_xbCEcw$LIbQnf9bMV2K!@pzPlb4c-gv`Ewz&SsaSA;Z^n@<0zovAn<3vi~rrwU`?Q$+}{l2;Nk5= z?sc;}<52TJb0lyr!Sc5;2!saD0DJy6hkJUGYki+l-Om$v;Uy*z=yx!-HZ*uAFlf}% z(-$Zi>Er_(Io#L9!<`)Ad^Cc=9(esSdJsrpt2yw92A2SPeiQy2J1K{3EUa*+*iQhk z=xwpk0b;tPvzc#Nu;NQ(}Hl*kB?M7^TA#m`uz%TK;s2_;#Z+~C7y_=J;W7fh>$h%g zXfUvD9^Q0|M_@`wC94YBm}L?N{x8q~Z;Aa|T;LH6mIJQ6{{~?Ty&2$tq2x{xp($}p zvqj0^wwwNjk`)D?oxX|$VwwuP*NH`QfP$|7CAwLNi1f`!SyUJB-iF&Iimz@&O1^40 zXW;-u)O=e+^?-A?jZ6^H%`zaQILe}*Wys*RiQ;REkdjFk+}=9-Xz;u&88U(B<}__m z9A!~?1v0p8qWHPrq-4?+ZSkTCz(lwIis*nRKBOyl9Ia88U(B<_%a<9A(j97#Z9) zQT$z9QZi{_f4pdj5g9Up=$~7sq&z}X;@ouFj0|s^=^rZDk9^Xi-FQ{KvwzSeCA-O% z@Fo=|twoHdwpkDe6ILcBMG*Gy?AvM&JfgujZHN#AM~m<5A*GSl65Rr#!8ltY+UB_+ zo6RT-r8^QK2!tqSFw#Z6Egn?hu>2W26fSRw?0 z5aoRuD)D_<;e} K1(;jEpZyQj_6{ll literal 0 HcmV?d00001 diff --git a/examples/sample-output/architecture.drawio b/examples/sample-output/architecture.drawio new file mode 100644 index 0000000..4c9d931 --- /dev/null +++ b/examples/sample-output/architecture.drawio @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/sample-output/migration-adb-ha-dr.drawio b/examples/sample-output/migration-adb-ha-dr.drawio new file mode 100644 index 0000000..8a0951e --- /dev/null +++ b/examples/sample-output/migration-adb-ha-dr.drawio @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kb/architecture-center/catalog.yaml b/kb/architecture-center/catalog.yaml new file mode 100644 index 0000000..f6f7857 --- /dev/null +++ b/kb/architecture-center/catalog.yaml @@ -0,0 +1,1537 @@ +# ============================================================================= +# ORACLE ARCHITECTURE CENTER — REFERENCE CATALOG +# ============================================================================= +# +# Index of Oracle Architecture Center content for the Deal Accelerator. +# Used to match customer architectures with official Oracle reference designs. +# +# Matching logic (Phase 2 — Architecture Composition): +# STRONG MATCH: ≥2 service matches + ≥1 tag match +# MODERATE MATCH: ≥1 service match + ≥2 tag matches +# +# Last refreshed: 2026-03-16 +# Entry count: 123 +# To refresh: python tools/refresh_arch_catalog.py --whats-new +# ============================================================================= + +last_refreshed: "2026-03-16" +source: "https://docs.oracle.com/en/solutions/oracle-architecture-center/" +entry_count: 123 + +entries: + + # ===== DATABASE — MULTICLOUD (Database@Azure) ===== + + - title: "Deploy Oracle Autonomous Database on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/deploy-autonomous-database-db-at-azure/index.html" + type: reference-architecture + date: "2024-10" + services: [adb-s, adg, azure] + tags: [database, multicloud, azure, ha-dr, autonomous] + summary: > + Multi-AZ deployment of ADB-S on Database@Azure with Autonomous Data Guard. + Recommends VNet peering between app and DB VNets, TAC for availability, + and ADG standby in a different AZ for automatic failover. + terraform: null + + - title: "Oracle MAA for Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/oracle-maa-db-at-azure/index.html" + type: reference-architecture + date: "2025-05" + services: [exacs, adg, vault, azure] + tags: [database, multicloud, azure, ha-dr, autonomous] + summary: > + Cross-AZ Data Guard on ExaCS in Database@Azure. Active Data Guard + recommended for cross-AZ replication (block repair, app continuity, + read offload). Backups to Autonomous Recovery Service. + terraform: null + + - title: "Implement disaster recovery with local and regional standbys on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/local-regional-standby-dr-db-at-azure/index.html" + type: reference-architecture + date: "2025-05" + services: [exacs, adg, azure, fsdr] + tags: [database, multicloud, azure, ha-dr] + summary: > + Local standby for HA within same region, regional standby for DR + across Azure regions. Uses Active Data Guard with Fast-Start Failover. + Recommends Full Stack DR service for automated failover orchestration. + terraform: null + + - title: "Implement disaster recovery with multi-region standby on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/multi-region-standby-dr-db-at-azure/index.html" + type: reference-architecture + date: "2025-04" + services: [exacs, adg, azure] + tags: [database, multicloud, azure, ha-dr] + summary: > + Multi-region DR for ExaCS on Database@Azure using Data Guard. + Cross-region standby with switchover/failover capabilities. + Includes network topology for Azure-to-Azure cross-region connectivity. + terraform: null + + - title: "Implement cross-region disaster recovery for Exadata Database on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/exadb-dr-on-db-azure/index.html" + type: reference-architecture + date: "2025-01" + services: [exacs, adg, azure, fsdr] + tags: [database, multicloud, azure, ha-dr] + summary: > + Cross-region DR for ExaCS on Database@Azure. OCI-managed networks for + peering (better performance, first 10 TB/month free). Data Guard for + cross-region replication, FSDR for automated failover orchestration. + terraform: "https://github.com/oracle-quickstart/oci-multicloud-azure" + + - title: "Deploy Active Data Guard Far Sync to protect data across Oracle Database@Azure regions" + url: "https://docs.oracle.com/en/solutions/active-data-guard-far-sync-oracle-db-at-azure/index.html" + type: reference-architecture + date: "2025-02" + services: [exacs, adg, azure] + tags: [database, multicloud, azure, ha-dr] + summary: > + Far Sync instance for zero data loss protection across Azure regions. + Synchronous redo transport to Far Sync, async to remote standby. + Eliminates performance impact of synchronous shipping over long distances. + terraform: null + + - title: "Design a multicloud network to deploy Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/network-for-db-at-azure/index.html" + type: reference-architecture + date: "2025-04" + services: [vcn, drg, azure, fastconnect] + tags: [networking, multicloud, azure] + summary: > + Network design patterns for Database@Azure deployments. Covers + VNet-to-VCN peering, hub-spoke topologies, and on-premises + connectivity via ExpressRoute + FastConnect. + terraform: null + + - title: "Learn about selecting network topologies for Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/network-topology-oracle-database-at-azure/index.html" + type: reference-architecture + date: "2025-05" + services: [vcn, drg, azure] + tags: [networking, multicloud, azure] + summary: > + Network topology options for Database@Azure. Compares single-VNet, + hub-spoke, and transit gateway patterns. Guidance on IP address + planning and NSG rules for database traffic. + terraform: null + + - title: "Build a secure multicloud architecture for Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/secure-db-azure/index.html" + type: reference-architecture + date: "2025-07" + services: [exacs, adb-s, vault, data-safe, cloud-guard, azure] + tags: [database, multicloud, azure, security] + summary: > + Security architecture for Database@Azure. Covers data encryption + with OCI Vault, database activity monitoring with Data Safe, + threat detection with Cloud Guard, and network security with NSGs. + terraform: null + + - title: "Build Azure CI/CD Pipelines using Oracle Exadata Database Service in Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/azure-pipeline-exacs-dbazure/index.html" + type: reference-architecture + date: "2025-04" + services: [exacs, azure] + tags: [database, multicloud, azure, devops] + summary: > + Azure DevOps pipeline integration with ExaCS on Database@Azure. + Automated schema deployment, testing, and migration using + Azure Pipelines with Oracle database tooling. + terraform: null + + - title: "Deploy a Data Lake leveraging Power BI on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/analytics-pipeline-db-at-azure/index.html" + type: reference-architecture + date: "2025-02" + services: [adb-s, azure, object-storage] + tags: [data-platform, multicloud, azure, autonomous] + summary: > + Analytics pipeline combining ADB-S on Database@Azure with Power BI. + Data ingestion to Object Storage, transformation in ADB-S, + visualization via Power BI DirectQuery or Import mode. + terraform: null + + - title: "Deploy Oracle E-Business Suite on Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/deploy-ebs-on-db-at-azure/index.html" + type: reference-architecture + date: "2025-05" + services: [exacs, azure, compute, load-balancer] + tags: [application, multicloud, azure, ebs] + summary: > + EBS application tier on Azure VMs with database on ExaCS via + Database@Azure. App-to-DB connectivity through VNet peering. + Load balancer for EBS web tier high availability. + terraform: null + + - title: "Implement Oracle Database Autonomous Recovery Service with Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/implement-recovery-service-db-at-azure/index.html" + type: reference-architecture + date: "2025-01" + services: [exacs, azure] + tags: [database, multicloud, azure, ha-dr] + summary: > + Autonomous Recovery Service for ExaCS backups on Database@Azure. + Automated backup management with real-time protection and + point-in-time recovery capabilities. + terraform: null + + - title: "Move to Oracle Database@Azure with Oracle Zero Downtime Migration" + url: "https://docs.oracle.com/en/solutions/oracle-db-at-azure-migration/index.html" + type: reference-architecture + date: "2024-12" + services: [exacs, azure, goldengate] + tags: [database, multicloud, azure, migration] + summary: > + Zero Downtime Migration to Database@Azure using logical or physical + online migration. GoldenGate for continuous replication during cutover. + Supports on-premises Oracle DB to ExaCS on Database@Azure. + terraform: null + + - title: "Migrate business critical applications to Oracle Database@Azure using a phased strategy" + url: "https://docs.oracle.com/en/solutions/phased-migration-to-oracle-dba/index.html" + type: reference-architecture + date: "2025-03" + services: [exacs, azure, goldengate, adg] + tags: [database, multicloud, azure, migration] + summary: > + Phased migration to Database@Azure. Leverages OCI networking for DR + replication with low-latency, high-bandwidth connectivity. Integrates + Autonomous Recovery Service with Data Guard for data protection. + terraform: "https://github.com/oracle-quickstart/oci-multicloud-azure" + + - title: "Implement Oracle GoldenGate in Microsoft Azure with Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/implement-goldengate-azure/index.html" + type: reference-architecture + date: "2024-10" + services: [goldengate, exacs, azure] + tags: [database, multicloud, azure, integration] + summary: > + GoldenGate deployment patterns for Database@Azure. Real-time + replication between on-premises and ExaCS, or between ExaCS + instances across Azure regions. + terraform: null + + - title: "Integrate Oracle Database@Azure with your Azure streaming platform using OCI GoldenGate" + url: "https://docs.oracle.com/en/solutions/oracle-db-at-azure-streaming/index.html" + type: reference-architecture + date: "2024-08" + services: [goldengate, exacs, azure, streaming] + tags: [database, multicloud, azure, integration] + summary: > + Stream change data from ExaCS on Database@Azure to Azure Event Hubs + using OCI GoldenGate. Enables real-time event-driven architectures + combining Oracle database with Azure messaging services. + terraform: null + + - title: "Create a modern platform with Oracle Database@Azure, OCI GoldenGate, and Azure services" + url: "https://docs.oracle.com/en/solutions/modern-platform-oracle-db-at-azure/index.html" + type: reference-architecture + date: "2024-08" + services: [goldengate, exacs, azure] + tags: [database, multicloud, azure, integration, data-platform] + summary: > + Modern data platform combining Database@Azure with Azure PaaS services. + GoldenGate for real-time data synchronization between ExaCS and + Azure SQL, Cosmos DB, or Synapse Analytics. + terraform: null + + - title: "Connect Azure Kubernetes with Oracle Exadata Database Service on Oracle Database at Azure" + url: "https://docs.oracle.com/en/solutions/connect-azure-kube-with-oracle/index.html" + type: reference-architecture + date: "2025-02" + services: [exacs, azure, oke] + tags: [application, multicloud, azure, database] + summary: > + AKS workloads connecting to ExaCS on Database@Azure. Network + connectivity via VNet peering, connection pooling with Oracle + Universal Connection Pool for Kubernetes pod scaling. + terraform: null + + - title: "Establish a multicloud data solution between OCI and Microsoft Azure" + url: "https://docs.oracle.com/en/solutions/oci-azure-multicloud-data-solution/index.html" + type: reference-architecture + date: "2024-08" + services: [adb-s, azure, data-integration, object-storage] + tags: [data-platform, multicloud, azure] + summary: > + Cross-cloud data integration between OCI and Azure. OCI Data + Integration for ETL between Azure Blob Storage and OCI Object Storage. + ADB-S for analytics with data from both clouds. + terraform: null + + # ===== DATABASE — MULTICLOUD (Database@Google Cloud) ===== + + - title: "Deploy Oracle Autonomous Database on Oracle Database@Google Cloud" + url: "https://docs.oracle.com/en/solutions/deploy-adb-db-at-google-cloud/index.html" + type: reference-architecture + date: "2025-03" + services: [adb-s, adg, google-cloud] + tags: [database, multicloud, ha-dr, autonomous] + summary: > + ADB-S on Database@Google Cloud with cross-region ADG. Non-overlapping + CIDR between VPC and VCN required. App tier should span 2+ AZs with + Google Global Load Balancer for failover. + terraform: null + + - title: "Deploy Oracle Database@Google Cloud" + url: "https://docs.oracle.com/en/solutions/deploy-oracle-database-at-google-cloud/index.html" + type: reference-architecture + date: "2025-10" + services: [exacs, adb-d, google-cloud] + tags: [database, multicloud, autonomous] + summary: > + ExaCS and ADB-D on Database@Google Cloud. RAC for active-active HA, + ASM for storage redundancy. Backups to OCI Object Storage via + Autonomous Recovery Service. + terraform: null + + - title: "Learn about selecting network topologies for Oracle Database@Google Cloud" + url: "https://docs.oracle.com/en/solutions/network-topology-oracle-database-at-google-cloud/index.html" + type: reference-architecture + date: "2025-10" + services: [vcn, drg, google-cloud] + tags: [networking, multicloud] + summary: > + Network topology options for Database@Google Cloud. Covers VPC-to-VCN + peering, shared VPC, and Private Service Connect patterns. + Guidance on DNS resolution and IP address management. + terraform: null + + - title: "Configure DNS resolution in Oracle Database@Google Cloud" + url: "https://docs.oracle.com/en/solutions/dns-resolution-oracle-db-at-google-cloud/index.html" + type: reference-architecture + date: "2025-11" + services: [dns, google-cloud, exacs] + tags: [networking, multicloud] + summary: > + DNS configuration for Database@Google Cloud. Cloud DNS forwarding + zones for OCI service resolution, private DNS for VCN name resolution + from Google Cloud applications. + terraform: null + + - title: "Move to Oracle Database@Google Cloud with Oracle Zero Downtime Migration" + url: "https://docs.oracle.com/en/solutions/oracle-db-at-google-cloud-migration/index.html" + type: reference-architecture + date: "2024-11" + services: [exacs, google-cloud, goldengate] + tags: [database, multicloud, migration] + summary: > + Zero Downtime Migration to Database@Google Cloud. Physical or logical + migration methods with GoldenGate for minimal-downtime cutover. + Supports on-premises Oracle DB to ExaCS on Google Cloud. + terraform: null + + - title: "Deploy Oracle Key Vault with Oracle Exadata Database Service (Oracle Database@Google Cloud)" + url: "https://docs.oracle.com/en/solutions/deploy-key-vault-database-at-google/index.html" + type: reference-architecture + date: "2025-01" + services: [exacs, vault, google-cloud] + tags: [database, multicloud, security] + summary: > + Oracle Key Vault deployment for TDE key management on ExaCS in + Database@Google Cloud. Centralized encryption key lifecycle management + across multiple database deployments. + terraform: null + + # ===== DATABASE — MULTICLOUD (Database@AWS) ===== + + - title: "Deploy Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/deploy-oracle-db-aws/index.html" + type: reference-architecture + date: "2025-07" + services: [exacs, adb-d, aws] + tags: [database, multicloud, aws] + summary: > + ExaCS and ADB-D colocated in AWS data centers. ODB peering between + VPC and ODB network in same AZ. Default limit of 2 DB servers and + 3 storage servers — request increase early. + terraform: null + + - title: "Learn about network topologies for Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/network-topology-oracle-database-at-aws/index.html" + type: reference-architecture + date: "2025-07" + services: [vcn, drg, aws] + tags: [networking, multicloud, aws] + summary: > + Network topology patterns for Database@AWS. VPC-to-ODB network + peering, Transit Gateway integration, and on-premises connectivity + via AWS Direct Connect alongside OCI FastConnect. + terraform: null + + - title: "Deploy Active Data Guard Far Sync to protect data across Oracle Database@AWS regions" + url: "https://docs.oracle.com/en/solutions/dr-active-dg-farsync-db-at-aws/index.html" + type: reference-architecture + date: "2025-09" + services: [exacs, adg, aws] + tags: [database, multicloud, aws, ha-dr] + summary: > + Far Sync instance for zero data loss protection across AWS regions. + Synchronous redo to local Far Sync, asynchronous to remote standby. + Requires cross-region AWS networking for redo transport. + terraform: null + + - title: "Implement disaster recovery with local and regional standbys on Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/dr-local-regional-standby-db-at-aws/index.html" + type: reference-architecture + date: "2025-09" + services: [exacs, adg, aws, fsdr] + tags: [database, multicloud, aws, ha-dr] + summary: > + Local standby for HA within same AWS region, regional standby for DR. + Active Data Guard with Fast-Start Failover for automated failover. + Full Stack DR service for cross-service failover orchestration. + terraform: null + + - title: "Implement disaster recovery with cross-zonal Data Guard on Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/cross-zone-dr-db-at-aws/index.html" + type: reference-architecture + date: "2025-08" + services: [exacs, adg, aws] + tags: [database, multicloud, aws, ha-dr] + summary: > + Cross-AZ Data Guard on Database@AWS for HA. Synchronous redo + transport between AZs for zero data loss. Fast-Start Failover + for automatic database switchover on failure. + terraform: null + + - title: "Implement disaster recovery with cross-regional Data Guard on Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/implement-dr-cross-region-dg-odb-aws/index.html" + type: reference-architecture + date: "2025-08" + services: [exacs, adg, aws] + tags: [database, multicloud, aws, ha-dr] + summary: > + Cross-region Data Guard on Database@AWS for DR. Asynchronous redo + transport for cross-region replication. Planned switchover for + maintenance, forced failover for disaster scenarios. + terraform: null + + - title: "Deploy Oracle E-Business Suite on Oracle Database@AWS" + url: "https://docs.oracle.com/en/solutions/ebs-on-db-at-aws/index.html" + type: reference-architecture + date: "2025-10" + services: [exacs, aws, compute, load-balancer] + tags: [application, multicloud, aws, ebs] + summary: > + EBS application tier on AWS EC2 with database on ExaCS via + Database@AWS. App-to-DB connectivity through VPC-ODB peering. + AWS ALB for web tier high availability. + terraform: null + + # ===== DATABASE — MIGRATION ===== + + - title: "Migrate on-premises Oracle Database to Autonomous Database" + url: "https://docs.oracle.com/en/solutions/migrate-to-atp/index.html" + type: reference-architecture + date: "2024-01" + services: [adb-s, object-storage, compute, vpn] + tags: [database, migration, autonomous] + summary: > + MV2ADB tool for on-prem EE to ADB-S migration using Data Pump. + Requires HTTP to Object Storage + SQL*Net to ADB. Terraform code + available on GitHub for networking + compute + ADB provisioning. + terraform: "https://github.com/oracle-quickstart/oci-arch-atp" + + - title: "Migrate Oracle RAC Databases to OCI" + url: "https://docs.oracle.com/en/solutions/ensure-ha-migrate-vmware-workloads-to-oci/migrate-oracle-rac-databases1.html" + type: reference-architecture + date: "2025-01" + services: [exacs, exascale, adb-d, base-db] + tags: [database, migration, ha-dr] + summary: > + Compares 4 RAC migration targets: ExaCS Dedicated, Exascale, ADB-D, + and 2-node RAC on Base DB. ExaCS recommended for full RAC feature + parity. Exascale for 23ai-only without dedicated infra commitment. + terraform: null + + - title: "Migrate an on-premises database to the cloud with zero downtime" + url: "https://docs.oracle.com/en/solutions/migrate-database-with-zdm/index.html" + type: reference-architecture + date: "2025-01" + services: [base-db, exacs, goldengate] + tags: [database, migration] + summary: > + Oracle Zero Downtime Migration for on-premises to OCI. Physical + migration for same-version moves, logical for cross-version/platform. + GoldenGate for continuous replication during extended cutover windows. + terraform: null + + - title: "Migrate a PeopleSoft database to Oracle Autonomous Database" + url: "https://docs.oracle.com/en/solutions/migrate-peoplesoft-db-autonomous-db-zdm/index.html" + type: reference-architecture + date: "2025-02" + services: [adb-s, compute] + tags: [database, migration, autonomous, peoplesoft] + summary: > + PeopleSoft database migration to ADB-S using Zero Downtime Migration. + Logical migration with Data Pump, ZDM for orchestration. PeopleTools + compatibility verification required before migration. + terraform: null + + - title: "Deploy a migrated MongoDB workload to Oracle Autonomous Transaction Processing Serverless" + url: "https://docs.oracle.com/en/solutions/mongodb-to-atp/index.html" + type: reference-architecture + date: "2025-06" + services: [adb-s, compute] + tags: [database, migration, autonomous] + summary: > + MongoDB to ADB-S migration using Oracle Database API for MongoDB. + Applications use MongoDB wire protocol against ADB-S JSON collections. + No application code changes required for basic CRUD operations. + terraform: null + + - title: "Deploy a migrated MongoDB workload to Oracle Autonomous Transaction Processing Serverless@Azure" + url: "https://docs.oracle.com/en/solutions/mongodb-to-atp-azure/index.html" + type: reference-architecture + date: "2025-08" + services: [adb-s, azure] + tags: [database, migration, multicloud, azure, autonomous] + summary: > + MongoDB to ADB-S migration on Database@Azure. Uses Oracle Database + API for MongoDB for wire protocol compatibility. Applications connect + via Azure VNet peering to ADB-S private endpoint. + terraform: null + + - title: "Deploy a migrated MongoDB workload to Oracle Autonomous Transaction Processing Serverless@Google Cloud" + url: "https://docs.oracle.com/en/solutions/mongodb-to-atp-google/index.html" + type: reference-architecture + date: "2025-08" + services: [adb-s, google-cloud] + tags: [database, migration, multicloud, autonomous] + summary: > + MongoDB to ADB-S migration on Database@Google Cloud. MongoDB API + compatibility for transparent application migration. Google Cloud + VPC peering for private database connectivity. + terraform: null + + - title: "Deploy a migrated MongoDB workload to Oracle Autonomous JSON Database" + url: "https://docs.oracle.com/en/solutions/mongodb-to-auto-json-db/index.html" + type: reference-architecture + date: "2025-06" + services: [adb-s] + tags: [database, migration, autonomous] + summary: > + MongoDB to Autonomous JSON Database migration. JSON-centric workloads + with document model support. SODA API and MongoDB API for application + compatibility. + terraform: null + + - title: "Deploy a migrated MongoDB workload to Oracle Exadata Database Machine" + url: "https://docs.oracle.com/en/solutions/mongodb-to-exadata-machine/index.html" + type: reference-architecture + date: "2025-06" + services: [exacs] + tags: [database, migration] + summary: > + MongoDB to Exadata migration for high-performance workloads. + Oracle Database API for MongoDB on Exadata infrastructure. + Suitable when performance requirements exceed ADB-S capabilities. + terraform: null + + # ===== DATABASE — HA/DR ===== + + - title: "Deploy ORDS with High Availability on OCI" + url: "https://docs.oracle.com/en/solutions/deploy-ords-ha-oci/index.html" + type: reference-architecture + date: "2025-06" + services: [compute, load-balancer, adb-s, bastion] + tags: [database, application, ha-dr] + summary: > + Multi-instance ORDS behind OCI Load Balancer for HA REST access to + Oracle DB. Works with ADB-S, DBCS, or ExaCS. Recommends DB in + private subnet, ORDS in public with granular NSG rules. + terraform: null + + - title: "Deploy secure ADB and APEX application" + url: "https://docs.oracle.com/en/solutions/deploy-autonomous-database-and-app/index.html" + type: reference-architecture + date: "2024-06" + services: [adb-s, apex, load-balancer, bastion, cloud-guard] + tags: [database, application, security, autonomous] + summary: > + APEX on ADB-S with private endpoint behind Load Balancer. OCI + Landing Zone via Terraform provisions in minutes. Recommends NSGs + over Security Lists, Cloud Guard with custom detector recipes. + terraform: "https://github.com/oracle-quickstart/oci-arch-apex-atp" + + - title: "Configure Data Guard for Oracle Exadata Database Service on Dedicated Infrastructure" + url: "https://docs.oracle.com/en/solutions/dataguard-exadata-dedicated-infrastructure/index.html" + type: reference-architecture + date: "2025-07" + services: [exacs, adg] + tags: [database, ha-dr] + summary: > + Data Guard configuration for ExaCS Dedicated. Active Data Guard for + read offload and automatic failover. Cross-region standby for DR + with Fast-Start Failover for automated switchover. + terraform: null + + - title: "Back up from Oracle Base Database Service to Zero Data Loss Autonomous Recovery" + url: "https://docs.oracle.com/en/solutions/back-up-base-database-zero-data-loss-recovery/index.html" + type: reference-architecture + date: "2025-07" + services: [base-db] + tags: [database, ha-dr] + summary: > + Autonomous Recovery Service for Base DB backups. Real-time protection + with continuous redo log shipping. Point-in-time recovery with + sub-second RPO for mission-critical workloads. + terraform: null + + - title: "Back up from Exadata on Dedicated Infrastructure to Zero Data Loss Autonomous Recovery" + url: "https://docs.oracle.com/en/solutions/back-up-exadata-dedicated-zero-data-loss-recovery/index.html" + type: reference-architecture + date: "2025-07" + services: [exacs] + tags: [database, ha-dr] + summary: > + Autonomous Recovery Service for ExaCS backups. Automated backup + management with real-time redo protection. Validates backup + recoverability automatically. + terraform: null + + - title: "Configure a standby database for disaster recovery using Oracle Exadata Database Service on Cloud@Customer" + url: "https://docs.oracle.com/en/solutions/exadata-cloud-at-customer-standby/index.html" + type: reference-architecture + date: "2025-07" + services: [exacs, adg] + tags: [database, ha-dr] + summary: > + Data Guard standby on ExaCS Cloud@Customer for hybrid DR. + On-premises primary with cloud standby, or cloud primary with + on-premises standby for data sovereignty requirements. + terraform: null + + - title: "Use remote synchronous block replication on Oracle Cloud Infrastructure" + url: "https://docs.oracle.com/en/solutions/remote-synchronous-block-replication-oci/index.html" + type: reference-architecture + date: "2024-10" + services: [block-storage, compute] + tags: [ha-dr] + summary: > + Block volume cross-region replication for non-database workloads. + Synchronous replication for RPO=0 within same metro, asynchronous + for cross-region DR with configurable RPO. + terraform: null + + # ===== DATABASE — OTHER ===== + + - title: "Move Oracle Forms applications to Oracle APEX on an Oracle Autonomous Database" + url: "https://docs.oracle.com/en/solutions/migrate-apps-to-apex/index.html" + type: reference-architecture + date: "2025-07" + services: [adb-s, apex] + tags: [application, migration, autonomous] + summary: > + Oracle Forms to APEX modernization on ADB-S. Automated conversion + tools for Forms modules to APEX pages. Manual refinement needed for + complex Forms triggers and PL/SQL logic. + terraform: null + + - title: "Stream Kafka topics to Oracle Autonomous Database using Oracle Integration 3" + url: "https://docs.oracle.com/en/solutions/oci-oad-kafka-oi/index.html" + type: reference-architecture + date: "2025-02" + services: [adb-s, oic3, streaming] + tags: [database, integration, autonomous] + summary: > + Kafka to ADB-S streaming ingestion via OIC3. Oracle Integration + adapter for Kafka topics with schema mapping to ADB-S tables. + Near-real-time data pipeline for analytics workloads. + terraform: null + + # ===== NETWORKING ===== + + - title: "Design network architecture for data and application integration workloads on OCI" + url: "https://docs.oracle.com/en/solutions/data-application-integration-workloads/index.html" + type: reference-architecture + date: "2025-11" + services: [vcn, drg, fastconnect, oic, data-integration, adb-s] + tags: [networking, integration, multicloud, data-platform] + summary: > + 4 integration patterns: single VCN, cross-VCN, cross-region, and + multicloud. FastConnect+DRG for on-prem, RPC for cross-region. + Multicloud via FastConnect+ExpressRoute/DirectConnect/PartnerInterconnect. + terraform: null + + - title: "Best practices for hybrid and multicloud OCI networking design" + url: "https://docs.oracle.com/en/solutions/oci-best-practices-networking/index.html" + type: reference-architecture + date: "2025-10" + services: [vcn, drg, fastconnect, load-balancer, waf] + tags: [networking, multicloud, security] + summary: > + Comprehensive OCI networking best practices. Hub-spoke with DRG, + network segmentation with NSGs, FastConnect for hybrid connectivity. + WAF and DDoS protection for internet-facing workloads. + terraform: null + + - title: "Set up a hub-and-spoke network topology using dynamic routing gateway" + url: "https://docs.oracle.com/en/solutions/hub-spoke-network-drg/index.html" + type: reference-architecture + date: "2025-09" + services: [vcn, drg, fastconnect] + tags: [networking] + summary: > + Hub-spoke topology using DRG as central router. Spoke VCNs attached + to DRG with route tables for inter-spoke and on-premises routing. + Centralized network security appliances in hub VCN. + terraform: null + + - title: "Set up a hub-and-spoke network topology by using local peering gateways" + url: "https://docs.oracle.com/en/solutions/hub-spoke-network/index.html" + type: reference-architecture + date: "2025-08" + services: [vcn] + tags: [networking] + summary: > + Hub-spoke topology using Local Peering Gateways. Simpler than DRG + for same-region peering. Limited to 10 LPGs per VCN. Suitable for + smaller deployments without cross-region requirements. + terraform: null + + - title: "Learn about dynamic routing gateway solutions" + url: "https://docs.oracle.com/en/solutions/learn-about-drg-solutions/index.html" + type: reference-architecture + date: "2024-09" + services: [drg, vcn, fastconnect] + tags: [networking] + summary: > + DRG v2 capabilities overview. Transit routing, cross-tenancy peering, + route distribution policies, and ECMP for link aggregation. + Foundation for enterprise-grade OCI network architectures. + terraform: null + + - title: "Use private DNS in interconnected VCNs and on-premises" + url: "https://docs.oracle.com/en/solutions/private-dns/index.html" + type: reference-architecture + date: "2024-11" + services: [vcn, dns, drg] + tags: [networking] + summary: > + Private DNS configuration for hybrid and multi-VCN environments. + DNS forwarding between on-premises and OCI, conditional forwarding + for split-horizon DNS, and cross-VCN name resolution via DRG. + terraform: null + + - title: "Deploy Palo Alto firewall in Active/Active mode with OCI Flexible Network Load Balancer" + url: "https://docs.oracle.com/en/solutions/oci-nlb-palo-alto-active-active/index.html" + type: reference-architecture + date: "2025-02" + services: [load-balancer, vcn, compute] + tags: [networking, security] + summary: > + Active/Active Palo Alto VM-Series behind OCI Flexible NLB. Symmetric + routing with NLB for ingress, route table rules for egress. Health + checks for automatic failover on firewall failure. + terraform: null + + # ===== SECURITY & COMPLIANCE ===== + + - title: "Deploy a secure landing zone that meets the CIS OCI Foundations Benchmark" + url: "https://docs.oracle.com/en/solutions/cis-oci-benchmark/index.html" + type: reference-architecture + date: "2025-02" + services: [cloud-guard, vault, vcn, bastion] + tags: [security] + summary: > + CIS-compliant OCI landing zone with compartment-based organization, + segregation of duties through IAM groups/policies. Supports standalone, + hub-spoke, and DMZ VCN patterns. Zero Trust Packet Routing enabled. + terraform: "https://github.com/oracle-quickstart/oci-landing-zone-core" + + - title: "Incorporate Cyber-Resilience Capabilities Into Your OCI Tenancy" + url: "https://docs.oracle.com/en/solutions/oci-tenancy-cyber-resilience-architecture/index.html" + type: reference-architecture + date: "2025-08" + services: [cloud-guard, vault, data-safe] + tags: [security] + summary: > + Cyber-resilience architecture for OCI tenancies. Immutable backups, + isolated recovery environments, threat detection with Cloud Guard, + and database activity monitoring with Data Safe. + terraform: null + + - title: "Protect your workloads in the cloud using security zones" + url: "https://docs.oracle.com/en/solutions/oci-security-zones/index.html" + type: reference-architecture + date: "2024-11" + services: [cloud-guard] + tags: [security] + summary: > + Security Zones for preventive policy enforcement. Prevents creation + of non-compliant resources (public buckets, unencrypted volumes). + Combines with Cloud Guard for detective + preventive controls. + terraform: null + + - title: "Secure TLS certificate deployment using OCI CA for OCI services and hybrid architectures" + url: "https://docs.oracle.com/en/solutions/secure-tls-oci-ca/index.html" + type: reference-architecture + date: "2025-11" + services: [vault, load-balancer] + tags: [security, networking] + summary: > + OCI Certificate Authority for TLS certificate lifecycle management. + Automated certificate rotation for Load Balancers, API Gateways, + and custom applications. Hybrid CA trust chain with on-premises PKI. + terraform: null + + - title: "Connect Oracle Data Safe to Oracle databases on multicloud and hybrid cloud environments" + url: "https://docs.oracle.com/en/solutions/data-safe-multicloud-ods-hybrid/index.html" + type: reference-architecture + date: "2025-06" + services: [data-safe, exacs, adb-s] + tags: [security, database, multicloud] + summary: > + Data Safe for database security assessment across multicloud and + hybrid deployments. Covers Database@Azure, Database@AWS, on-premises, + and OCI-native databases. + terraform: null + + - title: "Implement Oracle Data Safe for Exadata and Autonomous Databases" + url: "https://docs.oracle.com/en/solutions/data-safe-exadata-adb/index.html" + type: reference-architecture + date: "2025-06" + services: [data-safe, exacs, adb-s] + tags: [security, database] + summary: > + Data Safe deployment for ExaCS and ADB. Security assessment, user + assessment, activity auditing, data masking, and data discovery. + Private endpoint connectivity for databases in private subnets. + terraform: null + + - title: "Deploy Oracle Key Vault for Oracle Database@Azure" + url: "https://docs.oracle.com/en/solutions/deploy-key-vault-database-at-azure/index.html" + type: reference-architecture + date: "2024-10" + services: [vault, exacs, azure] + tags: [security, database, multicloud, azure] + summary: > + Key Vault for TDE key management on Database@Azure. Centralized + encryption key lifecycle with multi-master replication for HA. + Supports PKCS#11, REST API, and Oracle-native key management. + terraform: null + + - title: "Secure network architecture for on-premises database backups to OCI Object Storage with Private Endpoint" + url: "https://docs.oracle.com/en/solutions/secure-backup-oci-object-storage/index.html" + type: reference-architecture + date: "2025-06" + services: [object-storage, fastconnect, vcn] + tags: [security, networking, ha-dr] + summary: > + Private endpoint for Object Storage backup from on-premises. No + internet exposure — traffic stays on FastConnect/VPN. Granular IAM + policies for backup write access without delete permissions. + terraform: null + + # ===== DR & BUSINESS CONTINUITY ===== + + - title: "Implement mid-tier replication in an OCI disaster recovery architecture" + url: "https://docs.oracle.com/en/solutions/mid-tier-replication-oci-dr-arch/index.html" + type: reference-architecture + date: "2025-11" + services: [compute, fsdr, load-balancer] + tags: [ha-dr, application] + summary: > + Mid-tier replication patterns for OCI DR. Application server state + replication across regions using rsync, block volume replication, + or container image promotion. FSDR for automated failover. + terraform: null + + - title: "Deploy a hybrid DR solution on OCI for Oracle WebLogic or Fusion Middleware domain environments" + url: "https://docs.oracle.com/en/solutions/deploy-hybrid-dr-wls-fmw/index.html" + type: reference-architecture + date: "2025-03" + services: [wls, compute, fsdr, load-balancer, base-db, adg] + tags: [ha-dr, application] + summary: > + Hybrid DR for WebLogic/FMW with on-premises primary and OCI standby. + Greatest automation when primary follows Oracle EDG best practices. + Continuous replication via Data Guard, WLS HYDR Framework for mid-tier. + terraform: "https://github.com/oracle-samples/maa/tree/main/wls-hydr" + + - title: "Configure JD Edwards Disaster Recovery by using OCI Full Stack Disaster Recovery" + url: "https://docs.oracle.com/en/solutions/config-jde-dr-fsdr/index.html" + type: reference-architecture + date: "2025-07" + services: [compute, fsdr, base-db] + tags: [ha-dr, application] + summary: > + JD Edwards DR with Full Stack DR service. Automated failover of + application tier, batch servers, and database. Cross-region block + volume replication for JDE binary and config files. + terraform: null + + - title: "Perform cross-region disaster recovery for Oracle Essbase on OCI" + url: "https://docs.oracle.com/en/solutions/cross-region-dr-essbase-oci/index.html" + type: reference-architecture + date: "2025-10" + services: [compute, fsdr, base-db] + tags: [ha-dr, application] + summary: > + Cross-region DR for Essbase on OCI. Full Stack DR for orchestrated + failover of Essbase compute, database, and storage components. + Block volume replication for Essbase data and configuration. + terraform: null + + - title: "Deploy a disaster recovery solution for OCI API Gateway" + url: "https://docs.oracle.com/en/solutions/deploy-data-rcvy-oci-api-gateway/index.html" + type: reference-architecture + date: "2025-02" + services: [api-gateway, dns, load-balancer] + tags: [ha-dr, networking] + summary: > + Active-passive DR for API Gateway using DNS failover. Traffic Manager + for health-check-based routing. API deployment replication across + regions using Terraform or Resource Manager stacks. + terraform: null + + - title: "Use artifact snapshots to protect your OCI Kubernetes Engine clusters from disaster" + url: "https://docs.oracle.com/en/solutions/kubernetes-artifact-snapshot-dr/index.html" + type: reference-architecture + date: "2025-01" + services: [oke, object-storage] + tags: [ha-dr, application] + summary: > + OKE DR using etcd snapshots and artifact replication. Cross-region + backup of Kubernetes state, container images, and persistent volumes. + Velero for workload backup and restore. + terraform: null + + # ===== DATA PLATFORM ===== + + - title: "Data platform - decentralized data platform" + url: "https://docs.oracle.com/en/solutions/data-platform-decentralized/index.html" + type: reference-architecture + date: "2025-03" + services: [adw, data-catalog, data-integration, object-storage] + tags: [data-platform, autonomous] + summary: > + Decentralized data lakehouse with domain-level ADB-S instances + sharing data via Cloud Links or Delta Sharing. Centralized catalog, + IaC onboarding per domain. Hub-spoke model with OCI backbone routing. + terraform: null + + - title: "Cloud data lake house - process enterprise and streaming data" + url: "https://docs.oracle.com/en/solutions/oci-curated-analysis/index.html" + type: reference-architecture + date: "2024-02" + services: [adw, streaming, goldengate, data-integration, object-storage] + tags: [data-platform, integration] + summary: > + Full data lakehouse with batch and streaming ingestion. ADW with + auto-scaling for curated layer. Hybrid partitioned tables to move + cold data to Object Storage transparently. GoldenGate Stream Analytics + for real-time event processing. + terraform: null + + - title: "Multicloud data lake integration" + url: "https://docs.oracle.com/en/solutions/oci-multicloud-datalake/index.html" + type: reference-architecture + date: "2024-03" + services: [data-integration, oic, object-storage, adw, streaming] + tags: [data-platform, multicloud, integration] + summary: > + Bring data from AWS/Azure/on-prem into OCI data lake. OCI Data + Integration for batch ETL, OIC for app integration with pre-built + adapters. Read-only credentials for source systems recommended. + terraform: null + + - title: "Data platform - data lakehouse" + url: "https://docs.oracle.com/en/solutions/data-platform-lakehouse/index.html" + type: reference-architecture + date: "2024-10" + services: [adw, object-storage, data-integration, data-catalog, streaming, goldengate] + tags: [data-platform, autonomous, ai-ml] + summary: > + Full data lakehouse with ADW + autoscaling, hybrid partitioned tables, + Object Storage medallion architecture. Data Integration/Data Flow for + ETL, GoldenGate Stream Analytics for real-time, AI/ML with Data Science. + terraform: "https://github.com/oracle-quickstart/oci-data-lakehouse" + + - title: "Replicate Oracle Fusion SaaS data to a third-party data warehouse" + url: "https://docs.oracle.com/en/solutions/rep-fusion-saas-third-party-dw/index.html" + type: reference-architecture + date: "2024-05" + services: [oic, data-integration] + tags: [data-platform, integration] + summary: > + Fusion SaaS data replication to third-party data warehouses (Snowflake, + Databricks, BigQuery). OIC for extraction, OCI Data Integration for + transformation and loading to target platforms. + terraform: null + + - title: "Load Oracle Fusion Cloud ERP data into Snowflake" + url: "https://docs.oracle.com/en/solutions/load-fusion-erp-data-snowflake/index.html" + type: reference-architecture + date: "2025-10" + services: [oic, object-storage] + tags: [data-platform, integration, multicloud] + summary: > + Fusion ERP to Snowflake data pipeline. OIC adapter for Fusion data + extraction, Object Storage as staging area, Snowflake COPY INTO for + loading. Incremental extraction for ongoing synchronization. + terraform: null + + # ===== AI & MACHINE LEARNING ===== + + - title: "Deploy agentic AI with Oracle Cloud Infrastructure AI Agent Platform" + url: "https://docs.oracle.com/en/solutions/deploy-agentic-ai-agent-platform/index.html" + type: reference-architecture + date: "2025-12" + services: [genai, adb-s, functions] + tags: [ai-ml, autonomous] + summary: > + Enterprise AI chatbot integrating Visual Builder, Digital Assistant, and + OCI AI Agent Platform. RAG from unstructured data, structured queries + via ADB-S, custom business logic through serverless Functions. + terraform: null + + - title: "Build an agentic, high-fidelity, conversational AI framework with Select AI and Oracle APEX" + url: "https://docs.oracle.com/en/solutions/select-ai-apex-framework/index.html" + type: reference-architecture + date: "2025-12" + services: [adb-s, apex, genai] + tags: [ai-ml, application, autonomous] + summary: > + Select AI with APEX for natural language to SQL. Conversational + interface for database queries using LLMs. ADB-S profiles map + natural language to schema metadata for accurate SQL generation. + terraform: null + + - title: "Get actionable meeting insights with Oracle Autonomous AI Database and GenAI" + url: "https://docs.oracle.com/en/solutions/oci-speech-meeting-insights-genai/index.html" + type: reference-architecture + date: "2025-12" + services: [genai, adb-s, ai-services] + tags: [ai-ml, autonomous] + summary: > + Meeting transcription and insights using OCI Speech + GenAI. + Audio processing with OCI Speech service, summarization and action + item extraction with GenAI, storage in ADB-S for analytics. + terraform: null + + - title: "Enable secure and scalable self-service platforms for generative AI and LLMs within OCI" + url: "https://docs.oracle.com/en/solutions/oci-generative-ai-llm-platforms/index.html" + type: reference-architecture + date: "2025-06" + services: [genai, oke, compute, api-gateway, data-science] + tags: [ai-ml, security] + summary: > + Enterprise GenAI platform with AI CoE governance. NVIDIA MIG for + fractional GPU allocation, OCI DevOps for CI/CD, Oracle Database 23ai + for vector storage. IAM-based environment segmentation per team. + terraform: "https://github.com/oracle-quickstart/oci-hpc-oke" + + - title: "Build an enterprise level Generative AI stack on Oracle Cloud Infrastructure" + url: "https://docs.oracle.com/en/solutions/oci-genai-enterprise/index.html" + type: reference-architecture + date: "2024-09" + services: [genai, oke, object-storage] + tags: [ai-ml] + summary: > + Enterprise GenAI stack with model fine-tuning, RAG, and inference. + OCI AI Infrastructure for GPU compute, Object Storage for model + artifacts, OKE for scalable inference serving. + terraform: null + + - title: "Deploy multicloud generative AI retrieval augmented generation (RAG)" + url: "https://docs.oracle.com/en/solutions/oci-multicloud-genai-rag/index.html" + type: reference-architecture + date: "2025-02" + services: [genai, adb-s, object-storage] + tags: [ai-ml, multicloud, autonomous] + summary: > + Multicloud RAG architecture with OCI GenAI. Document ingestion to + ADB-S vector store, embedding generation with OCI GenAI, retrieval + and generation pipeline. Cross-cloud connectivity for data sources. + terraform: null + + - title: "Deploy an AI-powered chatbot" + url: "https://docs.oracle.com/en/solutions/deploy-ai-powered-chatbot/index.html" + type: reference-architecture + date: "2025-08" + services: [genai, functions, adb-s] + tags: [ai-ml, application, autonomous] + summary: > + AI chatbot with OCI GenAI and Functions. RAG for knowledge-grounded + responses, ADB-S for conversation history and vector search. + Serverless architecture with OCI Functions for cost efficiency. + terraform: null + + - title: "Deploy a multi-agent AI fraud detection system on OCI" + url: "https://docs.oracle.com/en/solutions/ai-fraud-detection/index.html" + type: reference-architecture + date: "2025-06" + services: [genai, streaming, adb-s] + tags: [ai-ml, autonomous] + summary: > + Multi-agent AI system for real-time fraud detection. Streaming + ingestion of transaction data, ML models for anomaly detection, + GenAI agents for investigation and decision support. + terraform: null + + - title: "Improve search results with Oracle Generative AI Agents, Vector Search, and OCI OpenSearch" + url: "https://docs.oracle.com/en/solutions/genai-vector-opensearch/index.html" + type: reference-architecture + date: "2024-12" + services: [genai, opensearch, adb-s] + tags: [ai-ml, autonomous] + summary: > + Hybrid search combining vector similarity (ADB-S) with keyword search + (OpenSearch). GenAI for query understanding and result re-ranking. + Suitable for enterprise knowledge bases and document search. + terraform: null + + - title: "Implement retrieval augmented generation by using Oracle Integration" + url: "https://docs.oracle.com/en/solutions/implement-rag-oci/index.html" + type: reference-architecture + date: "2024-09" + services: [genai, oic, adb-s] + tags: [ai-ml, integration, autonomous] + summary: > + RAG implementation using OIC for document ingestion pipeline. + Pre-built OIC adapters for SaaS data sources, ADB-S for vector + storage, GenAI for embedding and generation. + terraform: null + + - title: "Deploy LLM using AMD Instinct accelerators in OCI" + url: "https://docs.oracle.com/en/solutions/deploy-llm-amd-instinct-oci/index.html" + type: reference-architecture + date: "2025-07" + services: [compute] + tags: [ai-ml, hpc] + summary: > + LLM inference on AMD Instinct MI300X GPUs in OCI. ROCm software + stack for model serving. Cost-effective alternative to NVIDIA for + specific model architectures. + terraform: null + + - title: "Run a quantized GGUF large language model on an Ampere A2 cluster" + url: "https://docs.oracle.com/en/solutions/run-quantized-gguf-llm-ampere-cluster/index.html" + type: reference-architecture + date: "2025-06" + services: [compute] + tags: [ai-ml] + summary: > + CPU-based LLM inference on Ampere A2 shapes using quantized GGUF + models. Cost-efficient for moderate throughput requirements. + llama.cpp for model serving without GPU dependency. + terraform: null + + - title: "Deploy the CPU Inference blueprint with Ollama in OCI AI Blueprints" + url: "https://docs.oracle.com/en/solutions/oci-cpu-inference-ollama/index.html" + type: reference-architecture + date: "2025-09" + services: [compute, oke] + tags: [ai-ml] + summary: > + Ollama-based CPU inference on OKE using OCI AI Blueprints. + Pre-configured Kubernetes deployment for local LLM serving. + Suitable for development, testing, and low-throughput inference. + terraform: null + + # ===== APPLICATION MODERNIZATION ===== + + - title: "Deploy a microservices-based application in Kubernetes connected to an autonomous database" + url: "https://docs.oracle.com/en/solutions/cloud-native-ecommerce/index.html" + type: reference-architecture + date: "2025-04" + services: [oke, adb-s, api-gateway, streaming, waf, vault, functions] + tags: [application, autonomous] + summary: > + Cloud-native e-commerce on OKE with ADB-S. Regional VCN subnets, + VM.Standard2.1 shapes, up to 1000 nodes. API Gateway for ingress, + WAF + Vault for security, Streaming for event-driven microservices. + terraform: "https://github.com/oracle-quickstart/oci-cloudnative-mushop" + + - title: "Deploy Oracle WebLogic Server in a Kubernetes cluster" + url: "https://docs.oracle.com/en/solutions/deploy-wls-on-oke/index.html" + type: reference-architecture + date: "2025-04" + services: [oke, wls, load-balancer] + tags: [application] + summary: > + WebLogic on OKE using WebLogic Kubernetes Operator. Automated domain + lifecycle management, rolling upgrades, and scaling. Load Balancer + for external traffic, Traefik for internal routing. + terraform: null + + - title: "Deploy Oracle WebLogic Server for OKE using a marketplace stack" + url: "https://docs.oracle.com/en/solutions/wls-on-oke-marketplace/index.html" + type: reference-architecture + date: "2025-04" + services: [oke, wls, load-balancer] + tags: [application] + summary: > + One-click WebLogic on OKE via OCI Marketplace. Resource Manager stack + provisions OKE cluster, WebLogic domain, and networking. Simplified + deployment for standard WebLogic topologies. + terraform: null + + - title: "Multi-tenant application deployment model on OCI" + url: "https://docs.oracle.com/en/solutions/multi-tenant-app-deploy/index.html" + type: reference-architecture + date: "2025-11" + services: [oke, adb-s, load-balancer, api-gateway, functions, mysql, nosql] + tags: [application, autonomous, security] + summary: > + Three-layer security: IAM auth with JWTs, per-request middleware with + tenant context, ORM-based data isolation. Tiered strategies: discriminator + columns (standard), dedicated schemas (premium), separate DBs (enterprise). + terraform: null + + - title: "Modernize your application development with OCI-managed PostgreSQL, Redis, and OpenSearch" + url: "https://docs.oracle.com/en/solutions/modernize-app-dev-oci-postgresql-redis-opensearch/index.html" + type: reference-architecture + date: "2025-05" + services: [postgresql, redis, opensearch, oke] + tags: [application] + summary: > + Open-source database services on OCI for cloud-native apps. + PostgreSQL for relational, Redis for caching, OpenSearch for + full-text search. OKE for application tier deployment. + terraform: null + + - title: "Implement an Oracle Retail Xstore Point of Service on Oracle Cloud Infrastructure" + url: "https://docs.oracle.com/en/solutions/deploy-xstore-oci/index.html" + type: reference-architecture + date: "2025-06" + services: [compute, base-db, load-balancer] + tags: [application] + summary: > + Oracle Retail Xstore POS on OCI. Compute instances for Xstore + application, Base DB for central office database. Load Balancer + for HA across multiple Xstore instances. + terraform: null + + # ===== APPLICATION — EBS / PEOPLESOFT / JDE ===== + + - title: "Migrate an Oracle E-Business Suite environment to Oracle Cloud" + url: "https://docs.oracle.com/en/solutions/migrate-ebs-with-cloudmanager/index.html" + type: reference-architecture + date: "2025-10" + services: [compute, base-db, load-balancer] + tags: [application, migration, ebs] + summary: > + EBS lift-and-shift to OCI using EBS Cloud Manager. Automated + provisioning of app and DB tiers. Supports EBS 12.2 with + Base DB or ExaCS for database tier. + terraform: null + + - title: "Deploy JD Edwards EnterpriseOne with Oracle Autonomous Database on Dedicated Exadata Infrastructure" + url: "https://docs.oracle.com/en/solutions/oracle-adb-jde-exadata/index.html" + type: reference-architecture + date: "2025-03" + services: [adb-d, exacs, compute] + tags: [application, database] + summary: > + JDE on ADB-D for maximum database performance and isolation. + Dedicated Exadata infrastructure for JDE database, compute + instances for JDE application and batch servers. + terraform: null + + - title: "Learn about maximum availability architecture for PeopleSoft" + url: "https://docs.oracle.com/en/solutions/learn-about-maa-for-peoplesoft/index.html" + type: reference-architecture + date: "2024-08" + services: [exacs, adg, load-balancer, compute] + tags: [application, ha-dr, peoplesoft] + summary: > + MAA for PeopleSoft on OCI. Active Data Guard for database HA/DR, + multi-instance PIA behind Load Balancer, Process Scheduler on + multiple nodes for batch processing resilience. + terraform: null + + - title: "Provision and deploy a maximum availability solution for PeopleSoft on Oracle Cloud" + url: "https://docs.oracle.com/en/solutions/deploy-maa-for-peoplesoft-on-oci/index.html" + type: reference-architecture + date: "2024-08" + services: [exacs, adg, load-balancer, compute] + tags: [application, ha-dr, peoplesoft] + summary: > + Step-by-step PeopleSoft MAA deployment on OCI. Terraform stacks + for infrastructure provisioning, Data Guard configuration, and + PeopleSoft application tier setup. + terraform: null + + - title: "Run PeopleSoft ERP and Autonomous Database in an HA multicloud deployment" + url: "https://docs.oracle.com/en/solutions/bread-multicloud-on-oci/index.html" + type: reference-architecture + date: "2025-02" + services: [adb-s, compute, load-balancer] + tags: [application, ha-dr, multicloud, peoplesoft, autonomous] + summary: > + PeopleSoft HA with ADB-S across cloud providers. Application tier + on OCI compute with database on ADB-S. Multi-AZ deployment for + high availability with automatic failover. + terraform: null + + # ===== OBSERVABILITY & MONITORING ===== + + - title: "Monitor a Kubernetes cluster with OCI Logging Analytics" + url: "https://docs.oracle.com/en/solutions/kubernetes-oke-logging-analytics/index.html" + type: reference-architecture + date: "2025-10" + services: [oke, logging] + tags: [observability, application] + summary: > + Logging Analytics for OKE cluster monitoring. Automated log collection + from pods, nodes, and control plane. Pre-built dashboards for + cluster health, resource utilization, and application logs. + terraform: null + + - title: "Integrate, manage and secure E-Business Suite applications using Logging Analytics" + url: "https://docs.oracle.com/en/solutions/ebs-logging-analytics-oci/index.html" + type: reference-architecture + date: "2025-10" + services: [logging, compute] + tags: [observability, ebs, security] + summary: > + Logging Analytics for EBS log management. Centralized collection + of Apache, WebLogic, and Concurrent Manager logs. Security + analytics for unauthorized access detection. + terraform: null + + - title: "Enable observability and management stack monitoring for Oracle E-Business Suite" + url: "https://docs.oracle.com/en/solutions/enable-om-stack-monitoring-ebs/index.html" + type: reference-architecture + date: "2024-10" + services: [monitoring, compute] + tags: [observability, ebs] + summary: > + Stack Monitoring for EBS infrastructure and application health. + Automatic discovery of EBS components, pre-built metrics and + alarms for proactive monitoring. + terraform: null + + - title: "Enable Observability and Management Stack Monitoring for PeopleSoft" + url: "https://docs.oracle.com/en/solutions/enable-om-stack-monitoring-psft/index.html" + type: reference-architecture + date: "2024-12" + services: [monitoring, compute] + tags: [observability, peoplesoft] + summary: > + Stack Monitoring for PeopleSoft. Automatic discovery of PIA, + App Server, Process Scheduler, and database components. Metrics + and alarms for performance and availability monitoring. + terraform: null + + - title: "Uncover performance issues, forecast consumption, and plan capacity with OCI Ops Insights" + url: "https://docs.oracle.com/en/solutions/oci-ops-insights-and-em-bridge/index.html" + type: reference-architecture + date: "2024-03" + services: [ops-insights, exacs, adb-s] + tags: [observability, database] + summary: > + Ops Insights for database performance analysis and capacity planning. + SQL analytics for query optimization, AWR data integration from + Enterprise Manager, cross-database performance comparison. + terraform: null + + - title: "Well-architected framework for Oracle Cloud Infrastructure" + url: "https://docs.oracle.com/en/solutions/oci-best-practices/index.html" + type: reference-architecture + date: "2025-05" + services: [] + tags: [security, ha-dr, observability] + summary: > + OCI Well-Architected Framework covering 5 pillars: security, + reliability, performance, cost optimization, and operational + excellence. Best practices and review checklists for each pillar. + terraform: null + + # ===== INTEGRATION ===== + + - title: "Implement fine-grained access control on Oracle Integration" + url: "https://docs.oracle.com/en/solutions/integration-access-control/index.html" + type: reference-architecture + date: "2025-06" + services: [oic] + tags: [integration, security] + summary: > + Fine-grained RBAC for Oracle Integration flows. Project-level access + control with custom roles. Separation of duties between integration + developers and administrators. + terraform: null + + - title: "Configure single sign-on between Oracle Integration and Oracle Fusion Applications" + url: "https://docs.oracle.com/en/solutions/sso-between-process-fusion-applications/index.html" + type: reference-architecture + date: "2025-08" + services: [oic] + tags: [integration, security] + summary: > + SSO configuration between OIC and Fusion Applications. SAML-based + federation for seamless user experience across integration and + application platforms. + terraform: null + + # ===== VMWARE ===== + + - title: "Deploy a VMware vSAN Stretched Cluster across OCI Regions with Oracle Cloud VMware Solution" + url: "https://docs.oracle.com/en/solutions/ocvs-vsan-stretched-cluster/index.html" + type: reference-architecture + date: "2025-07" + services: [ocvs] + tags: [vmware, ha-dr] + summary: > + vSAN Stretched Cluster across OCI regions for VMware HA/DR. + Synchronous storage replication between sites, automatic VM + restart on site failure. Witness host in third location. + terraform: null + + # ===== HEALTHCARE ===== + + - title: "Implement a cloud-native DICOM store on Oracle Cloud Infrastructure" + url: "https://docs.oracle.com/en/solutions/cloud-native-dicom-on-oci/index.html" + type: reference-architecture + date: "2025-12" + services: [oke, object-storage, adb-s] + tags: [healthcare, application, autonomous] + summary: > + Cloud-native DICOM store on OKE with Object Storage for medical + images and ADB-S for metadata. DICOMweb API compliance for + interoperability with medical imaging systems. + terraform: null + + - title: "Analyze and visualize healthcare data and apply AI on OCI to solve real-world challenges" + url: "https://docs.oracle.com/en/solutions/oci-ai-healthcare/index.html" + type: reference-architecture + date: "2024-12" + services: [genai, adb-s, data-integration] + tags: [healthcare, ai-ml, data-platform] + summary: > + Healthcare analytics platform with AI on OCI. FHIR data ingestion, + clinical NLP with GenAI, predictive analytics for patient outcomes. + ADB-S for structured clinical data, Object Storage for imaging. + terraform: null + + # ===== DEVOPS ===== + + - title: "Build a CI/CD pipeline for OCI Functions deployment using GitHub Action" + url: "https://docs.oracle.com/en/solutions/cicd-pipeline-oci-functions/index.html" + type: reference-architecture + date: "2024-10" + services: [functions] + tags: [devops] + summary: > + GitHub Actions workflow for OCI Functions deployment. Automated + build, test, and deploy pipeline. Container image build and push + to OCI Container Registry, function deployment via OCI CLI. + terraform: null + + - title: "Deploy applications on a private OKE cluster using OCI Bastion and GitHub Actions" + url: "https://docs.oracle.com/en/solutions/deploy-oke-with-bastion-and-github/index.html" + type: reference-architecture + date: "2024-10" + services: [oke, bastion] + tags: [devops, application] + summary: > + GitHub Actions deployment to private OKE clusters via Bastion. + SSH tunnel through Bastion for kubectl access from CI/CD. + Eliminates need for public OKE API endpoint. + terraform: null + + - title: "Deploy GitOps with Argo CD and Oracle Container Engine for Kubernetes" + url: "https://docs.oracle.com/en/solutions/deploy-gitops-argocd-oke/index.html" + type: reference-architecture + date: "2024-10" + services: [oke] + tags: [devops, application] + summary: > + GitOps with Argo CD on OKE. Declarative application deployment + from Git repositories. Automated sync, drift detection, and + rollback capabilities for Kubernetes workloads. + terraform: null + + # ===== STORAGE ===== + + - title: "Learn About Storage Modernization with Oracle Cloud Infrastructure Object Storage" + url: "https://docs.oracle.com/en/solutions/oci-object-storage-modernization/index.html" + type: reference-architecture + date: "2024-11" + services: [object-storage] + tags: [data-platform] + summary: > + Object Storage modernization patterns. Tiered storage (Standard, + Infrequent Access, Archive), lifecycle policies for cost optimization, + and data lake integration with ADB and Data Integration. + terraform: null + + - title: "Deploy Lustre file systems in Oracle Cloud" + url: "https://docs.oracle.com/en/solutions/deploy-lustre-file-systems/index.html" + type: reference-architecture + date: "2025-04" + services: [compute, file-storage] + tags: [hpc] + summary: > + Lustre parallel file system on OCI for HPC workloads. High-throughput + storage for compute-intensive applications. Bare metal instances + for Lustre servers, RDMA networking for low-latency I/O. + terraform: null + + # ===== BUILT & DEPLOYED ===== + + - title: "Tharseo IT: Deploy a multicloud public health data management platform on Oracle Cloud" + url: "https://docs.oracle.com/en/solutions/tharseo-data-platform-on-oci/index.html" + type: built-deployed + date: "2025-11" + services: [oke, adb-s, object-storage] + tags: [healthcare, application, multicloud] + summary: > + Public health data platform on OKE with ADB-S. Multi-tenant SaaS + architecture for health data management across cloud providers. + terraform: null + + - title: "Takamol: Deploy Kubernetes and microservices for a government HR platform on Oracle Cloud" + url: "https://docs.oracle.com/en/solutions/takamol-on-oci/index.html" + type: built-deployed + date: "2024-11" + services: [oke, adb-s] + tags: [application] + summary: > + Government HR platform on OKE. Microservices architecture with + ADB-S for data persistence. Container-based deployment for + scalability and operational efficiency. + terraform: null + + - title: "Stream fraud detection with NVIDIA Morpheus on Oracle Compute Cloud@Customer" + url: "https://docs.oracle.com/en/solutions/fraud-detection-nvidia-morpheus-compute-cloud/index.html" + type: built-deployed + date: "2025-08" + services: [compute] + tags: [ai-ml, security] + summary: > + Real-time fraud detection with NVIDIA Morpheus on Compute Cloud@Customer. + GPU-accelerated ML pipeline for transaction monitoring. On-premises + deployment for data sovereignty requirements. + terraform: null + + - title: "Deploy TCS BaNCS on Oracle Cloud Infrastructure with Exadata Cloud Service" + url: "https://docs.oracle.com/en/solutions/deploy-tcs-banc-oci-exa/index.html" + type: built-deployed + date: "2024-08" + services: [exacs, compute, load-balancer] + tags: [application, database] + summary: > + TCS BaNCS banking platform on OCI with ExaCS. High-performance + database for core banking transactions, compute instances for + application tier with Load Balancer for HA. + terraform: null + + - title: "Deploy Oracle Banking Suite using OCI Dedicated Region" + url: "https://docs.oracle.com/en/solutions/oracle-banking-suite-oci-dedicated-region/index.html" + type: built-deployed + date: "2025-01" + services: [exacs, oke, compute] + tags: [application, database] + summary: > + Oracle Banking Suite on OCI Dedicated Region for data sovereignty. + ExaCS for database tier, OKE for microservices-based banking + applications. Dedicated Region for regulatory compliance. + terraform: null diff --git a/kb/compatibility/adb-feature-matrix.yaml b/kb/compatibility/adb-feature-matrix.yaml new file mode 100644 index 0000000..6510fb8 --- /dev/null +++ b/kb/compatibility/adb-feature-matrix.yaml @@ -0,0 +1,349 @@ +# ============================================================================= +# ADB FEATURE COMPATIBILITY MATRIX +# ============================================================================= +# +# INTERNAL — DO NOT SHARE EXTERNALLY +# +# This matrix tracks feature availability across ADB versions and deployment +# types. It is maintained by the field team based on real testing and customer +# engagements, NOT copied from Oracle documentation. +# +# Status values: +# GA — Generally Available, tested, works as expected +# GA_CAVEAT — Available but with significant caveats (see notes) +# PREVIEW — Available as preview/beta, not production-ready +# LIMITED — Available with restrictions not obvious from docs +# NOT_AVAIL — Not available in this combination +# BROKEN — Documented as available but does not work (see field_finding ref) +# UNTESTED — Not yet verified by the team +# +# Cell format (enhanced with attribution): +# status: Required — one of the status values above +# notes: Optional — concise description of caveats +# contributor: Optional — attribution block (name, team, date, confidence) +# field_finding_ref: Optional — link to field finding ID (e.g., FF-202603-008) +# +# To edit: add/modify entries and update last_verified. Keep notes concise. +# ============================================================================= + +--- +last_verified: "2026-03-14" +maintained_by: "Field Architecture Team" + +versions: + - id: "23ai" + full_name: "Oracle Database 23ai" + release_year: 2024 + notes: "First AI-branded release. Major feature additions." + - id: "26ai" + full_name: "Oracle Database 26ai" + release_year: 2026 + notes: "Latest. Check feature availability — not everything from 23ai carried forward identically." + +deployment_types: + - id: "adb_s" + name: "ADB-S Serverless" + - id: "adb_s_ep" + name: "ADB-S Elastic Pool" + - id: "adb_d_dep" + name: "ADB Dedicated Elastic Pool (DEP)" + - id: "adb_d" + name: "ADB on Dedicated Exadata" + - id: "exacs" + name: "Exadata Cloud Service" + - id: "dbcs_ee" + name: "Base DB Service (EE)" + +# ============================================================================= +# FEATURE MATRIX +# ============================================================================= + +features: + + # --- Auto Management --- + - name: "Auto Scaling (OCPU)" + category: "auto_management" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "Activation takes 2-3 min. Size base for P75. Billing per-second when active." } + "26ai": { status: "GA_CAVEAT", notes: "Same 2-3 min activation delay persists in 26ai." } + adb_s_ep: + "23ai": { status: "GA_CAVEAT", notes: "Scales within pool allocation. Pool itself doesn't auto-scale." } + "26ai": { status: "GA_CAVEAT", notes: "Same as 23ai." } + adb_d_dep: + "23ai": { status: "LIMITED", notes: "Scales within DEP allocation. DEP itself is fixed capacity." } + "26ai": { status: "UNTESTED" } + adb_d: + "23ai": { status: "NOT_AVAIL", notes: "Dedicated infra — manual scaling only." } + exacs: + "23ai": { status: "NOT_AVAIL", notes: "Manual OCPU enable/disable." } + + - name: "Auto Indexing" + category: "auto_management" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "Creates indexes HIDDEN by default. Instantly reversible. DML costing prevents over-indexing. Deduplication with manually created indexes." } + "26ai": { status: "GA", notes: "Improved in 26ai — better cost model, fewer false positives." } + adb_s_ep: + "23ai": { status: "GA_CAVEAT", notes: "Same as ADB-S Serverless." } + adb_d_dep: + "23ai": { status: "GA_CAVEAT", notes: "Available but less tested in DEP." } + adb_d: + "23ai": { status: "GA", notes: "Fully supported." } + exacs: + "23ai": { status: "NOT_AVAIL", notes: "Not an ADB feature. Use manual indexing." } + dbcs_ee: + "23ai": { status: "NOT_AVAIL" } + + - name: "Auto Patching" + category: "auto_management" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Automatic quarterly. No opt-out. Maintenance window configurable but change requires SR." } + "26ai": { status: "GA", notes: "Same policy." } + adb_d_dep: + "23ai": { status: "GA_CAVEAT", notes: "Maintenance window immutable post-DEP creation. Change via SR only. Saturday/Sunday default, other days via SR." } + exacs: + "23ai": { status: "GA_CAVEAT", notes: "Customer-managed patching schedule. Must apply within 90 days." } + + # --- High Availability --- + - name: "Transparent Application Continuity (TAC)" + category: "ha_dr" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "Zero perceived RTO for app. DB itself ~2 min unavailable during failover. Replay NOT guaranteed with UTL_HTTP, DBMS_PIPE, NOCACHE sequences. Requires JDBC thin driver with TAC config." } + "26ai": { status: "GA_CAVEAT", notes: "Same limitations persist." } + adb_s_ep: + "23ai": { status: "GA_CAVEAT", notes: "Same as Serverless." } + adb_d: + "23ai": { status: "GA_CAVEAT", notes: "Same limitations." } + exacs: + "23ai": { status: "GA_CAVEAT", notes: "Available with RAC. Same replay limitations." } + dbcs_ee: + "23ai": { status: "NOT_AVAIL", notes: "Requires RAC — only in EE-HP or EE-EP." } + + - name: "Autonomous Data Guard (Local)" + category: "ha_dr" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Synchronous. Same region. Auto-failover ~2 min." } + "26ai": { status: "GA" } + adb_d: + "23ai": { status: "GA" } + exacs: + "23ai": { status: "GA", notes: "Standard Data Guard, not 'Autonomous' — manual setup." } + + - name: "Autonomous Data Guard (Cross-Region)" + category: "ha_dr" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "Asynchronous only. RPO ≈ seconds. Manual switchover or auto-failover (configurable)." } + "26ai": { status: "GA_CAVEAT", notes: "Same." } + adb_d_dep: + "23ai": { status: "GA_CAVEAT", notes: "ADG must be disabled before joining DEP. Known issue." } + exacs: + "23ai": { status: "GA", notes: "Standard cross-region DG. Manual setup." } + + - name: "Refreshable Clone" + category: "ha_dr" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "Read-only. Auto-refresh frequency configurable. Failed refresh leaves clone AVAILABLE (not DISCONNECTED) — leader monitors lifecycle state, not refresh success. Detection delay = ADG RTO for unplanned." } + "26ai": { status: "GA_CAVEAT", notes: "Same detection delay issue." } + adb_d: + "23ai": { status: "GA" } + + # --- Security --- + - name: "Virtual Private Database (VPD)" + category: "security" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Full VPD support including OLS." } + "26ai": { status: "GA" } + exacs: + "23ai": { status: "GA" } + dbcs_ee: + "23ai": { status: "GA", notes: "Requires Advanced Security option license." } + + - name: "Private Endpoints" + category: "security" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Recommended for production. Required for some compliance." } + "26ai": { status: "GA" } + adb_d: + "23ai": { status: "GA", notes: "Default — all ADB-D is private." } + exacs: + "23ai": { status: "GA", notes: "Always private subnet." } + + - name: "Data Safe Integration" + category: "security" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Audit, masking, assessment, SQL Firewall." } + "26ai": { status: "GA", notes: "Enhanced SQL Firewall in 26ai." } + exacs: + "23ai": { status: "GA" } + dbcs_ee: + "23ai": { status: "GA_CAVEAT", notes: "Must register DB manually in Data Safe." } + + # --- Developer Features --- + - name: "Select AI" + category: "developer" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Natural language → SQL via LLM. Requires AI profile configuration." } + "26ai": { status: "GA", notes: "Improved accuracy in 26ai." } + adb_d: + "23ai": { status: "GA" } + exacs: + "23ai": { status: "NOT_AVAIL", notes: "ADB-only feature." } + + - name: "Property Graph / SQL/PGQ" + category: "developer" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "Node tables + edge tables. Only FK indexes essential. Additional indexes require filter evidence. See Graph DBA Advisor philosophy." } + "26ai": { status: "GA", notes: "Improved PGQ optimizer in 26ai." } + exacs: + "23ai": { status: "GA", notes: "Full graph support with PGQL and SQL/PGQ." } + + - name: "APEX" + category: "developer" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Built-in. No additional cost." } + "26ai": { status: "GA" } + adb_d: + "23ai": { status: "GA" } + exacs: + "23ai": { status: "GA_CAVEAT", notes: "Must install APEX manually." } + dbcs_ee: + "23ai": { status: "GA_CAVEAT", notes: "Must install and manage manually." } + + - name: "JSON Relational Duality Views" + category: "developer" + matrix: + adb_s: + "23ai": { status: "GA", notes: "Major 23ai feature." } + "26ai": { status: "GA" } + exacs: + "23ai": { status: "GA" } + dbcs_ee: + "23ai": { status: "GA", notes: "Requires 23ai version." } + + # --- AI / Vector Search --- + - name: "AI Vector Search (HNSW)" + category: "developer" + matrix: + adb_s: + "23ai": + status: "GA_CAVEAT" + notes: | + HNSW indexes are node-local on multi-node RAC. Works well up to + ~50M vectors on single node (≤24 ECPUs). At 100M+ on multi-node + (≥64 ECPUs), requires hash-partitioned vector table with LOCAL + HNSW index for <50ms P95. See FF-202603-008. + scale_limits: + single_node_max_vectors: "~50M with <50ms P95" + multi_node_requires: "Hash partitioning + LOCAL HNSW index" + validated_at: "120M vectors, 64 ECPU, P95 = 38ms (with workaround)" + without_workaround: "P95 >200ms at 100M+ on 2-node RAC" + field_finding_ref: "FF-202603-008" + "26ai": + status: "UNTESTED" + notes: "Check if distributed HNSW is added in 26ai. Expected to remain node-local." + adb_s_ep: + "23ai": + status: "GA_CAVEAT" + notes: "Same node-local limitation as ADB-S Serverless." + field_finding_ref: "FF-202603-008" + adb_d: + "23ai": + status: "GA_CAVEAT" + notes: "Same limitation. Dedicated infra doesn't change HNSW locality." + exacs: + "23ai": + status: "GA_CAVEAT" + notes: "RAC with HNSW has same node-local constraint. Partitioning workaround applies." + dbcs_ee: + "23ai": + status: "NOT_AVAIL" + notes: "AI Vector Search requires 23ai. Available in EE but HNSW performance limited without Exadata storage." + + - name: "AI Vector Search (IVF)" + category: "developer" + matrix: + adb_s: + "23ai": + status: "GA" + notes: | + IVF (Inverted File) indexes ARE distributed across RAC nodes. + Works at 100M+ scale without partitioning. However, IVF has + lower recall than HNSW at same latency budget. Trade-off: + IVF for scale without redesign, HNSW+partitioning for best recall. + "26ai": + status: "UNTESTED" + adb_s_ep: + "23ai": + status: "GA" + notes: "Same as ADB-S Serverless." + adb_d: + "23ai": + status: "GA" + exacs: + "23ai": + status: "GA" + notes: "IVF distributed across RAC nodes. Exadata storage accelerates scans." + dbcs_ee: + "23ai": + status: "NOT_AVAIL" + notes: "AI Vector Search requires 23ai. Available in EE but performance limited without Exadata." + + # --- Deployment / Operations --- + - name: "Elastic Pool Membership" + category: "operations" + matrix: + adb_s_ep: + "23ai": { status: "GA_CAVEAT", notes: "Billing starts at AVAILABLE. Co-location at next maintenance window. Pool can't auto-scale." } + adb_d_dep: + "23ai": + status: "GA_CAVEAT" + notes: "Provisioning days-weeks. ADG must be disabled before joining. Maintenance window immutable. Saturday/Sunday default." + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + date: "2026-03-14" + confidence: "validated" + field_finding_ref: "FF-202603-002" + + - name: "Cross-Region Read Offload (ProxySQL/Refreshable Clone)" + category: "operations" + matrix: + adb_s: + "23ai": + status: "LIMITED" + notes: "Architecturally impossible without pre-provisioned Elastic Pool in Region B post-switchover. Mitigation for planned: DBMS_PROXY_SQL.DISABLE_READ_ONLY_OFFLOAD. For unplanned: OCI Events + Functions." + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + date: "2026-03-14" + confidence: "validated" + field_finding_ref: "FF-202603-005" + "26ai": { status: "UNTESTED", notes: "Same architectural constraint expected to persist." } + + - name: "OCI CLI Management" + category: "operations" + matrix: + adb_s: + "23ai": { status: "GA_CAVEAT", notes: "--resource-pool-summary works on CLI 3.73+. --autonomous-database-maintenance-window has JSON serialization bug on CLI 3.73.1 AND 3.75.0. Must use Console for maintenance window." } + +# ============================================================================= +# HOW TO ADD A NEW FEATURE +# ============================================================================= +# 1. Add entry under "features:" following the template above +# 2. Fill matrix for every deployment_type + version you've tested +# 3. Mark untested combinations as UNTESTED +# 4. Add field_finding_ref if the entry comes from a specific incident +# 5. Update last_verified date +# 6. Commit with a message: "feat-matrix: added [feature_name]" diff --git a/kb/competitive/aws-mapping.yaml b/kb/competitive/aws-mapping.yaml new file mode 100644 index 0000000..e3ceb62 --- /dev/null +++ b/kb/competitive/aws-mapping.yaml @@ -0,0 +1,112 @@ +# Competitive Mapping: AWS → OCI +# Honest comparison — advantages AND disadvantages + +service_mapping: + compute: + aws: "EC2" + oci: "OCI Compute" + oci_advantage: + - "Consistent pricing (no per-second billing complexity)" + - "Flex shapes — pay for exact OCPUs/memory needed" + - "Dense I/O shapes with local NVMe included" + aws_advantage: + - "Broader instance family selection" + - "Spot instances (wider availability than OCI Preemptible)" + - "Graviton ecosystem maturity vs Ampere" + notes: "OCI compute is generally 30-50% cheaper for equivalent configurations" + + kubernetes: + aws: "EKS" + oci: "OKE" + oci_advantage: + - "Free control plane (EKS charges $0.10/hr = ~$73/month)" + - "Virtual Nodes (serverless) option" + aws_advantage: + - "Larger ecosystem (Fargate, App Mesh, more add-ons)" + - "More community resources and tooling" + + database_oracle: + aws: "RDS Oracle / Oracle on EC2" + oci: "ADB-S / DBCS / ExaCS" + oci_advantage: + - "Autonomous Database — no equivalent in AWS" + - "Exadata performance for Oracle workloads" + - "BYOL pricing ~50-75% cheaper than AWS RDS Oracle" + - "Native Oracle features (RAC, Data Guard, Advanced Compression)" + - "Direct migration path with ZDM/DMS" + aws_advantage: + - "Multi-engine flexibility (same tooling for Oracle, PostgreSQL, MySQL)" + - "Aurora for PostgreSQL/MySQL is excellent" + notes: "This is OCI's strongest competitive advantage. Lead with database." + + database_mysql: + aws: "RDS MySQL / Aurora MySQL" + oci: "MySQL HeatWave" + oci_advantage: + - "HeatWave in-memory query accelerator (up to 400x faster analytics)" + - "Combined OLTP + analytics without ETL" + aws_advantage: + - "Aurora is a mature, battle-tested platform" + - "Broader integration with AWS analytics stack" + + object_storage: + aws: "S3" + oci: "OCI Object Storage" + oci_advantage: + - "No data retrieval charges for Standard tier" + - "Cheaper per-GB pricing" + aws_advantage: + - "S3 ecosystem is unmatched (integrations, tooling, community)" + - "S3 Intelligent Tiering is more mature" + + networking: + aws: "VPC / Direct Connect / ALB/NLB" + oci: "VCN / FastConnect / Flexible LB" + oci_advantage: + - "Free inter-AZ data transfer (AWS charges $0.01/GB)" + - "Free inter-VCN data transfer within same region via DRG" + - "Significantly cheaper outbound data transfer" + - "Service Gateway (free access to OCI services without internet)" + aws_advantage: + - "More load balancer features (ALB path-based routing)" + - "Broader Direct Connect partner ecosystem" + notes: "Networking cost savings are a major OCI differentiator. Quantify them." + + security: + aws: "IAM / GuardDuty / SecurityHub / KMS" + oci: "IAM / Cloud Guard / Security Zones / Vault" + oci_advantage: + - "Cloud Guard is free (GuardDuty is per-event pricing)" + - "Security Zones enforce compliance at resource creation" + - "Data Safe included free for Autonomous Database" + aws_advantage: + - "Broader security partner ecosystem" + - "More mature SIEM integrations" + + observability: + aws: "CloudWatch / X-Ray / CloudTrail" + oci: "Monitoring / APM / Audit / Logging Analytics" + oci_advantage: + - "Generous free tier for monitoring" + - "Logging Analytics is powerful for centralized analysis" + aws_advantage: + - "CloudWatch is deeply integrated with all services" + - "More third-party observability integrations" + +competitive_positioning: + lead_with: + - "Oracle Database cost savings (BYOL, Exadata, Autonomous)" + - "Network cost savings (free inter-AZ, cheap egress)" + - "Overall price/performance for equivalent configurations" + honest_gaps: + - "Smaller service catalog than AWS" + - "Fewer regions (though expanding rapidly)" + - "Smaller community and ecosystem" + - "Less mature serverless/event-driven offerings" + neutralize: + - "Enterprise support: Oracle Premier Support is equivalent to AWS Enterprise" + - "Terraform support: full parity via OCI Terraform provider" + - "Multi-cloud: OCI Multicloud with Azure (Oracle Database@Azure)" + - "Multi-cloud: Oracle Database@AWS (GA in us-east-1, us-west-2, us-east-2, eu-central-1, ap-northeast-1 + expanding to 20+ regions)" + - "Database@AWS: co-located Exadata in AWS AZ, ~200-400μs same-AZ latency, single AWS bill" + - "Database@AWS limitation: ADB-D only, no ADB-S Serverless — requires Exadata dedicated infra" diff --git a/kb/competitive/azure-mapping.yaml b/kb/competitive/azure-mapping.yaml new file mode 100644 index 0000000..b935c8c --- /dev/null +++ b/kb/competitive/azure-mapping.yaml @@ -0,0 +1,54 @@ +--- +last_verified: 2026-03-14 +description: OCI to Azure service mapping for competitive positioning. +--- + +service_mapping: + database: + - oci: "ADB-S" + azure: "Azure SQL Managed Instance / Azure Database for PostgreSQL" + oci_advantage: | + ADB-S runs actual Oracle Database. Azure SQL MI is SQL Server only. + No native Oracle database service on Azure. + Oracle on Azure VMs loses Exadata performance and autonomous operations. + azure_advantage: "Native SQL Server support if migrating from SQL Server." + - oci: "ExaCS" + azure: "Oracle Database@Azure (ExaCS in Azure datacenters)" + notes: | + Oracle Database@Azure is ExaCS running in Azure datacenters with + direct connectivity to Azure services. Available in select regions. + This is a partnership offering, not a competitive comparison. + + compute: + - oci: "OCI Compute" + azure: "Azure Virtual Machines" + oci_advantage: "Flexible shapes, lower cost for Oracle workloads." + azure_advantage: "Deeper Microsoft ecosystem integration, Hybrid Benefit." + + networking: + - oci: "OCI Interconnect with Azure" + azure: "Azure ExpressRoute with OCI" + notes: | + OCI and Azure have a direct interconnect in multiple regions. + Low-latency (<2ms) connectivity for hybrid deployments. + Enables Oracle DB on OCI with apps on Azure. + +pricing_comparison: + notes: | + For Oracle workloads, OCI is significantly cheaper: + - No Oracle license surcharge on OCI + - Exadata performance not available on Azure + - BYOL works better on OCI + - Data egress cheaper on OCI + +common_objections: + - objection: "We're an Azure shop — Microsoft is our strategic partner" + response: | + Oracle Database@Azure allows running OCI database services directly + in Azure datacenters. Your team stays in the Azure portal while + getting OCI database performance and economics. + - objection: "We prefer Azure SQL for our databases" + response: | + For SQL Server workloads, Azure SQL is excellent. For Oracle database + workloads that must remain Oracle, OCI provides superior performance + and cost. Consider multi-cloud: Azure for .NET/SQL Server, OCI for Oracle. diff --git a/kb/competitive/common-objections.yaml b/kb/competitive/common-objections.yaml new file mode 100644 index 0000000..bb95816 --- /dev/null +++ b/kb/competitive/common-objections.yaml @@ -0,0 +1,85 @@ +--- +last_verified: 2026-03-14 +description: Common customer objections about OCI and evidence-based responses. +--- + +objections: + - id: OBJ-001 + category: market + objection: "OCI is a niche cloud — AWS/Azure are the leaders" + response: | + OCI is the #4 cloud globally and growing fastest in database PaaS. + For Oracle database workloads specifically, OCI is the leader: + - Only cloud with Exadata hardware + - Only cloud with Autonomous Database + - Best Oracle license portability + The question isn't "best cloud overall" but "best cloud for this workload." + evidence: "Gartner, IDC reports on cloud database market" + + - id: OBJ-002 + category: risk + objection: "What if Oracle EOLs or changes OCI pricing?" + response: | + OCI pricing has only decreased since launch. Oracle's cloud investment + is $50B+ in CapEx. OCI is Oracle's strategic platform — not a side project. + Contractual pricing protection exists via Annual Flex and Universal Credits. + mitigation: "Use annual flex contracts with pricing guarantees" + + - id: OBJ-003 + category: skills + objection: "Our team has no OCI experience" + response: | + ADB-S reduces DBA workload by 80% (no patching, tuning, scaling). + OCI console follows familiar cloud patterns. Terraform provider is mature. + Oracle provides free training and certification. Typical ramp-up is 2-4 weeks. + mitigation: "Include training budget in migration plan" + + - id: OBJ-004 + category: features + objection: "OCI doesn't have as many services as AWS" + response: | + True for breadth (AWS has 200+ services). For database and enterprise + workloads, OCI feature parity is strong. Key services are GA: + - Compute, networking, storage, Kubernetes, serverless + - Monitoring, logging, IAM, security + - Queue, Streaming, API Gateway + What matters is whether OCI has what *you* need, not a raw count. + + - id: OBJ-005 + category: cost + objection: "We can get better pricing from AWS/Azure" + response: | + OCI list prices are already 30-50% below AWS for equivalent configs. + For Oracle specifically: + - No Oracle license surcharge on OCI + - BYOL discount is deeper on OCI + - Data egress is 10x cheaper + Request a detailed cost comparison with matched configurations. + + - id: OBJ-006 + category: migration + objection: "Migration is too risky and expensive" + response: | + Oracle provides free migration tools (ZDM, DMS) and migration credits. + Phased migration reduces risk. ADB-S migrations from on-prem Exadata + are well-documented with reference architectures. Typical timeline is + 8-16 weeks for database migration. + mitigation: "Phased migration with rollback plan at each phase" + + - id: OBJ-007 + category: vendor_lock + objection: "We don't want vendor lock-in with Oracle" + response: | + You're already locked into Oracle Database (the software). OCI doesn't + add lock-in — it reduces cost and operational burden of running Oracle. + OCI also supports open source: PostgreSQL, MySQL, Kubernetes, Terraform. + If you want to reduce Oracle dependency, that's a separate conversation + about database modernization, not cloud choice. + + - id: OBJ-008 + category: availability + objection: "OCI has fewer regions than AWS" + response: | + OCI has 50+ public cloud regions globally, covering all major markets. + If a specific region is required, check current availability. + For Oracle Database@Azure, OCI services run inside Azure regions. diff --git a/kb/diagram/oci-icons.json b/kb/diagram/oci-icons.json new file mode 100644 index 0000000..e1337fa --- /dev/null +++ b/kb/diagram/oci-icons.json @@ -0,0 +1,1121 @@ +{ + "vm": { + "title": "Compute - Virtual Machine VM", + "w": 84, + "h": 130, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "compute": { + "title": "Compute - Virtual Machine VM", + "w": 84, + "h": 130, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "functions": { + "title": "Compute - Functions", + "w": 84, + "h": 109, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "flex_vm": { + "title": "Compute - Flex Virtual Machine Flex VM", + "w": 84, + "h": 109, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "block_storage": { + "title": "Storage - Block Storage", + "w": 113, + "h": 109, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "file_storage": { + "title": "Storage - File Storage", + "w": 92, + "h": 109, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "object_storage": { + "title": "Storage - Object Storage", + "w": 114, + "h": 109, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "vcn_icon": { + "title": "Networking - Virtual Cloud Network VCN", + "w": 100, + "h": 125.00000000000001, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "load_balancer": { + "title": "Networking - Load Balancer", + "w": 83.99999999999994, + "h": 129.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + }, + "flexible_lb": { + "title": "Networking - Flexible Load Balancer", + "w": 102.00000000000014, + "h": 129.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + }, + "drg": { + "title": "Networking - Dynamic Routing Gateway DRG", + "w": 135, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "dynamic_routing_gateway": { + "title": "Networking - Dynamic Routing Gateway DRG", + "w": 135, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "sgw": { + "title": "Networking - Service Gateway", + "w": 85, + "h": 124.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "service_gateway": { + "title": "Networking - Service Gateway", + "w": 85, + "h": 124.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "igw": { + "title": "Networking - Internet Gateway", + "w": 80, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "internet_gateway": { + "title": "Networking - Internet Gateway", + "w": 80, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "natgw": { + "title": "Networking - NAT Gateway", + "w": 72.99999999999987, + "h": 138.00000000000003, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "nat_gateway": { + "title": "Networking - NAT Gateway", + "w": 72.99999999999987, + "h": 138.00000000000003, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "dbcs": { + "title": "Database - Database System", + "w": 82.99999999999987, + "h": 129.0000000000001, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "db_system": { + "title": "Database - Database System", + "w": 82.99999999999987, + "h": 129.0000000000001, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "autonomous_db": { + "title": "Database - Autonomous DB", + "w": 103.99999999999994, + "h": 117.00000000000014, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "adb": { + "title": "Database - Autonomous DB", + "w": 103.99999999999994, + "h": 117.00000000000014, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "adb_s": { + "title": "Database - Autonomous DB", + "w": 103.99999999999994, + "h": 117.00000000000014, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "atp": { + "title": "Database - Autonomous DB", + "w": 103.99999999999994, + "h": 117.00000000000014, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "adw": { + "title": "Database - Autonomous DB", + "w": 103.99999999999994, + "h": 117.00000000000014, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "adb_d": { + "title": "Database - ADB-D", + "w": 77.99999999999987, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "exadata": { + "title": "Database - Exadata", + "w": 77.99999999999987, + "h": 129.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "exacs": { + "title": "Database - Exadata", + "w": 77.99999999999987, + "h": 129.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "data_safe": { + "title": "Database - Data Safe", + "w": 87.00000000000014, + "h": 108.00000000000004, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "nosql": { + "title": "Database - NoSQL", + "w": 83.99999999999994, + "h": 129.00000000000026, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "mysql": { + "title": "Database - MySQL", + "w": 101.00000000000007, + "h": 142.00000000000014, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "opensearch": { + "title": "Database - OpenSearch", + "w": 107.00000000000014, + "h": 109.00000000000027, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "goldengate": { + "title": "Database - GoldenGate", + "w": 100, + "h": 88.0000000000002, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "streaming": { + "title": "Analytics and AI - Streaming", + "w": 83.99999999999994, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "kafka": { + "title": "Analytics and AI - Streaming", + "w": 83.99999999999994, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "service_connector_hub": { + "title": "Analytics and AI - Service Connector Hub", + "w": 117.99999999999987, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "oke": { + "title": "Developer Services - Container Engine for Kubernetes", + "w": 137.9999999999999, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "apex": { + "title": "Developer Services - APEX", + "w": 83.99999999999994, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "apigw": { + "title": "Developer Services - API Gateway", + "w": 102.99999999999987, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "api_gateway": { + "title": "Developer Services - API Gateway", + "w": 102.99999999999987, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "notifications": { + "title": "Developer Services - Notifications", + "w": 102.99999999999987, + "h": 108.00000000000004, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "cloud_guard": { + "title": "Identity and Security - Cloud Guard", + "w": 117.99999999999987, + "h": 100, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + }, + "network_firewall": { + "title": "Identity and Security - Firewall", + "w": 83.99999999999994, + "h": 102.00000000000014, + "cells": [ + "", + "", + "", + "", + "", + "", + "" + ] + }, + "waf": { + "title": "Identity and Security - WAF", + "w": 87.00000000000014, + "h": 106.00000000000007, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "vault": { + "title": "Identity and Security - Vault", + "w": 83.99999999999994, + "h": 105.00000000000017, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "bastion": { + "title": "Identity and Security - Bastion", + "w": 70, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "nsg": { + "title": "Identity and Security - NSG", + "w": 65, + "h": 109.00000000000027, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "apm": { + "title": "Observability and Management - Application Performance Management", + "w": 107.00000000000014, + "h": 123.00000000000004, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "logging": { + "title": "Observability and Management - Logging", + "w": 86.00000000000007, + "h": 108.00000000000004, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "monitoring": { + "title": "Observability and Management - Monitoring", + "w": 93.99999999999994, + "h": 109.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "logging_analytics": { + "title": "Observability and Management - Logging Analytics", + "w": 85, + "h": 120.00000000000001, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "events": { + "title": "Observability and Management -&nbsp;Events", + "w": 84.00000000000027, + "h": 107.00000000000014, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + }, + "ops_insights": { + "title": "Observability and Management - Operations Insights", + "w": 90.99999999999974, + "h": 126.00000000000007, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + }, + "queue": { + "title": "Observability and Management - Queuing", + "w": 84.00000000000027, + "h": 108.00000000000004, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "oci_queue": { + "title": "Observability and Management - Queuing", + "w": 84.00000000000027, + "h": 108.00000000000004, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "db_management": { + "title": "Database Management", + "w": 135, + "h": 121.00000000000007, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "mysql_heatwave": { + "title": "Database - MySQL", + "w": 101.00000000000007, + "h": 142.00000000000014, + "cells": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "gen_ai": { + "w": 99.00000000000027, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "generative_ai": { + "w": 99.00000000000027, + "h": 125.00000000000017, + "cells": [ + "", + "", + "", + "", + "" + ] + }, + "cache": { + "w": 83.99999999999994, + "h": 104.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + }, + "redis": { + "w": 83.99999999999994, + "h": 104.0000000000001, + "cells": [ + "", + "", + "", + "", + "", + "" + ] + } +} \ No newline at end of file diff --git a/kb/diagram/oci-toolkit-styles.yaml b/kb/diagram/oci-toolkit-styles.yaml new file mode 100644 index 0000000..889ec9f --- /dev/null +++ b/kb/diagram/oci-toolkit-styles.yaml @@ -0,0 +1,240 @@ +# OCI Official Diagram Styles — Extracted from OCI Style Guide for Draw.io Toolkit v24.2 +# Source: OCI Library.xml + OCI Architecture Diagram Toolkit v24.2.drawio +# This supersedes earlier palette files based on inference from reference architectures. + +# ============================================================ +# CONTAINER STYLES (from Physical Groupings in the toolkit) +# These are the EXACT styles Oracle uses for diagram containers. +# ============================================================ + +containers: + + tenancy: + # Toolkit item [200]: Physical - Grouping - Tenancy + style: "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;fontColor=#312D2A;strokeColor=#9E9892;fontSize=12;spacingLeft=5;" + notes: + - "DASHED gray border, NO fill" + - "Text color is charcoal #312D2A" + - "Border is warm gray #9E9892" + + region: + # Toolkit item [202]: Physical - Grouping - OCI Region + style: "whiteSpace=wrap;html=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=#F5F4F2;rounded=1;arcSize=5;strokeColor=#9E9892;fontColor=#312D2A;fontSize=12;fontStyle=1;spacingLeft=8;spacingTop=5;" + notes: + - "SOLID border, rounded corners" + - "FILLED with warm gray #F5F4F2" + - "This is the only container with a solid background fill" + + availability_domain: + # From template styles + style: "whiteSpace=wrap;html=1;strokeWidth=1;align=center;fontFamily=Oracle Sans;verticalAlign=top;fillColor=#DFDCD8;fontColor=#312D2A;strokeColor=#9E9892;rounded=1;arcSize=1;fontStyle=1;" + notes: + - "Slightly darker gray fill #DFDCD8" + - "Bold text (fontStyle=1)" + + vcn: + # Toolkit item [197]: Physical - Grouping - VCN + style: "whiteSpace=wrap;html=1;strokeWidth=2;dashed=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;fontColor=#AE562C;strokeColor=#AE562C;perimeterSpacing=0;fontSize=12;spacingLeft=5;" + notes: + - "DASHED BURNT ORANGE border — this is the signature OCI VCN visual" + - "strokeWidth=2 (thicker than subnet)" + - "Text AND border are both #AE562C (burnt orange)" + - "NO fill — transparent background" + + subnet: + # Toolkit item [198]: Physical - Grouping - Subnet + style: "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;fontColor=#AE562C;perimeterSpacing=0;strokeColor=#AE562C;fontSize=12;spacingLeft=5;" + notes: + - "DASHED BURNT ORANGE border — same color as VCN but THINNER (strokeWidth=1)" + - "Inside templates, subnet interior uses fillColor=#FCFBFA (near-white)" + - "Text is #AE562C (burnt orange)" + + compartment: + # Toolkit item [199]: Physical - Grouping - Compartment + style: "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;fontColor=#312D2A;strokeColor=#9E9892;fontSize=12;spacingLeft=5;" + notes: + - "Same as tenancy style — dashed gray" + + fault_domain: + # Toolkit item [204] + style: "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;fontColor=#312D2A;strokeColor=#9E9892;fontSize=12;spacingLeft=5;" + + tier: + # Toolkit item [201] + style: "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;align=left;fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;fontColor=#312D2A;strokeColor=#9E9892;fontSize=12;spacingLeft=5;" + +# ============================================================ +# SERVICE ICON COLORS +# ============================================================ + +service_colors: + oci_teal: "#2D5967" + oracle_copper: "#AA643B" + oracle_purple: "#804998" + burnt_orange: "#AE562C" # Used ONLY for VCN/Subnet borders and text — NOT for service icons + oracle_red: "#C74634" # Accent only + +# ============================================================ +# BACKGROUND AND TEXT COLORS +# ============================================================ + +backgrounds: + region_fill: "#F5F4F2" # Warm gray — region containers + ad_fill: "#DFDCD8" # Slightly darker — AD containers + subnet_fill: "#FCFBFA" # Near-white — subnet interiors in templates + dormant: "#DFDCD8" # Dormant/inactive elements + +text: + primary: "#312D2A" # Charcoal — ALL text (never pure black) + secondary: "#70665E" # Medium gray — step circles, muted text + on_dark: "#FFFFFF" # White — text on colored service blocks + +borders: + container: "#9E9892" # Warm gray — tenancy, region, compartment borders + vcn_subnet: "#AE562C" # Burnt orange — VCN and subnet borders ONLY + connector: "#312D2A" # Charcoal — ALL connection arrows (matches text color) + +# ============================================================ +# CONNECTOR STYLES (from toolkit items [206-214]) +# ALL connectors use charcoal #312D2A — same as text color. +# Differentiation is via dash pattern, not color. +# ============================================================ + +connectors: + standard: + # Toolkit item [206]: Physical - Connector + style: "endArrow=open;endFill=0;strokeColor=#312D2A;strokeWidth=1;edgeStyle=orthogonalEdgeStyle;endSize=6;rounded=0" + notes: + - "Open arrow tip (not filled)" + - "Orthogonal routing (right-angle turns)" + - "endSize=6 for small consistent arrowheads" + - "Color is charcoal #312D2A — NOT gray" + + fastconnect: + # Toolkit items [209-210]: Physical - Special Connectors - FastConnect + style: "endArrow=open;endFill=0;startArrow=open;startFill=0;strokeColor=#312D2A;strokeWidth=1;edgeStyle=orthogonalEdgeStyle;endSize=6;rounded=0" + notes: + - "Bidirectional open arrows" + - "Same charcoal color as standard" + - "In toolkit, has lightning/zigzag icon overlay — not reproducible in simple edges" + + site_to_site_vpn: + # Toolkit items [211-212] + style: "endArrow=open;endFill=0;strokeColor=#312D2A;strokeWidth=1;dashed=1;edgeStyle=orthogonalEdgeStyle;endSize=6;rounded=0" + notes: "Dashed line with lock icon overlay" + + remote_peering: + # Toolkit items [213-214] + style: "endArrow=open;endFill=0;strokeColor=#312D2A;strokeWidth=1;dashed=1;edgeStyle=orthogonalEdgeStyle;endSize=6;rounded=0" + notes: "Dashed line for inter-region connectivity" + +# ============================================================ +# TYPOGRAPHY +# ============================================================ + +typography: + font_family: "Oracle Sans" + fallback_fonts: "Segoe UI, Helvetica Neue, Arial, sans-serif" + container_labels: "fontSize=12, fontColor=#312D2A" + service_labels: "fontSize=8-9, fontColor=#FFFFFF (on colored background)" + title_text: "fontSize=10, fontColor=#70665E, fontStyle=2 (italic)" + +# ============================================================ +# KEY VISUAL RULES (from toolkit analysis) +# ============================================================ + +design_rules: + # Container rules (confirmed from OCI Toolkit v24.2 + 7 Oracle Architecture Center reference diagrams) + - "VCN and Subnet borders are ALWAYS dashed burnt orange (#AE562C) — this is the signature Oracle visual" + - "VCN border is strokeWidth=2, Subnet border is strokeWidth=1" + - "Region containers are the ONLY ones with solid background fill (#F5F4F2)" + - "Tenancy and Compartment containers are dashed gray, NO fill" + - "VCN and Subnet labels are BOLD, ORANGE (#AE562C), left-aligned inside the container (top-left)" + - "Subnet labels sometimes include CIDR notation below in smaller text (10pt gray)" + + # VCN/Subnet containers — NO corner badges + - "VCN and Subnet containers do NOT have badges or icons at the corners" + - "Confirmed by re-checking original Architecture Center drawio files (e-commerce, ExaCS, etc.)" + + # Service icon rules (confirmed from 7 Oracle Architecture Center reference diagrams) + - "Service icons are LINE ART / OUTLINE style in teal (#2D5967) — NOT filled colored rectangles" + - "Service icons do NOT have colored background boxes when rendered as OCI library stencils" + - "Service label is BELOW the icon, centered, in dark charcoal (#312D2A)" + - "Service label font size is 10-11pt (NOT 14pt) — confirmed from Oracle ref arch diagrams" + - "Total service block height (icon + label) should be ~90px for OCI services, not 45px" + + # External cloud rules (confirmed from multicloud Oracle reference diagrams) + - "Azure containers use SOLID blue border (#0078D4) with light blue fill (#EBF5FF)" + - "Azure container labels are bold, dark blue (#0050A2)" + - "GCP containers use solid green border (#34A853) with light green fill (#E8F5E9)" + - "AWS/generic containers use dashed muted gray border (no specific Oracle style defined)" + + # Oracle Services Network panel (confirmed from hub-spoke, OKE, lakehouse Oracle diagrams) + - "Oracle Services Network is a right-side dashed gray panel containing managed OCI services" + - "Services in Oracle Services Network are NOT in any VCN/Subnet — they float in the panel" + - "Use compartment-style container with oracle_services_network label for this panel" + - "OSN panel has NO fill (transparent) — dashed gray border only" + + # Flow differentiation — Oracle uses line patterns, NOT numbered circles + - "Oracle differentiates flow types by DASH PATTERN, not numbered circle badges" + - "Ingest: dotted fine; Transform: dash-dot; Curate: fine dotted; Analyze: dashed" + - "Flow labels are text strings on the arrow path ('Process and store', 'Deploy model')" + - "A legend box at the bottom explains the line patterns if multiple flow types exist" + + # Connector and typography rules + - "Service blocks use solid fills with NO stroke (strokeColor=none)" + - "Text is NEVER pure black — always charcoal #312D2A" + - "Font is Oracle Sans (falls back to Segoe UI)" + - "Icons are composite multi-layer Visio stencils in the OCI Library.xml" + - "ALL connectors use charcoal #312D2A — same as text, NOT colored" + - "Connectors use open arrow tips (endArrow=open, endFill=0) — never filled blocks" + - "Connectors use orthogonal routing (edgeStyle=orthogonalEdgeStyle) — never diagonal" + - "Arrow size is small and consistent (endSize=6)" + - "Dashed lines differentiate replication/migration from standard flows" + - "FastConnect is bidirectional (startArrow=open + endArrow=open)" + - "Oracle DB-related services use copper #AA643B" + - "Integration/messaging services use purple #804998" + - "All other OCI services use teal #2D5967" + +reference_diagrams_analyzed: + # 7 Oracle Architecture Center diagrams analyzed visually (2026-03-17) + # Used to confirm and extend the style guide from OCI Toolkit v24.2 + - url: "https://docs.oracle.com/en/solutions/exadb-dr-on-db-azure/img/exadb-dr-db-azure.png" + topology: "multi-region multi-cloud (ExaDB DR on Database@Azure)" + - url: "https://docs.oracle.com/en/solutions/hub-spoke-network-drg/img/hub-and-spoke-drg.png" + topology: "hub-spoke networking with DRG" + - url: "https://docs.oracle.com/en/solutions/data-platform-lakehouse/img/lakehouse-architecture.png" + topology: "data lakehouse (multi-tier, analytics)" + - url: "https://docs.oracle.com/en/solutions/deploy-oke-with-bastion-and-github/img/oke-bastion-deployment-diagram.png" + topology: "OKE with Bastion and GitHub Actions" + - url: "https://docs.oracle.com/en/solutions/cis-oci-benchmark/img/oci-core-landingzone.png" + topology: "CIS Landing Zone (complex, multi-compartment)" + - url: "https://docs.oracle.com/en/solutions/deploy-autonomous-database-db-at-azure/img/autonomous-database-db-azure-diagram.png" + topology: "ADB-S on Database@Azure (multi-cloud)" + - url: "https://docs.oracle.com/en/solutions/data-application-integration-workloads/img/integration-architecuture-pattern-1.png" + topology: "data integration pattern" + +# ============================================================ +# FULL ICON CATALOG (224 icons in OCI Library.xml) +# ============================================================ +# See separate file for the full icon list. +# Key icons for Deal Accelerator: +# 37: Database - Autonomous DB +# 18: Networking - Load Balancer +# 32: Networking - Internet Gateway +# 33: Networking - NAT Gateway +# 31: Networking - Service Gateway +# 30: Networking - Dynamic Routing Gateway DRG +# 102: Identity and Security - WAF +# 105: Identity and Security - Vault +# 106: Identity and Security - Bastion +# 45: Database - Data Safe +# 46: Database - NoSQL +# 74: Developer Services - Container Engine for Kubernetes +# 2: Compute - Functions +# 3: Compute - Virtual Machine VM +# 65: Analytics and AI - Streaming +# 127: Observability and Management - Queuing +# 79: Developer Services - API Gateway +# 120: Observability and Management - Monitoring +# 123: Observability and Management - Logging Analytics diff --git a/kb/diagram/reference-layouts/database-ha-dr.yaml b/kb/diagram/reference-layouts/database-ha-dr.yaml new file mode 100644 index 0000000..5c588c5 --- /dev/null +++ b/kb/diagram/reference-layouts/database-ha-dr.yaml @@ -0,0 +1,55 @@ +--- +last_verified: 2026-03-14 +description: Reference layout for database HA/DR architecture diagram +--- + +layout: + name: "Database HA/DR — Dual Region" + page_size: "A3 landscape" + + structure: + # Primary region takes 60% of width + primary_region: + position: { x: 20, y: 40 } + size: { width: 680, height: 720 } + contains: + vcn: + position: { x: 20, y: 40 } + size: { width: 640, height: 660 } + subnets: + data_tier: + position: { x: 20, y: 30 } + size: { width: 300, height: 400 } + label_position: "top-left" + app_tier: + position: { x: 340, y: 30 } + size: { width: 280, height: 200 } + dmz: + position: { x: 340, y: 250 } + size: { width: 280, height: 100 } + + # DR region takes 35% of width + dr_region: + position: { x: 720, y: 40 } + size: { width: 460, height: 720 } + contains: + vcn: + position: { x: 20, y: 40 } + size: { width: 420, height: 400 } + subnets: + dr_data_tier: + position: { x: 20, y: 30 } + size: { width: 380, height: 350 } + + # Connection between regions (horizontal) + inter_region_connection: + from: "data_tier" + to: "dr_data_tier" + style: "adg" + label_position: "center" + + notes: + - "Primary region on left, DR region on right" + - "Data Guard arrow flows left → right" + - "FastConnect shown at bottom connecting to on-premises" + - "Security services (Vault, Data Safe) placed near data tier" diff --git a/kb/diagram/reference-layouts/single-region-ha.yaml b/kb/diagram/reference-layouts/single-region-ha.yaml new file mode 100644 index 0000000..b5a7094 --- /dev/null +++ b/kb/diagram/reference-layouts/single-region-ha.yaml @@ -0,0 +1,70 @@ +--- +last_verified: 2026-03-14 +layout: Single Region with HA +description: | + Reference layout for a single-region deployment with high availability. + Suitable for most production workloads not requiring cross-region DR. +--- + +canvas: + width: 1200 + height: 800 + +structure: + tenancy: + x: 0 + y: 0 + width: 1200 + height: 800 + + region: + x: 20 + y: 40 + width: 1160 + height: 740 + + vcn: + x: 20 + y: 40 + width: 1100 + height: 680 + + subnets: + public_web: + position: top + x: 20 + y: 30 + width: 520 + height: 180 + purpose: "Load balancers, WAF" + + private_app: + position: middle + x: 20 + y: 230 + width: 520 + height: 200 + purpose: "Application servers, OKE workers" + + private_db: + position: bottom + x: 20 + y: 450 + width: 1060 + height: 200 + purpose: "Database instances, Data Safe, Vault" + + private_mgmt: + position: right + x: 560 + y: 30 + width: 500 + height: 400 + purpose: "Bastion, monitoring, management" + +notes: | + - Place load balancers in public subnet at top + - App tier in private subnet in middle + - Database tier in private subnet at bottom (widest) + - Management/security services in right column + - Services flow top-to-bottom: LB → App → DB diff --git a/kb/field-findings/tracker.yaml b/kb/field-findings/tracker.yaml new file mode 100644 index 0000000..35108ad --- /dev/null +++ b/kb/field-findings/tracker.yaml @@ -0,0 +1,312 @@ +# ============================================================================= +# FIELD FINDINGS TRACKER +# ============================================================================= +# +# INTERNAL & CONFIDENTIAL — DO NOT SHARE EXTERNALLY +# +# Log of real issues, limitations, bugs, and surprises found during customer +# engagements. Each entry is a finding that another architect should know about +# before hitting the same wall. +# +# Entry format: +# id: Unique identifier (FF-YYYYMM-NNN) +# date: When discovered (YYYY-MM-DD) +# contributor: Attribution block (replaces flat "reported_by") +# name: Who found it (required) +# team: Team or role (required) +# client: Client name or anonymized reference (optional) +# context: Brief context — PoC, migration, assessment (optional) +# confidence: validated / observed / reported / inferred (required) +# product: OCI product/service affected +# version: Product version if relevant +# severity: CRITICAL / HIGH / MEDIUM / LOW / INFO +# category: bug / limitation / undocumented / gotcha / workaround / performance +# summary: One-line description +# detail: Full description of the finding +# workaround: How to work around it (if known) +# oracle_sr: Oracle SR number (if filed) +# status: open / resolved / wontfix / acknowledged / monitoring +# resolved_date: When resolved (if applicable) +# resolution: How it was resolved +# affects_matrix: Feature matrix entry this relates to (if any) +# tags: Searchable tags +# confirmations: List of peer confirmations (optional) +# +# HOW TO ADD: +# 1. Add entry at the TOP of the findings list (newest first) +# 2. Use next sequential ID: FF-YYYYMM-NNN +# 3. Be specific — include CLI versions, API errors, exact messages +# 4. If you filed an SR, include the number +# 5. Update status when resolved +# ============================================================================= + +--- +last_updated: "2026-03-14" + +findings: + + - id: "FF-202603-008" + date: "2026-03-14" + reported_by: "Diego Cabrera" + client: "Vector Search Customer" + product: "ADB-S" + version: "23ai" + severity: "HIGH" + category: "limitation" + summary: "Distributed HNSW indexes not available — 100M+ vector search requires partitioning workaround" + detail: | + ADB-S AI Vector Search supports HNSW indexes but they are node-local on + multi-node RAC (≥64 ECPUs). At 100M+ vectors, queries on a 2-node RAC + miss vectors on the other node. Results are either incomplete or require + cross-node scan that blows P95 >200ms. + + Workaround from internal DB PM team: hash-partition vector table on + vector_id, create partition-local HNSW index. Queries hit single + partition on single node, maintaining <50ms at 100M scale. + + Requires table redesign — cannot be applied to existing unpartitioned + vector tables without data reload. + workaround: | + Hash-partition vector table by vector_id. Create HNSW index as LOCAL + (partition-level). Ensure partition pruning in queries. + Source: internal DB PM team contact [name]. + Validated on ADB-S 23ai with 120M vectors, P95 = 38ms on 64 ECPU. + oracle_sr: "" + status: "acknowledged" + resolved_date: null + resolution: null + affects_matrix: "AI Vector Search (HNSW)" + tags: ["vector-search", "hnsw", "distributed", "rac", "partitioning", "100m", "latency", "ai-vector-search"] + + - id: "FF-202603-001" + date: "2026-03-10" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Strategic Migration Customer" + context: "Production migration" + confidence: "validated" + product: "OCI CLI" + version: "CLI 3.73.1 and 3.75.0" + severity: "MEDIUM" + category: "bug" + summary: "OCI CLI --autonomous-database-maintenance-window has JSON serialization bug" + detail: | + The --autonomous-database-maintenance-window parameter fails with a JSON + serialization error on both OCI CLI 3.73.1 (Cloud Shell) and 3.75.0 + (VM decabrer-4S46W64). The expected JSON schema is: + {"dayOfWeek": {"name": "SATURDAY"}, "hourOfDay": 4} + But the CLI cannot serialize this properly. API returns 403 at create time. + Tested via dry-run: the 204 response is received but actual creation fails. + workaround: "Use OCI Console to set maintenance window. Or set post-creation via API directly (not CLI)." + oracle_sr: "" + status: "open" + resolved_date: null + resolution: null + affects_matrix: "OCI CLI Management" + tags: ["cli", "maintenance-window", "adb", "dep", "json", "serialization"] + + - id: "FF-202603-002" + date: "2026-03-08" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Strategic Migration Customer" + context: "Production migration" + confidence: "validated" + product: "ADB-S Dedicated Elastic Pool (DEP)" + version: "23ai" + severity: "HIGH" + category: "limitation" + summary: "ADG must be disabled before joining Dedicated Elastic Pool" + detail: | + When attempting to add an ADB-S instance with active Autonomous Data Guard + to a Dedicated Elastic Pool, the operation fails. ADG must be explicitly + disabled before the instance can join the DEP. This is a known issue + acknowledged by Oracle but not prominently documented. + + Impact: requires a brief HA gap during DEP onboarding. Must coordinate + with the customer's change window. + workaround: "Disable ADG → join DEP → re-enable ADG. Plan for ~15 min HA gap." + oracle_sr: "" + status: "acknowledged" + resolved_date: null + resolution: null + affects_matrix: "Elastic Pool Membership" + tags: ["dep", "adg", "elastic-pool", "ha", "limitation"] + + - id: "FF-202603-003" + date: "2026-03-05" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Strategic Migration Customer" + context: "Production migration" + confidence: "validated" + product: "ADB-S Dedicated Elastic Pool (DEP)" + version: "23ai" + severity: "MEDIUM" + category: "undocumented" + summary: "DEP maintenance window immutable post-creation — change only via SR" + detail: | + The maintenance window for a Dedicated Elastic Pool cannot be changed + after creation via Console, CLI, or API. The only way to change it is + by filing a Service Request with Oracle Support. + + Default window is Saturday/Sunday. Other days require specifying at + creation time (which itself has the CLI bug — see FF-202603-001) or + requesting via SR. + workaround: "Set desired window at DEP creation time (via Console, not CLI). If missed, file SR." + oracle_sr: "" + status: "acknowledged" + resolved_date: null + resolution: null + affects_matrix: "Auto Patching" + tags: ["dep", "maintenance-window", "immutable", "sr-required"] + + - id: "FF-202603-004" + date: "2026-03-01" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Strategic Migration Customer" + context: "Production migration" + confidence: "validated" + product: "ADB-S" + version: "23ai" + severity: "MEDIUM" + category: "gotcha" + summary: "DEP provisioning takes days to weeks depending on capacity" + detail: | + Dedicated Elastic Pool provisioning is NOT instant like ADB-S Serverless. + It requires physical Exadata infrastructure allocation. Lead time varies + from 3 days (if capacity available in region) to 2-3 weeks (if capacity + needs to be provisioned). + + Billing starts only at AVAILABLE state, not at request time. + Co-location of existing ADB-S instances happens at the next maintenance + window after joining, not immediately. + workaround: "Request DEP early in the project timeline. Don't make it a critical-path dependency in week 1." + oracle_sr: "" + status: "acknowledged" + resolved_date: null + resolution: null + affects_matrix: "Elastic Pool Membership" + tags: ["dep", "provisioning", "lead-time", "capacity", "planning"] + + - id: "FF-202603-005" + date: "2026-02-20" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Strategic Migration Customer" + context: "Production migration" + confidence: "observed" + product: "ADB-S" + version: "23ai" + severity: "LOW" + category: "gotcha" + summary: "Refreshable Clone failed refresh leaves clone in AVAILABLE state — detection delay" + detail: | + When a Refreshable Clone's auto-refresh fails, the clone remains in + AVAILABLE state (not DISCONNECTED). The Elastic Pool leader monitors + lifecycle state changes, not refresh success/failure. + + This means a failed refresh is NOT immediately detected by the + infrastructure. The stale data window equals the ADG RTO for + unplanned scenarios. + workaround: | + Planned switchovers: DBMS_PROXY_SQL.DISABLE_READ_ONLY_OFFLOAD proactively. + Unplanned: OCI Events + OCI Function that monitors refresh state and + disables read offload automatically. Reduces stale window to ~ADG RTO. + oracle_sr: "" + status: "monitoring" + resolved_date: null + resolution: null + affects_matrix: "Refreshable Clone" + tags: ["refreshable-clone", "stale-reads", "elastic-pool", "detection-delay", "proxysql"] + + - id: "FF-202603-006" + date: "2026-02-15" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Strategic Migration Customer" + context: "Migration assessment" + confidence: "validated" + product: "OCI DMS" + version: "current" + severity: "MEDIUM" + category: "limitation" + summary: "Bidirectional GoldenGate replication not yet available in DMS" + detail: | + OCI Database Migration Service does not support bidirectional GoldenGate + replication. This is needed for zero-hard-cutover migration workflows + where the customer wants to run both source and target in parallel with + data flowing both ways during transition. + + Feature request submitted to Oracle DMS PM team. In their pipeline but + no committed timeline. Offered beta participation. + workaround: "Use standalone GoldenGate deployment (not DMS-managed) for bidirectional replication." + oracle_sr: "" + status: "open" + resolved_date: null + resolution: null + affects_matrix: null + tags: ["dms", "goldengate", "bidirectional", "migration", "zero-downtime"] + + - id: "FF-202603-007" + date: "2026-02-10" + contributor: + name: "Diego Cabrera" + team: "Field Architecture" + client: "Pepe SRL" + context: "Discovery engagement" + confidence: "validated" + product: "ADB-S" + version: "23ai" + severity: "INFO" + category: "gotcha" + summary: "ADB-S Serverless RAC node split threshold at 64 ECPUs" + detail: | + ADB-S Serverless runs on 1 RAC node at ≤24 ECPUs and splits to 2 nodes + at 64 ECPUs (16 cpu_count each). The gv$instance view only shows nodes + where the PDB is open. + + This affects connection pooling behavior and session distribution. + Applications should use UCP or JDBC connection pool with FAN events + to handle the multi-node scenario properly. + workaround: "Ensure connection pool configuration handles multi-node. Use UCP with FAN." + oracle_sr: "" + status: "acknowledged" + resolved_date: null + resolution: null + affects_matrix: null + tags: ["adb-s", "rac", "ecpu", "node-split", "connection-pooling"] + + # ========================================================================= + # TEMPLATE — Copy this for new entries: + # ========================================================================= + # - id: "FF-YYYYMM-NNN" + # date: "YYYY-MM-DD" + # contributor: + # name: "" + # team: "" + # client: "" # Optional — anonymize if sensitive + # context: "" # Optional — PoC, migration, assessment + # confidence: "" # validated / observed / reported / inferred + # product: "" + # version: "" + # severity: "" # CRITICAL / HIGH / MEDIUM / LOW / INFO + # category: "" # bug / limitation / undocumented / gotcha / workaround / performance + # summary: "" + # detail: | + # + # workaround: "" + # oracle_sr: "" + # status: "" # open / resolved / wontfix / acknowledged / monitoring + # resolved_date: null + # resolution: null + # affects_matrix: null # Feature matrix entry name, or null + # tags: [] + # confirmations: [] # Peer confirmations diff --git a/kb/field-knowledge/gotchas.yaml b/kb/field-knowledge/gotchas.yaml new file mode 100644 index 0000000..037601e --- /dev/null +++ b/kb/field-knowledge/gotchas.yaml @@ -0,0 +1,247 @@ +# Field Knowledge: Real-World Gotchas and Lessons Learned +# From actual OCI deployments — things the documentation doesn't emphasize + +gotchas: + - id: FK-001 + area: "ADB-S Auto-Scaling" + severity: HIGH + description: | + ADB-S auto-scaling takes 2-3 minutes to activate. If you size base OCPUs + for average load (P50), short spikes will hit before scale-out completes. + Size base OCPUs for P75 sustained load. + recommendation: "Base OCPU = P75 CPU usage, not P50" + + - id: FK-002 + area: "ADB-S Private Endpoint" + severity: HIGH + description: | + ADB-S with public endpoint allows connections from the internet to the + DB listener. Always use private endpoints for production. This requires + a VCN subnet and Service Gateway. + recommendation: "Always use private endpoints. Never public for production." + + - id: FK-003 + area: "FastConnect Redundancy" + severity: HIGH + description: | + A single FastConnect virtual circuit is a single point of failure. + For production, deploy 2 virtual circuits on diverse physical paths. + Also configure IPSec VPN as a backup path. + recommendation: "2x FastConnect (diverse paths) + VPN backup" + + - id: FK-004 + area: "Service Limits" + severity: MEDIUM + description: | + OCI has default service limits per tenancy that are often too low for + production deployments. Request limit increases BEFORE go-live, as + approval can take 1-3 business days. + recommendation: "Submit service limit increase requests during planning phase" + + - id: FK-005 + area: "Block Volume Performance" + severity: MEDIUM + description: | + Block Volume IOPS scale with volume size. A 50 GB Balanced volume only + provides 3,000 IOPS. If you need 25,000 IOPS, you need at least 417 GB + (even if your data is only 50 GB). + recommendation: "Size volumes for IOPS, not just capacity. Over-provision if needed." + + - id: FK-006 + area: "OKE LoadBalancer Annotations" + severity: LOW + description: | + OKE creates a 10 Mbps Flexible Load Balancer by default for + LoadBalancer-type Kubernetes services. For production, set the + bandwidth annotation explicitly. + recommendation: | + Set annotation: service.beta.kubernetes.io/oci-load-balancer-shape: "flexible" + And: service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "100" + + - id: FK-007 + area: "Terraform State" + severity: MEDIUM + description: | + Don't store Terraform state locally or in git. Use OCI Object Storage + with versioning as the remote backend. Enable state locking. + recommendation: "Use OCI Object Storage as Terraform remote backend with state locking." + + - id: FK-008 + area: "Cross-Region Data Transfer" + severity: MEDIUM + description: | + Cross-region data transfer is not free. Data Guard redo transport, + Object Storage replication, and cross-region backup copy all incur + egress charges. Factor this into DR cost estimates. + recommendation: "Include cross-region egress in DR cost estimates" + + - id: FK-009 + area: "Identity Domain Limits" + severity: LOW + description: | + Free tier Identity Domain supports up to 2,000 users. For larger + deployments, upgrade to Premium Identity Domain. + recommendation: "Check user count against Identity Domain tier limits" + + - id: FK-010 + area: "Compartment Depth" + severity: LOW + description: | + Compartments can be nested up to 6 levels deep. In practice, 3-4 levels + is the sweet spot. Deeper nesting makes IAM policies harder to manage. + recommendation: "Limit compartment depth to 3-4 levels" + + - id: FK-011 + area: "VCN CIDR Planning" + severity: HIGH + description: | + VCN CIDRs cannot be changed after creation (you can add secondary CIDRs + but not modify the primary). Plan CIDR ranges carefully, especially + when on-premises integration is required. + recommendation: "Use /16 per VCN, document all CIDRs, avoid overlap with on-prem" + + - id: FK-012 + area: "Data Guard Switchover vs Failover" + severity: HIGH + description: | + Switchover is planned (zero data loss, both DBs running). Failover is + unplanned (potential data loss with async). Ensure runbooks distinguish + between the two and teams practice both. + recommendation: "Document and practice both switchover (planned) and failover (unplanned)" + + - id: FK-013 + area: "TAC Replay Limitations" + severity: MEDIUM + description: | + Transparent Application Continuity (TAC) cannot replay operations that + have external side effects: UTL_HTTP calls, DBMS_PIPE, NOCACHE sequences, + and certain PL/SQL constructs. These cause replay to abort and the + application sees the error. Audit application code before relying on TAC. + recommendation: "Audit application for non-replayable operations before enabling TAC" + + - id: FK-014 + area: "ADB-S Auto Indexing" + severity: LOW + description: | + ADB-S Auto Indexing creates indexes with VISIBILITY=INVISIBLE (HIDDEN). + They exist and are used by the optimizer but don't appear in standard + DBA_INDEXES views unless you filter for invisible. This confuses DBAs + who think no indexes were created. + recommendation: "Query DBA_INDEXES with VISIBILITY='INVISIBLE' to see auto-created indexes" + + - id: FK-015 + area: "Database Enterprise Patching (DEP)" + severity: MEDIUM + description: | + DEP provisioning can take 30-60 minutes for DBCS/ExaCS. Plan maintenance + windows accordingly. DEP also requires Fleet Management setup which has + its own prerequisites (OS Management Hub agent, etc.). + recommendation: "Set up DEP and Fleet Management early; plan for 60-minute patching windows" + + - id: FK-016 + area: "ExaCS Minimum Cost" + severity: HIGH + description: | + ExaCS requires dedicated Exadata infrastructure. Minimum configuration + (quarter rack) starts at ~$6,000/month before database licensing. + Don't recommend for workloads under 16 OCPUs sustained. + recommendation: "Use ADB-S or DBCS for workloads < 16 OCPUs; ExaCS only for isolation/RAC/high IOPS" + + - id: FK-017 + area: "Cross-Region ADG is Async Only" + severity: HIGH + description: | + Cross-region Autonomous Data Guard uses asynchronous replication only. + RPO is always > 0 (typically seconds). For zero RPO you need same-region + standby or ADB-D with local synchronous Data Guard. + recommendation: "Set customer expectations: cross-region DR = RPO > 0 (seconds, not zero)" + + - id: FK-018 + area: "OKE VCN-Native Pod Networking" + severity: MEDIUM + description: | + VCN-Native Pod Networking (OCI CNI) assigns VCN IPs directly to pods. + This enables NSG attachment to pods but consumes subnet IP addresses + rapidly. A /24 subnet supports ~250 pods. Size subnets for pod count, + not node count. + recommendation: "Use /21 or larger subnets for pod networking; calculate IPs from max pod count" + + - id: FK-019 + area: "Object Storage Eventual Consistency" + severity: MEDIUM + description: | + OCI Object Storage provides strong read-after-write consistency for PUTs + of new objects, but LIST operations may show eventual consistency. + If your application lists then reads immediately after upload, the new + object may not appear in the list yet. + recommendation: "Don't rely on LIST immediately after PUT; use specific GET for confirmation" + + - id: FK-020 + area: "Database@AWS — ADB-S NOT available" + severity: HIGH + description: | + Oracle Database@AWS only supports ADB on Dedicated Exadata Infrastructure + (ADB-D), NOT ADB-S Serverless. Customers expecting serverless auto-scaling + and pay-per-query pricing will be disappointed. If ADB-S is required, + the architecture must use native OCI with FastConnect from AWS — which + adds ~1-2ms RTT vs the ~0.2-0.4ms of Database@AWS co-located infra. + recommendation: | + Clarify ADB-S vs ADB-D early in discovery. If P95 <1ms AND serverless + are both hard requirements, they are mutually exclusive with current + Database@AWS offering. Trade-off: co-located ADB-D (low latency) vs + native OCI ADB-S (serverless) via FastConnect (higher latency). + + - id: FK-021 + area: "Database@AWS — Same-AZ placement is critical" + severity: HIGH + description: | + Database@AWS latency drops from ~0.2-0.4ms (same AZ) to ~1ms+ (cross-AZ). + If app EC2 and ODB network are in different AZs, you lose the latency + benefit that justifies co-location. Transit Gateway attachment must also + be in the same AZ as the ODB network. + recommendation: | + Always deploy app and ODB network in the same AZ. Document AZ choice + early — VM clusters cannot be moved between ODB networks after creation. + + - id: FK-022 + area: "Flex Shape Memory Ratio" + severity: LOW + description: | + VM.Standard.E4.Flex allows 1-64 GB per OCPU (default 16 GB). For + memory-intensive workloads, you can increase memory without adding OCPUs. + But memory is billed separately — verify it's cheaper than adding OCPUs + with default memory. + recommendation: "Compare cost of extra memory vs extra OCPUs for memory-intensive workloads" + + - id: FK-024 + area: "Exadata X11M — Minimum Configuration (ADB-D and ExaCS)" + severity: HIGH + description: | + Exadata X11M applies the same minimum configuration for both ADB-D and ExaCS: + 2 DB servers + 3 storage servers. It is not possible to provision a single DB + server. This applies to X8M, X9M, and X11M generations. + Elastic range: 2–32 DB servers, 3–64 storage servers. + The tenancy service limits for X11M must be set to at least DB=2, Storage=3 + before provisioning. + recommendation: "Always start X11M sizing at 2 DB servers + 3 storage servers minimum. Applies to both ADB-D and ExaCS." + source: "Verified — Oracle docs (ExaDB-D X11M datasheet, ADB-D X11M datasheet, OCI docs)" + references: + - "https://www.oracle.com/a/ocom/docs/engineered-systems/exadata/exadb-d-x11m-ds.pdf" + - "https://docs.oracle.com/en/cloud/paas/autonomous-database/dedicated/adbde/" + + - id: FK-023 + area: "GPU Bare Metal — Preemptible Availability" + severity: HIGH + description: | + OCI Preemptible instances (~50% off) are technically supported for BM shapes, + but for high-demand GPU bare metal shapes (BM.GPU.H100, BM.GPU.H200, BM.GPU.B200, + BM.GPU.B300) preemptible capacity is practically unavailable. These shapes are + sold almost entirely via reservations or Universal Credits commitments. + Additionally, preemptible instances can be reclaimed with only 30 seconds notice, + making them operationally unviable for multi-day or multi-week training runs. + recommendation: | + Do NOT model GPU BM cost savings based on preemptible pricing. + Use Universal Credits 1yr (~35% off) or 3yr (~55% off) for realistic estimates. + If the workload is short batch jobs tolerant to interruptions, ask your PDM + about spot/preemptible availability before committing to that model. + source: "Field experience — Diego, OCI SA" diff --git a/kb/field-knowledge/lessons-learned.yaml b/kb/field-knowledge/lessons-learned.yaml new file mode 100644 index 0000000..9330b77 --- /dev/null +++ b/kb/field-knowledge/lessons-learned.yaml @@ -0,0 +1,96 @@ +--- +last_verified: 2026-03-14 +description: | + Lessons learned from real OCI deployments. Anonymized but based on + actual customer experiences. +--- + +lessons: + - id: LL-001 + category: migration + title: "Always test GoldenGate replication before cutover" + context: | + Customer planned zero-downtime migration using GoldenGate replication + from on-prem Exadata to ADB-S. During cutover rehearsal, discovered + that several PL/SQL packages used database links that didn't work with + ADB-S private endpoints. + lesson: | + Run full regression testing with GoldenGate replication active for + at least 2 weeks before cutover. Test all database links, PL/SQL + packages, and application connectivity against the target. + recommendation: "Add 2-week parallel-run phase to every migration plan" + + - id: LL-002 + category: sizing + title: "P95 is not enough for seasonal workloads" + context: | + Retail customer sized ADB-S for P95 utilization from normal months. + During Black Friday, actual peak was 4x normal (well above P95). + Auto-scaling helped but wasn't fast enough for the initial surge. + lesson: | + For seasonal workloads, get metrics from the peak period specifically. + Generic P95 from normal months underestimates seasonal peaks. + recommendation: "Ask for peak-period metrics separately, size auto-scale max for peak" + + - id: LL-003 + category: networking + title: "Plan IP addressing before anything else" + context: | + Customer deployed dev, staging, and production with overlapping CIDR + ranges. When they needed VCN peering for shared services, had to + re-architect networking — required rebuilding all subnets. + lesson: | + IP addressing must be planned enterprise-wide before deploying + any VCN. Include on-prem ranges, future VCNs, and DR regions. + recommendation: "Create IP addressing document as first deliverable" + + - id: LL-004 + category: operations + title: "IaC from day one, not after" + context: | + Customer built initial infrastructure manually via console, planning + to "Terraformify" later. After 6 months, infrastructure had drifted + so far that reverse-engineering Terraform was more work than starting over. + lesson: | + Start with Terraform from the first resource. The cost of IaC on day + one is low; the cost of retrofitting is very high. + recommendation: "Include OCI Terraform provider setup in project kickoff" + + - id: LL-005 + category: cost + title: "Auto-scaling costs can surprise" + context: | + Customer enabled ADB-S auto-scaling with 3x maximum. Workload was + consistently hitting auto-scale range, resulting in 2x the expected + database costs. They were effectively paying for auto-scaled OCPUs + most of the time. + lesson: | + If workload consistently auto-scales, the base OCPU is too low. + Right-size base to P75 and use auto-scaling for genuine peaks. + recommendation: "Review auto-scaling usage monthly, adjust base OCPUs accordingly" + + - id: LL-006 + category: dr + title: "DR drill found app-tier gap" + context: | + Customer had cross-region ADG for database DR. During DR drill, + database failed over successfully in 3 minutes. But app tier had + no DR — compute instances were only in primary region. Recovery + took 2 hours to provision app tier in DR region. + lesson: | + DR must cover all tiers, not just the database. App tier, load + balancers, DNS, and certificates all need DR strategy. + recommendation: "Include app tier DR in all architecture proposals" + + - id: LL-007 + category: security + title: "Cloud Guard catches what humans miss" + context: | + Customer deployed without Cloud Guard. 3 months later, security + audit found public Object Storage buckets, overly permissive + security lists, and an unused API key with admin privileges. + Cloud Guard would have flagged all of these on day one. + lesson: | + Enable Cloud Guard in every deployment. It's free and catches + common misconfigurations automatically. + recommendation: "Cloud Guard is mandatory in security baseline" diff --git a/kb/field-knowledge/real-world-limits.yaml b/kb/field-knowledge/real-world-limits.yaml new file mode 100644 index 0000000..ddbc154 --- /dev/null +++ b/kb/field-knowledge/real-world-limits.yaml @@ -0,0 +1,77 @@ +--- +last_verified: 2026-03-14 +description: | + Real-world limits that differ from official documentation or are not + well-documented. These come from actual deployment experience. +--- + +limits: + - service: ADB-S + limit: "Concurrent connections" + documented: "300 per OCPU" + real_world: | + 300 per OCPU is the hard limit, but performance degrades noticeably + past 200 active sessions per OCPU. Size for 200 concurrent active + sessions per OCPU, not 300. + recommendation: "Plan for 200 active sessions per OCPU as practical limit" + + - service: ADB-S + limit: "Auto-scaling speed" + documented: "Automatic" + real_world: | + Takes 2-3 minutes to begin scaling. Not instantaneous. Very short + bursts (< 3 minutes) may complete before scaling activates. + recommendation: "Size base OCPUs for P75 to absorb initial burst" + + - service: ExaCS + limit: "Provisioning time" + documented: "Hours" + real_world: | + Can take 4-8 hours for infrastructure provisioning. VM cluster + creation adds another 1-2 hours. Total: 6-10 hours first time. + recommendation: "Plan provisioning 1-2 business days ahead" + + - service: OKE + limit: "Node provisioning" + documented: "Minutes" + real_world: | + Node provisioning: 3-5 minutes for compute instance. + Node ready (kubelet + image pull): 5-10 minutes total. + Pod scheduling after node ready: 10-30 seconds. + recommendation: "Plan for 10-minute node scale-up, keep warm pool for critical workloads" + + - service: FastConnect + limit: "Provisioning time" + documented: "Varies by partner" + real_world: | + Partner-based: 2-6 weeks depending on partner and location. + Co-location: 1-2 weeks if already in the same facility. + Always have IPSec VPN as backup during provisioning. + recommendation: "Start procurement 6-8 weeks early, use VPN as interim" + + - service: Block Volume + limit: "IOPS per volume" + documented: "Up to 25K (Balanced), 35K (Higher Performance)" + real_world: | + Documented limits are achievable with sufficient volume size. + Small volumes (< 200 GB) won't reach max IOPS due to per-GB ratio. + Need 417 GB for 25K IOPS on Balanced (60 IOPS/GB). + recommendation: "Size volumes for IOPS requirement, not just capacity" + + - service: Object Storage + limit: "API rate limits" + documented: "Request-based" + real_world: | + Burst throttling can occur at ~1000 requests/second per bucket. + Use multipart upload for large objects. Prefix partitioning helps + for high-throughput scenarios. + recommendation: "Use multipart upload, prefix partitioning for high throughput" + + - service: ADB-S + limit: "Maximum databases per tenancy" + documented: "Service limits page" + real_world: | + Default limit is often 10-20 ADB instances per region. Need to + request limit increase via support ticket. Increases are usually + approved within 1-2 business days. + recommendation: "Request limit increases early in project planning" diff --git a/kb/patterns/application-patterns.yaml b/kb/patterns/application-patterns.yaml new file mode 100644 index 0000000..795367f --- /dev/null +++ b/kb/patterns/application-patterns.yaml @@ -0,0 +1,149 @@ +# Application Patterns Library +# Proven application-level architecture patterns for common workload types +# Referenced during DESIGN/Future to select the right application architecture + +patterns: + + # --- Web Application Patterns --- + - id: app_web_traditional + name: "Traditional Web Application (3-Tier)" + description: "Classic presentation → application → database architecture on OCI" + applicable_when: + - "Java/J2EE, .NET, PHP, or Python web application" + - "WebLogic, Tomcat, or similar app server" + - "Stateful sessions or shared filesystem dependencies" + oci_architecture: + presentation_tier: "Load Balancer (public) → WAF" + application_tier: "Compute instances (VM.Standard.E4.Flex) in Instance Pool with auto-scaling" + database_tier: "ADB-S or DBCS depending on feature requirements" + networking: "Public subnet (LB) → Private subnet (app) → Private subnet (DB)" + technology_patterns: [compute_scaling, database_ha_adb_s, networking_basic, security_baseline] + considerations: + - "WebLogic clustering needs shared storage (FSS) or session replication" + - "Consider OKE if team has container skills and wants to modernize" + + - id: app_web_cloud_native + name: "Cloud-Native Web Application" + description: "Containerized microservices on OKE with managed services" + applicable_when: + - "New application or major refactoring opportunity" + - "Team has Kubernetes experience" + - "Need rapid scaling and deployment" + oci_architecture: + presentation_tier: "Load Balancer → API Gateway" + application_tier: "OKE (managed K8s) with HPA auto-scaling" + database_tier: "ADB-S (OLTP) + autonomous JSON for microservice data" + messaging: "OCI Streaming (Kafka-compatible) for event-driven" + technology_patterns: [compute_scaling, database_ha_adb_s, networking_hub_spoke] + + # --- Batch Processing Patterns --- + - id: app_batch_processing + name: "Batch / ETL Processing" + description: "Scheduled data processing, reporting, or ETL workloads" + applicable_when: + - "Nightly batch jobs, report generation, data warehousing" + - "Variable compute needs (high during batch windows)" + oci_architecture: + compute: "Preemptible instances or Functions for cost optimization" + orchestration: "Resource Scheduler or cron-based" + database: "ADB-S with auto-scaling (scale up during batch, scale down after)" + storage: "Object Storage for staging data" + technology_patterns: [database_ha_adb_s, compute_scaling] + considerations: + - "ADB-S auto-scaling takes 2-3 min to activate — pre-scale for predictable batch windows" + + # --- Analytics Patterns --- + - id: app_analytics_lakehouse + name: "Analytics / Lakehouse" + description: "Data warehouse and analytics platform on OCI" + applicable_when: + - "Business intelligence, reporting, or advanced analytics" + - "Data from multiple sources needs consolidation" + - "Self-service analytics for business users" + oci_architecture: + ingestion: "GoldenGate or OCI Data Integration for CDC" + storage: "Object Storage (data lake) + ADB-DW (warehouse)" + analytics: "Oracle Analytics Cloud (OAC)" + governance: "Data Catalog for metadata management" + technology_patterns: [database_ha_adb_s] + + # --- Hybrid Patterns --- + - id: app_hybrid_cloud + name: "Hybrid Cloud (On-Prem + OCI)" + description: "Application split across on-prem and OCI with connectivity" + applicable_when: + - "Cannot migrate everything at once" + - "Some workloads must stay on-prem (data gravity, regulation)" + - "Want to extend capacity to cloud" + oci_architecture: + connectivity: "FastConnect (primary) + IPSec VPN (backup)" + networking: "Hub-spoke VCN with DRG" + data_sync: "GoldenGate for bidirectional replication" + dns: "OCI DNS with split-horizon" + technology_patterns: [networking_hub_spoke, database_ha_adb_s] + considerations: + - "Latency between on-prem and OCI affects application performance" + - "FastConnect provisioning takes 2-4 weeks" + - "Consider data gravity — move compute to where data lives" + + # --- Migration Patterns --- + - id: app_lift_and_shift + name: "Lift and Shift Migration" + description: "Move existing workloads to OCI with minimal changes" + applicable_when: + - "Timeline pressure (hardware EOL, contract expiry)" + - "Application is stable and not being modernized" + - "Want to get to cloud quickly, optimize later" + oci_architecture: + compute: "VM shapes matching current specs (use sizing ratios from kb/sizing/)" + database: "DBCS if feature compatibility requires, ADB-S if possible" + storage: "Block Volume (replacing SAN), FSS (replacing NFS)" + technology_patterns: [networking_basic, security_baseline] + considerations: + - "May not realize full cloud benefits initially" + - "Plan a Phase 2 optimization after stabilization" + + - id: app_replatform + name: "Re-Platform (Lift, Tinker, Shift)" + description: "Migrate with targeted changes to leverage managed services" + applicable_when: + - "Want cloud benefits without full re-architecture" + - "Can accept minor application changes" + - "Database can move to managed service (ADB-S, MySQL HeatWave)" + oci_architecture: + compute: "OKE or Compute with auto-scaling" + database: "ADB-S (autonomous) vs DBCS (managed)" + storage: "Object Storage replacing file-based integrations" + technology_patterns: [database_ha_adb_s, compute_scaling] + considerations: + - "Test application compatibility with ADB-S restrictions early" + - "Connection string changes may require app config updates" + + # --- Disaster Recovery Patterns --- + - id: app_dr_active_passive + name: "Active-Passive DR" + description: "Primary region active, standby region for failover" + applicable_when: + - "RTO 1-4 hours, RPO < 1 hour" + - "Cost-conscious — don't want to run full duplicate" + oci_architecture: + primary: "Full deployment in primary region" + standby: "Dormant compute (scaled down) + DB replica (Data Guard)" + failover: "DNS switchover + compute scale-up" + technology_patterns: [database_dr_cross_region, networking_hub_spoke] + + - id: app_dr_active_active + name: "Active-Active Multi-Region" + description: "Both regions serve traffic, mutual failover" + applicable_when: + - "RTO < 15 min, RPO ~ 0" + - "Geo-distributed users" + - "Budget supports dual infrastructure" + oci_architecture: + both_regions: "Full deployment with load balancing" + database: "ADB-S with Autonomous Data Guard (bidirectional)" + traffic: "OCI Traffic Management for geo-steering" + technology_patterns: [database_dr_cross_region, database_ha_adb_s] + considerations: + - "Application must handle eventual consistency for cross-region writes" + - "Significantly higher cost (nearly 2x)" diff --git a/kb/patterns/architecture-principles.yaml b/kb/patterns/architecture-principles.yaml new file mode 100644 index 0000000..f69a421 --- /dev/null +++ b/kb/patterns/architecture-principles.yaml @@ -0,0 +1,107 @@ +--- +last_verified: 2026-03-16 +pattern: + id: architecture-principles + name: "Architecture Principles (ECAL)" + category: governance +--- + +description: > + Formalized decision-making principles aligned with Oracle's ECAL framework. + The skill selects applicable principles based on the workload profile and + presents them in the proposal as the governance framework for the architecture. + +principles: + + design: + - id: "D-01" + name: "Operational Simplicity" + principle: "Keep architecture simple. If still complex, simplify again." + rationale: "Complex solutions tend to be inflexible, non-agile, and operationally fragile." + applies_when: "always" + + - id: "D-02" + name: "Application First (SaaS > PaaS > IaaS)" + principle: "Always consider application services first, platform services second, infrastructure services as last resort." + rationale: "Higher-level services reduce operational burden and accelerate delivery." + applies_when: "always" + + - id: "D-03" + name: "Use-Case Fit" + principle: "Map workloads into applicable use-cases. Architectural simplicity and operational success come from re-use." + rationale: "Custom architectures are expensive to build and maintain. Reference architectures exist for a reason." + applies_when: "always" + + - id: "D-04" + name: "Hybrid-Ready" + principle: "Solution should run in hybrid mode, accounting for network bandwidth and latency." + rationale: "Most enterprise migrations have a hybrid phase. Architecture must work during transition." + applies_when: "on-prem exists or migration is phased" + + - id: "D-05" + name: "HA for Business Continuity" + principle: "Architect for availability, prepare for disaster, leverage cloud's built-in resilience." + rationale: "HA is cheaper to build in from day 1 than to retrofit." + applies_when: "always" + + - id: "D-06" + name: "Compliance by Design" + principle: "Consider required compliance requirements and existing policies from the initial design phase." + rationale: "Retrofitting compliance is orders of magnitude more expensive than designing for it." + applies_when: "compliance requirements identified (PCI, HIPAA, banking, GDPR)" + + deployment: + - id: "P-01" + name: "Automation First" + principle: "Strive to automate production processes from the beginning — infrastructure as code, seeded features." + rationale: "Manual processes don't scale, are error-prone, and create key-person dependencies." + applies_when: "always" + + - id: "P-02" + name: "Environment Separation" + principle: "Production always separated from test and dev. Clear lifecycle policy." + rationale: "Prevents accidental data exposure and configuration drift." + applies_when: "always" + + - id: "P-03" + name: "API-Centric Integration" + principle: "Prefer integration via REST APIs over lower-level network protocols." + rationale: "APIs are versionable, documentable, and testable. Direct DB links are fragile." + applies_when: "integration points exist" + + - id: "P-04" + name: "Data Security at Rest and In Transit" + principle: "Encrypt data at rest (TDE) and in transit (SQL*Net encryption). Key management from day 1." + rationale: "Encryption is a baseline, not an option. Key management complexity grows with scale." + applies_when: "always" + + - id: "P-05" + name: "Unified Identity Management" + principle: "All users managed in central identity system. SSO. Federation where applicable." + rationale: "Distributed identity is a security liability." + applies_when: "always" + + service: + - id: "S-01" + name: "Cloud Monitoring & Management" + principle: "Map management and monitoring requirements explicitly. Use cloud-native tools first." + rationale: "Observability gaps become operational crises during incidents." + applies_when: "always" + + - id: "S-02" + name: "Issue & Incident Management" + principle: "Design for service. Adopt incident management with clear ownership, governance, and SLAs." + rationale: "Without defined escalation paths, incidents become crises." + applies_when: "always" + + - id: "S-03" + name: "Solution Lifecycle" + principle: "Map governance for cloud solution lifecycle. Apply ITIL/ITSM frameworks. Readiness for continuous change." + rationale: "Cloud evolves constantly. The architecture must evolve with it." + applies_when: "always" + + - id: "S-04" + name: "Cloud Onboarding" + principle: "Setup PoC environment first. Early Customer Success Management involvement." + rationale: "Hands-on validation before commitment reduces risk and builds confidence." + applies_when: "customer is new to OCI" diff --git a/kb/patterns/business-drivers.yaml b/kb/patterns/business-drivers.yaml new file mode 100644 index 0000000..bced4ab --- /dev/null +++ b/kb/patterns/business-drivers.yaml @@ -0,0 +1,217 @@ +--- +last_verified: 2026-03-18 +pattern: + id: business-drivers + name: "Business Drivers Framework" + category: define +--- + +description: > + Categorizes customer business drivers into four pillars (Strategic, + Financial, Business Ops, IT Ops) and maps them to cloud value + propositions. Used during DEFINE phase to structure the Value Story + and during CONFIRM to frame the Solution Proposal. Source: ECAL 3.1. + +# ═══════════════════════════════════════════════════════════════ +# The Four Pillars +# ═══════════════════════════════════════════════════════════════ + +pillars: + + strategic: + label: "Strategic" + headline: "Business Transformation to Cloud" + description: > + Long-term strategic positioning. Cloud as enabler for business + model evolution, M&A, market expansion, or competitive defense. + common_drivers: + - "Cloud-first / cloud-native strategy mandated by CxO" + - "Digital transformation initiative" + - "Merger or acquisition requiring platform consolidation" + - "Geographic expansion requiring global infrastructure" + - "Competitive pressure from cloud-native disruptors" + - "Board/investor mandate for technology modernization" + - "Innovation enablement (AI/ML, IoT, blockchain)" + value_propositions: + - "Cloud-based solution with enterprise features" + - "On-premise security with cloud economics (C@C, dedicated region)" + - "Customer-defined security model" + - "Data sovereignty and residency guarantees" + - "Multi-cloud and hybrid flexibility" + + financial: + label: "Financial" + headline: "Cost Optimization" + description: > + Direct financial impact. CAPEX→OPEX shift, license optimization, + support cost reduction, consumption-based pricing. + common_drivers: + - "Reduce total cost of ownership (TCO)" + - "Shift from CAPEX to OPEX model" + - "Optimize Oracle licensing (ULA wind-down, BYOL)" + - "Reduce data center costs (power, cooling, space, staff)" + - "Support rebate / simplified licensing" + - "Avoid hardware refresh cycle" + - "Consumption-based pricing alignment with business cycles" + value_propositions: + - "Subscription and consumption-based commercials" + - "Simplified licensing (BYOL, universal credits)" + - "PAYG flexibility for variable workloads" + - "Support cost reduction via managed services" + - "Elimination of hardware refresh capex" + quantification_tips: + - "Compare 3-year TCO: current state vs. OCI (include staff, power, space)" + - "Factor in support rebate for moving to OCI" + - "Account for BYOL savings (if eligible)" + - "Include non-prod savings (auto-scaling, auto-stop)" + + business_ops: + label: "Business Operations" + headline: "Faster Time to Market" + description: > + Business agility and speed. Faster development cycles, quicker + deployment of new capabilities, reduced time-to-value. + common_drivers: + - "Faster development and deployment of new applications" + - "Reduce release cycle from months to weeks" + - "Enable self-service provisioning for developers" + - "Data platform for analytics and decision-making" + - "Support rapid experimentation and A/B testing" + - "Enable mobile/digital channels faster" + - "Partner and ecosystem integration" + value_propositions: + - "Faster development of customer-facing and internal apps" + - "Data platform implementation (lakehouse, analytics)" + - "Self-service database provisioning" + - "CI/CD and DevOps enablement" + - "Integration with partner ecosystems" + + it_ops: + label: "IT Operations" + headline: "Operational Efficiency" + description: > + IT team productivity and operational excellence. Reduce toil, + improve reliability, automate operations. + common_drivers: + - "Reduce DBA / sysadmin toil (patching, backups, monitoring)" + - "Improve availability and reduce unplanned downtime" + - "Standardize operations across environments" + - "Automate provisioning and lifecycle management" + - "Improve disaster recovery posture" + - "Address skill gaps (hard to hire Oracle DBAs)" + - "Consolidate tooling and reduce tool sprawl" + - "Performance improvement without hardware throw" + value_propositions: + - "Oracle Managed Cloud environment" + - "Autonomous operations (ADB self-tuning, self-patching)" + - "Data platform implementation and consolidation" + - "Performance improvement via Exadata architecture" + - "Unified monitoring and management (OCI native tools)" + +# ═══════════════════════════════════════════════════════════════ +# Mapping: Business Driver → Hypothesis Family +# ═══════════════════════════════════════════════════════════════ +# Use this to quickly map a customer's stated driver to a +# hypothesis family for the Value Story + +hypothesis_families: + + db_consolidation: + triggers: + - "Too many databases across too many platforms" + - "Hardware refresh coming due" + - "DBA team stretched thin" + - "License compliance concerns" + use_cases: + - "Consolidation of N databases to shared platform" + - "Transform to fully managed operations" + - "Improved elasticity and cost control" + - "Standardized backup, HA, and DR" + typical_services: + - "ADB-S or ADB-D (for consolidation)" + - "ExaCS or ExaCC (for large scale)" + - "OEM for fleet management" + pillar_alignment: ["financial", "it_ops"] + + cloud_migration: + triggers: + - "Data center lease expiring" + - "Cloud-first mandate" + - "Competitive pressure" + - "EOL hardware or software" + use_cases: + - "Lift-and-shift existing Oracle workloads" + - "Re-platform to managed services" + - "Hybrid cloud (keep some on-prem)" + typical_services: + - "OCI Compute (VM/BM)" + - "ExaCS (for Oracle DB workloads)" + - "ADB-S (for re-platform)" + - "FastConnect + VPN" + pillar_alignment: ["strategic", "financial"] + + modernization: + triggers: + - "Legacy app can't support new business requirements" + - "Technical debt is slowing delivery" + - "Need for microservices or API-first" + - "AI/ML capabilities required" + use_cases: + - "Re-architect legacy apps to cloud-native" + - "Add AI/ML capabilities to existing workflows" + - "Microservices decomposition" + - "APEX rapid app development" + typical_services: + - "OKE (Kubernetes)" + - "ADB-S + APEX" + - "OCI AI Services" + - "API Gateway + Functions" + pillar_alignment: ["business_ops", "strategic"] + + dr_and_resilience: + triggers: + - "Recent outage or near-miss" + - "Audit finding on DR gaps" + - "Insurance or compliance requirement" + - "Business continuity plan update" + use_cases: + - "Cross-region DR for critical workloads" + - "Active-active for zero RPO" + - "Pilot light DR for cost optimization" + typical_services: + - "Cross-region ADG" + - "Full Stack DR" + - "Object Storage for backups" + pillar_alignment: ["it_ops", "strategic"] + + compliance_driven: + triggers: + - "New regulation (DORA, NIS2, GDPR enforcement)" + - "Audit finding requiring remediation" + - "Data sovereignty requirement" + - "Industry-specific mandate (banking, healthcare)" + use_cases: + - "Data residency with OCI regions or dedicated region" + - "Encryption and key management (customer-managed keys)" + - "Access controls and audit trail" + - "Isolated compute (C@C, dedicated region)" + typical_services: + - "OCI Vault (KMS)" + - "Data Safe" + - "Cloud Guard" + - "ExaCC or Dedicated Region" + pillar_alignment: ["strategic", "it_ops"] + +# ═══════════════════════════════════════════════════════════════ +# Usage in the skill +# ═══════════════════════════════════════════════════════════════ +skill_usage: + define_phase: + - "Parse discovery notes to identify which pillar(s) are dominant" + - "Use pillar alignment to select the right hypothesis family" + - "Structure the Value Story around the dominant 1-2 pillars" + - "Use value_propositions as talking points for the proposal" + confirm_phase: + - "Frame the Solution Proposal business case around the identified pillars" + - "Use quantification_tips for the financial pillar" + - "Map each architecture decision back to the business driver" diff --git a/kb/patterns/business-patterns.yaml b/kb/patterns/business-patterns.yaml new file mode 100644 index 0000000..967fead --- /dev/null +++ b/kb/patterns/business-patterns.yaml @@ -0,0 +1,128 @@ +# Business Patterns Library +# Proven business-level patterns that map customer drivers to OCI solution strategies +# Referenced during DEFINE/Ideate to formulate value hypotheses + +patterns: + + # --- Cost Optimization Patterns --- + - id: biz_cost_reduction_db + name: "Database Cost Reduction" + business_driver: cost_reduction + hypothesis_template: "Migrate {n} Oracle databases from {current_platform} to ADB-S, reducing total DB cost by {pct}% through autonomous operations and BYOL/ULA leverage" + applicable_when: + - "Customer runs Oracle databases on-prem or in AWS/Azure" + - "Existing ULA or BYOL licenses available" + - "DBA team spends >50% time on maintenance tasks" + typical_outcome: + cost_savings: "40-70% vs on-prem, 30-50% vs AWS RDS Oracle" + operational_savings: "50-80% reduction in DBA administrative effort" + evidence: + - "ADB-S automates patching, tuning, backups" + - "BYOL cost advantage vs AWS License Included" + technology_patterns: [database_ha_adb_s, database_dr_cross_region] + risks: ["Feature compatibility gaps", "Migration complexity for customized DBs"] + + - id: biz_cost_reduction_compute + name: "Compute Cost Optimization" + business_driver: cost_reduction + hypothesis_template: "Right-size and modernize {n} compute workloads to OCI Flex shapes with auto-scaling, reducing compute spend by {pct}%" + applicable_when: + - "Over-provisioned VMs on-prem or in another cloud" + - "Variable workload patterns with steady-state over-provisioning" + typical_outcome: + cost_savings: "30-50% through right-sizing + auto-scaling" + technology_patterns: [compute_scaling] + + # --- Modernization Patterns --- + - id: biz_app_modernization + name: "Application Modernization" + business_driver: modernization + hypothesis_template: "Modernize {app_name} from {current_stack} to cloud-native on OKE, improving deployment frequency from {current_freq} to {target_freq}" + applicable_when: + - "Monolithic application limiting agility" + - "Slow deployment cycles (monthly or longer)" + - "Team has or is building container/K8s skills" + typical_outcome: + deployment_frequency: "From monthly to daily/weekly" + scalability: "Horizontal auto-scaling vs vertical only" + technology_patterns: [compute_scaling] + risks: ["Requires significant app refactoring", "Team skill gap"] + + - id: biz_db_modernization + name: "Database Modernization (non-Oracle)" + business_driver: modernization + hypothesis_template: "Migrate {db_engine} to OCI-managed {target_service}, eliminating operational overhead while maintaining application compatibility" + applicable_when: + - "Running self-managed MySQL, PostgreSQL, or MongoDB" + - "Want to reduce operational burden without app rewrite" + typical_outcome: + operational_savings: "Automated backups, patching, HA" + technology_patterns: [database_ha_adb_s] + + # --- Compliance Patterns --- + - id: biz_compliance_driven + name: "Compliance-Driven Migration" + business_driver: compliance + hypothesis_template: "Migrate {scope} to OCI to meet {compliance_framework} requirements by {deadline}, leveraging OCI's built-in compliance controls" + applicable_when: + - "Upcoming compliance audit or certification deadline" + - "Current infrastructure doesn't meet regulatory requirements" + - "Data residency or sovereignty requirements" + typical_outcome: + compliance: "Certification-ready infrastructure" + timeline: "Typically 8-16 weeks for standard compliance scope" + technology_patterns: [security_baseline, compliance_pci] + risks: ["Compliance scope creep", "Shared responsibility model gaps"] + + # --- Hardware EOL / Contract Expiry Patterns --- + - id: biz_hardware_eol + name: "Hardware End-of-Life Migration" + business_driver: hardware_eol + hypothesis_template: "Migrate {n} workloads from EOL {hardware} to OCI before {eol_date}, avoiding {cost} hardware refresh while gaining cloud elasticity" + applicable_when: + - "On-prem hardware approaching end of support" + - "Capital expenditure avoidance is a driver" + - "Timeline is fixed by hardware support dates" + typical_outcome: + capex_avoidance: "Avoid $X hardware refresh" + opex_model: "Shift to predictable OpEx" + technology_patterns: [database_ha_adb_s, compute_scaling, networking_basic] + + - id: biz_contract_expiry + name: "Cloud Contract Expiry / Repatriation" + business_driver: contract_expiry + hypothesis_template: "Migrate {n} workloads from {incumbent_cloud} to OCI before contract renewal, reducing cloud spend by {pct}% with better Oracle licensing economics" + applicable_when: + - "AWS/Azure contract up for renewal" + - "Running Oracle workloads on non-Oracle cloud" + - "Significant Oracle license cost on AWS/Azure" + typical_outcome: + cost_savings: "30-60% for Oracle DB workloads (BYOL advantage)" + technology_patterns: [database_ha_adb_s, networking_hub_spoke] + + # --- Performance Patterns --- + - id: biz_performance_improvement + name: "Performance Improvement" + business_driver: performance + hypothesis_template: "Migrate {workload} to OCI {target_service} to achieve {target_metric}, resolving current {pain_point}" + applicable_when: + - "Current platform cannot meet performance SLAs" + - "Latency or throughput is a business-impacting issue" + - "Exadata-class performance needed without Exadata cost" + typical_outcome: + performance: "2-5x improvement typical with ADB-S/ExaCS" + technology_patterns: [database_ha_adb_s] + + # --- Consolidation Patterns --- + - id: biz_consolidation + name: "Database Consolidation" + business_driver: consolidation + hypothesis_template: "Consolidate {n} Oracle databases into {target_n} ADB-S instances, reducing management complexity and licensing footprint" + applicable_when: + - "Many small Oracle databases (sprawl)" + - "Under-utilized database instances" + - "License optimization opportunity" + typical_outcome: + license_reduction: "40-60% fewer required OCPUs" + operational_reduction: "Manage N instead of N*10 instances" + technology_patterns: [database_ha_adb_s] diff --git a/kb/patterns/compliance-pci/pattern.yaml b/kb/patterns/compliance-pci/pattern.yaml new file mode 100644 index 0000000..dd786ca --- /dev/null +++ b/kb/patterns/compliance-pci/pattern.yaml @@ -0,0 +1,94 @@ +--- +last_verified: 2026-03-14 +pattern: PCI-DSS Compliance Overlay +category: compliance +--- + +description: | + Additional security controls required for PCI-DSS compliance on OCI. + Builds on top of the security-baseline pattern. + +pci_scope_definition: + cardholder_data_environment: + - "Databases storing PAN (Primary Account Number)" + - "Application servers processing card data" + - "Network segments carrying card data" + out_of_scope: + - "Reporting databases (if no PAN)" + - "Development environments (if no real card data)" + - "Management/monitoring infrastructure" + recommendation: | + Minimize PCI scope aggressively. Use tokenization to remove PAN + from most systems. Only the tokenization service and payment + gateway should be in scope. + +controls: + network_segmentation: + pci_req: "Requirement 1: Network segmentation" + implementation: + - "Dedicated VCN for CDE (Cardholder Data Environment)" + - "NSGs restricting traffic to/from CDE" + - "Network Firewall for traffic inspection between CDE and non-CDE" + - "No internet-facing services in CDE subnet" + - "Document all network flows in and out of CDE" + + encryption: + pci_req: "Requirement 3 & 4: Protect stored and transmitted data" + implementation: + - "TDE with customer-managed keys (OCI Vault) for databases" + - "TLS 1.2+ for all data in transit" + - "Column-level encryption for PAN if stored" + - "Key rotation per PCI requirements (annual minimum)" + - "Data masking for non-production environments (Data Safe)" + + access_control: + pci_req: "Requirement 7 & 8: Access control" + implementation: + - "Dedicated compartment for CDE resources" + - "IAM policies restricting CDE access to authorized personnel" + - "MFA required for all CDE access" + - "Privileged access management with session recording" + - "Quarterly access reviews" + + monitoring: + pci_req: "Requirement 10: Logging and monitoring" + implementation: + - "Centralized logging (OCI Logging + SIEM integration)" + - "Audit trail for all CDE access (OCI Audit)" + - "Real-time alerting for security events (Cloud Guard)" + - "Log retention minimum 12 months (1 year online, archive older)" + - "Daily log review process" + + vulnerability_management: + pci_req: "Requirement 5 & 6: Vulnerability management" + implementation: + - "Vulnerability Scanning service for compute instances" + - "Data Safe security assessments for databases" + - "Automated patching via OS Management" + - "Web Application Firewall (WAF) for public endpoints" + - "Quarterly vulnerability scans, annual penetration tests" + + dr_for_pci: + pci_req: "Requirement 12: DR and business continuity" + implementation: + - "Cross-region DR for CDE (same PCI controls in DR region)" + - "DR region must also be PCI-compliant" + - "Annual DR drill with PCI scope validation" + +oci_pci_services: + - service: "OCI Vault" + role: "Customer-managed encryption keys" + - service: "Data Safe" + role: "Database security assessment, data masking, audit" + - service: "Cloud Guard" + role: "Threat detection, security posture management" + - service: "WAF" + role: "Web application firewall for public endpoints" + - service: "Bastion" + role: "Secure access to CDE without public IPs" + - service: "Network Firewall" + role: "Traffic inspection between CDE and non-CDE" + - service: "Vulnerability Scanning" + role: "Automated vulnerability detection" + - service: "OS Management" + role: "Automated patching" diff --git a/kb/patterns/compute-scaling/pattern.yaml b/kb/patterns/compute-scaling/pattern.yaml new file mode 100644 index 0000000..d9933ce --- /dev/null +++ b/kb/patterns/compute-scaling/pattern.yaml @@ -0,0 +1,72 @@ +--- +last_verified: 2026-03-14 +pattern: Compute Scaling +category: compute +--- + +description: | + Auto-scaling and load balancing patterns for OCI compute workloads. + +patterns: + instance_pool_autoscaling: + name: "Instance Pool Auto-Scaling" + components: + - "Instance Pool with instance configuration" + - "Auto-scaling policy (metric-based or schedule-based)" + - "Load Balancer (flexible or network)" + scaling_metrics: + - "CPU utilization" + - "Memory utilization" + - "Custom metrics via Monitoring service" + scaling_speed: "New instances in 2-5 minutes" + when_to_use: + - "Stateless applications" + - "Web/API tiers" + - "Batch processing with variable load" + + oke_horizontal_pod_autoscaling: + name: "OKE Horizontal Pod Autoscaler" + components: + - "OKE cluster with managed nodes" + - "HPA resource definitions" + - "Cluster Autoscaler for node scaling" + scaling_metrics: + - "CPU utilization" + - "Memory utilization" + - "Custom metrics" + scaling_speed: "Pods: seconds, Nodes: 5-10 minutes" + when_to_use: + - "Containerized microservices" + - "Applications already on Kubernetes" + + oke_virtual_nodes: + name: "OKE Virtual Nodes (Serverless)" + components: + - "OKE cluster with virtual node pool" + - "No node management required" + scaling_speed: "Seconds (no node provisioning)" + when_to_use: + - "Burst workloads" + - "Event-driven container workloads" + - "Teams that don't want to manage nodes" + +load_balancing: + flexible_lb: + type: "Layer 7 (HTTP/HTTPS)" + features: + - "SSL termination" + - "Path-based routing" + - "Session persistence" + - "Health checks" + - "WAF integration" + bandwidth: "10 Mbps to 8 Gbps" + use_for: "Web applications, API endpoints" + + network_lb: + type: "Layer 4 (TCP/UDP)" + features: + - "Ultra-low latency" + - "Preserves source IP" + - "Health checks" + bandwidth: "Up to line rate" + use_for: "Database connections, non-HTTP protocols, high-throughput" diff --git a/kb/patterns/database-dr-cross-region.yaml b/kb/patterns/database-dr-cross-region.yaml new file mode 100644 index 0000000..dd458c6 --- /dev/null +++ b/kb/patterns/database-dr-cross-region.yaml @@ -0,0 +1,90 @@ +# Pattern: Cross-Region Database DR +# Composable architecture block for cross-region disaster recovery + +pattern: + name: "Cross-Region Database DR" + id: database_dr_cross_region + category: database-dr + +description: | + Cross-region disaster recovery using Autonomous Data Guard or Active Data Guard to + maintain an asynchronous standby database in a remote OCI region. Provides protection + against full-region outages with RPO measured in seconds and RTO in minutes. Applicable + to ADB-S, DBCS, and ExaCS deployments. + +variants: + - id: adb_s_cross_region + name: "ADB-S Cross-Region Autonomous Data Guard" + topology: "ADB-S primary in Region A, Autonomous Data Guard standby in Region B" + rpo: "Seconds (asynchronous replication)" + rto: "< 15 minutes (automated switchover/failover)" + use_when: + - "ADB-S workload requires regional DR" + - "RPO of seconds is acceptable" + - "Fully managed DR with minimal operational overhead" + + - id: dbcs_cross_region + name: "DBCS Cross-Region Active Data Guard" + topology: "DBCS primary in Region A, ADG standby in Region B" + rpo: "Seconds to minutes (asynchronous)" + rto: "Minutes to 1 hour depending on automation" + use_when: + - "DBCS workload requires regional DR" + - "Custom database configuration needed" + - "Manual or scripted failover acceptable" + + - id: exacs_cross_region + name: "ExaCS Cross-Region Active Data Guard" + topology: "ExaCS primary in Region A, ADG standby in Region B" + rpo: "Seconds to minutes (asynchronous)" + rto: "Minutes to 1 hour depending on automation" + use_when: + - "ExaCS workload requires regional DR" + - "High-performance database with regional protection" + - "Consolidated databases on Exadata with cross-region standby" + +preconditions: + - "Primary database provisioned and operational" + - "DR region selected with matching service availability" + - "Network connectivity between regions (remote VCN peering via DRG or dedicated infrastructure)" + - "Sufficient quota in DR region for standby compute and storage" + - "For DBCS/ExaCS: Active Data Guard license (EE-HP or EE-EP, or BYOL with ADG option)" + +implied_services: + - service: dns_traffic_management + reason: "Automated DNS failover to redirect application traffic to DR region" + - service: oci_notifications + reason: "Alerting on replication lag, failover events, and switchover status" + - service: monitoring + reason: "Track replication lag, standby apply rate, and DR readiness" + +conflicts: + - pattern: "sync_data_guard_cross_region" + reason: "Synchronous Data Guard is not supported cross-region due to latency; only asynchronous replication is available" + +gotchas: + - "Cross-region Active/Autonomous Data Guard is asynchronous only — RPO is always > 0" + - "Cross-region data transfer costs apply for redo log transport between regions" + - "Switchover is planned (zero data loss, brief outage) vs. failover is unplanned (potential data loss up to RPO)" + - "After failover, old primary must be reinstated or re-created — not automatic for DBCS/ExaCS" + - "Application connection strings must be updated or use DNS-based failover" + - "ADB-S cross-region DR requires both regions to support ADB-S" + - "DR readiness should be validated with periodic switchover drills" + +cost_impact: + - "Full standby compute and storage cost in DR region (same as primary)" + - "Cross-region network egress charges for redo transport" + - "For ADB-S: standby OCPU/ECPU billed at same rate as primary" + - "DNS Traffic Management: per-zone and per-query charges" + - "Consider reserved capacity in DR region for cost optimization" + +sizing_rules: + - "Standby must be same shape and size as primary to ensure failover performance" + - "Network bandwidth: estimate redo generation rate (typically 10-50 Mbps per 10K TPS)" + - "Storage in DR region must match primary storage allocation" + - "For ExaCS: same Exadata infrastructure shape required in DR region" + +references: + - "https://docs.oracle.com/en/cloud/paas/autonomous-database/adbsa/autonomous-data-guard.html" + - "https://docs.oracle.com/en/cloud/paas/base-database/index.html" + - "https://docs.oracle.com/en/database/oracle/oracle-database/19/sbydb/" diff --git a/kb/patterns/database-dr/pattern.yaml b/kb/patterns/database-dr/pattern.yaml new file mode 100644 index 0000000..de55e39 --- /dev/null +++ b/kb/patterns/database-dr/pattern.yaml @@ -0,0 +1,97 @@ +--- +last_verified: 2026-03-14 +pattern: Database Disaster Recovery +category: database +--- + +description: | + DR patterns for Oracle databases on OCI. Covers active-passive, + active-active, and pilot light approaches. + +tiers: + tier_1_mission_critical: + name: "Mission Critical" + rto: "<15 minutes" + rpo: "<1 minute" + pattern: "Active-passive with automatic failover" + implementation: + adb_s: "Cross-region ADG with auto-failover" + exacs: "Cross-region Data Guard with FSFO + Observer" + cost_multiplier: "2x (full standby)" + testing: "Quarterly automated DR drill" + + tier_2_business_critical: + name: "Business Critical" + rto: "<1 hour" + rpo: "<15 minutes" + pattern: "Active-passive with manual failover" + implementation: + adb_s: "Cross-region ADG with manual switchover" + exacs: "Cross-region Data Guard without FSFO" + cost_multiplier: "2x (full standby) or 1.5x (scaled-down standby)" + testing: "Semi-annual DR drill" + + tier_3_standard: + name: "Standard" + rto: "<4 hours" + rpo: "<1 hour" + pattern: "Pilot light or backup-restore" + implementation: + adb_s: "Cross-region backup with restore-on-demand" + exacs: "RMAN backups to Object Storage, cross-region replicated" + cost_multiplier: "1.1x (backup storage only)" + testing: "Annual DR drill" + +dr_regions: + recommended_pairs: + - primary: "US East (Ashburn)" + dr: "US West (Phoenix)" + latency_ms: "~60" + - primary: "UK South (London)" + dr: "Germany Central (Frankfurt)" + latency_ms: "~15" + - primary: "Japan East (Tokyo)" + dr: "Japan Central (Osaka)" + latency_ms: "~10" + - primary: "Brazil East (Sao Paulo)" + dr: "Brazil Southeast (Vinhedo)" + latency_ms: "~5" + +migration_cutover: + zero_downtime: + tools: ["ZDM with GoldenGate", "Data Guard switchover"] + prerequisites: + - "GoldenGate configured and replicating" + - "Application connection strings use TNS aliases" + - "DNS TTL reduced before cutover" + steps: + - "Verify replication lag is zero" + - "Stop writes to source" + - "Verify final sync" + - "Switch application connection strings" + - "Validate application functionality" + - "Decommission source (after bake period)" + + planned_downtime: + tools: ["Data Pump", "RMAN restore", "ZDM physical"] + typical_downtime: "2-8 hours depending on database size" + steps: + - "Announce maintenance window" + - "Take final backup/export" + - "Restore/import to target" + - "Switch application connections" + - "Validate" + +gotchas: + - id: DR-001 + issue: "Cross-region ADG network latency affects commit performance" + impact: "Synchronous mode adds latency to every commit" + recommendation: "Use asynchronous for cross-region (default), accept seconds of RPO" + - id: DR-002 + issue: "DR standby compute may not be available during regional disaster" + impact: "If DR region is also impacted, compute capacity may be limited" + recommendation: "Use capacity reservations in DR region for critical workloads" + - id: DR-003 + issue: "Application tier DR often forgotten" + impact: "Database fails over but app tier is not available" + recommendation: "Include app tier in DR plan — Terraform for on-demand provisioning" diff --git a/kb/patterns/database-ha-adb-s.yaml b/kb/patterns/database-ha-adb-s.yaml new file mode 100644 index 0000000..31df5f0 --- /dev/null +++ b/kb/patterns/database-ha-adb-s.yaml @@ -0,0 +1,71 @@ +# Pattern: ADB-S High Availability +# Composable architecture block for Autonomous Database Serverless HA + +pattern: + name: "ADB-S High Availability" + id: database_ha_adb_s + category: database-ha + +description: | + Autonomous Database Serverless (ADB-S) provides built-in high availability through + Transparent Application Continuity (TAC) and automated failover. TAC replays in-flight + transactions transparently after infrastructure failures, masking outages from applications. + For additional protection, Autonomous Data Guard provides a same-region standby with + synchronous replication for zero data loss. + +variants: + - id: local_ha + name: "Built-in Local HA (TAC)" + topology: "Same-region, ADB-S infrastructure-managed failover" + rpo: "0" + rto: "~0 perceived (transparent replay via TAC)" + use_when: + - "Standard HA for ADB-S workloads" + - "Application can tolerate brief transparent replay" + - "No separate standby database required" + + - id: local_standby + name: "Same-Region Autonomous Data Guard Standby" + topology: "Primary + Standby in same region via Autonomous Data Guard" + rpo: "0 (synchronous replication)" + rto: "< 30 seconds (automatic switchover)" + use_when: + - "Zero RPO mandatory" + - "Protection against full AD or FD failure" + - "Read-only offload to standby needed" + +preconditions: + - "ADB-S instance provisioned (Shared infrastructure)" + - "Private endpoint recommended for production workloads" + - "VCN with Service Gateway for secure OCI service access" + - "Wallet or mTLS connectivity configured for clients" + +implied_services: + - service: service_gateway + reason: "Secure access to OCI services without internet traversal" + - service: oci_vault + reason: "Customer-managed encryption keys for TDE" + - service: data_safe + reason: "Database security posture management, auditing, and masking" + +gotchas: + - "TAC replay does not cover all operations — UTL_HTTP calls, DBMS_PIPE, NOCACHE sequences, and external interactions are not replayed" + - "Auto-scaling takes 2-3 minutes to add OCPUs; plan base capacity for burst tolerance" + - "Autonomous Data Guard standby is read-only; cannot run DML on standby" + - "Switchover requires brief downtime; failover is automatic on primary failure" + - "TAC requires JDBC Thin driver 18c+ or ODP.NET with TAC enabled in connection string" + +cost_impact: + - "Built-in local HA (TAC): no additional cost, included in ADB-S" + - "Autonomous Data Guard standby: billed at same compute (OCPU/ECPU) rate as primary" + - "Standby storage is additional and billed at standard ADB-S storage rates" + - "Auto-scaling can increase OCPU cost up to 3x base during peaks" + +sizing_rules: + - "Standby must be same shape and OCPU/ECPU count as primary for seamless failover" + - "Storage on standby mirrors primary automatically" + - "Base OCPU count should handle normal workload without relying on auto-scaling for HA events" + +references: + - "https://docs.oracle.com/en/cloud/paas/autonomous-database/adbsa/autonomous-data-guard.html" + - "https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/application-continuity-autonomous.html" diff --git a/kb/patterns/database-ha/active-data-guard.yaml b/kb/patterns/database-ha/active-data-guard.yaml new file mode 100644 index 0000000..bd2ed78 --- /dev/null +++ b/kb/patterns/database-ha/active-data-guard.yaml @@ -0,0 +1,76 @@ +# Pattern: Active Data Guard HA/DR +# Composable architecture block for Oracle Database high availability + +pattern: + name: "Active Data Guard" + id: active_data_guard + category: database-ha + applies_to: [adb_s, adb_d, dbcs, exacs] + +description: | + Oracle Active Data Guard provides real-time data protection with a standby database + that can be used for read-only queries, reporting, and backups while maintaining + synchronous or asynchronous replication from the primary. + +variants: + - id: adg_same_region + name: "Same-Region ADG (HA)" + topology: "Primary + Standby in same region, different ADs or FDs" + rpo: "0 (synchronous)" + rto: "< 30 seconds (automatic failover with FSFO)" + use_when: + - "99.99% SLA requirement" + - "Zero RPO mandatory" + - "Read-only offload needed" + + - id: adg_cross_region + name: "Cross-Region ADG (DR)" + topology: "Primary in Region A, Standby in Region B" + rpo: "> 0 (asynchronous, typically seconds to minutes)" + rto: "Minutes to 1 hour depending on automation" + use_when: + - "Regional disaster recovery required" + - "RPO of seconds acceptable" + - "Regulatory requirement for geo-separation" + + - id: adg_hybrid + name: "Same-Region HA + Cross-Region DR" + topology: "Primary + Local Standby (sync) + Remote Standby (async)" + rpo: "0 local, seconds remote" + rto: "< 30s local, minutes remote" + use_when: + - "Maximum protection: HA + DR" + - "Mission-critical workloads" + +preconditions: + - "Oracle Database Enterprise Edition with Active Data Guard option" + - "BYOL: customer must have ADG license or ULA" + - "License Included: ADG included with ADB-S, ExaCS EE-HP/EE-EP" + - "For ADB-S: Autonomous Data Guard (managed, no manual config)" + +implied_services: + - service: data_guard + - service: dns_traffic_management + reason: "DNS failover for application connection string update" + when: "cross_region" + - service: oci_notifications + reason: "Alert on failover events" + +conflicts: + - pattern: "backup_restore_only" + reason: "ADG provides real-time replication; backup-restore is a subset" + +cost_impact: + - "Standby database incurs full compute + storage cost" + - "For ADB-S: standby OCPUs billed at same rate" + - "Cross-region: additional network egress for redo transport" + - "BYOL: ADG license required (included in EE-EP)" + +sizing_rules: + - "Standby must be same shape/OCPU as primary for failover" + - "For read-only offload: standby can handle ~80% of primary read capacity" + - "Network bandwidth for redo: estimate 10-50 Mbps per 10K TPS (varies by workload)" + +references: + - "https://docs.oracle.com/en/cloud/paas/autonomous-database/adbsa/autonomous-data-guard.html" + - "https://docs.oracle.com/en/database/oracle/oracle-database/19/sbydb/" diff --git a/kb/patterns/database-ha/pattern.yaml b/kb/patterns/database-ha/pattern.yaml new file mode 100644 index 0000000..25ba309 --- /dev/null +++ b/kb/patterns/database-ha/pattern.yaml @@ -0,0 +1,87 @@ +--- +last_verified: 2026-03-14 +pattern: Database High Availability +category: database +--- + +description: | + Patterns for achieving high availability for Oracle databases on OCI. + Covers ADB-S, ADB-D, ExaCS, and DBCS configurations. + +patterns: + adb_s_ha: + name: "ADB-S Built-in HA" + availability: "99.95% SLA" + rto: "<30 seconds" + rpo: "0 (synchronous)" + components: + - "ADB-S with Autonomous Data Guard (same-region)" + - "Automatic failover enabled" + - "No additional configuration needed" + cost_impact: "2x OCPU cost (standby billed at same rate)" + when_to_use: + - "Standard HA requirement" + - "Same-region protection sufficient" + notes: + - "Standby is provisioned automatically when ADG is enabled" + - "Failover is transparent to applications using recommended connection strings" + - "Switchover for planned maintenance is automatic" + + adb_s_cross_region: + name: "ADB-S Cross-Region ADG" + availability: "99.95% SLA per region, survives region failure" + rto: "<5 minutes (automatic), <30 minutes (manual)" + rpo: "Seconds (asynchronous replication)" + components: + - "ADB-S primary in Region 1" + - "ADB-S standby in Region 2 (cross-region ADG)" + - "Optional: Refreshable clone in Region 2 for reporting" + cost_impact: "2x OCPU + inter-region data transfer" + when_to_use: + - "Compliance requires geo-redundant DR" + - "RTO < 30 minutes for region failure" + - "Business-critical workloads" + notes: + - "Cross-region ADG is asynchronous — RPO is not zero" + - "Automatic failover requires ADG Auto-Failover configuration" + - "Test failover quarterly" + + exacs_rac_ha: + name: "ExaCS RAC High Availability" + availability: "99.95%+ (RAC + Data Guard)" + rto: "<60 seconds (RAC failover)" + rpo: "0 (synchronous within RAC)" + components: + - "ExaCS with multi-node RAC" + - "Data Guard to standby ExaCS (same or cross-region)" + - "Fast-Start Failover (FSFO) with Observer" + cost_impact: "2x infrastructure for Data Guard standby" + when_to_use: + - "Application requires RAC" + - "Sub-minute RTO required" + - "High-throughput OLTP workloads" + + dbcs_ha: + name: "DBCS 2-Node RAC + Data Guard" + availability: "99.95%" + rto: "<60 seconds (RAC), <5 minutes (Data Guard)" + rpo: "0 (synchronous within region)" + components: + - "DBCS 2-node RAC (primary)" + - "DBCS Data Guard standby" + cost_impact: "3x compute (2 RAC + 1 DG standby)" + when_to_use: + - "Moderate workloads needing HA" + - "Budget doesn't allow Exadata" + +decision_tree: | + Need HA for Oracle Database? + ├── Using ADB-S? + │ ├── Same-region sufficient → Enable ADG (built-in) + │ └── Cross-region required → Cross-region ADG + ├── Using ExaCS? + │ ├── RAC for local HA + │ └── Data Guard for DR (same or cross-region) + └── Using DBCS? + ├── 2-node RAC for local HA + └── Data Guard for DR diff --git a/kb/patterns/ecal-artefacts-catalog.yaml b/kb/patterns/ecal-artefacts-catalog.yaml new file mode 100644 index 0000000..6a1033f --- /dev/null +++ b/kb/patterns/ecal-artefacts-catalog.yaml @@ -0,0 +1,678 @@ +--- +last_verified: 2026-03-18 +pattern: + id: ecal-artefacts-catalog + name: "ECAL 3.1 Artefacts Catalog" + category: governance +--- + +description: > + Complete catalog of ECAL 3.1 artefacts across all 9 steps. + Each entry has: description, purpose, ECAL role, and whether + the OCI Deal Accelerator skill can generate it. Source: ECAL 3.1 + Practitioners Guides (01-09). + +# auto_generate: skill produces this artefact +# assist: skill provides partial content or structure +# reference: skill references this but customer/team produces it +# out_of_scope: purely delivery/commercial, skill doesn't touch + +# ═══════════════════════════════════════════════════════════════ +# DEFINE PHASE +# ═══════════════════════════════════════════════════════════════ + +define: + ideate: + - id: "DEF-01" + name: "Customer Profile" + what: "Strategic business goals, Oracle footprint, industry analysis, competitor positioning" + why: "Understand what drives the customer so the PoV resonates with their strategic goals" + how: "Account team intel, annual reports, web research, Business Model Canvas" + skill_support: "assist" + skill_notes: "Skill can research public info and structure into profile template" + customer_facing: false + + - id: "DEF-02" + name: "Strategy Map" + what: "Customer goals → strategies → capabilities → enablers mapping" + why: "Links Oracle products to business strategy. Arguably most important ECAL artefact" + how: "Leverage Customer Profile, annual reports, Value Navigator" + skill_support: "assist" + skill_notes: "Skill can draft from discovery notes; needs customer validation" + customer_facing: false + + - id: "DEF-03" + name: "Point of View (PoV)" + what: "Hypothesis about customer need, business benefit, SMART-evaluated" + why: "Go to customer with validated ideas that have significant business value" + how: "Review Pattern Library, Sales Central, quantify with Business Case" + skill_support: "auto_generate" + skill_notes: "Skill generates as 'value hypothesis' in value-story.yaml" + customer_facing: false + + - id: "DEF-04" + name: "Ideation Workshop" + what: "Design Thinking workshop to formulate and validate PoV" + why: "Ensure no bias, everyone thinking about customer needs" + how: "Brainstorming, group discussion, NEXT/V2V methodology" + skill_support: "reference" + skill_notes: "Skill can generate agenda and talking points" + customer_facing: true + + - id: "DEF-05" + name: "Business Case" + what: "Executive summary, drivers, recommended solution, benefits, timeline, risks" + why: "Communicate business value of cloud adoption" + how: "Use Value Navigator, work with customer on the model" + skill_support: "auto_generate" + skill_notes: "Skill generates via business-case.yaml template + deck" + customer_facing: true + + - id: "DEF-06" + name: "Value Story" + what: "Compelling vision of the PoV and why customer should adopt it" + why: "Continue discussion with customer, tailor the PoV" + how: "External Ideation template, PowerPoint, demo, mockup" + skill_support: "auto_generate" + skill_notes: "Skill generates via value-story.yaml template" + customer_facing: true + + validate: + - id: "DEF-07" + name: "Value Story Delivery" + what: "Half-day workshop presenting the Value Story to customer" + why: "Validate ideas with customer, do discovery through engagement" + how: "Presentations, whiteboards, exercises — be creative" + skill_support: "assist" + skill_notes: "Skill can generate workshop deck and discussion guide" + customer_facing: true + + plan: + - id: "DEF-08" + name: "Joint Engagement Plan (JEP)" + what: "Outline plan for Design phase: activities, resources, timeline, scope" + why: "Customer must commit resources; cannot design in isolation" + how: "Use ECAL template, be clear on scope and outcomes" + skill_support: "auto_generate" + skill_notes: "Skill generates via joint-engagement-plan.yaml template" + customer_facing: true + +# ═══════════════════════════════════════════════════════════════ +# DESIGN PHASE — CURRENT STEP +# ═══════════════════════════════════════════════════════════════ + +design: + current: + identify_constraints: + - id: "DES-01" + name: "Business Constraints" + what: "Time, funding, legal, compliance, resource, technology constraints" + why: "Constraints impact Future State Architecture, Roadmap, and Proposal" + how: "Collect from customer via workshops or Constraints Questionnaire" + skill_support: "assist" + skill_notes: "Skill captures in workload-profile.yaml constraints section" + customer_facing: false + + - id: "DES-02" + name: "Security Constraints" + what: "Security requirements, GDPR, regulatory needs" + why: "Security is major concern in cloud adoption" + how: "Security checklist, assess need for Oracle Security team" + skill_support: "assist" + skill_notes: "Skill flags compliance requirements from discovery notes" + customer_facing: true + + capture_current_state: + - id: "DES-03" + name: "Current State Process & Capability Map" + what: "Business processes/capabilities currently in scope, single-slide view" + why: "Shows Oracle understands customer business needs" + how: "Cloud Notation Business Architecture, neutral language (no Oracle buzzwords)" + skill_support: "reference" + skill_notes: "Business architecture — needs customer input" + customer_facing: true + + - id: "DES-04" + name: "Discovery Questionnaire" + what: "Structured collection of customer IT landscape" + why: "Build/understand current state, validate hypotheses" + how: "Send template to customer or fill collaboratively" + skill_support: "auto_generate" + skill_notes: "Skill generates via templates/discovery-questionnaire.yaml" + customer_facing: true + + - id: "DES-05" + name: "Current State Apps & Tech Portfolio" + what: "Systems, assets, integrations, strategy per application" + why: "Understand environment, build Current State Architecture" + how: "Portfolio Assessment Workbook — Systems, Assets, Integrations tabs" + skill_support: "assist" + skill_notes: "Skill structures from discovery questionnaire responses" + customer_facing: true + + - id: "DES-06" + name: "Security Baseline & Guidance" + what: "Current security posture and requirements" + why: "Baseline for designing security in future state" + how: "Security team assessment" + skill_support: "reference" + skill_notes: "References kb/well-architected/security.yaml" + customer_facing: true + + - id: "DES-07" + name: "Operational RACI (Current)" + what: "How customer currently runs operations — services, SLAs, responsibilities" + why: "Understand current ops model to design future ops that fit" + how: "Request operational definitions, frameworks (ITIL), service levels" + skill_support: "assist" + skill_notes: "Skill maps to operational-raci.yaml models" + customer_facing: false + + - id: "DES-08" + name: "Prioritization Matrix" + what: "Applications scored by criticality, fit, potential, risk" + why: "Focus on highest-value, lowest-risk initiatives first" + how: "Score each app on 5 criteria, weighted calculation" + skill_support: "auto_generate" + skill_notes: "Skill generates from discovery-questionnaire.yaml prioritization section" + customer_facing: true + + - id: "DES-09" + name: "Current State Architecture" + what: "Architecture diagrams of current environment in scope" + why: "Baseline for Future State Architecture" + how: "Receive from customer or draw using Cloud Notation" + skill_support: "assist" + skill_notes: "Skill can generate .drawio from description" + customer_facing: true + + future: + solution_design: + - id: "DES-10" + name: "Future State Process & Capability Map" + what: "Business capabilities covered by proposed solution" + why: "Shows Oracle understands and addresses customer needs" + how: "Map capabilities to Oracle products, use Cloud Notation" + skill_support: "reference" + skill_notes: "Business architecture — needs customer validation" + customer_facing: true + + - id: "DES-11" + name: "Future State Apps & Tech Portfolio" + what: "Oracle/3rd party products mapped to business capabilities" + why: "Bridges capability map to architecture" + how: "List capabilities per product, show integration relationships" + skill_support: "assist" + skill_notes: "Skill can map from service selection" + customer_facing: true + + - id: "DES-12" + name: "Future State Architecture" + what: "Application + Technology architecture diagrams" + why: "Communicates solution vision internally and to customer" + how: "Cloud Notation, review with ECAL team, iterate" + skill_support: "auto_generate" + skill_notes: "Skill generates .drawio via oci_diagram_gen.py" + customer_facing: true + + - id: "DES-13" + name: "Solution Roadmap" + what: "Phased implementation plan by value/risk" + why: "Complex solutions can't be delivered in one step" + how: "Divide by criteria (org, region, risk), prioritize high-value/low-risk" + skill_support: "auto_generate" + skill_notes: "Skill includes roadmap slide in deck" + customer_facing: true + + - id: "DES-14" + name: "Extension Catalog" + what: "Custom extensions beyond OOTB Oracle products" + why: "Highlights gaps requiring custom development" + how: "Identify missing capabilities, clarify implementation" + skill_support: "reference" + skill_notes: "Customer-specific; skill flags potential gaps" + customer_facing: true + + - id: "DES-15" + name: "Integration Catalog" + what: "All non-OOTB integrations with data flows, volumes, frequency" + why: "Communicate integration effort and responsibility" + how: "Map all inter-component data flows, include volumes" + skill_support: "assist" + skill_notes: "Skill can list integrations from architecture" + customer_facing: true + + - id: "DES-16" + name: "Information & BI Model" + what: "Data flows, MDM, analytics architecture" + why: "Document where data resides and how it's managed" + how: "Data Ecosystem reference architecture, Cloud Notation" + skill_support: "reference" + skill_notes: "Reference patterns in kb/patterns/" + customer_facing: true + + deployment_design: + - id: "DES-17" + name: "Datacentre Design" + what: "Which services run in which region/DC for each environment" + why: "Data locality, regulatory compliance, HA strategy" + how: "Map components to OCI regions, include on-prem/multi-cloud" + skill_support: "auto_generate" + skill_notes: "Skill includes region mapping in architecture" + customer_facing: true + + - id: "DES-18" + name: "Lifecycle Management Design" + what: "CI/CD, dev-to-prod automation, IaC approach" + why: "Automation is critical for cloud operational success" + how: "OCI DevOps, Terraform, Resource Manager" + skill_support: "assist" + skill_notes: "Skill recommends IaC approach in deployment design" + customer_facing: true + + - id: "DES-19" + name: "Network Design" + what: "Connectivity between DCs, within DCs, to users" + why: "Well-defined networking is critical for solution success" + how: "Cloud Notation, show gateways, protocols, public/private" + skill_support: "auto_generate" + skill_notes: "Skill includes networking in .drawio diagrams" + customer_facing: true + + - id: "DES-20" + name: "Identity & Access Design" + what: "How users access the system, authentication, authorization" + why: "IAM is critical non-functional requirement" + how: "End-to-end identity across proposed and existing systems" + skill_support: "assist" + skill_notes: "Skill recommends IAM approach from patterns" + customer_facing: true + + - id: "DES-21" + name: "Environment Catalog" + what: "List of all environments: prod, dev, test, DR with sizing" + why: "Understand total solution size for accurate BoM" + how: "Discuss with team and customer, use environment-catalogue.yaml" + skill_support: "auto_generate" + skill_notes: "Skill generates from kb/patterns/environment-catalogue.yaml" + customer_facing: true + + - id: "DES-22" + name: "Recovery Model" + what: "HA/DR architecture for different failure scenarios" + why: "Customers need to understand business continuity" + how: "Map RTO/RPO requirements to HA/DR patterns" + skill_support: "auto_generate" + skill_notes: "Skill generates from service-tiering.yaml + HA/DR patterns" + customer_facing: true + + - id: "DES-23" + name: "Management Design" + what: "Monitoring tools, alerting, operations management" + why: "Operational readiness is critical for customer confidence" + how: "Map to OCI native tools or customer's existing tools" + skill_support: "assist" + skill_notes: "Skill recommends monitoring stack in operations model" + customer_facing: true + + - id: "DES-24" + name: "Data Locality & Data Security" + what: "Where controlled data resides, encryption, key management" + why: "Data security is highest customer concern" + how: "Map data locations, encryption methods, key management" + skill_support: "assist" + skill_notes: "Skill flags data residency and encryption in architecture" + customer_facing: true + + transition_design: + - id: "DES-25" + name: "Implementation Plan" + what: "Prioritized implementation/migration of capabilities" + why: "Most organizations can't change architecture in one phase" + how: "Implementation partner responsibility; SA validates compatibility" + skill_support: "assist" + skill_notes: "Skill generates high-level migration plan slides" + customer_facing: true + + - id: "DES-26" + name: "Implementation RACI" + what: "Who does what during implementation" + why: "Clear roles prevent rework and delays" + how: "High-level tasks with R/A/C/I per role" + skill_support: "assist" + skill_notes: "Skill can draft from engagement-raci.yaml adopt section" + customer_facing: true + + - id: "DES-27" + name: "Provisioning Plan / Map" + what: "How each component gets provisioned and brought into service" + why: "Basis for actual provisioning of services" + how: "Map BoM components to provisioning steps" + skill_support: "reference" + skill_notes: "Delivery partner responsibility" + customer_facing: true + + - id: "DES-28" + name: "Adoption Plan" + what: "How customer adopts the cloud service post-provisioning" + why: "Ensure availability of resources and service levels" + how: "Cloud Adoption Services (CAS), business readiness" + skill_support: "reference" + skill_notes: "CAS/delivery partner responsibility" + customer_facing: true + + service_design: + - id: "DES-29" + name: "Operational Services RACI" + what: "Runtime responsibilities for operating/supporting the solution" + why: "Clear ops responsibilities prevent go-live surprises" + how: "Map to operational-raci.yaml models (fully/co/self managed)" + skill_support: "auto_generate" + skill_notes: "Skill generates from kb/patterns/operational-raci.yaml" + customer_facing: true + + - id: "DES-30" + name: "Operational Services Design" + what: "9 categories of operational service model for running the solution" + why: "Customer needs to know best practices for operating in cloud" + how: "Use 9 Enduring Capabilities framework" + skill_support: "assist" + skill_notes: "Skill generates operations model from template" + customer_facing: true + + demo_poc: + - id: "DES-31" + name: "PoC Joint Execution Plan" + what: "Scope, approach, timeline, success criteria for PoC" + why: "Structured PoC with bilateral commitment" + how: "Define scope, test architectures, use cases, ground rules" + skill_support: "reference" + skill_notes: "Customer-specific; skill can outline structure" + customer_facing: true + + - id: "DES-32" + name: "Demo / PoC Delivery" + what: "Execution of demo or proof of concept" + why: "Prove technical feasibility or address customer concerns" + how: "Demo Central, Cloud Trials, custom PoC" + skill_support: "reference" + skill_notes: "Execution activity, not a document" + customer_facing: true + + bom_sizing: + - id: "DES-33" + name: "Architecture Review" + what: "Team review of all architecture artefacts before BoM" + why: "Ensure team alignment on proposed solution" + how: "Review meeting with complete account team" + skill_support: "auto_generate" + skill_notes: "Skill runs WA validation via validate-architecture.py" + customer_facing: false + + - id: "DES-34" + name: "Bill of Materials & Sizing" + what: "All pricing and sizing for each solution component" + why: "Summarizes all software/hardware/service costs" + how: "Map from Future State Architecture + sizing model" + skill_support: "auto_generate" + skill_notes: "Skill generates cost estimate from kb/pricing/ + kb/sizing/" + customer_facing: true + + confirm: + - id: "DES-35" + name: "Solution Proposal" + what: "High-impact customer presentation of the proposed solution" + why: "Enable customer to decide whether to proceed to Deliver" + how: "Collect all designs, simplify, merge with business drivers" + skill_support: "auto_generate" + skill_notes: "Skill generates .pptx deck via oci_deck_gen.py" + customer_facing: true + + - id: "DES-36" + name: "Partner / Services Proposal" + what: "Implementation + operational services cost estimate" + why: "Complete solution = products + implementation + operations" + how: "Work with OC/ACS/partners based on Design artefacts" + skill_support: "reference" + skill_notes: "OC/ACS/partner responsibility" + customer_facing: true + + - id: "DES-37" + name: "RFx Response" + what: "Formal response to customer RFI/RFQ/RFP" + why: "Formal stage in customer buying cycle" + how: "Qualify, plan, manage response, QA, submit" + skill_support: "reference" + skill_notes: "Sales/BD responsibility; skill provides technical content" + customer_facing: true + + - id: "DES-38" + name: "Commercial Agreement" + what: "Commercial terms: cloud credits, BYOL, ELA, pricing" + why: "Customer needs to understand costs to proceed" + how: "Sales/CSM use BoM to produce commercial terms" + skill_support: "reference" + skill_notes: "Sales responsibility" + customer_facing: true + +# ═══════════════════════════════════════════════════════════════ +# DELIVER PHASE +# ═══════════════════════════════════════════════════════════════ + +deliver: + adopt: + establish_governance: + - id: "DEL-01" + name: "Architecture Governance" + what: "Compliance review process for projects vs. enterprise architecture" + why: "Ensure original vision is realized during implementation" + how: "Regular meetings, architecture compliance checklists" + skill_support: "reference" + skill_notes: "Implementation team responsibility" + customer_facing: true + + - id: "DEL-02" + name: "Operating Model" + what: "How existing processes/tools change for the new solution" + why: "Without agreed model, risk of project failure" + how: "Assess org capabilities using Operational Service RACI/Design" + skill_support: "auto_generate" + skill_notes: "Skill generates via templates/operations-model.yaml" + customer_facing: true + + - id: "DEL-03" + name: "Business Transition Plan" + what: "Managing business change and readiness for go-live" + why: "Business must change processes/org to realize value" + how: "Analysis of business strategy, process standardization" + skill_support: "reference" + skill_notes: "CAS/delivery partner responsibility" + customer_facing: true + + - id: "DEL-04" + name: "Training & Communication Plan" + what: "End-user training strategy and project communication" + why: "Effective communication is difference between success and failure" + how: "Review Adoption Plan, determine training delivery method" + skill_support: "reference" + skill_notes: "Delivery partner responsibility" + customer_facing: true + + manage_project: + - id: "DEL-05" + name: "Project Governance" + what: "Oversight framework: steering committee, change control, escalation" + why: "Key elements that make a project successful" + how: "Define governance structure, reporting, risk management" + skill_support: "reference" + skill_notes: "PM responsibility" + customer_facing: true + + - id: "DEL-06" + name: "Project Plan" + what: "Detailed steps with timelines for deployment" + why: "Identify critical path, stick to timelines" + how: "Detailed iteration of high-level Implementation Plan" + skill_support: "reference" + skill_notes: "Implementation partner responsibility" + customer_facing: true + + - id: "DEL-07" + name: "Project Delivery" + what: "Execute the project per plan and governance" + why: "Correctly mobilising prevents delays" + how: "Onboard team, enable, kick off, deliver" + skill_support: "reference" + skill_notes: "Execution activity" + customer_facing: false + + - id: "DEL-08" + name: "Signoff & Close" + what: "Validate and sign off all elements of solution" + why: "Formal confirmation before moving to production" + how: "Confirm configs, integrations, data migrations complete" + skill_support: "reference" + skill_notes: "PM responsibility" + customer_facing: true + + customer_adoption: + - id: "DEL-09" + name: "Contracts & Policy Review" + what: "Review all agreements associated with the project" + why: "Incomplete understanding puts project at risk" + how: "Review with customer, note risks" + skill_support: "reference" + skill_notes: "Legal/commercial responsibility" + customer_facing: true + + - id: "DEL-10" + name: "Readiness Assessment" + what: "Ensure project team has right skills and assets" + why: "Lack of tools/docs/processes becomes obstacle" + how: "Review requirements, define readiness assets" + skill_support: "reference" + skill_notes: "PM responsibility" + customer_facing: false + + - id: "DEL-11" + name: "Configuration & Customization" + what: "Build the solution: configs, customizations, integrations" + why: "Build per agreed architecture and deployment design" + how: "Delivery partner method (e.g., TCM)" + skill_support: "reference" + skill_notes: "Delivery team execution" + customer_facing: false + + - id: "DEL-12" + name: "Security Build" + what: "Final review and build of security configuration" + why: "Confirm solution built per Identity & Access Design" + how: "Delivery partner method" + skill_support: "reference" + skill_notes: "Delivery team execution" + customer_facing: false + + - id: "DEL-13" + name: "Data Migration" + what: "Extract, load, validate data from source to target" + why: "Confirm data migrates correctly and safely" + how: "DMS, GoldenGate, Data Pump, RMAN, ZDM" + skill_support: "assist" + skill_notes: "Skill recommends migration tools in architecture" + customer_facing: false + + - id: "DEL-14" + name: "Validation & Testing" + what: "Business process validation and technical testing" + why: "Confirm solution ready for production" + how: "Process playback, final validation, sign-off" + skill_support: "reference" + skill_notes: "Delivery team + customer execution" + customer_facing: true + + - id: "DEL-15" + name: "Business Enablement" + what: "End-user training delivery" + why: "Users must be effective with new solution" + how: "Per Training Plan, Oracle University" + skill_support: "reference" + skill_notes: "Training team responsibility" + customer_facing: true + + - id: "DEL-16" + name: "Go-Live & Hypercare" + what: "Cutover to production + intensive post-go-live support" + why: "Reduce risk and provide better customer experience" + how: "Support, data integrity, availability monitoring" + skill_support: "auto_generate" + skill_notes: "Skill generates go-live-checklist.yaml" + customer_facing: true + + operate: + - id: "DEL-17" + name: "Cloud Operating Framework" + what: "Detailed 52-week operational plan based on Operating Model" + why: "Cloud operations very different from on-premise" + how: "Create plan covering all 9 operational capability areas" + skill_support: "reference" + skill_notes: "ACS/delivery partner responsibility" + customer_facing: true + + - id: "DEL-18" + name: "OCI Operationalization Framework" + what: "5-milestone methodology for deploying workloads to OCI" + why: "Blueprint for operationalizing OCI quickly" + how: "Preparation → Conceptual Arch → Physical Arch → Operations → Workloads" + skill_support: "reference" + skill_notes: "NAC framework; skill aligns architecture with milestones" + customer_facing: true + + improve: + - id: "DEL-19" + name: "Retrospective" + what: "Team review: what worked, what didn't, what to improve" + why: "Key ECAL principle: learn fast, capture feedback" + how: "3-4 hour meeting, inspect → identify → output actions" + skill_support: "auto_generate" + skill_notes: "Skill generates via templates/lessons-learned.yaml" + customer_facing: false + + - id: "DEL-20" + name: "Value Realization" + what: "Post-implementation review against Business Case metrics" + why: "Validate if benefits were actually realized" + how: "Formal or informal review of adoption, impact, benefits" + skill_support: "auto_generate" + skill_notes: "Skill generates via templates/success-criteria.yaml" + customer_facing: true + + - id: "DEL-21" + name: "Customer Success Story" + what: "Single-slide summary: who, challenge, approach, value" + why: "Share success with wider community and other customers" + how: "Collect from Business Case, build storyline with customer" + skill_support: "reference" + skill_notes: "Account team responsibility; skill can structure" + customer_facing: true + + - id: "DEL-22" + name: "Improvement Actions" + what: "Consolidated improvement plan from operations insights" + why: "Continuous improvement, may lead back to new DEFINE cycle" + how: "Consolidate actions, discuss with customer PM, track" + skill_support: "reference" + skill_notes: "Feeds back into DEFINE for next engagement" + customer_facing: true + +# ═══════════════════════════════════════════════════════════════ +# SUMMARY STATS +# ═══════════════════════════════════════════════════════════════ + +summary: + total_artefacts: 60 + auto_generate: 16 # skill produces complete artefact + assist: 14 # skill provides partial content + reference: 30 # skill references but team produces + define_phase: 8 + design_phase: 38 + deliver_phase: 22 diff --git a/kb/patterns/engagement-raci.yaml b/kb/patterns/engagement-raci.yaml new file mode 100644 index 0000000..1669c50 --- /dev/null +++ b/kb/patterns/engagement-raci.yaml @@ -0,0 +1,916 @@ +--- +last_verified: 2026-03-18 +pattern: + id: engagement-raci + name: "ECAL Engagement RACI" + category: governance +--- + +description: > + Defines who is Responsible, Accountable, Consulted, and Informed for each + ECAL artefact during the engagement lifecycle. This is the PROCESS RACI + (who does what during pre-sales), not the operational RACI (who runs + the solution post-go-live). Source: ECAL 3.1 Practitioners Guides. + +roles: + EA: "Enterprise Architect" + SE: "Solution Engineer" + BA: "Business Architect" + CS: "Customer Success" + DA: "Deployment Architect" + VC: "Value Consulting" + Sales: "Sales" + MS: "Managed Services" + CloudOps: "Cloud Operations" + Customer: "Customer" + +raci_definitions: + R: "Responsible — executes and delivers the artefact" + A: "Accountable — signs off, owns the outcome (max one per artefact)" + C: "Consulted — contributes expertise, reviewed before completion" + I: "Informed — notified of the result" + +# ═══════════════════════════════════════════════════════════════ +# DEFINE PHASE +# ═══════════════════════════════════════════════════════════════ + +define: + + ideate: + step_purpose: > + Understand the customer situation and propose a potential solution + (value story) that resonates with their business drivers. + artefacts: + - name: "Customer Profile" + EA: "A/R" + SE: "R" + BA: "C" + CS: "C" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "I" + - name: "Strategy Map" + EA: "C" + SE: "R/A" + BA: "I" + CS: "I" + DA: "I" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "" + - name: "Point of View" + EA: "A/R" + SE: "R" + BA: "C" + CS: "C" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "" + - name: "Ideation Workshop" + EA: "C" + SE: "A/R" + BA: "C" + CS: "C" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "R" + - name: "Business Case" + # Roles filled during Ideate are minimal; revisited in Confirm + EA: "" + SE: "" + BA: "" + CS: "" + DA: "" + VC: "" + Sales: "" + MS: "" + CloudOps: "" + Customer: "" + - name: "Value Story" + EA: "A/R" + SE: "R" + BA: "C" + CS: "C" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "" + + validate: + step_purpose: > + Present the Point of View to the customer and validate the ideas. + Assess impact and customer willingness to investigate a solution. + artefacts: + - name: "Value Story Delivery" + EA: "A/R" + SE: "R" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "R" + Customer: "R" + + plan: + step_purpose: > + Once the PoV is validated, plan the Design phase activities, + resources, and timeline as a joint plan with the customer. + artefacts: + - name: "Joint Engagement Plan" + EA: "A/R" + SE: "C" + BA: "R" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "R" + +# ═══════════════════════════════════════════════════════════════ +# DESIGN PHASE +# ═══════════════════════════════════════════════════════════════ + +design: + + current: + step_purpose: > + Understand the customer's context: current architecture, business + processes, constraints, and priorities relevant to the engagement. + artefacts: + - name: "Business Constraints" + EA: "R/A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "I" + Sales: "C" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Security Constraints" + EA: "R/A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "C" + Sales: "C" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Current State Process & Capability Map" + EA: "R/A" + SE: "R" + BA: "C" + CS: "C" + DA: "C" + VC: "C" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "R/C" + - name: "Discovery Questionnaire" + EA: "R/A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "R" + Sales: "C" + MS: "C" + CloudOps: "I" + Customer: "R/C" + - name: "Current State Apps & Tech Portfolio" + EA: "R/A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "I" + Sales: "C" + MS: "C" + CloudOps: "I" + Customer: "R/C" + - name: "Operational RACI (Current)" + EA: "R/A" + SE: "" + BA: "" + CS: "C" + DA: "C" + VC: "" + Sales: "C" + MS: "" + CloudOps: "" + Customer: "R/C" + - name: "Prioritization Matrix" + EA: "R/A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "I" + Sales: "C" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Current State Architecture" + EA: "R/A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "I" + Sales: "C" + MS: "C" + CloudOps: "I" + Customer: "" + + future: + step_purpose: > + Create the overall solution: architecture, deployment design, + transition plan, service design, sizing, and BoM. + + solution_design: + - name: "Future State Process & Capability Map" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Future State Apps & Tech Portfolio" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Future State Architecture" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Solution Roadmap" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Extension Catalog" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Integration Catalog" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Information & BI Model" + EA: "R/A" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + + deployment_design: + - name: "Deployment Design" + EA: "R/A" + SE: "" + BA: "C" + CS: "I" + DA: "R/C" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Datacentre Design" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Lifecycle Management Design" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Network Design" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Identity & Access Design" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Environment Catalog" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Recovery Model" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Management Design" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Data Locality & Data Security" + EA: "R/A" + SE: "" + BA: "C" + CS: "" + DA: "R/C" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + + transition_design: + - name: "Implementation Plan" + EA: "A" + SE: "C" + BA: "" + CS: "I" + DA: "R/C" + VC: "" + Sales: "C" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Implementation RACI" + EA: "A" + SE: "C" + BA: "" + CS: "" + DA: "R" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + - name: "Provisioning Plan / Map" + EA: "A" + SE: "C" + BA: "C" + CS: "" + DA: "R" + VC: "" + Sales: "" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Adoption Plan" + EA: "A" + SE: "C" + BA: "" + CS: "" + DA: "R" + VC: "" + Sales: "" + MS: "" + CloudOps: "I" + Customer: "C" + + service_design: + - name: "Operational Services RACI" + EA: "A" + SE: "" + BA: "" + CS: "" + DA: "" + VC: "" + Sales: "R" + MS: "C" + CloudOps: "I" + Customer: "C" + - name: "Operational Services Design" + EA: "A" + SE: "" + BA: "" + CS: "" + DA: "" + VC: "" + Sales: "R" + MS: "C" + CloudOps: "I" + Customer: "C" + + demo_poc: + - name: "PoC Joint Execution Plan" + EA: "A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "I" + - name: "Demo / PoC Delivery" + EA: "A" + SE: "" + BA: "R" + CS: "" + DA: "C" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "I" + + bom_sizing: + - name: "Architecture Review" + EA: "C/I" + SE: "" + BA: "C/R" + CS: "" + DA: "C/I" + VC: "" + Sales: "C/I" + MS: "" + CloudOps: "R/A" + Customer: "" + - name: "Bill of Materials & Sizing" + EA: "C/I" + SE: "" + BA: "C/R" + CS: "" + DA: "C/I" + VC: "" + Sales: "C/I" + MS: "" + CloudOps: "R/A" + Customer: "" + + confirm: + step_purpose: > + Assemble design work into a high-impact Solution Proposal that + enables the customer to validate and decide to proceed. + artefacts: + - name: "Solution Proposal" + EA: "A/R" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "C" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "I" + - name: "Partner / Services Proposal" + EA: "C" + SE: "" + BA: "" + CS: "" + DA: "A/R" + VC: "" + Sales: "R" + MS: "" + CloudOps: "C" + Customer: "C" + - name: "RFx Response" + EA: "C" + SE: "C" + BA: "A/R/C" + CS: "I" + DA: "" + VC: "" + Sales: "" + MS: "" + CloudOps: "C" + Customer: "I" + - name: "Commercial Agreement" + EA: "C" + SE: "C" + BA: "C" + CS: "" + DA: "C" + VC: "" + Sales: "C" + MS: "" + CloudOps: "A/R" + Customer: "R" + +# ═══════════════════════════════════════════════════════════════ +# DELIVER PHASE +# ═══════════════════════════════════════════════════════════════ + +deliver: + + adopt: + step_purpose: > + Transition from proposed architecture to implementation. + Pre-sales → delivery handover without rework. + + establish_governance: + - name: "Architecture Governance" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Operating Model" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Business Transition Plan" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Training & Communication Plan" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + + manage_project: + - name: "Project Governance" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Project Plan" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Project Delivery" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Signoff & Close" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + + customer_adoption: + - name: "Contracts & Policy Review" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Readiness Assessment" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Configuration & Customization" + EA: "A/R" + SE: "" + BA: "I" + CS: "" + DA: "R" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Security Build" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Data Migration" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Validation & Testing" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Business Enablement" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + - name: "Go-Live & Hypercare" + EA: "C" + SE: "" + BA: "I" + CS: "" + DA: "R/A" + VC: "R/C/I" + Sales: "R" + MS: "I" + CloudOps: "" + Customer: "R" + + operate: + step_purpose: > + Solution is live. Operational capabilities from the Operating Model + are performed to retain vitality. Business outcome reporting active. + artefacts: + - name: "Cloud Operating Framework" + EA: "" + SE: "" + BA: "" + CS: "I" + DA: "R" + VC: "" + Sales: "R" + MS: "R" + CloudOps: "" + Customer: "R/A" + - name: "OCI Operationalization Framework" + EA: "" + SE: "" + BA: "" + CS: "" + DA: "R/A" + VC: "" + Sales: "R/C" + MS: "" + CloudOps: "" + Customer: "C/I" + + improve: + step_purpose: > + Capture learnings, validate value delivery, identify next + hypothesis to tackle. Feed improvements back into ECAL. + artefacts: + - name: "Retrospective" + EA: "C/I" + SE: "C/I" + BA: "" + CS: "R" + DA: "R/A" + VC: "" + Sales: "R" + MS: "R" + CloudOps: "" + Customer: "C" + - name: "Value Realization" + EA: "C/I" + SE: "C/I" + BA: "" + CS: "R" + DA: "R/A" + VC: "" + Sales: "R" + MS: "R" + CloudOps: "" + Customer: "C" + - name: "Customer Success Story" + EA: "C/I" + SE: "C/I" + BA: "" + CS: "R" + DA: "R/A" + VC: "" + Sales: "R" + MS: "R" + CloudOps: "" + Customer: "C" + - name: "Improvement Actions" + EA: "C/I" + SE: "C/I" + BA: "" + CS: "R/A" + DA: "R" + VC: "" + Sales: "R" + MS: "R" + CloudOps: "" + Customer: "C" + +# ═══════════════════════════════════════════════════════════════ +# LESSONS LEARNED PER STEP (from ECAL Practitioners Guides) +# ═══════════════════════════════════════════════════════════════ + +lessons_learned: + + define: + - "Listen to the customer and also to each other" + - "Don't defend your Point of View to the customer, listen for ideas" + - "Understand what the customer is saying" + - "Make Point of View SMART: Specific, Measurable, Attainable, Relevant, Time-based" + - "Think carefully about whether it's right to proceed" + - "Do you have a senior customer sponsor that will support you?" + + current: + - "Frame the problem — don't gather requirements" + - "Capture current state in terms of people, process and technology" + - "Be collaborative with the customer and share everything" + - "Use whatever works — try top down and bottom up" + - "Understand the business context, business drivers and desired outcomes" + - "Be very clear about the scope — which systems are in and out" + + future: + - "Always look for a quick win for the customer" + - "The operational model is just as important as the architecture" + - "Don't forget non-production environments" + - "Security and data locality must be understood early, don't leave it too late" + + confirm: + - "Propositions must be SMART in a customer context" + - "Ensure you have evidence to support all positions" + - "The Roadmap must be achievable" + - "Think about the person you are presenting to — what does it mean for them" + - "Quality of the presentation is very important — it must look professional" + + adopt: + - "Look for a Minimum Viable Product (or service) that delivers real customer benefits early" + - "Incremental delivery phases should maximize customer value per the roadmap" + - "Always consider how to help the customer with adoption and ownership" + - "Architecture artifacts are constantly refined as more is learned during Deliver" + + improve: + - "Leverage any opportunity to capture a successful outcome as a customer story" + - "Look back at every project for opportunities to improve" + - "Keep in touch with the customer and track how the project is going" + - "Share your learning with your team" + - "Feed back suggested improvements to the ECAL process" diff --git a/kb/patterns/environment-catalogue.yaml b/kb/patterns/environment-catalogue.yaml new file mode 100644 index 0000000..9c4169f --- /dev/null +++ b/kb/patterns/environment-catalogue.yaml @@ -0,0 +1,48 @@ +--- +last_verified: 2026-03-16 +pattern: + id: environment-catalogue + name: "Environment Catalogue" + category: operations +--- + +description: > + Defines the standard environments for each workload tier. The skill + generates the catalogue from the workload profile by expanding each + database into its required environments based on tier. + +environment_templates: + + platinum: + environments: + - { env: "Production", sizing: "full", isolation: "dedicated CDB, dedicated infra", ha: "RAC + ADG sync" } + - { env: "Pre-Production", sizing: "75% of prod", isolation: "dedicated CDB", ha: "optional RAC" } + - { env: "Dev/Test", sizing: "25% of prod", isolation: "shared CDB OK", ha: "none" } + - { env: "DR", sizing: "100% of prod", isolation: "same as prod", ha: "sync ADG or GG active-active" } + + gold: + environments: + - { env: "Production", sizing: "full", isolation: "dedicated CDB, private endpoint", ha: "TAC + ADG local" } + - { env: "Pre-Production", sizing: "50% of prod", isolation: "refreshable clone OK", ha: "none" } + - { env: "Dev/Test", sizing: "25% of prod", isolation: "full clone, auto-stop nights", ha: "none" } + - { env: "DR", sizing: "100% of prod", isolation: "cross-region ADG", ha: "async ADG" } + + silver: + environments: + - { env: "Production", sizing: "full", isolation: "shared CDB OK", ha: "single instance" } + - { env: "Pre-Production", sizing: "50% of prod", isolation: "refreshable clone", ha: "none" } + - { env: "Dev/Test", sizing: "25% of prod", isolation: "full clone, auto-stop", ha: "none" } + - { env: "DR", sizing: "N/A", isolation: "backup to Object Storage", ha: "restore from backup" } + + bronze: + environments: + - { env: "Production", sizing: "full", isolation: "shared CDB, oversubscribed OK", ha: "none" } + - { env: "Dev/Test", sizing: "same as prod", isolation: "shared CDB", ha: "none" } + - { env: "DR", sizing: "N/A", isolation: "DB export only", ha: "restore from export" } + +cost_optimization_rules: + - "Pre-Prod: use Refreshable Clones when read-only testing is sufficient" + - "Dev/Test: auto-stop weeknights (7PM-7AM) + weekends = ~65% savings" + - "Dev/Test: use minimum OCPU (1-2), no auto-scaling" + - "DR standby: consider pilot light (Terraform on-demand) for Silver/Bronze" + - "Bronze DR: DB export to Object Storage is cheapest, no standby OCPU" diff --git a/kb/patterns/networking-basic.yaml b/kb/patterns/networking-basic.yaml new file mode 100644 index 0000000..3ae0530 --- /dev/null +++ b/kb/patterns/networking-basic.yaml @@ -0,0 +1,98 @@ +# Pattern: Basic VCN Networking +# Composable architecture block for single-VCN 3-tier network layout + +pattern: + name: "Basic VCN Networking" + id: networking_basic + category: networking + +description: | + Single VCN with a 3-tier subnet layout providing public, application, and data + tiers. Suitable for standalone workloads, small-to-medium deployments, and as a + starting point before evolving to hub-spoke topology. Public subnet hosts load + balancers and bastion, private app subnet hosts compute/containers, and private + data subnet hosts databases and storage. + +topology: + vcn: + cidr: "10.0.0.0/16" + dns_label: "vcnmain" + subnets: + - name: "Public Subnet" + cidr: "10.0.0.0/24" + type: public + purpose: "Load balancers, bastion host, WAF endpoints" + usable_ips: 251 + - name: "App Private Subnet" + cidr: "10.0.1.0/24" + type: private + purpose: "Compute instances, OKE worker nodes, application servers" + usable_ips: 251 + - name: "Data Private Subnet" + cidr: "10.0.2.0/24" + type: private + purpose: "Databases (ADB-S, DBCS), file storage, object storage private endpoints" + usable_ips: 251 + +components: + - name: "VCN" + purpose: "Virtual Cloud Network — isolated network boundary" + - name: "Public Subnet" + purpose: "Internet-facing resources (LBaaS, Bastion)" + - name: "App Private Subnet" + purpose: "Application tier — no direct internet access" + - name: "Data Private Subnet" + purpose: "Database tier — no direct internet access" + - name: "Internet Gateway (IGW)" + purpose: "Inbound and outbound internet access for public subnet" + - name: "NAT Gateway" + purpose: "Outbound-only internet access for private subnets (patches, APIs)" + - name: "Service Gateway" + purpose: "Private access to OCI services (Object Storage, ADB-S) without internet" + - name: "Route Tables" + purpose: "Per-subnet routing rules for IGW, NAT GW, and Service GW" + - name: "Network Security Groups (NSGs)" + purpose: "Stateful firewall rules applied per VNIC for fine-grained access control" + +preconditions: + - "Compartment created for the workload" + - "CIDR ranges planned and non-overlapping with other VCNs or on-premises networks" + - "Region and availability domain selected" + +implied_services: + - service: igw + reason: "Internet access for public subnet resources" + - service: nat_gateway + reason: "Outbound internet for private subnets without exposing inbound" + - service: service_gateway + reason: "Private connectivity to OCI services (Object Storage, Autonomous DB)" + +design_rules: + - "Place application and database resources in private subnets — never expose directly to internet" + - "Use Network Security Groups (NSGs) instead of Security Lists for per-resource granularity" + - "Use Service Gateway for all OCI service access to avoid internet routing and egress costs" + - "Assign one route table per subnet for clarity and maintainability" + - "Use DNS hostnames within the VCN for service discovery" + - "Enable VCN Flow Logs on all subnets for security auditing" + - "Place load balancers in the public subnet; backends in the app private subnet" + +gotchas: + - "VCN CIDR blocks are immutable after creation — plan address space carefully before provisioning" + - "NSG rules are limited to 120 rules per NSG (60 ingress + 60 egress by default)" + - "Security Lists are stateful by default but have a 5-tuple limit; prefer NSGs for complex rule sets" + - "NAT Gateway is regional and supports up to 20 Gbps; may need multiple for high-throughput workloads" + - "Service Gateway only routes to OCI services — not a general-purpose private link" + - "Subnet size cannot be changed after creation; plan for growth" + - "Default security list allows all egress; tighten for production" + +cost_impact: + - "VCN: free (no charge for the VCN itself)" + - "Subnets: free" + - "Internet Gateway: free" + - "NAT Gateway: per-hour charge + per-GB data processing" + - "Service Gateway: free" + - "VCN Flow Logs: charged via Logging service ingestion rates" + +references: + - "https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/overview.htm" + - "https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/bestpractices.htm" diff --git a/kb/patterns/networking-hub-spoke/hub-spoke-drg.yaml b/kb/patterns/networking-hub-spoke/hub-spoke-drg.yaml new file mode 100644 index 0000000..6e82644 --- /dev/null +++ b/kb/patterns/networking-hub-spoke/hub-spoke-drg.yaml @@ -0,0 +1,86 @@ +# Pattern: Hub-Spoke Network Topology via DRG +# Composable architecture block for enterprise networking + +pattern: + name: "Hub-Spoke Network via DRG" + id: hub_spoke_drg + category: networking + +description: | + Hub-spoke topology using OCI Dynamic Routing Gateway (DRG) v2 as the central + routing hub. Spoke VCNs attach to the DRG for inter-VCN routing, on-premises + connectivity (FastConnect/VPN), and centralized security inspection. + +topology: + hub: + components: + - "DRG (Dynamic Routing Gateway) — central router" + - "Hub VCN (optional) — for shared services, Network Firewall, Bastion" + - "Service Gateway — OCI service access without internet" + - "NAT Gateway — outbound internet for private subnets" + - "Network Firewall (optional) — deep packet inspection, IDS/IPS" + spokes: + components: + - "Spoke VCN per workload/environment" + - "VCN attachment to DRG" + - "Route table on DRG attachment for traffic steering" + +use_when: + - "Multiple workloads or environments needing isolation" + - "On-premises connectivity via FastConnect or VPN" + - "Centralized security inspection required" + - "Shared services (Bastion, DNS, NTP) across workloads" + - "Enterprise landing zone deployment" + +when_not_to_use: + - "Single VCN deployment (no need for hub-spoke complexity)" + - "< 3 VCNs and no on-prem connectivity" + - "POC or dev-only environment" + +preconditions: + - "DRG v2 (default in all regions)" + - "Non-overlapping CIDR ranges across VCNs" + - "Sufficient IP address planning" + +implied_services: + - service: drg + reason: "Central routing hub" + - service: vcn + reason: "Hub and spoke VCNs" + - service: service_gateway + reason: "OCI service access" + - service: nat_gateway + reason: "Outbound internet for private subnets" + - service: route_tables + reason: "Traffic steering via DRG attachments" + +optional_services: + - service: network_firewall + when: "Deep packet inspection, IDS/IPS required" + placement: "Hub VCN, inline between DRG and spokes" + - service: fastconnect + when: "On-premises connectivity" + - service: vpn + when: "Backup connectivity or lower-cost option" + +conflicts: + - pattern: "local_peering" + reason: "DRG replaces local peering for inter-VCN routing. Don't mix both." + +cidr_guidance: + - "Use /16 per VCN for growth headroom" + - "Use /24 per subnet (251 usable IPs)" + - "Reserve /8 or /12 supernet for all OCI CIDRs" + - "Avoid overlapping with on-prem ranges" + - "Example: 10.0.0.0/16 (hub), 10.1.0.0/16 (spoke 1), 10.2.0.0/16 (spoke 2)" + +cost_impact: + - "DRG: free (no charge for the gateway itself)" + - "VCN peering via DRG: no data transfer charges within same region" + - "Cross-region via DRG: data transfer charges apply" + - "Network Firewall: per-hour + per-GB processing" + - "FastConnect: port fee + data transfer (inbound free)" + +references: + - "https://docs.oracle.com/en-us/iaas/Content/Network/Tasks/managingDRGs.htm" + - "https://docs.oracle.com/en/solutions/hub-spoke-network-drg/" diff --git a/kb/patterns/networking-hub-spoke/pattern.yaml b/kb/patterns/networking-hub-spoke/pattern.yaml new file mode 100644 index 0000000..b3db8b8 --- /dev/null +++ b/kb/patterns/networking-hub-spoke/pattern.yaml @@ -0,0 +1,72 @@ +--- +last_verified: 2026-03-14 +pattern: Hub-and-Spoke Networking +category: networking +--- + +description: | + Hub-and-spoke VCN topology for enterprise OCI deployments. + Centralizes shared services (firewalls, DNS, connectivity) in a hub VCN + with spoke VCNs for workloads. + +architecture: + hub_vcn: + purpose: "Shared services, connectivity, security inspection" + components: + - "DRG (Dynamic Routing Gateway)" + - "Network Firewall (optional)" + - "DNS resolver" + - "FastConnect / VPN termination" + - "Bastion service" + - "Management subnet" + cidr_example: "10.0.0.0/16" + + spoke_vcns: + purpose: "Workload isolation" + examples: + - name: "Production" + cidr: "10.1.0.0/16" + subnets: + - "Public subnet (LB): 10.1.0.0/24" + - "Private subnet (App): 10.1.1.0/24" + - "Private subnet (DB): 10.1.2.0/24" + - name: "Non-Production" + cidr: "10.2.0.0/16" + - name: "DMZ" + cidr: "10.3.0.0/16" + + connectivity: + hub_to_spoke: "VCN attachments via DRG" + spoke_to_spoke: "Transit routing through DRG (no direct peering needed)" + on_prem: "FastConnect or VPN to DRG in hub" + +when_to_use: + - "Multiple environments (prod, non-prod, DMZ)" + - "Centralized connectivity to on-prem" + - "Security inspection required between workloads" + - "Shared services (DNS, NTP, monitoring) across workloads" + +when_NOT_to_use: + - "Single-VCN deployments" + - "Simple architectures with 1-2 workloads" + +ip_planning: + recommendation: | + Plan CIDR ranges before deployment. Overlapping CIDRs prevent peering. + Use /16 for VCNs, /24 for subnets. Reserve ranges for future growth. + example_scheme: + hub: "10.0.0.0/16" + production: "10.1.0.0/16" + non_production: "10.2.0.0/16" + dmz: "10.3.0.0/16" + dr_region: "10.128.0.0/9" + +gotchas: + - id: HUB-001 + issue: "DRG route table limits" + impact: "Max 200 route rules per DRG route table" + recommendation: "Aggregate routes where possible, plan for scale" + - id: HUB-002 + issue: "Network Firewall throughput" + impact: "Inspect all traffic adds latency and cost" + recommendation: "Only inspect north-south and selected east-west traffic" diff --git a/kb/patterns/operational-raci.yaml b/kb/patterns/operational-raci.yaml new file mode 100644 index 0000000..bc77837 --- /dev/null +++ b/kb/patterns/operational-raci.yaml @@ -0,0 +1,70 @@ +--- +last_verified: 2026-03-16 +pattern: + id: operational-raci + name: "Operational RACI Matrix" + category: operations +--- + +description: > + Defines responsibilities across customer and Oracle/partner teams for + ongoing operations. R=Responsible (does the work), A=Accountable (owns + the outcome), C=Consulted, I=Informed. Generated from the engagement + model (fully managed, co-managed, or self-managed). + +# Three operational models — the skill selects based on discovery +models: + + fully_managed: + description: "Oracle/partner manages everything. Customer consumes DB as a service." + trigger: "Customer says 'DBA-less', 'no DBA team', 'fully managed', or team_skills is low" + raci: + - { activity: "DB provisioning", customer: "I", oracle: "R/A" } + - { activity: "DB patching & upgrades", customer: "I", oracle: "R/A" } + - { activity: "Backup & recovery", customer: "I", oracle: "R/A" } + - { activity: "Performance monitoring", customer: "I", oracle: "R/A" } + - { activity: "Performance tuning", customer: "C", oracle: "R/A" } + - { activity: "Security configuration", customer: "A", oracle: "R" } + - { activity: "Access control (IAM)", customer: "R/A", oracle: "C" } + - { activity: "Data Safe / audit review", customer: "A", oracle: "R" } + - { activity: "Incident response (DB)", customer: "I", oracle: "R/A" } + - { activity: "Incident response (app)", customer: "R/A", oracle: "C" } + - { activity: "Capacity planning", customer: "C", oracle: "R/A" } + - { activity: "DR testing", customer: "A", oracle: "R" } + - { activity: "Schema changes", customer: "R/A", oracle: "I" } + - { activity: "App deployment", customer: "R/A", oracle: "I" } + - { activity: "Network changes", customer: "R/A", oracle: "C" } + - { activity: "Cost optimization review", customer: "A", oracle: "R" } + - { activity: "Oracle SR escalation", customer: "C", oracle: "R/A" } + - { activity: "Compliance reporting", customer: "A", oracle: "R" } + + co_managed: + description: "Customer has DBAs but wants Oracle/partner for complex ops." + trigger: "Customer says 'we have DBAs but no cloud experience', or team exists but is small" + raci: + - { activity: "DB provisioning", customer: "R", oracle: "C" } + - { activity: "DB patching & upgrades", customer: "R", oracle: "C" } + - { activity: "Backup & recovery", customer: "R/A", oracle: "C" } + - { activity: "Performance monitoring", customer: "R", oracle: "I" } + - { activity: "Performance tuning", customer: "R", oracle: "C" } + - { activity: "Security configuration", customer: "R/A", oracle: "C" } + - { activity: "Access control (IAM)", customer: "R/A", oracle: "I" } + - { activity: "Incident response (DB)", customer: "R", oracle: "C" } + - { activity: "Incident response (app)", customer: "R/A", oracle: "I" } + - { activity: "Capacity planning", customer: "R", oracle: "C" } + - { activity: "DR testing", customer: "R", oracle: "C" } + - { activity: "Schema changes", customer: "R/A", oracle: "I" } + - { activity: "Oracle SR escalation", customer: "R", oracle: "C" } + + self_managed: + description: "Customer manages everything. Oracle provides platform only." + trigger: "Customer has strong DBA team with cloud experience, or explicitly says 'we manage'" + raci: + - { activity: "DB provisioning", customer: "R/A", oracle: "I" } + - { activity: "DB patching & upgrades", customer: "R/A", oracle: "I" } + - { activity: "Backup & recovery", customer: "R/A", oracle: "I" } + - { activity: "Performance monitoring", customer: "R/A", oracle: "I" } + - { activity: "Security configuration", customer: "R/A", oracle: "I" } + - { activity: "Incident response", customer: "R/A", oracle: "I" } + - { activity: "DR testing", customer: "R/A", oracle: "I" } + - { activity: "Oracle SR escalation", customer: "R/A", oracle: "C" } diff --git a/kb/patterns/security-baseline/cis-baseline.yaml b/kb/patterns/security-baseline/cis-baseline.yaml new file mode 100644 index 0000000..756c38c --- /dev/null +++ b/kb/patterns/security-baseline/cis-baseline.yaml @@ -0,0 +1,64 @@ +# Pattern: CIS Security Baseline +# Composable architecture block for security foundation + +pattern: + name: "CIS OCI Security Baseline" + id: cis_baseline + category: security + +description: | + Security baseline aligned with CIS Oracle Cloud Infrastructure Foundations + Benchmark. Provides IAM, network, encryption, monitoring, and governance + controls as a foundation for all OCI deployments. + +always_include: true +applies_to: "all_workloads" + +controls: + identity: + - "Least-privilege IAM policies per compartment" + - "MFA for all human users" + - "Break-glass admin account (not used for daily ops)" + - "Instance principals for service-to-service auth" + - "Federation with customer IdP (AD, Okta, SAML)" + - "No API keys stored in code or config files" + + compartments: + - "Hierarchical compartment structure by workload/environment" + - "Network, Security, AppDev, Database compartments at minimum" + - "Defined tags for cost tracking, environment, owner" + + networking: + - "NSGs preferred over Security Lists" + - "No SSH from 0.0.0.0/0 — use Bastion Service" + - "Service Gateway for OCI service access" + - "VCN Flow Logs enabled" + - "WAF on all internet-facing endpoints" + + encryption: + - "OCI Vault for customer-managed keys" + - "TDE with customer-managed keys for databases" + - "Encryption at rest for all storage services" + - "TLS on all load balancers" + + monitoring: + - "Cloud Guard enabled with detector recipes" + - "OCI Audit with 365-day retention" + - "Vulnerability Scanning for compute instances" + - "Security Zones for production compartments" + + governance: + - "Budgets and cost alerts per compartment" + - "Notification topics for security events" + - "Tagging enforcement via tag defaults" + +implied_services: + - cloud_guard + - oci_vault + - bastion + - vulnerability_scanning + - notifications + +references: + - "https://www.cisecurity.org/benchmark/oracle_cloud" + - "https://github.com/oracle-quickstart/oci-cis-landingzone-quickstart" diff --git a/kb/patterns/security-baseline/pattern.yaml b/kb/patterns/security-baseline/pattern.yaml new file mode 100644 index 0000000..c471d1c --- /dev/null +++ b/kb/patterns/security-baseline/pattern.yaml @@ -0,0 +1,96 @@ +--- +last_verified: 2026-03-14 +pattern: Security Baseline +category: security +--- + +description: | + Minimum security configuration for any OCI deployment. Covers IAM, + networking, encryption, monitoring, and compliance foundations. + +layers: + identity: + name: "Identity & Access Management" + requirements: + - item: "MFA for all users" + implementation: "Enable MFA in IAM, enforce via policy" + priority: critical + - item: "Least-privilege policies" + implementation: "Use compartments + IAM policies, avoid broad allow statements" + priority: critical + - item: "No API keys for production" + implementation: "Use instance principals or resource principals" + priority: high + - item: "Federated identity" + implementation: "SAML/OIDC federation with corporate IdP" + priority: high + - item: "Break-glass procedure" + implementation: "Documented emergency access with MFA-protected tenancy admin" + priority: high + + network: + name: "Network Security" + requirements: + - item: "Private subnets for all workloads" + implementation: "Only load balancers and bastion in public subnets" + priority: critical + - item: "NSGs over security lists" + implementation: "Granular VNIC-level firewall rules" + priority: high + - item: "Service Gateway for OCI services" + implementation: "Private path to Object Storage, ADB, etc." + priority: high + - item: "WAF for public-facing applications" + implementation: "OCI WAF with OWASP rule sets" + priority: high + - item: "No public IPs on databases" + implementation: "Use private endpoints, Bastion for admin access" + priority: critical + + encryption: + name: "Encryption" + requirements: + - item: "Encryption at rest (default)" + implementation: "OCI encrypts all data at rest by default (Oracle-managed keys)" + priority: critical + - item: "Customer-managed keys for sensitive data" + implementation: "OCI Vault with HSM-backed keys" + priority: high + - item: "TLS 1.2+ for all connections" + implementation: "Enforce in load balancer, database, API configurations" + priority: critical + - item: "TDE for databases" + implementation: "Enabled by default on ADB, configure on DBCS/ExaCS" + priority: critical + + monitoring: + name: "Monitoring & Audit" + requirements: + - item: "Audit logging enabled" + implementation: "OCI Audit service (enabled by default, 365-day retention)" + priority: critical + - item: "Cloud Guard enabled" + implementation: "Detect misconfigurations and threats" + priority: high + - item: "VCN Flow Logs" + implementation: "Enable on critical subnets for network forensics" + priority: medium + - item: "Alarms for security events" + implementation: "Monitoring alarms + Notifications for critical events" + priority: high + - item: "Data Safe for databases" + implementation: "Security assessment, user assessment, data masking" + priority: high + + compute: + name: "Compute Security" + requirements: + - item: "OS Management for patching" + implementation: "OCI OS Management service for automated patching" + priority: high + - item: "Bastion service for SSH" + implementation: "No direct SSH from internet, use OCI Bastion" + priority: critical + - item: "Instance metadata v2" + implementation: "Disable v1 to prevent SSRF attacks" + priority: high diff --git a/kb/patterns/service-tiering.yaml b/kb/patterns/service-tiering.yaml new file mode 100644 index 0000000..ccad738 --- /dev/null +++ b/kb/patterns/service-tiering.yaml @@ -0,0 +1,106 @@ +--- +last_verified: 2026-03-16 +pattern: + id: service-tiering + name: "Database Service Tiering Model" + category: operations +--- + +description: > + Maps customer workloads to standardized service tiers based on business + criticality, availability requirements, and compliance needs. Each tier + defines the HA/DR topology, support model, backup strategy, and operational + responsibilities. Derived from Oracle's ECAL transformational framework. + +tiers: + + platinum: + label: "Platinum" + description: "Mission-critical, zero tolerance for downtime" + example_workloads: ["Core banking", "Payment processing", "Trading systems", "Telco network"] + sla: + uptime: "99.999%" + rto: "0 (near-zero with TAC + RAC)" + rpo: "0 (synchronous replication)" + planned_downtime: "0 (even for upgrades — edition-based redefinition)" + support: "24x7, 10 min response" + architecture: + ha: "RAC (multi-node) + TAC/AC" + dr: "Synchronous ADG + GoldenGate active-active (optional)" + backup: "ZDLRA or Autonomous Recovery Service, continuous" + isolation: "Full physical isolation — dedicated CDB, dedicated infrastructure" + encryption: "TDE + DB Vault + Key Vault + SQL*Net encryption" + oci_mapping: + primary: "ExaCS or ADB-D (for RAC + full control)" + alternative: "ADB-S only if application is TAC-compatible and RPO=0 is met by local ADG" + cost_impact: "Highest — dedicated infra, dual-site, 24x7 support" + + gold: + label: "Gold" + description: "Business-critical, tolerable brief outage" + example_workloads: ["CRM", "ERP", "eCommerce", "Risk analytics"] + sla: + uptime: "99.99%" + rto: "1-4 hours" + rpo: "5-15 minutes" + planned_downtime: "4x 4hr/year maintenance windows" + support: "24x7, 4 hour response" + architecture: + ha: "TAC + local ADG (auto-failover)" + dr: "Asynchronous cross-region ADG" + backup: "Autonomous Recovery Service, incremental forever" + isolation: "Dedicated CDB (dedicated home), instance caged (not oversubscribed)" + encryption: "TDE + Key Vault" + oci_mapping: + primary: "ADB-S Serverless (with TAC + ADG)" + alternative: "ExaCS if RAC explicitly needed or >128 OCPU sustained" + cost_impact: "High — primary + standby OCPU cost, cross-region DR" + + silver: + label: "Silver" + description: "Important but recoverable from backup" + example_workloads: ["HR systems", "Financials", "Internal portals", "Reporting"] + sla: + uptime: "99.5%" + rto: "1 day" + rpo: "1 day" + planned_downtime: "4x 4hr/year maintenance windows" + support: "24x7, 8 hour response" + architecture: + ha: "Single instance or RAC optional" + dr: "Offsite backup only — no real-time replication" + backup: "Automated daily backup, 30-day retention" + isolation: "Shared CDB (shared binaries) acceptable" + encryption: "TDE recommended, DB Vault optional" + oci_mapping: + primary: "ADB-S Serverless (no ADG needed)" + alternative: "Base DB Service for legacy compatibility" + cost_impact: "Medium — no standby cost, shared infrastructure" + + bronze: + label: "Bronze" + description: "Non-critical, best-effort recovery" + example_workloads: ["Dev/Test", "Room booking", "Internal tools", "Sandbox"] + sla: + uptime: "98%" + rto: "Best effort" + rpo: "Best effort" + planned_downtime: "Flexible" + support: "Business hours, 2 day response" + architecture: + ha: "None — single instance" + dr: "Database export or backup to Object Storage" + backup: "Weekly backup, 7-day retention" + isolation: "Shared CDB, oversubscribed OK" + encryption: "TDE optional" + oci_mapping: + primary: "ADB-S Serverless (minimum OCPU, auto-scaling off)" + alternative: "VM DB Standard Edition for cost" + cost_impact: "Lowest — minimal resources, no redundancy" + +auto_assignment_rules: + - "If customer says '99.99%' or 'zero downtime' → Platinum or Gold (ask which)" + - "If customer says 'PCI' or 'banking regulations' → minimum Gold" + - "If workload type is 'dev/test' → Bronze unless explicitly stated otherwise" + - "If customer says 'reporting' or 'read-only' → Silver unless SLA says otherwise" + - "If no SLA stated → assign Silver and flag for confirmation" diff --git a/kb/pricing/ai-ml.yaml b/kb/pricing/ai-ml.yaml new file mode 100644 index 0000000..fbaf85c --- /dev/null +++ b/kb/pricing/ai-ml.yaml @@ -0,0 +1,69 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI AI and Machine Learning service pricing. +currency: USD +--- + +# ── OCI Generative AI ───────────────────────────────────────────── + +generative_ai: + cohere_models: + per_10k_transactions: 0.0001 - 0.0003 + notes: "Command, Embed models" + meta_llama: + per_10k_transactions: 0.0001 - 0.0004 + xai_grok: + per_1m_tokens_range: "$0.00015 – $0.0018" + google_gemini: + per_1m_tokens_range: "$0.000075 – $0.0006" + +generative_ai_agents: + per_10k_transactions: 0.0001 + notes: "Plus storage and ingestion fees" + +# ── Anomaly Detection ───────────────────────────────────────────── + +anomaly_detection: + training: "Free up to 100M data points/month per region" + +# ── Data Science ─────────────────────────────────────────────────── + +data_science: + notebook_sessions: + notes: "Billed at compute shape rates (OCPU/hour + GB memory/hour + GPU options)" + jobs: + notes: "Same compute pricing as notebook sessions" + model_deployment: + notes: "Compute + networking + storage combined pricing" + model_catalog_storage: + notes: "Object Storage rates" + +# ── Document Understanding ───────────────────────────────────────── + +document_understanding: + training_per_hour: 0.50 - 1.00 + ocr_per_1k_transactions: 0.025 - 0.050 + +# ── Language Services ────────────────────────────────────────────── + +language: + pretrained_per_transaction: 0.0001 - 0.0003 + custom_training: "Available at additional cost" + +# ── Vision ───────────────────────────────────────────────────────── + +vision: + image_analysis_per_1k: 0.0001 - 0.0003 + custom_training_per_hour: 0.50 - 1.00 + +# ── Speech ───────────────────────────────────────────────────────── + +speech: + per_transcription_hour: 0.0025 - 0.005 + +# ── Digital Assistant ────────────────────────────────────────────── + +digital_assistant: + per_request: 0.009 + minimum: "250 requests/hour" diff --git a/kb/pricing/analytics.yaml b/kb/pricing/analytics.yaml new file mode 100644 index 0000000..9ef0d52 --- /dev/null +++ b/kb/pricing/analytics.yaml @@ -0,0 +1,36 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Analytics and BI pricing. +currency: USD +--- + +# ── Oracle Analytics Cloud ───────────────────────────────────────── + +oracle_analytics_cloud: + per_user_month: + professional: 0.99 + enterprise: 2.00 + ocpu_based: + notes: "Also available as OCPU-based pricing for embedded/API use" + +# ── Essbase ──────────────────────────────────────────────────────── + +essbase: + ocpu_per_hour: 0.0252 + +# ── Data Lake / Big Data ────────────────────────────────────────── + +ai_data_platform: + notes: "Pricing by AI Data Platform unit" + +big_data_service_oracle: + ocpu_per_hour: 0.0252 + notes: "Oracle distribution, includes Spark SQL compute" + +big_data_service_cloudera: + ocpu_per_hour: 0.0168 - 0.0336 + +data_flow: + ocpu_usage: "Free" + notes: "Apache Spark, no charge for OCPU usage" diff --git a/kb/pricing/compute.yaml b/kb/pricing/compute.yaml new file mode 100644 index 0000000..a668496 --- /dev/null +++ b/kb/pricing/compute.yaml @@ -0,0 +1,191 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Compute pricing for estimation purposes. + Prices are approximate and subject to change. Always verify with + OCI pricing calculator for final quotes. +currency: USD +--- + +# ── Virtual Machine Instances ────────────────────────────────────── + +flexible_shapes: + VM.Standard.E6.Flex: + ocpu_per_hour: 0.0168 + memory_per_gb_hour: 0.0011 + monthly_730h: + ocpu: 12.26 + memory_per_gb: 0.80 + notes: "AMD EPYC (latest gen), best price/performance for general workloads" + + VM.Standard.E5.Flex: + ocpu_per_hour: 0.0210 + memory_per_gb_hour: 0.0014 + monthly_730h: + ocpu: 15.33 + memory_per_gb: 1.02 + notes: "AMD EPYC, general-purpose workloads" + + VM.Standard.E4.Flex: + ocpu_per_hour: 0.0252 + memory_per_gb_hour: 0.0017 + monthly_730h: + ocpu: 18.40 + memory_per_gb: 1.24 + notes: "AMD EPYC (previous gen)" + + VM.Standard.E3.Flex: + ocpu_per_hour: 0.0252 + memory_per_gb_hour: 0.0017 + monthly_730h: + ocpu: 18.40 + memory_per_gb: 1.24 + notes: "AMD EPYC (older gen)" + + VM.Standard3.Flex: + ocpu_per_hour: 0.0336 + memory_per_gb_hour: 0.0022 + monthly_730h: + ocpu: 24.53 + memory_per_gb: 1.61 + notes: "Intel Xeon, for Intel-optimized workloads" + + VM.Standard.A2.Flex: + ocpu_per_hour: 0.0254 + memory_per_gb_hour: 0.0017 + monthly_730h: + ocpu: 18.54 + memory_per_gb: 1.24 + notes: "Ampere Arm (latest A2), best cost for Arm-compatible workloads" + + VM.Standard.A1.Flex: + ocpu_per_hour: 0.0127 + memory_per_gb_hour: 0.0008 + monthly_730h: + ocpu: 9.27 + memory_per_gb: 0.58 + free_tier: "3,000 OCPU-hours + 18,000 GB-hours per month" + notes: "Ampere Arm A1, eligible for Always Free tier" + + VM.Standard.x86.Generic: + ocpu_per_hour: 0.0252 + memory_per_gb_hour: 0.0017 + notes: "Generic x86 pricing when specific shape not yet selected" + + VM.Optimized3.Flex: + ocpu_per_hour: 0.0504 + memory_per_gb_hour: 0.0034 + monthly_730h: + ocpu: 36.79 + memory_per_gb: 2.48 + notes: "Highest single-thread performance" + +# ── Bare Metal Instances ─────────────────────────────────────────── + +bare_metal_shapes: + BM.Standard.E5: + ocpu_per_hour: 0.0168 + notes: "AMD EPYC bare metal" + BM.Standard.E4: + ocpu_per_hour: 0.0210 + notes: "AMD EPYC bare metal (previous gen)" + BM.Standard.E3: + ocpu_per_hour: 0.0420 + notes: "AMD EPYC bare metal (older gen)" + BM.Standard3: + ocpu_per_hour: 0.0504 + notes: "Intel Xeon bare metal" + BM.Standard.A1: + ocpu_per_hour: 0.0127 + notes: "Ampere Arm bare metal" + BM.Standard.x9: + ocpu_per_hour: 0.0672 + notes: "Intel Xeon x9 bare metal" + +# ── GPU Accelerated Compute ──────────────────────────────────────── + +gpu_shapes: + # NVIDIA Blackwell (latest) + BM.GPU.B300: + per_gpu_hour: 26.88 + gpu_count: 8 + gpu_model: "NVIDIA B300" + notes: "Blackwell latest, HPC/AI training" + BM.GPU.B200: + per_gpu_hour: 20.16 + gpu_count: 8 + gpu_model: "NVIDIA B200" + notes: "Blackwell, large-scale training" + + # NVIDIA Hopper + BM.GPU.H200: + per_gpu_hour: 18.54 + gpu_count: 8 + gpu_model: "NVIDIA H200" + notes: "Hopper H200, large model training" + BM.GPU.H100: + per_gpu_hour: 12.96 + gpu_count: 8 + gpu_model: "NVIDIA H100" + notes: "Hopper H100, training workloads" + + # NVIDIA Ampere / Ada + BM.GPU.A100: + per_gpu_hour: 6.72 + gpu_count: 8 + gpu_model: "NVIDIA A100 40GB/80GB" + notes: "Training and inference, 40GB or 80GB HBM2e" + VM.GPU.A10.1: + per_gpu_hour: 0.72 + gpu_count: 1 + gpu_model: "NVIDIA A10" + notes: "Inference workloads, cost-effective" + VM.GPU.A10.2: + per_gpu_hour: 0.72 + gpu_count: 2 + gpu_model: "NVIDIA A10" + notes: "Inference workloads" + BM.GPU.L40S: + per_gpu_hour: 2.42 + gpu_count: 4 + gpu_model: "NVIDIA L40S" + notes: "Ada Lovelace, inference and graphics" + +# ── Secure Desktops ─────────────────────────────────────────────── + +secure_desktops: + per_desktop_month: 1750.00 + notes: "Managed virtual desktop infrastructure" + +# ── Estimation Helpers ───────────────────────────────────────────── + +estimation_helpers: + typical_app_server: + config: "VM.Standard.E5.Flex, 4 OCPU, 64 GB" + monthly: 126.60 # (4 * 15.33) + (64 * 1.02) + typical_web_server: + config: "VM.Standard.E5.Flex, 2 OCPU, 16 GB" + monthly: 46.98 # (2 * 15.33) + (16 * 1.02) + typical_bastion: + config: "VM.Standard.E5.Flex, 1 OCPU, 8 GB" + monthly: 23.49 # (1 * 15.33) + (8 * 1.02) + typical_arm_app_server: + config: "VM.Standard.A1.Flex, 4 OCPU, 24 GB" + monthly: 50.96 # (4 * 9.27) + (24 * 0.58) + +discounts: + preemptible: "~50% off on-demand" + capacity_reservation: "~15% off on-demand (85% of list)" + reserved_1yr: "~35% off on-demand" + reserved_3yr: "~55% off on-demand" + universal_credits: "Volume discounts on committed spend" + +os_pricing: + free: + - "Oracle Linux" + - "CentOS" + - "Ubuntu" + - "Oracle Autonomous Linux" + paid: + windows_server: + notes: "Pricing varies by edition, added to compute cost" diff --git a/kb/pricing/containers-serverless.yaml b/kb/pricing/containers-serverless.yaml new file mode 100644 index 0000000..e449bcd --- /dev/null +++ b/kb/pricing/containers-serverless.yaml @@ -0,0 +1,46 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Containers, Kubernetes, and Serverless pricing. +currency: USD +--- + +# ── Oracle Kubernetes Engine (OKE) ──────────────────────────────── + +oke: + basic_cluster: + per_hour: "Free" + notes: "Basic cluster management at no cost" + enhanced_cluster: + per_cluster_hour: 0.10 + monthly_per_cluster: 73.00 + notes: "Enhanced features: virtual nodes, workload identity, etc." + virtual_nodes: + notes: "Additional per-pod pricing on enhanced clusters" + worker_nodes: + notes: "Billed at standard compute shape rates" + +# ── Container Instances ──────────────────────────────────────────── + +container_instances: + notes: "Billed at compute rates: OCPU/hour + GB memory/hour" + shapes_available: + - "A1 (Arm)" + - "E3 (AMD)" + - "E4 (AMD)" + +# ── Functions (Serverless) ───────────────────────────────────────── + +functions: + invocations: + free_per_month: 2000000 + per_invocation_beyond: 0.0000002 + memory: + free_gb_seconds_per_month: 400000 + per_gb_second_beyond: 0.00001417 + notes: "Generous free tier, very cost-effective for event-driven workloads" + +# ── OCI Batch ────────────────────────────────────────────────────── + +batch: + notes: "Pay-as-you-go compute pricing, no additional service charge" diff --git a/kb/pricing/database.yaml b/kb/pricing/database.yaml new file mode 100644 index 0000000..f27e944 --- /dev/null +++ b/kb/pricing/database.yaml @@ -0,0 +1,283 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Database Service pricing for estimation purposes. +currency: USD +disclaimer: | + Approximate pricing for estimation (within ~15%). Actual pricing varies by + region, commitment level, and negotiated discounts. Always verify against + https://www.oracle.com/cloud/pricing/ +--- + +# ── Autonomous AI Database (Serverless) ─────────────────────────── +# NOTE: ADB now uses ECPU billing model (not OCPU) + +autonomous_database_serverless: + billing_unit: ECPU + license_included: + ecpu_per_hour: 0.0420 + byol: + ecpu_per_hour: 0.0168 + storage: + per_gb_month: 0.0850 + per_tb_month: 87.04 + auto_scaling: + notes: "Billed per ECPU-second for additional ECPUs above base. Same rate." + adg_standby: + notes: "Standby ECPU cost at same rate as primary. Storage shared (no extra)." + backup: + notes: "Automated backups included in storage cost. Manual backups use Object Storage rates." + examples: + 8_ecpu_2tb_payg: + monthly_li: "$2,699" + monthly_byol: "$1,150" + breakdown: "8 ECPU × $0.0420/hr × 730hr + 2048GB × $0.0850" + 8_ecpu_2tb_byol_with_adg: + monthly: "$2,300" + breakdown: "2 × (8 ECPU × $0.0168/hr × 730hr) + 2048GB × $0.0850" + +# ── Autonomous AI JSON Database ──────────────────────────────────── + +autonomous_json_database: + billing_unit: ECPU + ecpu_per_hour: 0.0420 + storage_per_gb_month: 0.0850 + notes: "Same pricing as ADB-S, optimized for JSON document workloads" + +# ── Autonomous Database Dedicated ────────────────────────────────── + +autonomous_database_dedicated: + billing_unit: ECPU + infrastructure: + # X9M / X8M — fixed rack shapes (legacy, still available in some regions) + x9m: + quarter_rack: + base_per_hour: "~$10.50" + monthly_estimate: "$7,500–$8,000" + included: "2 DB servers, 3 storage servers, 100TB usable" + half_rack: + base_per_hour: "~$21.00" + monthly_estimate: "$15,000–$16,000" + included: "4 DB servers, 6 storage servers, 200TB usable" + full_rack: + base_per_hour: "~$42.00" + monthly_estimate: "$30,000–$32,000" + included: "8 DB servers, 12 storage servers, 400TB usable" + # X11M — elastic scaling (new default as of 2025, replaces fixed rack shapes) + x11m: + model: "Elastic — 2 to 32 DB servers + 3 to 64 storage servers (no fixed rack shapes)" + minimum_config: "2 DB servers minimum (cannot order 1 DB server) + 3 storage servers" + billing: "Per ECPU per second (8-ECPU minimum per active DB VM)" + infrastructure_rate: "NOT PUBLICLY LISTED — requires OCI PDM/sales confirmation" + monthly_estimate: "Confirm with Oracle sales; X9M rates used as proxy for estimation" + notes: | + X11M is the current-generation Exadata infrastructure (announced 2025). + Key changes vs X9M: + - No fixed quarter/half/full rack — elastic scale in/out + - MINIMUM: 2 DB servers + 3 storage servers (cannot provision less) + - Infrastructure pricing parity with X9M (no premium) per Oracle comms, + but exact per-hour rates are not published in the public price list + - ECPU billing by the second (vs hourly in older generations) + - Improved compute, storage density, and RDMA throughput vs X9M + ecpu_pricing: + license_included_per_hour: 0.0672 + byol_per_hour: 0.0504 + notes: | + Infrastructure cost is fixed per rack size (X9M) or elastic per server (X11M). ECPU cost is on top. + DEP (Database Enterprise Patching) co-location is at next maintenance window. + Billing starts only at AVAILABLE state. + Provisioning takes days to weeks depending on capacity. + IMPORTANT: For X11M sizing, confirm infrastructure rates with PDM — use X9M as proxy only. + +# ── Base Database Service (DBCS) ────────────────────────────────── + +dbcs: + billing_unit: ECPU + license_included: + ecpu_per_hour_se: 0.0336 # Standard Edition + ecpu_per_hour_ee: 0.0420 # Enterprise Edition + ecpu_per_hour_ee_hp: 0.0504 # High Performance + ecpu_per_hour_ee_ep: 0.0672 # Extreme Performance — BYOL only on newer docs + byol: + ecpu_per_hour: 0.0168 + storage_per_gb_month: 0.0255 + notes: | + SE = Standard Edition, EE = Enterprise Edition, + HP = High Performance (partitioning, advanced compression, etc.), + EP = Extreme Performance (RAC, Active Data Guard, In-Memory). + Backup storage charged separately at Object Storage rates. + examples: + 4_ecpu_ee_hp_500gb: + monthly_li: "$160" + breakdown: "4 ECPU × $0.0504/hr × 730hr + 500GB × $0.0255" + +# ── Exadata Database Service Dedicated ───────────────────────────── + +exadata_dedicated: + billing_unit: ECPU + infrastructure: + # X9M — fixed rack shapes (available in all regions) + x9m: + quarter_rack: + per_hour: 2.10 + monthly_estimate: "$1,533" + half_rack: + per_hour: 6.30 + monthly_estimate: "$4,599" + full_rack: + per_hour: 12.60 + monthly_estimate: "$9,198" + # X11M — elastic scaling (current generation, replaces fixed rack shapes) + x11m: + model: "Elastic — 2 to 32 DB servers + 3 to 64 storage servers" + minimum_config: "2 DB servers + 3 storage servers (hard minimum — applies to ADB-D and ExaCS)" + billing: "Per ECPU per second (8-ECPU minimum per active DB VM)" + infrastructure_rate: "Line items exist in OCI price list ('Database Server - X11M' and 'Storage Server - X11M') but dollar amounts not visible without Oracle Cost Estimator or authenticated access" + proxy_for_estimation: "Use X9M quarter rack ($2.10/hr) as proxy for 2 DB + 3 storage" + notes: | + X11M is confirmed current-gen Exadata for both ExaCS and ADB-D on OCI. + MINIMUM: 2 DB servers + 3 storage servers — cannot provision less (verified against Oracle datasheets). + Per-server line items exist in price list but amounts require Oracle Cost Estimator or PDM quote. + Use X9M quarter rack ($2.10/hr infrastructure) as proxy for minimum config estimation. + ecpu_pricing: + license_included_per_hour: 0.0504 + byol_per_hour: 0.0420 + what_is_included: + - "Exadata smart storage (flash cache, storage indexes)" + - "Automated patching (GI + DB separate schedules)" + - "Automated backups to Object Storage" + - "RAC licensing included in Extreme Performance edition" + what_is_extra: + - "Data Guard setup and standby infrastructure" + - "Additional storage beyond rack capacity" + - "Object Storage for long-term backup retention" + gotchas: + - "Infrastructure cost is fixed regardless of ECPU utilization (X9M)." + - "X11M elastic model: scale in/out by server, not by rack." + - "Provisioning takes 4-8 hours." + - "X11M has improved storage density, RDMA throughput, and CPU vs X9M." + +# ── Exadata Exascale ─────────────────────────────────────────────── + +exadata_exascale: + billing_unit: ECPU + ecpu_per_hour: 0.0504 + storage: + notes: "Storage priced separately per Exascale vault capacity" + notes: "Elastic scaling of compute and storage independently. Next-gen Exadata." + +# ── Exadata Cloud@Customer ───────────────────────────────────────── + +exadata_cloud_at_customer: + billing_unit: ECPU + ecpu_pricing: + license_included_per_hour: 0.0504 + byol_per_hour: 0.0420 + infrastructure: + base_per_hour: 7.35 + monthly_estimate: "$5,365" + notes: "Exadata infrastructure in customer datacenter, managed by Oracle" + +# ── MySQL HeatWave ───────────────────────────────────────────────── + +mysql_heatwave: + billing_unit: ECPU + ecpu_per_hour: 0.0180 + storage_per_gb_month: 0.0085 + heatwave_cluster: + notes: "Additional capacity pricing for HeatWave in-memory acceleration" + free_tier: + notes: "MySQL HeatWave Free Tier available with limits" + mysql_heatwave_on_aws: + ecpu_per_hour: 0.0180 + notes: "Cross-cloud deployment, same base pricing" + +# ── PostgreSQL ───────────────────────────────────────────────────── + +postgresql: + ocpu_per_hour: 0.0168 + storage_per_gb_month: 0.0850 + notes: "Managed PostgreSQL, includes HA options" + +# ── Cache with Redis ─────────────────────────────────────────────── + +cache_redis: + up_to_10gb: + per_gb_hour: 0.0194 + monthly_per_gb: 14.16 + above_10gb: + per_gb_hour: 0.0136 + monthly_per_gb: 9.93 + +# ── OpenSearch ───────────────────────────────────────────────────── + +opensearch: + per_node_hour: 0.0252 + notes: "HA configuration, pricing per node" + +# ── NoSQL Database ───────────────────────────────────────────────── + +nosql: + provisioned: + storage_per_gb_month: 0.0085 + write_unit_per_month: 0.0001 + read_unit_per_month: 0.000020 + on_demand: + storage_per_gb_month: 0.0085 + notes: "Auto-scaled read/write units" + free_tier: + storage_gb: 25 + write_units: 50 + read_units: 50 + max_tables: 3 + +# ── TimesTen for OKE ────────────────────────────────────────────── + +timesten: + x86_ocpu_per_hour: 0.0252 + arm_ocpu_per_hour: 0.0168 + notes: "In-memory relational database deployed on Kubernetes" + +# ── Data Safe ────────────────────────────────────────────────────── + +data_safe: + per_target_month: 0.0150 + audit_records_per_10k_month: 0.0001 + +# ── Database Migration ───────────────────────────────────────────── + +database_migration: + per_migration_hour: 0.0420 + notes: "First 183 days free if migration is active" + +# ── Database Autonomous Recovery ─────────────────────────────────── + +database_autonomous_recovery: + per_gb_virtualized_month: 0.0085 + notes: "Autonomous backup recovery service" + +# ── GoldenGate ───────────────────────────────────────────────────── + +goldengate: + ocpu_per_hour: 0.0252 + byol_available: true + notes: "Real-time data integration and replication" + +# ── Estimation Helpers ───────────────────────────────────────────── + +estimation_helpers: + monthly_hours: 730 + byol_savings: "50-60% on ECPU cost for Oracle Database products" + adb_s_sweet_spot: "4-32 ECPUs with auto-scaling. Below 4, consider Always Free. Above 128, consider ADB-D or ExaCS." + cost_comparison_tip: | + When comparing with AWS RDS or Azure SQL MI, include: + - OCI inbound data transfer is free + - OCI outbound is ~80% cheaper than AWS + - OCI BYOL discount is deeper than AWS BYOL + - ADB-S includes patching, tuning, indexing — RDS does not + - ADB now uses ECPU (not OCPU) — 1 ECPU ≈ fraction of an OCPU + +reserved_discounts: + one_year: "~35-40% vs PAYG" + three_year: "~55-65% vs PAYG" + notes: "Available for all database services. Commitment is per-region." diff --git a/kb/pricing/developer.yaml b/kb/pricing/developer.yaml new file mode 100644 index 0000000..3fcd550 --- /dev/null +++ b/kb/pricing/developer.yaml @@ -0,0 +1,59 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Developer Services pricing. +currency: USD +--- + +# ── APEX Application Development ────────────────────────────────── + +apex: + ecpu_per_hour: 0.36 + free_tier: true + notes: "Low-code development platform, free tier available" + +# ── Visual Builder ───────────────────────────────────────────────── + +visual_builder: + ocpu_per_hour: 0.0252 + notes: "Low-code app builder with storage" + +# ── DevOps ───────────────────────────────────────────────────────── + +devops: + pipeline_execution: "Free" + notes: "No charge for pipeline execution; compute/memory billed separately" + +# ── Blockchain Platform ─────────────────────────────────────────── + +blockchain: + ocpu_per_hour: 0.0252 - 0.0504 + notes: "Plus storage costs" + +# ── IoT Platform ────────────────────────────────────────────────── + +iot: + ecpu_per_hour: 0.0360 + notes: "Plus storage costs" + +# ── Media Flow ───────────────────────────────────────────────────── + +media_flow: + per_minute: 0.005 - 0.120 + notes: "Varies by codec, resolution, and fps" + +# ── Media Streams ────────────────────────────────────────────────── + +media_streams: + per_gb_packaged: 0.015 + +# ── Tuxedo ───────────────────────────────────────────────────────── + +tuxedo: + ocpu_per_hour: 0.0252 - 0.0504 + notes: "For legacy Tuxedo workload migration" + +# ── WebCenter ────────────────────────────────────────────────────── + +webcenter: + ocpu_per_hour: 0.0252 - 0.0504 diff --git a/kb/pricing/hybrid-vmware.yaml b/kb/pricing/hybrid-vmware.yaml new file mode 100644 index 0000000..38e6c9c --- /dev/null +++ b/kb/pricing/hybrid-vmware.yaml @@ -0,0 +1,53 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Hybrid Cloud and VMware Solution pricing. +currency: USD +--- + +# ── Oracle Cloud VMware Solution (OCVS) ─────────────────────────── + +vmware_solution: + base_nodes: + BM.Standard2.12: + per_node_hour: 2.10 - 4.20 + notes: "Hourly to 3-year commitment range" + BM.Standard3.16: + per_node_hour: 3.03 - 6.06 + BM.Standard.E4.32: + per_node_hour: 4.20 - 8.40 + BM.Standard.E5.48: + per_node_hour: 4.99 - 9.98 + expansion: + ocpu_per_hour: 0.0168 - 0.0420 + notes: "Commitment-based pricing" + hcx_enterprise: + ocpu_per_hour: 0.0084 + minimum_commitment: "1 month" + gpu_storage_nodes: + per_node_hour: 2.10 - 4.20 + notes: "Commitment-based pricing" + +# ── Compute Cloud@Customer ───────────────────────────────────────── + +compute_cloud_at_customer: + e6_compute: + ocpu_per_hour: 0.0336 - 0.0504 + storage: + per_gb_month: 0.0085 - 0.0170 + load_balancer: + per_hour: 0.10 + +# ── Managed Service for Mac ──────────────────────────────────────── + +managed_mac: + per_server_hour: + m2_pro: 1.20 + m4_pro: 4.20 + notes: "Range depends on Mac model (M2 Pro to M4 Pro)" + +# ── Roving Edge Infrastructure ───────────────────────────────────── + +roving_edge: + per_device_day: 300 - 1000 + notes: "Portable compute/GPU/storage for edge and disconnected scenarios" diff --git a/kb/pricing/integration.yaml b/kb/pricing/integration.yaml new file mode 100644 index 0000000..1a91e56 --- /dev/null +++ b/kb/pricing/integration.yaml @@ -0,0 +1,55 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Integration and Middleware pricing. +currency: USD +--- + +# ── Integration Cloud ───────────────────────────────────────────── + +integration_cloud: + per_5k_messages_hour: 0.36 - 0.72 + notes: "Standard and Enterprise editions" + +# ── API Management ───────────────────────────────────────────────── + +api_management: + per_api_call: 0.0000005 + monthly_1m_calls: 0.50 + notes: "Very low cost per call" + +# ── Data Integration ─────────────────────────────────────────────── + +data_integration: + workspace_per_hour: 0.0252 + data_processed_per_gb_hour: 0.0252 + +# ── Data Integrator ──────────────────────────────────────────────── + +data_integrator: + ocpu_per_hour: 0.0252 + +# ── Streaming (Kafka compatible) ────────────────────────────────── + +streaming: + storage_per_gb_hour: 0.025 + data_transfer_per_gb: 0.025 + +streaming_with_kafka: + ocpu_per_hour: 0.0252 + +# ── Queue ────────────────────────────────────────────────────────── + +queue: + free_per_month: 1000000 # requests + per_request_beyond: 0.0000005 + notes: "1M free requests/month, very cost-effective for decoupling" + +# ── SOA Suite ────────────────────────────────────────────────────── + +soa_suite: + ocpu_per_hour: 0.0504 - 0.0672 + notes: "For legacy SOA workload migration" + +# ── GoldenGate ───────────────────────────────────────────────────── +# See database.yaml for GoldenGate pricing diff --git a/kb/pricing/networking.yaml b/kb/pricing/networking.yaml new file mode 100644 index 0000000..c6c6e56 --- /dev/null +++ b/kb/pricing/networking.yaml @@ -0,0 +1,70 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Networking pricing for estimation purposes. +currency: USD +--- + +# ── Load Balancing ───────────────────────────────────────────────── + +load_balancer: + base_instance_per_hour: 0.10 + bandwidth_per_mbps_hour: 0.0084 + monthly_base: 73.00 + notes: "Flexible shape, bandwidth billed separately" + +network_load_balancer: + per_hour: "Free" + notes: "Layer 4, no per-hour charge, data processing fees apply" + +# ── FastConnect ──────────────────────────────────────────────────── + +fastconnect: + port_per_hour: + 1gbps: 0.30 + 2gbps: 0.50 + 5gbps: 1.26 + 10gbps: 2.52 + 40gbps: 5.04 + 100gbps: 6.30 + 400gbps: 12.60 + monthly_730h: + 1gbps: 219.00 + 10gbps: 1839.60 + 100gbps: 4599.00 + data_transfer: "Free (ingress and egress)" + notes: "Private dedicated connectivity. No data transfer charges." + +# ── VPN Connect ──────────────────────────────────────────────────── + +vpn_connect: + notes: "No additional charge for IPSec VPN tunnels. Included with DRG." + +# ── DNS ──────────────────────────────────────────────────────────── + +dns: + queries_included_per_month: 1000000 + additional: "Free for most use cases" + notes: "1M queries/month included at no cost" + +# ── WAF ──────────────────────────────────────────────────────────── + +waf: + notes: "Pricing based on requests processed. See OCI price list for current rates." + +# ── Data Transfer Summary ────────────────────────────────────────── + +data_transfer: + ingress: "Free (all sources)" + egress_internet: + first_10tb_month: "Free" + per_gb_beyond: 0.0085 + inter_region: + per_gb: 0.0085 + fastconnect: "Free (both directions)" + key_advantage: | + OCI is dramatically cheaper than hyperscalers for data transfer: + - AWS charges $0.09/GB egress (OCI: $0.0085 after 10TB free) + - Azure charges $0.087/GB egress + - GCP charges $0.12/GB egress + - OCI FastConnect egress is FREE (AWS DirectConnect charges per GB) diff --git a/kb/pricing/observability.yaml b/kb/pricing/observability.yaml new file mode 100644 index 0000000..0e227d1 --- /dev/null +++ b/kb/pricing/observability.yaml @@ -0,0 +1,89 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Observability and Management pricing. +currency: USD +--- + +# ── Monitoring ───────────────────────────────────────────────────── + +monitoring: + ingestion: + free_per_month: 500000000 # 500M datapoints + per_datapoint_beyond: 0.00002 + retrieval: + free_per_month: 1000000000 # 1B datapoints + per_datapoint_beyond: 0.00002 + notes: "Very generous free tier for most workloads" + +# ── Logging ──────────────────────────────────────────────────────── + +logging: + free_per_month_gb: 10 + per_gb_month_beyond: 0.0050 + notes: "10 GB/month free, very affordable" + +# ── Log Analytics ────────────────────────────────────────────────── + +log_analytics: + per_storage_unit_month: 2.50 - 5.00 + storage_unit_size_gb: 300 + notes: "1 storage unit = 300 GB" + +# ── Notifications ────────────────────────────────────────────────── + +notifications: + free_per_month: + deliveries: 1000000 + emails: 1000 + per_operation_beyond: 0.00001 + per_email_beyond: 0.00001 + +# ── Events ───────────────────────────────────────────────────────── + +events: + notes: "No additional charge for Events service" + +# ── APM / Application Performance Monitoring ────────────────────── + +apm: + notes: "Pricing based on data ingestion volume and synthetic monitors" + +# ── Database Management ──────────────────────────────────────────── + +database_management: + ecpu_per_hour: 0.0360 + notes: "Fleet-level database monitoring and management" + +# ── Ops Insights ─────────────────────────────────────────────────── + +ops_insights: + basic: "Free" + autonomous: "Free" + cloud_external: + ocpu_per_hour: 0.0420 + notes: "Basic and Autonomous tiers are free" + +# ── Email Delivery ───────────────────────────────────────────────── + +email_delivery: + per_1k_emails: 0.00001 - 0.000015 + +# ── Full Stack Disaster Recovery ─────────────────────────────────── + +full_stack_dr: + ecpu_per_hour: 0.0360 + ocpu_per_hour: 0.0420 + notes: "Orchestrates DR failover across compute, database, and networking" + +# ── Fleet Application Management ────────────────────────────────── + +fleet_app_management: + per_managed_resource_month: 3.00 - 7.50 + +# ── OS & Java Management ────────────────────────────────────────── + +os_management_hub: "Free" +java_management_service: "Free" +autonomous_linux: "Free" +resource_analytics: "Free" diff --git a/kb/pricing/pricing-models.yaml b/kb/pricing/pricing-models.yaml new file mode 100644 index 0000000..a375e9a --- /dev/null +++ b/kb/pricing/pricing-models.yaml @@ -0,0 +1,83 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI pricing models and discount structures. +--- + +models: + payg: + name: "Pay As You Go" + commitment: none + discount: none + billing: "Per second (compute), per hour (database), per month (storage)" + when_to_use: + - "Variable/unpredictable workloads" + - "Short-term projects" + - "Dev/test environments" + - "Initial cloud adoption (before committing)" + + monthly_flex: + name: "Monthly Flex (Universal Credits)" + commitment: "Minimum $1,000/month or custom commitment" + discount: "Volume discounts on committed spend" + billing: "Committed credits consumed by usage" + when_to_use: + - "Steady-state production workloads" + - "Known baseline consumption" + - "Multiple services across OCI" + discount_tiers: + - spend: "$1K-$10K/month" + discount: "~5-10%" + - spend: "$10K-$50K/month" + discount: "~10-20%" + - spend: "$50K+/month" + discount: "~20-30% (negotiated)" + + annual_flex: + name: "Annual Flex (Committed)" + commitment: "1-year or 3-year commitment" + discount: "Deeper discounts than monthly flex" + when_to_use: + - "Established production workloads with predictable spend" + - "Organizations committed to OCI long-term" + + byol: + name: "Bring Your Own License" + discount: "~50% on database OCPU costs" + eligible: + - "Oracle Database Enterprise Edition" + - "Oracle Database Standard Edition 2" + - "Oracle WebLogic" + - "Oracle Java SE" + conversion: "1 processor license = 2 OCPUs" + when_to_use: + - "Existing Oracle on-prem licenses with active support" + - "Licenses not being used elsewhere" + when_NOT_to_use: + - "No existing licenses" + - "Licenses still needed for on-prem during migration" + - "Standard Edition workloads on ADB (requires EE)" + + reserved_capacity: + name: "Reserved Capacity" + commitment: "1-year or 3-year for specific shapes" + discount: + 1_year: "~35% off on-demand" + 3_year: "~55% off on-demand" + when_to_use: + - "Known compute shapes running 24/7" + - "Predictable database capacity" + notes: "Shape-specific, less flexible than Universal Credits" + +estimation_approach: + step_1: "Calculate PAYG cost first (baseline)" + step_2: "Apply BYOL if customer has eligible licenses" + step_3: "Show savings with Monthly Flex / Annual Flex for steady-state" + step_4: "Compare total annual cost vs current on-prem spending" + step_5: "Include migration costs in year 1" + assumptions_to_state: + - "Hours per month (730 for 24/7)" + - "Auto-scaling hours per day (estimate)" + - "Data transfer volumes" + - "Backup retention" + - "Support tier" diff --git a/kb/pricing/security.yaml b/kb/pricing/security.yaml new file mode 100644 index 0000000..7bd5793 --- /dev/null +++ b/kb/pricing/security.yaml @@ -0,0 +1,56 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Security service pricing. +currency: USD +--- + +# ── Access Governance ────────────────────────────────────────────── + +access_governance: + premium: + workforce_user_month: 0.0018 - 0.0036 + consumer_user_month: 0.36 + for_oracle_workloads: + workforce_user_month: 0.0009 - 0.0018 + consumer_user_month: 0.36 + for_oci: + workforce_user_month: 0.00009 - 0.00018 + +# ── Vault ────────────────────────────────────────────────────────── + +vault: + notes: "Key management — see OCI price list for current rates. Free tier for software keys." + +# ── Cloud Guard ──────────────────────────────────────────────────── + +cloud_guard: + notes: "Free for OCI tenancy monitoring and threat detection" + +# ── Security Zones ───────────────────────────────────────────────── + +security_zones: + notes: "Free — policy enforcement on compartments" + +# ── Vulnerability Scanning ───────────────────────────────────────── + +vulnerability_scanning: + notes: "Free — host and container image scanning" + +# ── Certificates ─────────────────────────────────────────────────── + +certificates: + notes: "Free — TLS certificate management" + +# ── Bastion ──────────────────────────────────────────────────────── + +bastion: + notes: "Free — managed SSH/RDP bastion service" + +# ── Network Firewall ────────────────────────────────────────────── + +network_firewall: + notes: "Pricing based on instance hours and data processed" + +# ── Data Safe ────────────────────────────────────────────────────── +# See database.yaml for Data Safe pricing diff --git a/kb/pricing/storage.yaml b/kb/pricing/storage.yaml new file mode 100644 index 0000000..a682d80 --- /dev/null +++ b/kb/pricing/storage.yaml @@ -0,0 +1,60 @@ +--- +last_verified: 2025-09-11 +source: https://www.oracle.com/cloud/price-list/ +description: OCI Storage pricing for estimation purposes. +currency: USD +--- + +block_volume: + balanced: + per_gb_month: 0.0255 + per_tb_month: 26.11 + higher_performance: + per_gb_month: 0.0340 + per_tb_month: 34.82 + lower_cost: + per_gb_month: 0.0170 + per_tb_month: 17.41 + uhp: + notes: "Priced per VPU, varies by configuration" + boot_volume: + free_tier: "200 GB included" + additional: "Same as block volume pricing" + backup: + per_gb_month: 0.0050 + +object_storage: + standard: + per_gb_month: 0.0255 + per_tb_month: 26.11 + infrequent_access: + per_gb_month: 0.0100 + per_tb_month: 10.24 + archive: + per_gb_month: 0.0026 + per_tb_month: 2.66 + api_requests: + per_10k_put: 0.0040 + per_10k_get: 0.0003 + free_tier: + storage_gb: 10 + requests: 50000 + +file_storage: + per_gb_month: 0.0340 + per_tb_month: 34.82 + +data_transfer: + ingress: "Free" + egress_internet: + first_10tb_month: "Free" + per_gb_after: 0.0085 + inter_region: + per_gb: 0.0085 + fastconnect: + ingress: "Free" + egress: "Free" + notes: | + OCI egress pricing is significantly cheaper than AWS/Azure/GCP. + First 10 TB/month internet egress is free. + FastConnect egress is free (no per-GB charge). diff --git a/kb/services/adb-dedicated.yaml b/kb/services/adb-dedicated.yaml new file mode 100644 index 0000000..d5db4ba --- /dev/null +++ b/kb/services/adb-dedicated.yaml @@ -0,0 +1,80 @@ +--- +last_verified: 2026-03-14 +service: Autonomous Database Dedicated (ADB-D) +category: database +oci_color: "#AA643B" +--- + +what: Autonomous Database on dedicated Exadata infrastructure. Provides isolation, + higher resource limits, and more control while retaining autonomous operations + (auto-patching, auto-tuning, auto-scaling). + +when_to_use: + - Regulatory/compliance requirement for dedicated infrastructure + - Need for container database (CDB) level control + - Sustained workloads >128 OCPUs per database + - Multiple databases that benefit from shared Exadata infrastructure + - Organizations that want autonomous features but need infrastructure isolation + - Need to control maintenance windows precisely + +when_NOT_to_use: + - Small/medium workloads (ADB-S is simpler and cheaper) + - Teams that don't need infrastructure isolation + - Budget-constrained projects (dedicated infrastructure has higher base cost) + - Rapid prototyping or dev/test (ADB-S provisions faster) + +limits: + max_ocpu_per_vm_cluster: 400 + max_adb_per_vm_cluster: 200 + max_storage_per_vm_cluster_tb: 192 + min_exadata_infrastructure: "Quarter rack (base)" + max_vm_clusters_per_infra: 8 + +features: + auto_scaling: true + auto_indexing: true + auto_partitioning: true + autonomous_data_guard: true + cross_region_adg: true + private_endpoint: true + customer_managed_keys: true + fleet_management: true + maintenance_window_control: true + resource_pool_management: true + os_access: false + rac: false # Not traditional RAC, but Exadata-managed HA + +ha_dr: + local_ha: "Exadata-managed, automatic failover within infrastructure" + autonomous_data_guard: + same_region: "Synchronous, automatic failover" + cross_region: "Asynchronous, manual or automatic failover" + +sizing_rules: + minimum_infrastructure: "Quarter rack equivalent" + ocpu_allocation: "Shared across all ADBs in the VM cluster" + storage_allocation: "Shared Exadata storage pool" + +pricing: + model: dedicated_infrastructure + infrastructure_base: "Exadata infrastructure (fixed cost per shape)" + ocpu_payg: "Per OCPU per hour on top of infrastructure" + byol_discount: "~50% on OCPU costs" + notes: + - "Infrastructure cost is fixed regardless of utilization" + - "OCPU billing is for databases provisioned on the infrastructure" + - "Higher base cost but better economics at scale (>128 OCPUs sustained)" + +gotchas: + - id: ADB-D-001 + issue: "Provisioning infrastructure takes hours, not minutes" + impact: "Cannot spin up quickly like ADB-S" + recommendation: "Plan infrastructure provisioning well ahead of database needs" + - id: ADB-D-002 + issue: "Minimum infrastructure commitment" + impact: "Even at low utilization, you pay for the base infrastructure" + recommendation: "Only choose ADB-D when sustained usage justifies the base cost" + - id: ADB-D-003 + issue: "Maintenance windows affect all databases on the infrastructure" + impact: "Need to coordinate maintenance across all tenants" + recommendation: "Use maintenance window control to schedule during off-peak" diff --git a/kb/services/adb-elastic-pool.yaml b/kb/services/adb-elastic-pool.yaml new file mode 100644 index 0000000..4b8a6b2 --- /dev/null +++ b/kb/services/adb-elastic-pool.yaml @@ -0,0 +1,49 @@ +--- +last_verified: 2026-03-14 +service: Autonomous Database Serverless Elastic Pool +category: database +oci_color: "#AA643B" +--- + +what: Pool of pre-provisioned ADB-S resources that multiple databases share. + Reduces provisioning time and allows resource sharing across databases. + +when_to_use: + - Many small databases (SaaS per-tenant model) + - Need fast provisioning (<1 minute vs 2-5 minutes for standard ADB-S) + - Workloads with staggered peak times across databases + - Dev/test environments with many short-lived databases + +when_NOT_to_use: + - Single large database (just use ADB-S directly) + - All databases peak simultaneously (no benefit from pooling) + - Need strict resource isolation between databases + +limits: + max_ocpu_per_pool: 512 + max_databases_per_pool: 1000 + max_storage_per_pool_tb: 4096 + +features: + shared_resources: true + fast_provisioning: true + auto_scaling: "Per database within pool limits" + leader_follower: "Leader database manages pool, followers consume" + +pricing: + model: pool_based + pool_ocpu_payg: "Per OCPU per hour for the pool" + notes: + - "Pool resources are billed whether databases use them or not" + - "Individual databases draw from the pool allocation" + - "Cost-effective when average utilization across pool is >50%" + +gotchas: + - id: EP-001 + issue: "Pool resources are committed even when idle" + impact: "Under-utilized pools cost more than individual ADB-S instances" + recommendation: "Size pool for aggregate average, not sum of peaks" + - id: EP-002 + issue: "Noisy neighbor risk within pool" + impact: "One database can consume disproportionate resources" + recommendation: "Set per-database OCPU limits to prevent resource hogging" diff --git a/kb/services/adb-serverless.yaml b/kb/services/adb-serverless.yaml new file mode 100644 index 0000000..83c13d7 --- /dev/null +++ b/kb/services/adb-serverless.yaml @@ -0,0 +1,146 @@ +# OCI Service: Autonomous Database Serverless (ADB-S) +# Last verified: 2026-03 + +service: + name: "Autonomous Database Serverless (ADB-S)" + id: adb_serverless + category: database + color: "#AA643B" + +features: + auto_scaling: true + auto_indexing: true + auto_tuning: true + auto_patching: true + transparent_application_continuity: true + autonomous_data_guard: true + private_endpoints: true + byol_eligible: true + +ha_dr_options: + local_ha: "Built-in TAC (Transparent Application Continuity) — ~0 perceived RTO" + same_region_adg: "Autonomous Data Guard standby — synchronous, RPO 0" + cross_region_adg: "Cross-region ADG — asynchronous only, RPO seconds, RTO < 15 min" + +limits: + max_ocpu: 128 + max_storage_tb: 383 + max_auto_scale: "3x base OCPUs" + auto_scale_activation: "2-3 minutes" + min_ocpu: 1 + min_storage_tb: 1 + +variants: + - id: adb_s + name: "Autonomous Database Serverless (ADB-S)" + description: "Shared Exadata infrastructure, fully managed, auto-scaling" + workload_types: + - ATP (Transaction Processing / Mixed) + - ADW (Data Warehouse / Analytics) + - AJD (JSON Document Store) + - APEX (Low-code application development) + + - id: adb_d + name: "Autonomous Database on Dedicated Exadata (ADB-D)" + description: "Dedicated Exadata infrastructure, full isolation" + use_when: + - "Strict isolation requirements (regulatory, multi-tenant)" + - "Predictable performance with no noisy-neighbor risk" + - "Need RAC for horizontal compute scaling" + - "> 128 OCPUs sustained" + +when_to_use: + - "Oracle Database workloads migrating to cloud" + - "Need automated patching, tuning, and scaling" + - "BYOL opportunity with existing Oracle DB licenses" + - "JSON document workloads (AJD)" + - "Rapid APEX application development" + - "Data warehouse / analytics with auto-scaling" + +when_not_to_use: + - "Non-Oracle database engines (use MySQL HeatWave, PostgreSQL, NoSQL)" + - "Need OS-level access or custom DB home configuration → use DBCS" + - "Need RAC but not ADB-D budget → use ExaCS or DBCS VM" + - "Workloads requiring specific DB patches not yet in ADB" + - "Extreme IOPS requirements > what Exadata provides" + +sizing: + adb_s: + min_ocpu: 1 + max_ocpu: 128 + auto_scaling: "Up to 3x base OCPUs (takes 2-3 minutes to activate)" + sizing_guidance: "Size base OCPUs for P75 sustained load, not P50. Auto-scaling covers peaks but has activation latency." + storage_min_tb: 1 + storage_max_tb: 383 + iops: "Proportional to OCPU count, Exadata storage servers" + + adb_d: + min_ocpu: "2 per VM (minimum 2 VMs for HA)" + max_ocpu: "Per Exadata infrastructure shape" + rac: true + storage: "Exadata storage dependent on infrastructure shape" + +gotchas: + - id: adb_s_autoscale_latency + severity: MEDIUM + description: "Auto-scaling activation takes 2-3 minutes. Size base OCPUs for P75, not average." + + - id: adb_s_private_endpoint + severity: HIGH + description: "Always use private endpoints for production. Public endpoints allow internet access to the DB listener." + + - id: adb_s_service_gateway + severity: MEDIUM + description: "ADB-S requires a Service Gateway in the VCN for automated backups to Object Storage." + + - id: adb_s_byol_savings + severity: LOW + description: "BYOL pricing is ~50% cheaper than License Included. Always check if customer has existing Oracle DB licenses." + + - id: adb_d_minimum_infra + severity: HIGH + description: "ADB-D requires dedicated Exadata infrastructure — minimum cost is significant. Don't recommend for small workloads." + + - id: adb_data_guard_async + severity: MEDIUM + description: "Cross-region Autonomous Data Guard is async only (RPO > 0). For zero RPO, need same-region standby or ADB-D with local Data Guard." + + - id: adb_19c_only + severity: LOW + description: "ADB currently runs Oracle 19c. If customer needs 23ai features, verify availability." + +implied_dependencies: + - service: service_gateway + reason: "Required for automated backups to Object Storage" + - service: private_endpoint + reason: "Production workloads must use private endpoints" + - service: oci_vault + reason: "Customer-managed encryption keys (recommended for production)" + - service: data_safe + reason: "Security assessment, audit, and data masking" + +competitive: + vs_aws_rds: + advantages: + - "Autonomous operations (auto-patching, auto-tuning, auto-scaling)" + - "Dramatically lower DB licensing cost with BYOL" + - "Exadata performance for Oracle workloads" + disadvantages: + - "No multi-engine support (Oracle only)" + - "Auto-scaling latency vs. Aurora's faster scale-out" + vs_azure_sql: + advantages: + - "Oracle-native features (RAC, Data Guard, Advanced Compression)" + - "BYOL cost advantage" + disadvantages: + - "Azure has broader SQL Server ecosystem integration" + +references: + documentation: "https://docs.oracle.com/en/cloud/paas/autonomous-database/" + pricing: "https://www.oracle.com/autonomous-database/pricing/" + sizing_guide: "https://docs.oracle.com/en/cloud/paas/autonomous-database/adbsa/autonomous-database-cpu.html" + +changelog: + - date: "2026-03-14" + contributor: { name: "Diego Cabrera", team: "Field Architecture" } + change: "Initial creation with sizing rules, gotchas, competitive notes, HA/DR options" diff --git a/kb/services/compute.yaml b/kb/services/compute.yaml new file mode 100644 index 0000000..ff3e4e2 --- /dev/null +++ b/kb/services/compute.yaml @@ -0,0 +1,73 @@ +--- +last_verified: 2026-03-14 +service: OCI Compute +category: compute +oci_color: "#4F7B6E" +--- + +what: Virtual machines and bare metal instances. Flexible shapes allow custom + OCPU/memory ratios. AMD, Intel, and Arm options available. + +shape_families: + - name: VM.Standard.E5.Flex + processor: "AMD EPYC 9J14 (Genoa)" + max_ocpu: 94 + memory_per_ocpu_gb: "1-64 GB per OCPU" + use_for: "General purpose, most workloads" + - name: VM.Standard3.Flex + processor: "Intel Xeon (Ice Lake)" + max_ocpu: 32 + memory_per_ocpu_gb: "1-64 GB per OCPU" + use_for: "Intel-optimized workloads" + - name: VM.Standard.A2.Flex + processor: "Ampere Altra (Arm)" + max_ocpu: 80 + memory_per_ocpu_gb: "1-64 GB per OCPU" + use_for: "Arm-compatible workloads, cost optimization" + - name: VM.Optimized3.Flex + processor: "Intel Xeon (Ice Lake)" + max_ocpu: 18 + memory_per_ocpu_gb: "1-64 GB per OCPU" + use_for: "High-frequency trading, HPC" + - name: VM.GPU.A10.1 + processor: "NVIDIA A10" + gpu_count: 1 + use_for: "AI/ML inference, GPU workloads" + - name: BM.GPU.H100.8 + processor: "NVIDIA H100 (8 GPUs)" + gpu_count: 8 + use_for: "AI/ML training, large model inference" + +when_to_use: + - Traditional VM-based applications + - Applications not suited for containers + - Database clients and application servers + - Bastion hosts and jump boxes + - GPU workloads (AI/ML, rendering) + +when_NOT_to_use: + - Containerized microservices (use OKE) + - Serverless event-driven workloads (use Functions) + - Managed database workloads (use ADB/DBCS) + +features: + flexible_shapes: true + burstable: "Burstable instances available (VM.Standard.E5.Flex.Burstable)" + preemptible: "Preemptible instances at reduced cost" + dedicated_hosts: true + live_migration: true + boot_volume_backup: true + instance_pools: true + autoscaling: "Instance pool autoscaling" + +pricing: + model: per_ocpu_per_hour + e5_flex_per_ocpu: 0.0230 + standard3_flex_per_ocpu: 0.0360 + a2_flex_per_ocpu: 0.0100 + memory_per_gb: 0.0015 + preemptible_discount: "~50% off on-demand" + notes: + - "Flexible shapes: OCPU + memory billed separately" + - "Always Free tier: 1 VM.Standard.E2.1.Micro (AMD)" + - "Preemptible instances can be reclaimed with 30s notice" diff --git a/kb/services/database-at-aws.yaml b/kb/services/database-at-aws.yaml new file mode 100644 index 0000000..d9de212 --- /dev/null +++ b/kb/services/database-at-aws.yaml @@ -0,0 +1,186 @@ +# Oracle Database@AWS — KB Service Card +# Last verified: 2026-03-17 +# Sources: +# - https://www.oracle.com/cloud/aws/ +# - https://docs.oracle.com/en-us/iaas/Content/database-at-aws/oaaws.htm +# - https://docs.oracle.com/en/solutions/network-topology-oracle-database-at-aws/ +# - https://aws.amazon.com/about-aws/whats-new/2025/12/oracle-database-aws-available-three-additional-regions/ +# - https://www.oracle.com/news/announcement/oracle-database-at-aws-now-generally-available-2025-07-08/ + +service: + name: "Oracle Database@AWS" + also_known_as: + - "ADB Multicloud (AWS)" + - "ODB@AWS" + - "Oracle Database Service for AWS (legacy name)" + category: "Multicloud Database" + ga_date: "2025-07-08" + + what: | + Oracle Database running on OCI Exadata infrastructure co-located inside + AWS datacenters. AWS apps connect via ODB peering (private, low-latency + network bridging AWS VPC to OCI infrastructure in the same AZ). + Control plane runs in OCI; data plane is physically in the AWS region. + Single invoice through AWS Marketplace. Usage counts toward AWS commitments. + + services_available: + - name: "OCI Exadata Database Service on Dedicated Infrastructure" + versions: ["19c", "26ai"] + notes: "Full Exadata performance, RAC, Data Guard" + - name: "OCI Autonomous AI Database on Dedicated Exadata Infrastructure (ADB-D)" + versions: ["19c", "26ai"] + notes: "Autonomous Database on dedicated Exadata — NOT serverless" + - name: "OCI Autonomous Recovery Service" + notes: "Automated backup and recovery" + + services_NOT_available: + - name: "Autonomous Database Serverless (ADB-S)" + reason: | + ADB-S is NOT available on Database@AWS. Only ADB on Dedicated + Exadata Infrastructure (ADB-D) is supported. This is a critical + distinction when designing architectures — if the customer + specifically needs ADB-S serverless, they must use native OCI + or connect via FastConnect/VPN. + + # --- Regions --- + regions_ga: + - {aws_region: "us-east-1", name: "US East (N. Virginia)", ga_date: "2025-07-08"} + - {aws_region: "us-west-2", name: "US West (Oregon)", ga_date: "2025-07-08"} + - {aws_region: "us-east-2", name: "US East (Ohio)", ga_date: "2025-12-23"} + - {aws_region: "eu-central-1", name: "Europe (Frankfurt)", ga_date: "2025-12-23"} + - {aws_region: "ap-northeast-1", name: "Asia Pacific (Tokyo)", ga_date: "2025-12-23"} + - {aws_region: "ca-central-1", name: "Canada (Central)", ga_date: "2026-Q1"} + - {aws_region: "eu-west-1", name: "Europe (Ireland)", ga_date: "2026-Q1"} + - {aws_region: "ap-southeast-2", name: "Asia Pacific (Sydney)", ga_date: "2026-Q1"} + + regions_planned: + - "US West (N. California)" + - "South America (São Paulo)" + - "Europe (London)" + - "Europe (Milan)" + - "Europe (Paris)" + - "Europe (Spain)" + - "Europe (Stockholm)" + - "Europe (Zurich)" + - "Asia Pacific (Hyderabad)" + - "Asia Pacific (Melbourne)" + - "Asia Pacific (Mumbai)" + - "Asia Pacific (Osaka)" + - "Asia Pacific (Seoul)" + - "Asia Pacific (Singapore)" + regions_planned_notes: "Oracle and AWS announced plans to expand to 20+ total regions" + + # --- Network Architecture --- + network: + connectivity: "ODB Peering" + description: | + ODB peering creates a private, direct connection between an AWS VPC + and the OCI-managed ODB network inside the same AWS AZ. Traffic never + traverses the public internet. This is NOT a standard FastConnect — + it's a co-located, same-datacenter interconnect. + + latency: + same_az: "~200-400 microseconds RTT (extrapolated from Database@Azure benchmarks)" + cross_az: "~1 ms+ (variable, must validate)" + cross_region: "Variable, depends on distance" + notes: | + Oracle does not publish official RTT benchmarks for Database@AWS. + Database@Azure (same architecture pattern) shows 200-400μs same-AZ + and ~1.5ms cross-AZ per Accenture testing (32,000+ tests, 2 weeks). + Database@AWS should be comparable since the architecture is identical + (co-located OCI infra inside cloud provider datacenter). + + best_practices: + - "Deploy app EC2 and ODB network in the SAME Availability Zone" + - "Use Transit Gateway with attachment in same AZ as ODB network" + - "CIDR blocks must not overlap with AWS VPC subnets or OCI VCNs" + - "Client subnet minimum /27, recommended /24 for expansion" + - "Backup subnet minimum /28 (optional for ADB-D)" + - "Use Direct Connect for hybrid (on-premises) connectivity" + + topologies: + - name: "Same-AZ Direct Peering" + use_case: "Latency-sensitive apps" + path: "App VPC → ODB Peering → ODB Network → Oracle DB" + recommendation: "Primary topology for low-latency workloads" + - name: "Hub-and-Spoke via Transit Gateway" + use_case: "Multi-VPC architectures" + path: "Spoke VPCs → Transit Gateway → ODB Network → Oracle DB" + caveat: "Cross-VPC latency varies — must validate" + - name: "Cross-Region via Transit Gateway Peering" + use_case: "Multi-region apps" + caveat: "Significant latency increase — not for latency-sensitive" + + # --- Pricing --- + pricing: + model: | + Single invoice through AWS Marketplace. + - Exadata: Upfront subscription for infrastructure + pay-per-use for CPUs + - ADB-D: Upfront subscription + pay-per-use for active CPUs + aws_commitment: "Oracle Database@AWS usage counts toward AWS spending commitments" + + # --- When to use vs alternatives --- + when_to_use: + - "App runs on AWS and needs Oracle Database with Exadata performance" + - "Customer wants single AWS bill (Marketplace billing)" + - "Low-latency (<1ms) app-to-DB connectivity required without migrating app" + - "Customer has AWS spending commitments to burn down" + - "Data residency requires DB to be in same region as app" + + when_NOT_to_use: + - "Need ADB-S Serverless — use native OCI + FastConnect instead" + - "Target AWS region not yet GA — check availability" + - "Workload fits RDS Oracle or doesn't need Exadata performance" + - "Budget-sensitive: minimum Exadata infra commitment applies" + - "Need full OCI service breadth beyond database (OKE, Functions, etc.)" + + # --- Gotchas --- + gotchas: + - id: "DBAWS-001" + severity: HIGH + issue: "ADB-S Serverless NOT available — only ADB-D Dedicated" + impact: | + Customers expecting serverless auto-scaling and pay-per-query will + need to use ADB-D with manual OCPU scaling, or use native OCI ADB-S + with FastConnect from AWS. + recommendation: | + If the customer specifically needs serverless, design with native + OCI ADB-S + FastConnect. If they need co-located low latency, + use ADB-D on Database@AWS and size Exadata appropriately. + + - id: "DBAWS-002" + severity: MEDIUM + issue: "Minimum Exadata infrastructure commitment" + impact: | + Database@AWS requires dedicated Exadata infrastructure. + Minimum quarter-rack equivalent — not suitable for small workloads. + recommendation: | + For workloads < 16 OCPUs, evaluate if the latency benefit of + co-location justifies the Exadata minimum vs FastConnect to + native OCI ADB-S. + + - id: "DBAWS-003" + severity: MEDIUM + issue: "VM clusters cannot be moved between ODB networks" + impact: "Once deployed in an AZ, the cluster stays there." + recommendation: "Choose the AZ carefully — align with app tier AZ placement." + + - id: "DBAWS-004" + severity: LOW + issue: "Control plane runs in OCI region, not in AWS" + impact: | + Management operations (provisioning, patching, scaling) go through + OCI control plane. If OCI region has issues, management is affected + but data plane continues to serve queries. + recommendation: "Monitor both OCI and AWS health dashboards." + + # --- Integration with AWS services --- + aws_integrations: + - "Amazon VPC (ODB peering)" + - "AWS IAM (federated access)" + - "Amazon CloudWatch (monitoring)" + - "Amazon EventBridge (events)" + - "Amazon S3 (data integration)" + - "AWS SageMaker / Bedrock (AI/ML via zero-ETL)" + - "AWS Transit Gateway / Cloud WAN (networking)" + - "AWS Direct Connect (hybrid connectivity)" diff --git a/kb/services/dbcs.yaml b/kb/services/dbcs.yaml new file mode 100644 index 0000000..24e4719 --- /dev/null +++ b/kb/services/dbcs.yaml @@ -0,0 +1,77 @@ +--- +last_verified: 2026-03-14 +service: Database Cloud Service (DBCS) +category: database +oci_color: "#AA643B" +--- + +what: Oracle Database on VM or Bare Metal in OCI. Full DBA control with + automated provisioning and backup. Available as single-instance or 2-node RAC. + +editions: + - Standard Edition + - Enterprise Edition + - Enterprise Edition High Performance + - Enterprise Edition Extreme Performance + +when_to_use: + - Standard Edition workloads (ADB requires EE) + - Need full OS and database control without Exadata + - Moderate workloads that don't need Exadata performance + - Budget-conscious deployments where Exadata is over-provisioned + - Development and test environments mirroring production DB config + - Applications requiring specific Oracle versions or patches + +when_NOT_to_use: + - High-performance OLTP (Exadata or ADB better) + - Variable workloads benefiting from auto-scaling (use ADB-S) + - No DBA team available (use ADB-S) + - Need Exadata features (Smart Scan, HCC, Flash Cache) + +limits: + vm_shapes: + - "VM.Standard.E5.Flex: 1-94 OCPUs" + - "VM.Standard3.Flex: 1-32 OCPUs" + bm_shapes: + - "BM.Standard.E5: 128 OCPUs" + - "BM.DenseIO.E5: 128 OCPUs with NVMe" + max_storage_tb: 80 # block storage + rac_max_nodes: 2 + +features: + os_access: true + rac: "2-node RAC on VM shapes" + data_guard: true + cross_region_data_guard: true + custom_db_home: true + custom_patches: true + automated_backup: true + database_management: true + +ha_dr: + local_ha: "2-node RAC for HA, single-instance with automated restart" + data_guard: + same_region: "Synchronous or async, FSFO available" + cross_region: "Asynchronous, manual or auto failover" + +pricing: + model: per_ocpu_per_hour + vm_ocpu_payg: 0.1360 # per OCPU per hour (EE) + vm_ocpu_byol: 0.0680 + bm_per_hour: "Fixed per shape" + storage: "Block Volume pricing applies" + notes: + - "VM shapes billed per enabled OCPU per hour" + - "BM shapes billed per hour for the full shape" + - "Storage is separate (Block Volume pricing)" + - "Backup storage at Object Storage rates" + +gotchas: + - id: DBCS-001 + issue: "2-node RAC is maximum on VM shapes" + impact: "Cannot scale RAC beyond 2 nodes (use ExaCS for more)" + recommendation: "If >2 RAC nodes needed, evaluate ExaCS" + - id: DBCS-002 + issue: "Performance limited by block storage" + impact: "Cannot match Exadata IOPS/latency" + recommendation: "Use higher performance block volumes or move to ExaCS for I/O intensive workloads" diff --git a/kb/services/dbexpert-api-reference.yaml b/kb/services/dbexpert-api-reference.yaml new file mode 100644 index 0000000..6ca4f9a --- /dev/null +++ b/kb/services/dbexpert-api-reference.yaml @@ -0,0 +1,89 @@ +--- +last_verified: 2026-03-17 +description: | + Oracle DBExpert public REST API reference for refreshing the database service catalog. + Use this to keep kb/services/dbexpert-catalog.yaml up to date. +--- + +# ── DBExpert ORDS REST API ───────────────────────────────────────── + +swagger_ui: https://oracle-dbexpert.github.io/swagger/ +base_url: https://apexadb.oracle.com/ords/dbexpert + +endpoints: + all_db_services: + method: GET + url: https://apexadb.oracle.com/ords/dbexpert/db-services/v1/all + description: "Complete database service catalog with capabilities, multicloud, compliance" + response_format: JSON + fields_per_service: + - DATABASE_SERVICE_NAME + - DATABASE_TYPE # Single Database, Distributed, Consolidation Pool + - MANAGEMENT_MODEL # Fully Managed PaaS, Co-managed PaaS, Customer Managed + - SUPPORTED_CLOUD_ENVIRONMENTS + - RECOMMENDED_WORKLOADS + - RECOMMENDED_DATA_MODELS + - CERTIFIED_ORACLE_APPLICATIONS + - ORACLE_AI_DATABASE_BYOL_SUPPORT + - CPU_RANGE + - SHAPES + - CPU_SCALING # Online, Auto scale up/down + - STORAGE_SCALING + - MAX_IOPS + - MAX_THROUGHPUT + - MAX_MEMORY + - NINES_OF_AVAILABILITY # SLA/SLO + - ORACLE_DB_MAXIMUM_AVAILABILITY_ARCHITECTURE_MEDALS + - AUTOMATED_BACKUPS_MAX_RETENTION + - AZURE_COMPLIANCE + - GOOGLE_CLOUD_COMPLIANCE + - AWS_COMPLIANCE + - ALLOWS_SYSDBA_ACCESS + - SELECTAI_SUPPORT + - MONGO-COMPATIBLE_API + - SUPPORTED_DATABASE_VERSIONS + - AUTONOMOUS + - HARDWARE_INFRASTRUCTURE + - INCLUDED_ORACLE_DB_OPTIONS_FOR_LICENSE-INCLUDED_SERVICE + - FREE_ADD-ONS_(NO_EXTRA_LICENSING_REQUIRED) + - OCI_LOCATIONS + - ORACLE_DATABASE@AZURE_LOCATIONS + - ORACLE_DATABASE@GOOGLE_CLOUD_LOCATIONS + - ORACLE_DATABASE@AWS_LOCATIONS + - IS_AVAILABLE_AT_GOOGLE_CLOUD_(GCP) + - IS_AVAILABLE_AT_AZURE + - IS_AVAILABLE_AT_AWS + + multicloud_regions: + url: https://apexadb.oracle.com/ords/r/dbexpert/multicloud-capabilities/multicloud-regions + description: "Multicloud region availability matrix (APEX app, WAF-blocked for curl)" + notes: "Must use browser or Swagger UI" + + service_advisor: + url: https://apexadb.oracle.com/ords/r/dbexpert/dbsn/service-advisor + description: "Interactive service advisor/comparison tool (APEX app, WAF-blocked for curl)" + notes: "Must use browser" + + dbsn_home: + url: https://apexadb.oracle.com/ords/r/dbexpert/dbsn/home + description: "Cloud Tools home page (APEX app)" + +access_notes: | + - The REST API at /ords/dbexpert/db-services/v1/all is accessible from Swagger UI + but blocked by Akamai WAF when called from curl/Python/programmatic clients + - The APEX apps (multicloud-capabilities, dbsn) are fully WAF-blocked + - To refresh data: open Swagger UI, execute the endpoint, copy JSON response + +refresh_procedure: + step_1: "Open https://oracle-dbexpert.github.io/swagger/" + step_2: "Find GET /db-services/v1/all and click 'Try it out' → 'Execute'" + step_3: "Copy the Response body JSON" + step_4: "Paste to Claude with instruction: 'actualiza kb/services/dbexpert-catalog.yaml con esto'" + alternative: | + curl -s 'https://apexadb.oracle.com/ords/dbexpert/db-services/v1/all' \ + -H 'accept: application/json' + (may fail due to WAF — use browser if it does) + +kb_target_file: kb/services/dbexpert-catalog.yaml +services_count: 35 +last_data_date: 2026-03-17 diff --git a/kb/services/dbexpert-catalog.yaml b/kb/services/dbexpert-catalog.yaml new file mode 100644 index 0000000..04f3e28 --- /dev/null +++ b/kb/services/dbexpert-catalog.yaml @@ -0,0 +1,679 @@ +--- +last_verified: 2026-03-17 +source: https://apexadb.oracle.com/ords/dbexpert/db-services/v1/all +swagger: https://oracle-dbexpert.github.io/swagger/ +description: Oracle Database services catalog from DBExpert API. Comprehensive capabilities, multicloud availability, SLAs, workload recommendations, and compliance data. +refresh_command: "curl -s 'https://apexadb.oracle.com/ords/dbexpert/db-services/v1/all' -H 'accept: application/json'" +notes: | + API may be blocked by Akamai WAF from non-browser clients. + Use Swagger UI at https://oracle-dbexpert.github.io/swagger/ to test. + (A) = Azure colocated region, (G) = Google Cloud colocated region +--- + +# ============================================================================= +# Fully Managed PaaS +# ============================================================================= +fully_managed_paas: + + - short_name: ATP Dedicated + full_name: Autonomous AI Transaction Processing on Dedicated Infrastructure + management_model: Fully Managed PaaS + cloud_environments: [OCI, AWS] + multicloud: { azure: false, gcp: false, aws: true } + autonomous: true + sla: "99.995" + maa_medals: [Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "2-21504" + max_storage_tb: 1572 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [OLTP, Mixed, AI/ML, JSON, Graph, Blockchain, IoT] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: LAK Dedicated + full_name: Autonomous AI Lakehouse on Dedicated Infrastructure + management_model: Fully Managed PaaS + cloud_environments: [OCI, AWS] + multicloud: { azure: false, gcp: false, aws: true } + autonomous: true + sla: "99.995" + maa_medals: [Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "2-21504" + max_storage_tb: 1572 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [DW, Analytics, Data Lake, AI/ML, Mixed] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: ATP Serverless + full_name: Autonomous AI Transaction Processing Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: true + sla: "99.995" + sla_note: "with Autonomous Data Guard" + maa_medals: [Silver, Gold] + cpu_unit: ECPU + cpu_range: "2-16128" + max_storage_tb: 393 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [OLTP, Mixed, Microservices, AI/ML, JSON, Graph, Blockchain, IoT] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: LAK Serverless + full_name: Autonomous AI Lakehouse Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: true + sla: "99.995" + sla_note: "with Autonomous Data Guard" + maa_medals: [Silver, Gold] + cpu_unit: ECPU + cpu_range: "2-16128" + max_storage_tb: 393 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [DW, Analytics, Data Lake, AI/ML, Mixed] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: AJD Serverless + full_name: Autonomous AI JSON Database Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: true + sla: "99.95" + maa_medals: [] + cpu_unit: ECPU + cpu_range: "2-512" + max_storage_tb: 10 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [JSON, Document Store, Microservices, IoT] + byol_support: false + db_versions: ["26ai", "19c"] + + - short_name: ADB-S Elastic Pool + full_name: Autonomous AI Database Serverless in an Elastic Pool + management_model: Fully Managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: true + sla: "99.995" + maa_medals: [Silver, Gold] + cpu_unit: ECPU + cpu_range: "128-16384" + max_storage_tb: 393 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [Multi-tenant SaaS, ISV, Consolidated workloads] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: ADB-D Elastic Pool + full_name: Autonomous AI Database Dedicated in an Elastic Pool + management_model: Fully Managed PaaS + cloud_environments: [OCI, AWS] + multicloud: { azure: false, gcp: false, aws: true } + autonomous: true + sla: "99.95" + maa_medals: [Silver] + cpu_unit: ECPU + cpu_range: "128-16384" + max_storage_tb: 1572 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [Multi-tenant SaaS, ISV, Consolidated workloads] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: Globally Distributed ATP-D + full_name: Globally Distributed Autonomous AI Transaction Processing + management_model: Fully Managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: true + sla: "99.995" + maa_medals: [] + cpu_unit: ECPU + cpu_range: "2-16128" + max_storage_tb: 393 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Globally distributed OLTP, Sharded databases, Low-latency geo-distributed] + byol_support: false + db_versions: ["26ai", "19c"] + + - short_name: NoSQL + full_name: NoSQL Database Cloud Service + management_model: Fully Managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.995" + sla_note: "99.999 with Global Active Tables" + maa_medals: [] + cpu_unit: Serverless + cpu_range: serverless + max_storage_tb: unlimited + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [IoT, Mobile backend, Gaming, Ad-tech, Key-value, Document] + byol_support: false + db_versions: [] + + - short_name: MySQL HeatWave + full_name: MySQL Database Service with HeatWave + management_model: Fully Managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.99" + sla_note: "with High Availability" + maa_medals: [] + cpu_unit: OCPU + cpu_range: "1-128" + max_storage_tb: 64 + select_ai: false + mongo_api: false + open_source: true + recommended_workloads: [Web applications, SaaS, E-commerce, OLTP+Analytics, HeatWave ML/GenAI] + byol_support: false + db_versions: ["8.4", "8.0"] + +# ============================================================================= +# Co-managed PaaS +# ============================================================================= +co_managed_paas: + + - short_name: ExaDB-D + full_name: Exadata Database Service on Dedicated Infrastructure + management_model: Co-managed PaaS + cloud_environments: [OCI, Azure, GCP, AWS] + multicloud: { azure: true, gcp: true, aws: true } + autonomous: false + sla: "99.95" + maa_medals: [Bronze, Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "16-24320" + max_storage_tb: 3924 + select_ai: false + mongo_api: true + open_source: false + recommended_workloads: [Mission-critical OLTP, Large DW, Consolidation, EBS, RAC, Data Guard] + byol_support: true + db_versions: ["26ai", "19c", "12.2", "12.1", "11.2"] + + - short_name: ExaDB-XS + full_name: Exadata Database Service on Exascale Infrastructure (ExaDB-XS) + management_model: Co-managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: false + sla: "99.95" + maa_medals: [Bronze, Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "0-2000" + max_storage_tb: 300 + select_ai: false + mongo_api: true + open_source: false + recommended_workloads: [Small-to-medium Exadata, Dev/Test on Exadata, Entry-level mission-critical] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: ExaDB-C@C + full_name: Exadata Database Service on Cloud@Customer + management_model: Co-managed PaaS + cloud_environments: [Cloud@Customer] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.95" + sla_note: SLO + maa_medals: [Bronze, Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "16-24320" + max_storage_tb: 3924 + select_ai: false + mongo_api: true + open_source: false + recommended_workloads: [Data sovereignty, Regulated industries, Latency-sensitive on-prem, EBS] + byol_support: true + db_versions: ["26ai", "19c", "12.1", "11.2"] + + - short_name: BaseDB SE + full_name: Base Database Service - Standard Edition - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "1-8" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Small departmental, Dev/Test, Non-critical OLTP] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB EE + full_name: Base Database Service - Enterprise Edition - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "1-64" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Enterprise OLTP, Mixed workloads, Moderate DW] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB EE HP + full_name: Base Database Service - Enterprise Edition High Performance - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "1-64" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Enterprise OLTP with partitioning, Advanced compression, OLAP] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB EE EP + full_name: Base Database Service - Enterprise Edition Extreme Performance - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze, Silver, Gold, Platinum] + cpu_unit: OCPU + cpu_range: "1-128" + cpu_note: "RAC up to 128 OCPUs across 2 nodes" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Mission-critical OLTP, RAC, Data Guard, In-Memory, Active Data Guard] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB SE - ECPU + full_name: Base Database Service - Standard Edition - ECPU + management_model: Co-managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: ECPU + cpu_range: "4-32" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Small departmental, Dev/Test, Non-critical OLTP, Multicloud] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB EE - ECPU + full_name: Base Database Service - Enterprise Edition - ECPU + management_model: Co-managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: ECPU + cpu_range: "4-256" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Enterprise OLTP, Mixed workloads, Multicloud] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB EE HP - ECPU + full_name: Base Database Service - Enterprise Edition High Performance - ECPU + management_model: Co-managed PaaS + cloud_environments: [OCI, Azure, GCP] + multicloud: { azure: true, gcp: true, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: ECPU + cpu_range: "4-256" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Enterprise OLTP with partitioning, Advanced compression, Multicloud] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: Globally Distributed ExaDB-XS + full_name: Globally Distributed Exadata Database on Exascale Infrastructure + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.95" + sla_note: SLO + maa_medals: [] + cpu_unit: ECPU + cpu_range: "0-2000" + max_storage_tb: 300 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Globally distributed databases, Sharded Exadata, Geo-distributed OLTP] + byol_support: true + db_versions: ["26ai"] + + - short_name: PostgreSQL + full_name: Oracle Cloud Infrastructure Database with PostgreSQL + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.99" + sla_note: "with multi-node HA" + maa_medals: [] + cpu_unit: OCPU + cpu_range: "2-64" + max_storage_tb: 64 + select_ai: false + mongo_api: false + open_source: true + recommended_workloads: [PostgreSQL workloads, Open source migration, Web applications] + byol_support: false + db_versions: ["15", "14"] + + - short_name: LAK-ExaC@C + full_name: Autonomous AI Lakehouse on Dedicated Infrastructure Cloud@Customer + management_model: Co-managed PaaS + cloud_environments: [Cloud@Customer] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: true + sla: "99.995" + maa_medals: [Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "2-21504" + max_storage_tb: 1572 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [Data sovereignty DW, On-prem analytics, Regulated data lake] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: ATP-ExaC@C + full_name: Autonomous AI Transaction Processing on Dedicated Infrastructure Cloud@Customer + management_model: Co-managed PaaS + cloud_environments: [Cloud@Customer] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: true + sla: "99.995" + maa_medals: [Silver, Gold, Platinum] + cpu_unit: ECPU + cpu_range: "2-21504" + max_storage_tb: 1572 + select_ai: true + mongo_api: true + open_source: false + recommended_workloads: [Data sovereignty OLTP, On-prem mission-critical, Regulated workloads] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB Arm EE + full_name: Base Database Service Arm - Enterprise Edition - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "2-80" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Cost-optimized Enterprise, Arm-native workloads] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB Arm EE EP + full_name: Base Database Service Arm - Enterprise Edition Extreme Performance - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze, Silver, Gold, Platinum] + cpu_unit: OCPU + cpu_range: "2-160" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Cost-optimized mission-critical, Arm RAC, Arm Data Guard] + byol_support: true + db_versions: ["26ai", "19c"] + + - short_name: BaseDB Arm EE HP + full_name: Base Database Service Arm - Enterprise Edition High Performance - VM + management_model: Co-managed PaaS + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: "99.9" + sla_note: SLO + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "2-80" + max_storage_tb: 40 + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Cost-optimized with partitioning, Arm advanced compression] + byol_support: true + db_versions: ["26ai", "19c"] + +# ============================================================================= +# Customer Managed +# ============================================================================= +customer_managed: + + - short_name: IaaS Bare Metal + full_name: OCI Compute - Bare Metal + management_model: Customer Managed + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: null + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "64-192" + max_storage_tb: unlimited + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Full OS control, Custom RAC, Legacy versions, Bare-metal performance] + byol_support: true + db_versions: ["26ai", "19c", "12.2", "12.1", "11.2"] + + - short_name: IaaS VM + full_name: OCI Compute - Virtual Machine + management_model: Customer Managed + cloud_environments: [OCI] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: null + maa_medals: [Bronze] + cpu_unit: OCPU + cpu_range: "1-94" + cpu_note: "E6.Flex up to 126 OCPUs" + max_storage_tb: unlimited + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [Dev/Test, Custom config, Legacy versions, Full OS control] + byol_support: true + db_versions: ["26ai", "19c", "12.2", "12.1", "11.2"] + + - short_name: Exadata DB Machine + full_name: Oracle Database on Exadata Database Machine (on-prem) + management_model: Customer Managed + cloud_environments: [On-Premises] + multicloud: { azure: false, gcp: false, aws: false } + autonomous: false + sla: null + maa_medals: [Bronze, Silver, Gold, Platinum] + cpu_unit: Cores + cpu_range: "0-2880" + max_storage_tb: unlimited + select_ai: false + mongo_api: false + open_source: false + recommended_workloads: [On-prem Exadata, Air-gapped, Full hardware control] + byol_support: true + db_versions: ["26ai", "19c", "12.2", "12.1", "11.2"] + +# ============================================================================= +# Always Free & Developer Editions +# ============================================================================= +always_free_and_developer: + + - short_name: ATP Serverless - Always Free + full_name: Autonomous AI Transaction Processing Serverless - Always Free + base_service: ATP Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI] + autonomous: true + cpu_unit: ECPU + cpu_range: "2" + max_storage_tb: 0.02 + select_ai: true + mongo_api: true + db_versions: ["26ai", "19c"] + notes: "Always Free tier, 20 GB storage, no SLA" + + - short_name: LAK Serverless - Always Free + full_name: Autonomous AI Lakehouse Serverless - Always Free + base_service: LAK Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI] + autonomous: true + cpu_unit: ECPU + cpu_range: "2" + max_storage_tb: 0.02 + select_ai: true + mongo_api: true + db_versions: ["26ai", "19c"] + notes: "Always Free tier, 20 GB storage, no SLA" + + - short_name: AJD Serverless - Always Free + full_name: Autonomous AI JSON Database Serverless - Always Free + base_service: AJD Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI] + autonomous: true + cpu_unit: ECPU + cpu_range: "2" + max_storage_tb: 0.02 + select_ai: true + mongo_api: true + db_versions: ["26ai", "19c"] + notes: "Always Free tier, 20 GB storage, no SLA" + + - short_name: ADB-S Developer + full_name: Autonomous AI Database Serverless - Developer + base_service: ATP Serverless + management_model: Fully Managed PaaS + cloud_environments: [OCI] + autonomous: true + cpu_unit: ECPU + cpu_range: "2-512" + max_storage_tb: 10 + select_ai: true + mongo_api: true + db_versions: ["26ai", "19c"] + notes: "Developer edition, reduced cost, non-production use" + + - short_name: BaseDB Arm Developer + full_name: Base Database Service Arm - Developer Edition - VM + base_service: BaseDB Arm EE + management_model: Co-managed PaaS + cloud_environments: [OCI] + autonomous: false + cpu_unit: OCPU + cpu_range: "2-80" + max_storage_tb: 40 + select_ai: false + mongo_api: false + db_versions: ["26ai", "19c"] + notes: "Arm Developer edition, non-production use" + +# ============================================================================= +# Quick-reference: Multicloud availability matrix +# ============================================================================= +# Service | OCI | Azure | GCP | AWS | C@C +# -------------------------|-----|-------|-----|-----|---- +# ExaDB-D | Y | Y | Y | Y | +# ExaDB-XS | Y | Y | Y | | +# ATP Dedicated | Y | | | Y | +# LAK Dedicated | Y | | | Y | +# ATP Serverless | Y | Y | Y | | +# LAK Serverless | Y | Y | Y | | +# AJD Serverless | Y | Y | Y | | +# ADB-S Elastic Pool | Y | Y | Y | | +# ADB-D Elastic Pool | Y | | | Y | +# BaseDB SE/EE/HP - ECPU | Y | Y | Y | | +# ExaDB-C@C | | | | | Y +# LAK-ExaC@C / ATP-ExaC@C | | | | | Y +# All others | Y | | | | diff --git a/kb/services/exacs.yaml b/kb/services/exacs.yaml new file mode 100644 index 0000000..9adf15c --- /dev/null +++ b/kb/services/exacs.yaml @@ -0,0 +1,102 @@ +--- +last_verified: 2026-03-14 +service: Exadata Cloud Service (ExaCS) +category: database +oci_color: "#AA643B" +--- + +what: Full Oracle Exadata system in OCI. Provides OS access, RAC, full DBA control + with cloud elasticity. Runs the same Exadata software as on-prem. + +when_to_use: + - Need OS-level access for custom agents, scripts, or compliance tools + - RAC required for application architecture + - Sustained high OCPU workloads (>128 OCPUs) + - Lift-and-shift from on-prem Exadata with minimal changes + - Need custom database parameters not available in ADB + - Applications that use features incompatible with ADB (e.g., dblinks to non-Oracle) + +when_NOT_to_use: + - Small/medium workloads (over-provisioned) + - No DBA team (ExaCS requires database administration) + - Variable workloads that benefit from ADB auto-scaling + - Budget-constrained (high minimum commitment) + +limits: + shapes: + - name: "Exadata.Quarter3.100" + base_ocpu: 100 + max_ocpu: 100 + storage_tb: 49.5 + - name: "Exadata.Half3.200" + base_ocpu: 200 + max_ocpu: 200 + storage_tb: 99 + - name: "Exadata.Full3.400" + base_ocpu: 400 + max_ocpu: 400 + storage_tb: 198 + max_vm_clusters: 8 + max_databases_per_cluster: "Limited by resources" + max_pdb_per_cdb: 252 + +features: + os_access: true + rac: true + data_guard: true + cross_region_data_guard: true + custom_db_home: true + custom_patches: true + grid_infrastructure: true + flash_cache: true + smart_scan: true + storage_compression: true + hybrid_columnar_compression: true + +ha_dr: + local_ha: "RAC across Exadata nodes, automatic failover" + data_guard: + same_region: "Synchronous, automatic failover with FSFO" + cross_region: "Asynchronous or far-sync, manual or automatic failover" + +sizing_rules: + minimum: "Quarter rack (100 OCPUs base)" + scaling: "Can enable additional OCPUs in increments" + storage: "Comes with infrastructure, expandable" + +pricing: + model: infrastructure_plus_ocpu + infrastructure_monthly: "Fixed per shape" + enabled_ocpu_per_hour: 0.336 # approximate + byol_ocpu_per_hour: 0.168 + notes: + - "Infrastructure cost is fixed per shape" + - "Only enabled OCPUs are billed" + - "BYOL provides ~50% discount on OCPU portion" + - "Storage included with infrastructure" + +gotchas: + - id: EXACS-001 + issue: "Provisioning takes hours to days" + impact: "Cannot provision on-demand like ADB-S" + recommendation: "Plan capacity well in advance" + - id: EXACS-002 + issue: "Minimum quarter rack commitment" + impact: "Significant base cost even at low utilization" + recommendation: "Ensure sustained usage justifies the infrastructure cost" + - id: EXACS-003 + issue: "DBA team required for ongoing operations" + impact: "Patching, tuning, and maintenance are customer responsibility" + recommendation: "Ensure DBA team capacity before choosing ExaCS over ADB" + - id: EXACS-004 + issue: "Scale-down requires VM cluster modification" + impact: "Not as elastic as ADB-S for variable workloads" + recommendation: "Right-size for steady-state, not peak" + +migration_tools: + - name: Oracle Data Guard + use_for: "On-prem Exadata to ExaCS with minimal downtime" + - name: RMAN Restore + use_for: "Backup-based migration with planned downtime" + - name: Zero Downtime Migration (ZDM) + use_for: "Automated physical migration" diff --git a/kb/services/exadata-cloud.yaml b/kb/services/exadata-cloud.yaml new file mode 100644 index 0000000..af3961e --- /dev/null +++ b/kb/services/exadata-cloud.yaml @@ -0,0 +1,101 @@ +# OCI Service: Exadata Cloud Service (ExaCS) +# Last verified: 2026-03 + +service: + name: "Exadata Cloud Service (ExaCS)" + id: exacs + category: database + color: "#AA643B" + +description: "Dedicated Exadata infrastructure running in OCI, providing the full performance and isolation of Exadata hardware with cloud elasticity. Supports Oracle RAC, Data Guard, and direct OS/CDB-level access for maximum DBA control." + +when_to_use: + - "Oracle RAC workloads requiring multi-node clustering" + - "Regulatory or compliance scenarios demanding dedicated infrastructure isolation" + - "High IOPS / low-latency database workloads (OLTP or mixed)" + - "Sustained compute needs exceeding 128 OCPUs" + - "Need direct OS-level or CDB-level access for custom patching, tuning, or agents" + +when_not_to_use: + - "Small workloads under 16 OCPUs — use Autonomous Database Serverless (ADB-S) or Base Database Service" + - "No dedicated DBA team to manage patching, RAC, and infrastructure" + - "Simple OLTP with no RAC or isolation requirements — use ADB-S instead" + - "Dev/test environments where cost efficiency matters more than performance" + +variants: + - id: exacs_x9m + name: "ExaCS X9M" + description: "Exadata X9M shape — current-generation infrastructure with RDMA over Converged Ethernet and persistent memory acceleration." + - id: exacs_x10m + name: "ExaCS X10M" + description: "Exadata X10M shape — next-generation infrastructure with improved compute, storage density, and throughput." + +sizing: + rack_configurations: + quarter_rack: + ocpus: "100 (scalable)" + memory: "1.44 TB" + storage: "Up to 76.8 TB usable (Exadata Storage)" + db_nodes: 2 + storage_servers: 3 + half_rack: + ocpus: "200 (scalable)" + memory: "2.88 TB" + storage: "Up to 153.6 TB usable" + db_nodes: 4 + storage_servers: 6 + full_rack: + ocpus: "400 (scalable)" + memory: "5.76 TB" + storage: "Up to 307.2 TB usable" + db_nodes: 8 + storage_servers: 12 + ocpu_scaling: "OCPUs can be scaled online in increments without downtime. You pay for enabled OCPUs, not the total available in the rack." + sizing_guidance: "Start with a quarter rack for most production workloads. Scale OCPUs on demand. Choose half/full rack only when storage capacity or aggregate memory requirements exceed quarter rack limits." + +gotchas: + - id: exacs_minimum_cost + severity: HIGH + description: "Minimum infrastructure cost is approximately $6,000+/month for a quarter rack, regardless of OCPU usage. Ensure sustained workload justifies the commitment." + + - id: exacs_provisioning_time + severity: MEDIUM + description: "Exadata infrastructure provisioning takes 4-8 hours. Plan deployments accordingly — this is not an instant resource like a Compute VM." + + - id: exacs_patching_window + severity: MEDIUM + description: "Patching requires a scheduled maintenance window. Database nodes are patched in a rolling fashion but plan for reduced capacity during the process." + + - id: exacs_gi_db_patch_separate + severity: MEDIUM + description: "Grid Infrastructure (GI) and Database Home patches are applied separately and may have different schedules. Coordinate both to avoid version skew." + + - id: exacs_overprovisioning_risk + severity: HIGH + description: "Customers frequently over-provision by choosing half or full racks when a quarter rack with OCPU scaling would suffice. Always validate actual IOPS and CPU requirements before sizing up." + + - id: exacs_network_placement + severity: LOW + description: "ExaCS requires placement in a private subnet with sufficient IP addresses. Each DB node and each virtual IP consumes an IP; plan your subnet CIDR accordingly." + +implied_dependencies: + - service: vcn + reason: "ExaCS infrastructure must be deployed in a VCN" + - service: private_subnet + reason: "DB nodes and SCAN listeners require private subnet placement" + - service: service_gateway + reason: "Access to OCI object storage for backups and patching from private subnets" + - service: oci_vault + reason: "TDE wallet management and encryption key storage" + +competitive_notes: + vs_aws: "AWS has no equivalent dedicated Oracle Exadata infrastructure offering. RDS Oracle and RDS Custom lack RAC support and Exadata storage acceleration. Customers needing true Exadata performance have no AWS-native path." + +references: + documentation: "https://docs.oracle.com/en-us/iaas/Content/Database/Concepts/exaoverview.htm" + pricing: "https://www.oracle.com/cloud/price-list/#exadata" + +changelog: + - date: "2026-03-14" + contributor: { name: "Diego Cabrera", team: "Field Architecture" } + change: "Initial creation with shapes, sizing, gotchas, dependencies" diff --git a/kb/services/networking.yaml b/kb/services/networking.yaml new file mode 100644 index 0000000..06627bb --- /dev/null +++ b/kb/services/networking.yaml @@ -0,0 +1,91 @@ +--- +last_verified: 2026-03-14 +service: OCI Networking +category: networking +oci_color: "#4F7B6E" +--- + +what: Software-defined networking layer in OCI. Includes VCN, subnets, gateways, + load balancers, DNS, and connectivity options. + +components: + vcn: + what: "Virtual Cloud Network — isolated network in OCI" + cidr: "Supports multiple CIDR blocks, /16 to /30" + max_per_region: 50 + subnet: + what: "Subdivision of VCN, can be regional (spans all ADs)" + types: ["public", "private"] + recommendation: "Always use private subnets for databases and app servers" + security_list: + what: "Stateful firewall rules at subnet level" + recommendation: "Prefer NSGs (Network Security Groups) for granular control" + nsg: + what: "Network Security Group — firewall rules at VNIC level" + recommendation: "Preferred over security lists for micro-segmentation" + internet_gateway: + what: "Enables internet access for public subnets" + nat_gateway: + what: "Enables outbound internet for private subnets" + service_gateway: + what: "Private access to OCI services (Object Storage, ADB, etc.)" + recommendation: "Always use for database and storage access — no internet traversal" + drg: + what: "Dynamic Routing Gateway — hub for VCN peering and on-prem connectivity" + supports: ["VCN peering", "FastConnect", "IPSec VPN", "Remote peering"] + load_balancer: + types: + - name: "Flexible Load Balancer" + what: "Layer 7 (HTTP/HTTPS) load balancer" + bandwidth: "10 Mbps to 8 Gbps (flexible)" + - name: "Network Load Balancer" + what: "Layer 4 (TCP/UDP) load balancer" + bandwidth: "Up to line rate" + +connectivity: + fastconnect: + what: "Dedicated private connection to OCI (like AWS Direct Connect)" + speeds: ["1 Gbps", "2 Gbps", "5 Gbps", "10 Gbps", "100 Gbps"] + redundancy: "Always deploy 2 circuits for HA" + notes: "Partner or co-location model, typical setup takes 2-4 weeks" + ipsec_vpn: + what: "Encrypted tunnel over internet" + bandwidth: "Up to 250 Mbps per tunnel, aggregate with multiple tunnels" + notes: "Good for dev/test or backup connectivity, not production data transfer" + vcn_peering: + local: "Within same region, no bandwidth limits" + remote: "Cross-region via DRG, standard inter-region latency" + +pricing: + vcn: "Free" + subnet: "Free" + nat_gateway_per_hour: 0.0084 + service_gateway: "Free" + drg_per_hour: 0.016 + fastconnect: + port_per_month: + 1gbps: 180 + 10gbps: 680 + data_ingress: "Free" + data_egress: "Free over FastConnect" + load_balancer: + flexible_base: 0.014 # per hour + per_mbps: 0.0013 + notes: + - "Inter-region data transfer: $0.0085/GB" + - "Internet egress: first 10 TB/month free, then $0.0085/GB" + - "OCI egress pricing is significantly cheaper than AWS/Azure/GCP" + +gotchas: + - id: NET-001 + issue: "VCN CIDR blocks cannot overlap for peering" + impact: "Plan CIDR ranges carefully for multi-VCN architectures" + recommendation: "Use a documented IP addressing scheme before deployment" + - id: NET-002 + issue: "FastConnect provisioning takes 2-4 weeks with partners" + impact: "Can delay migration timelines" + recommendation: "Start FastConnect procurement early, use VPN as interim" + - id: NET-003 + issue: "Security lists apply to entire subnet, NSGs to specific VNICs" + impact: "Security lists can be too broad for micro-segmentation" + recommendation: "Use NSGs as primary, security lists as fallback" diff --git a/kb/services/oci-networking-core.yaml b/kb/services/oci-networking-core.yaml new file mode 100644 index 0000000..8b2d85e --- /dev/null +++ b/kb/services/oci-networking-core.yaml @@ -0,0 +1,97 @@ +# OCI Service: OCI Networking Core (VCN, Subnets, Gateways) +# Last verified: 2026-03 + +service: + name: "OCI Networking Core" + id: networking_core + category: infrastructure + +description: "Foundational networking bundle covering Virtual Cloud Networks (VCN), subnets, and gateways. Every OCI deployment depends on these components for connectivity, segmentation, and traffic routing." + +components: + - name: "Virtual Cloud Network (VCN)" + id: vcn + description: "Software-defined private network in OCI. Provides the IP address space, DNS, and route table framework for all attached resources." + when_to_use: + - "Required for every OCI deployment — compute, database, Kubernetes, and managed services all reside in a VCN" + + - name: "Subnet (Public)" + id: public_subnet + description: "Subnet where instances can receive public IP addresses and are reachable from the internet via an Internet Gateway." + when_to_use: + - "Load balancers, bastion hosts, or resources that must accept inbound internet traffic" + - "Dev/test instances needing direct internet access" + + - name: "Subnet (Private)" + id: private_subnet + description: "Subnet with no public IP assignment. Resources communicate outbound via NAT Gateway and access OCI services via Service Gateway." + when_to_use: + - "Application servers, databases, and backend services that should not be internet-facing" + - "Any workload subject to security or compliance restrictions on public exposure" + + - name: "Internet Gateway (IGW)" + id: igw + description: "Provides a path for inbound and outbound internet traffic to/from resources in public subnets." + when_to_use: + - "Public-facing load balancers or bastion hosts need internet connectivity" + + - name: "NAT Gateway" + id: nat_gateway + description: "Enables outbound-only internet access for resources in private subnets. No inbound initiation from the internet." + when_to_use: + - "Private subnet resources need to pull OS patches, container images, or call external APIs" + + - name: "Service Gateway" + id: service_gateway + description: "Private pathway from a VCN to supported OCI services (e.g., Object Storage, Autonomous Database) without traffic traversing the internet." + when_to_use: + - "Any private subnet workload that needs to reach OCI Object Storage, streaming, or other supported services" + - "Backup traffic from databases or compute that should stay on the Oracle backbone" + + - name: "Dynamic Routing Gateway (DRG)" + id: drg + description: "Virtual router that connects a VCN to on-premises networks (via FastConnect or IPSec VPN), other VCNs, or remote regions." + when_to_use: + - "Hybrid cloud connectivity to on-premises data centers" + - "VCN-to-VCN peering across regions or tenancies" + - "Hub-and-spoke network topologies" + + - name: "Local Peering Gateway (LPG)" + id: lpg + description: "Connects two VCNs in the same region for private traffic exchange without traversing the internet or a DRG." + when_to_use: + - "Intra-region VCN-to-VCN peering when a DRG hub is not needed" + - "Simple two-VCN peering scenarios" + +design_rules: + - "Allocate a /16 CIDR block per VCN to allow room for future subnet expansion" + - "Plan CIDR ranges for growth — adding non-overlapping CIDRs later is supported but the original CIDR cannot be changed" + - "Avoid CIDR overlap with on-premises networks and other VCNs that will be peered" + - "Create separate subnets per tier (web, app, database) and per availability domain when needed for fault isolation" + - "Prefer Network Security Groups (NSGs) over Security Lists — NSGs are attached to individual resources and are easier to manage at scale" + +gotchas: + - id: vcn_cidr_immutable + severity: HIGH + description: "A VCN's original CIDR block cannot be modified or removed after creation. Additional non-overlapping CIDRs can be added, but plan the initial range carefully." + + - id: security_rule_limit + severity: MEDIUM + description: "Each security list supports a maximum of 200 rules (ingress + egress combined). For complex environments, use multiple NSGs instead of overloading a single security list." + + - id: service_gateway_required + severity: HIGH + description: "Resources in private subnets cannot reach OCI services (Object Storage, Autonomous Database, etc.) without a Service Gateway and corresponding route rule. This is a common misconfiguration." + + - id: drg_required_hybrid + severity: MEDIUM + description: "A DRG is required for FastConnect, IPSec VPN, and cross-VCN peering via transit routing. Plan DRG attachment early — retrofitting routing later adds complexity." + +references: + documentation: "https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/overview.htm" + vcn_best_practices: "https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/bestpractices.htm" + +changelog: + - date: "2026-03-14" + contributor: { name: "Diego Cabrera", team: "Field Architecture" } + change: "Initial creation with VCN, subnets, gateways, DRG design rules and gotchas" diff --git a/kb/services/oci-queue.yaml b/kb/services/oci-queue.yaml new file mode 100644 index 0000000..3748962 --- /dev/null +++ b/kb/services/oci-queue.yaml @@ -0,0 +1,45 @@ +--- +last_verified: 2026-03-14 +service: OCI Queue +category: integration +oci_color: "#6B4D9A" +--- + +what: Fully managed serverless message queue. Supports standard queue semantics + with at-least-once delivery, visibility timeout, and dead letter queues. + +when_to_use: + - Decoupling microservices + - Work distribution across consumers + - Buffering requests during traffic spikes + - Simple pub/sub messaging without Kafka complexity + +when_NOT_to_use: + - Need strict ordering (use OCI Streaming) + - Need real-time streaming analytics (use OCI Streaming) + - Need message replay (use OCI Streaming) + - Complex routing/filtering (use OCI Streaming or third-party) + +limits: + max_message_size_kb: 512 + max_retention_days: 7 + max_queues_per_compartment: 100 + max_channels_per_queue: 10 + max_visibility_timeout_hours: 12 + +features: + dead_letter_queue: true + visibility_timeout: true + long_polling: true + batched_operations: true + encryption_at_rest: true + private_endpoint: true + +pricing: + model: per_request + per_million_requests: 0.40 + data_transfer: "Standard OCI egress pricing" + notes: + - "No minimum commitment" + - "Billed per API request (send, receive, delete)" + - "Very cost-effective for moderate throughput" diff --git a/kb/services/oke.yaml b/kb/services/oke.yaml new file mode 100644 index 0000000..7e17f09 --- /dev/null +++ b/kb/services/oke.yaml @@ -0,0 +1,74 @@ +# OCI Service: Oracle Container Engine for Kubernetes (OKE) +# Last verified: 2026-03 + +service: + name: "Oracle Container Engine for Kubernetes (OKE)" + id: oke + category: infrastructure + color: "#2D5967" + +description: "Managed Kubernetes service with Oracle-optimized control plane, integrated with OCI networking, IAM, and observability." + +when_to_use: + - "Containerized applications (microservices, 12-factor apps)" + - "Need horizontal auto-scaling for application tier" + - "Multi-team environments needing namespace isolation" + - "CI/CD-driven deployments" + - "Workloads requiring service mesh, ingress controllers" + +when_not_to_use: + - "Simple stateless functions → use OCI Functions instead" + - "Single monolithic app with no container strategy → use Compute VMs" + - "Team has no Kubernetes experience and timeline is tight" + - "Batch processing only → consider OCI Data Flow or Compute" + +variants: + - id: oke_managed + name: "OKE Managed Nodes" + description: "Customer manages worker nodes (OS patching, scaling config)" + - id: oke_virtual + name: "OKE Virtual Nodes" + description: "Serverless worker nodes — no OS management, pay per pod" + use_when: + - "Want zero node management overhead" + - "Bursty workloads with variable pod count" + - "Cost optimization for intermittent workloads" + +sizing: + control_plane: "Free (managed by Oracle)" + worker_nodes: + recommended_shapes: + - "VM.Standard.E4.Flex (general purpose, best price/performance)" + - "VM.Standard.A1.Flex (ARM/Ampere, 30-50% cheaper for compatible workloads)" + - "VM.Standard3.Flex (Intel, when Intel compatibility required)" + - "VM.GPU.A10.1 / VM.GPU.A100 (GPU workloads)" + sizing_guidance: "Start with 3 nodes across fault domains for HA. Size OCPUs based on aggregate pod CPU requests + 20% headroom." + +gotchas: + - id: oke_lb_shape + severity: LOW + description: "OKE creates OCI Load Balancers for LoadBalancer-type services. Default is 10 Mbps flexible — adjust annotation for production bandwidth." + + - id: oke_cni_choice + severity: MEDIUM + description: "VCN-Native Pod Networking (OCI CNI) gives pods VCN IPs directly — better for NSG integration but consumes subnet IPs. Flannel overlay is simpler but no direct VCN integration." + + - id: oke_pv_block_volume + severity: LOW + description: "Persistent volumes use OCI Block Volumes. For ReadWriteMany, use OCI File Storage (FSS) CSI driver." + +implied_dependencies: + - service: vcn + reason: "Worker nodes and pods need VCN subnets" + - service: load_balancer + reason: "Ingress traffic to services" + - service: block_storage + reason: "Persistent volumes for stateful pods" + +references: + documentation: "https://docs.oracle.com/en-us/iaas/Content/ContEng/home.htm" + +changelog: + - date: "2026-03-14" + contributor: { name: "Diego Cabrera", team: "Field Architecture" } + change: "Initial creation with managed/virtual nodes, sizing, gotchas, dependencies" diff --git a/kb/services/storage.yaml b/kb/services/storage.yaml new file mode 100644 index 0000000..fcea9ad --- /dev/null +++ b/kb/services/storage.yaml @@ -0,0 +1,70 @@ +--- +last_verified: 2026-03-14 +service: OCI Storage Services +category: storage +oci_color: "#4F7B6E" +--- + +what: OCI storage services including Block Volume, Object Storage, File Storage, + and Archive Storage. + +services: + block_volume: + what: "Network-attached block storage for compute instances" + performance_tiers: + - name: Lower Cost + iops_per_gb: 2 + throughput_kbps_per_gb: 240 + - name: Balanced + iops_per_gb: 60 + throughput_kbps_per_gb: 480 + - name: Higher Performance + iops_per_gb: 75 + throughput_kbps_per_gb: 600 + - name: Ultra High Performance + max_iops: 450000 + max_throughput_mbps: 2680 + max_volume_size_tb: 32 + max_volumes_per_instance: 32 + + object_storage: + what: "S3-compatible object storage" + tiers: + - name: Standard + use_for: "Frequently accessed data" + - name: Infrequent Access + use_for: "Data accessed less than once per month" + - name: Archive + use_for: "Long-term retention, compliance" + retrieval_time: "1 hour (standard), 1 minute (expedited)" + features: + - "S3-compatible API" + - "Versioning" + - "Lifecycle policies" + - "Server-side encryption (SSE)" + - "Replication (same-region and cross-region)" + max_object_size_tb: 10 + + file_storage: + what: "NFSv3-compatible managed file system" + use_for: "Shared file access across multiple compute instances" + max_size: "8 EB (exabytes)" + iops: "Up to 25,000 IOPS per file system" + throughput: "Up to 1.2 GB/s per mount target" + +pricing: + block_volume: + balanced_per_gb_month: 0.0255 + higher_performance_per_gb_month: 0.0340 + uhp_per_gb_month: "Varies by VPU" + object_storage: + standard_per_gb_month: 0.0255 + infrequent_per_gb_month: 0.0100 + archive_per_gb_month: 0.0026 + api_per_10k_requests: 0.0034 + file_storage: + per_gb_month: 0.0340 + notes: + - "Block Volume: first 200 GB free (boot volume)" + - "Object Storage: first 10 GB free, 50k API requests free" + - "Data transfer to Object Storage: free (within region)" diff --git a/kb/sizing/adb-scaling-behavior.yaml b/kb/sizing/adb-scaling-behavior.yaml new file mode 100644 index 0000000..b4375eb --- /dev/null +++ b/kb/sizing/adb-scaling-behavior.yaml @@ -0,0 +1,57 @@ +--- +last_verified: 2026-03-14 +description: ADB-S auto-scaling behavior, timing, and sizing implications. +--- + +auto_scaling: + ocpu_scaling: + trigger: "CPU demand exceeds current allocation" + max_scale: "3x base OCPUs (up to 128 OCPU maximum)" + ramp_up_time: "2-3 minutes to begin scaling" + ramp_down_time: "After sustained low utilization (5-10 minutes)" + billing: "Only billed during active scaling period, per OCPU per second" + + storage_scaling: + trigger: "Storage usage approaches allocated storage" + behavior: "Auto-expands storage allocation" + billing: "Billed for allocated storage" + +sizing_recommendations: + base_ocpu_strategy: | + Size base OCPUs for P75 utilization, not average. + Auto-scaling handles P95+ bursts, but the 2-3 minute ramp-up + means very short spikes may complete before scaling activates. + + guidelines: + - scenario: "Steady OLTP workload" + base: "P75 utilization" + auto_scale: "Enable, set max to P95 or 2x base" + - scenario: "Highly variable workload (2x+ peak/baseline)" + base: "P50 utilization" + auto_scale: "Enable, set max to 3x base" + - scenario: "Batch processing with known peak times" + base: "Baseline requirement" + auto_scale: "Enable, supplement with scheduled scaling if available" + - scenario: "Dev/test" + base: "1-2 OCPUs" + auto_scale: "Disable to control costs" + +connection_impact: + connections_per_ocpu: 300 + example: | + Base 8 OCPUs → 2,400 connections + Auto-scale to 16 OCPUs → 4,800 connections + Connection limits scale with OCPUs automatically + +performance_during_scaling: + notes: + - "Active sessions are not interrupted during scale-up" + - "New connections get additional capacity immediately" + - "Scale-down uses connection draining — active sessions complete normally" + - "Brief performance dip possible during scaling transition" + +gotchas: + - "Short bursts (<3 min) may complete before auto-scaling activates" + - "Very rapid burst from 0 to max can cause connection queuing" + - "Auto-scaling cost can surprise if workload is consistently above base" + - "Monitor auto-scaling events to right-size base OCPUs over time" diff --git a/kb/sizing/cpu-conversion-ratios.yaml b/kb/sizing/cpu-conversion-ratios.yaml new file mode 100644 index 0000000..235c576 --- /dev/null +++ b/kb/sizing/cpu-conversion-ratios.yaml @@ -0,0 +1,72 @@ +# CPU Conversion Ratios: vCPU → OCPU +# Used for right-sizing when migrating from other platforms + +conversion_ratios: + description: | + OCI uses OCPUs (Oracle Compute Units). 1 OCPU = 1 physical core = 2 vCPUs. + When migrating from AWS/Azure/GCP, convert vCPUs to OCPUs using these ratios. + + # Source → OCI OCPU conversion + aws: + general: + ratio: "2 AWS vCPUs = 1 OCI OCPU" + notes: "AWS vCPU = 1 hyperthread. OCI OCPU = 1 physical core (2 threads)." + by_processor: + intel_xeon_2nd_gen: # m5, c5, r5 + vcpu_to_ocpu: 2.0 + intel_xeon_3rd_gen: # m6i, c6i, r6i + vcpu_to_ocpu: 2.0 + amd_epyc_2nd_gen: # m5a, c5a, r5a + vcpu_to_ocpu: 2.0 + notes: "OCI E4 (AMD EPYC 3rd gen) is typically faster per core" + graviton2: # m6g, c6g, r6g + vcpu_to_ocpu: 1.0 + notes: "Graviton2 vCPU = 1 physical core. Map 1:1 to Ampere A1." + graviton3: # m7g, c7g, r7g + vcpu_to_ocpu: 1.0 + notes: "Map to Ampere A2 for comparable performance." + + azure: + general: + ratio: "2 Azure vCores = 1 OCI OCPU (for Intel/AMD)" + by_processor: + intel_dv5: + vcpu_to_ocpu: 2.0 + amd_dv5a: + vcpu_to_ocpu: 2.0 + + on_prem: + oracle_database: + description: "Use AWR Top Activity to determine actual core utilization" + approach: | + 1. Get AWR "DB Time per second" for P75 interval + 2. Divide by number of available cores + 3. Result = core utilization ratio + 4. Multiply by source cores to get required OCPUs + 5. Apply 1.2x headroom factor + fallback: "If no AWR data: 1 on-prem core = 1 OCPU (conservative)" + vmware: + ratio: "2 VMware vCPUs = 1 OCI OCPU" + notes: "VMware vCPU is a hyperthread equivalent" + +adb_s_sizing: + description: | + For ADB-S, size base OCPUs for P75 sustained load: + - Auto-scaling can go up to 3x base OCPUs + - Auto-scaling activation takes 2-3 minutes + - If P95 spikes are short (< 2 min), they may hit before scale-out + - Size base for P75 to ensure good baseline performance + + formula: | + base_ocpus = ceil(p75_cpu_usage / cores_per_ocpu * headroom_factor) + headroom_factor = 1.2 (default) + auto_scale_max = base_ocpus * 3 + +memory_sizing: + oci_shapes: + standard_e4: "16 GB per OCPU (VM.Standard.E4.Flex)" + standard_e5: "16 GB per OCPU (VM.Standard.E5.Flex)" + standard_a1: "6 GB per OCPU (VM.Standard.A1.Flex / Ampere)" + standard_a2: "8 GB per OCPU (VM.Standard.A2.Flex / Ampere)" + optimized3: "14 GB per OCPU (VM.Optimized3.Flex)" + dense_io_e4: "32 GB per OCPU + local NVMe (VM.DenseIO.E4.Flex)" diff --git a/kb/sizing/memory-sizing-rules.yaml b/kb/sizing/memory-sizing-rules.yaml new file mode 100644 index 0000000..f24a382 --- /dev/null +++ b/kb/sizing/memory-sizing-rules.yaml @@ -0,0 +1,57 @@ +--- +last_verified: 2026-03-14 +description: Memory sizing guidelines for OCI compute and database workloads. +--- + +compute_memory: + flexible_shapes: + min_gb_per_ocpu: 1 + max_gb_per_ocpu: 64 + default_gb_per_ocpu: 16 + recommendation: | + Start with 16 GB per OCPU for general workloads. + Java applications: 8-16 GB per OCPU. + In-memory caches (Redis, Memcached): size for data set + 20%. + Database client/app servers: 8 GB per OCPU usually sufficient. + + common_ratios: + general_purpose: 16 # GB per OCPU + memory_intensive: 32 + compute_intensive: 8 + java_application: 12 + web_server: 8 + +database_memory: + adb_s: + notes: "Memory managed automatically by ADB. Not directly configurable." + sga_estimate: "~7 GB per OCPU" + pga_estimate: "~2.5 GB per OCPU" + + exacs: + notes: "Memory allocated per VM cluster from Exadata infrastructure pool" + recommendation: "Size VM cluster memory based on SGA+PGA requirements" + typical_sga_per_ocpu: 8 + typical_pga_per_ocpu: 3 + + dbcs: + notes: "Memory = compute shape memory. Choose shape accordingly." + recommendation: | + Total DB memory = SGA + PGA aggregate + SGA = 60-80% of available memory + PGA = 10-20% of available memory + OS overhead = 10-15% + +sizing_from_source: + on_prem_oracle_to_oci: + steps: + - "Get current SGA + PGA sizes from AWR/ASH" + - "Add 15% for OS overhead" + - "Map to compute shape with sufficient memory" + - "For ADB: translate to OCPU count (memory scales automatically)" + notes: "ADB auto-tunes memory, so OCPU sizing is the key input" + + aws_rds_to_oci: + steps: + - "Note current instance class memory" + - "Map to equivalent OCI shape memory" + - "For ADB: divide memory by 10 to estimate OCPU count" diff --git a/kb/sizing/storage-iops.yaml b/kb/sizing/storage-iops.yaml new file mode 100644 index 0000000..cb06d59 --- /dev/null +++ b/kb/sizing/storage-iops.yaml @@ -0,0 +1,74 @@ +# OCI Storage IOPS and Throughput Reference +# Used for right-sizing block and file storage + +block_volume: + description: "OCI Block Volume performance tiers" + tiers: + - name: "Lower Cost" + iops_per_gb: 2 + max_iops: 3000 + throughput_mbps_per_gb: 0.024 + max_throughput_mbps: 480 + use_when: "Archive, cold data, infrequent access" + + - name: "Balanced" + iops_per_gb: 60 + max_iops: 25000 + throughput_mbps_per_gb: 0.48 + max_throughput_mbps: 480 + use_when: "General purpose, boot volumes, typical workloads" + default: true + + - name: "Higher Performance" + iops_per_gb: 75 + max_iops: 35000 + throughput_mbps_per_gb: 0.6 + max_throughput_mbps: 480 + use_when: "Database data files, high-throughput applications" + + - name: "Ultra High Performance" + iops_per_gb: 90 + max_iops: 120000 + throughput_mbps_per_gb: 0.72 + max_throughput_mbps: 2680 + use_when: "Extreme IOPS requirements, OLTP databases" + notes: "Only available with certain compute shapes" + + sizing_guidance: | + 1. Determine required IOPS (from AWR, CloudWatch, or application benchmarks) + 2. Select tier that provides needed IOPS/GB ratio + 3. Calculate minimum volume size: required_iops / iops_per_gb + 4. Ensure volume size also covers storage capacity needs + 5. If capacity-driven size exceeds IOPS needs, lower tier may suffice + +object_storage: + tiers: + - name: "Standard" + use_when: "Frequently accessed data" + first_10tb_per_month: "$0.0255/GB" + + - name: "Infrequent Access" + use_when: "Accessed < once per month" + retrieval_fee: true + min_retention: "31 days" + + - name: "Archive" + use_when: "Rarely accessed, long-term retention" + restore_time: "1 hour" + min_retention: "90 days" + + auto_tiering: | + Enable auto-tiering to automatically move objects between Standard and + Infrequent Access based on access patterns. No retrieval fee when + auto-tiering moves data back to Standard. + +file_storage: + description: "OCI File Storage Service (NFS)" + performance: "Scales with file system size — larger = more IOPS/throughput" + iops_baseline: "Up to 50,000 IOPS" + throughput: "Up to 1.2 GB/s" + use_when: + - "Shared file system across multiple compute instances" + - "NFS-compatible workloads" + - "Kubernetes ReadWriteMany persistent volumes" + pricing: "$0.30/GB/month" diff --git a/kb/sizing/storage-performance.yaml b/kb/sizing/storage-performance.yaml new file mode 100644 index 0000000..dd91430 --- /dev/null +++ b/kb/sizing/storage-performance.yaml @@ -0,0 +1,70 @@ +--- +last_verified: 2026-03-14 +description: Storage performance characteristics across OCI storage tiers. +--- + +storage_tiers: + adb_serverless: + type: "Exadata managed storage" + iops_random_read: "Up to 600K per ADB instance" + iops_random_write: "Workload dependent" + throughput: "Scales with OCPUs" + latency_read_us: "<200 (Smart Scan eligible)" + latency_write_us: "<500" + notes: "Performance scales with OCPU count, Exadata Smart Scan offloads queries" + + exadata_storage: + type: "Exadata Smart Storage" + iops_random_read: "Up to 6.4M per rack (X9M)" + throughput_gbps: "Up to 100 GB/s (X9M)" + smart_scan: true + flash_cache: true + hcc: true + notes: "Highest performance tier, Smart Scan can offload full table scans" + + block_volume_balanced: + type: "Block Volume (Balanced)" + iops_per_gb: 60 + max_iops: 25000 + throughput_kbps_per_gb: 480 + max_throughput_mbps: 480 + latency_us: "Sub-millisecond" + + block_volume_higher_performance: + type: "Block Volume (Higher Performance)" + iops_per_gb: 75 + max_iops: 35000 + throughput_kbps_per_gb: 600 + max_throughput_mbps: 600 + latency_us: "Sub-millisecond" + + block_volume_uhp: + type: "Block Volume (Ultra High Performance)" + max_iops: 450000 + max_throughput_mbps: 2680 + notes: "Requires UHP-enabled shapes" + + object_storage: + type: "Object Storage" + throughput: "High (parallel multipart uploads)" + latency: "Milliseconds (first byte)" + use_for: "Backups, archives, unstructured data" + not_for: "Random IOPS workloads" + + file_storage: + type: "File Storage (NFS)" + max_iops: 25000 + max_throughput_mbps: 1200 + latency: "Sub-millisecond" + use_for: "Shared file access, NFS workloads" + +sizing_from_source: + exadata_to_adb: + notes: "ADB-S on Exadata infra — performance is comparable or better" + recommendation: "Map directly, ADB auto-tunes storage" + physical_server_to_block_volume: + notes: "Match IOPS and throughput requirements to block volume tier" + recommendation: "Use Higher Performance for database, Balanced for app tier" + san_to_block_volume: + notes: "SAN IOPS vary widely — get actual metrics" + recommendation: "Size for P95 IOPS, test with fio before migration" diff --git a/kb/well-architected/distributed-cloud.yaml b/kb/well-architected/distributed-cloud.yaml new file mode 100644 index 0000000..e6a2abc --- /dev/null +++ b/kb/well-architected/distributed-cloud.yaml @@ -0,0 +1,131 @@ +# OCI Well-Architected Framework — Pillar 5: Distributed Cloud +# Source: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-distributed-cloud-implementation1.html + +pillar: + name: "Distributed Cloud" + id: distributed_cloud + wa_reference: "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-distributed-cloud-implementation1.html" + reference: "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-distributed-cloud-implementation1.html" + conditional: true + applies_when: "multi_region OR hybrid_cloud OR multicloud OR data_residency_requirements OR sovereign_requirements" + +categories: + - name: "Deployment Strategy" + id: dc_deployment_strategy + checks: + - id: DC-DEP-001 + name: "Deployment model selected" + description: "Appropriate deployment model selected (public, dedicated, hybrid, multicloud)" + auto_detect: + pass_if: "deployment model explicitly chosen with justification" + gap_if: "no explicit deployment model decision" + severity: HIGH + applies_when: "distributed_cloud_applicable" + + - id: DC-DEP-002 + name: "Data residency mapping" + description: "Data residency requirements mapped to region selection" + auto_detect: + pass_if: "data residency requirements documented with region mapping" + gap_if: "data residency requirements exist but no region mapping" + severity: HIGH + applies_when: "has_data_residency_requirements" + + - id: DC-DEP-003 + name: "Sovereign/Dedicated Region" + description: "Sovereign Cloud or Dedicated Region considered if required" + auto_detect: + pass_if: "Sovereign Cloud or Dedicated Region evaluated" + gap_if: "sovereignty requirements exist without cloud model evaluation" + severity: HIGH + applies_when: "has_sovereign_requirements" + + - name: "Integration Across Environments" + id: dc_integration + checks: + - id: DC-INT-001 + name: "Consistent IAM" + description: "Consistent IAM across all locations" + auto_detect: + pass_if: "unified IAM strategy across all deployment locations" + gap_if: "separate, uncoordinated IAM in different locations" + severity: HIGH + applies_when: "distributed_cloud_applicable" + + - id: DC-INT-002 + name: "Data synchronization" + description: "Data synchronization strategy between sites" + auto_detect: + pass_if: "data sync strategy defined (replication, ETL, streaming)" + gap_if: "no data synchronization strategy between sites" + severity: HIGH + applies_when: "distributed_cloud_applicable" + + - id: DC-INT-003 + name: "Cross-environment networking" + description: "Network connectivity between environments (FastConnect, VPN, DRG)" + auto_detect: + pass_if: "network connectivity defined between all environments" + gap_if: "no network connectivity plan between environments" + severity: HIGH + applies_when: "distributed_cloud_applicable" + + - name: "Compliance & Sovereignty" + id: dc_compliance + checks: + - id: DC-CMP-001 + name: "Data residency laws" + description: "Regional data residency laws addressed" + auto_detect: + pass_if: "applicable data residency laws identified and addressed" + gap_if: "data residency laws not evaluated" + severity: HIGH + applies_when: "has_data_residency_requirements" + + - id: DC-CMP-002 + name: "Encryption key locality" + description: "Encryption key locality requirements met" + auto_detect: + pass_if: "encryption keys stored in required jurisdictions" + gap_if: "key locality requirements not addressed" + severity: HIGH + applies_when: "has_data_residency_requirements" + + - id: DC-CMP-003 + name: "Cross-environment audit" + description: "Audit trail spans all environments" + auto_detect: + pass_if: "unified audit across all deployment locations" + gap_if: "audit gaps between environments" + severity: MEDIUM + applies_when: "distributed_cloud_applicable" + + - name: "Unified Operations" + id: dc_operations + checks: + - id: DC-OPS-001 + name: "Single pane of glass" + description: "Single pane of glass monitoring across environments" + auto_detect: + pass_if: "centralized monitoring spanning all environments" + gap_if: "separate monitoring per environment" + severity: MEDIUM + applies_when: "distributed_cloud_applicable" + + - id: DC-OPS-002 + name: "IaC consistency" + description: "IaC consistency across all deployment targets" + auto_detect: + pass_if: "same IaC tooling and patterns across all environments" + gap_if: "different IaC approaches in different environments" + severity: MEDIUM + applies_when: "distributed_cloud_applicable" + + - id: DC-OPS-003 + name: "Operations feedback loop" + description: "Feedback loop from operations to design" + auto_detect: + pass_if: "operational feedback process defined" + gap_if: "no formal feedback loop from operations to architecture" + severity: LOW + applies_when: "distributed_cloud_applicable" diff --git a/kb/well-architected/landing-zones.yaml b/kb/well-architected/landing-zones.yaml new file mode 100644 index 0000000..01f2d65 --- /dev/null +++ b/kb/well-architected/landing-zones.yaml @@ -0,0 +1,115 @@ +# OCI Well-Architected Framework — Landing Zone Patterns +# Reference: https://docs.oracle.com/en/solutions/cis-oci-benchmark/ + +landing_zones: + - name: "CIS OCI Landing Zone" + id: cis_landing_zone + description: "CIS Benchmark-aligned landing zone with security best practices baked in" + reference: "https://github.com/oracle-quickstart/oci-cis-landingzone-quickstart" + use_when: + - "Enterprise customers requiring CIS compliance" + - "Greenfield OCI deployments" + - "Customers with regulatory requirements" + provides: + compartments: + - name: "Network" + purpose: "Hub VCN, DRG, FastConnect, VPN" + - name: "Security" + purpose: "Cloud Guard, Vault, Bastion, VSS" + - name: "AppDev" + purpose: "Application workloads, OKE, Functions" + - name: "Database" + purpose: "Database services, Data Safe" + - name: "ExaCS" + purpose: "Exadata Cloud Service (if applicable)" + networking: + - "Hub-spoke VCN topology via DRG" + - "Service Gateway for OCI service access" + - "NAT Gateway for outbound internet" + - "Bastion service for secure access" + - "NSGs for fine-grained network security" + identity: + - "Break-glass admin account" + - "Separation of duties (network, security, appdev admins)" + - "Least-privilege IAM policies" + - "Dynamic groups for instance principals" + security: + - "Cloud Guard with default recipes" + - "Security Zones for production" + - "VCN Flow Logs enabled" + - "OCI Vault for key management" + - "Vulnerability Scanning service" + governance: + - "Defined tags for cost tracking" + - "Budgets per compartment" + - "Notification topics for events" + + - name: "Oracle Enterprise Landing Zone" + id: oelz + description: "Multi-environment enterprise landing zone with full lifecycle management" + reference: "https://github.com/oracle-quickstart/terraform-oci-open-lz" + use_when: + - "Large enterprise deployments" + - "Multi-workload environments" + - "Customers needing full environment isolation" + provides: + compartments: + - name: "Shared Infrastructure" + purpose: "Common services, networking hub, security" + - name: "Production" + purpose: "Production workloads with Security Zones" + - name: "Non-Production" + purpose: "Dev, test, staging environments" + - name: "Logging" + purpose: "Centralized logging and audit" + networking: + - "Hub VCN with Network Firewall option" + - "Spoke VCNs per workload/environment" + - "DRG for transit routing" + - "DNS views per environment" + identity: + - "Multiple Identity Domains" + - "Federated authentication" + - "Role-based access control" + security: + - "Cloud Guard with custom recipes" + - "Security Zones" + - "WAF integration" + - "Data Safe" + + - name: "Minimal Landing Zone" + id: minimal_lz + description: "Lightweight landing zone for POCs, small workloads, or single-application deployments" + use_when: + - "Proof of concept" + - "Small workloads (< 10 resources)" + - "Single application deployment" + - "Developer sandbox" + provides: + compartments: + - name: "Workload" + purpose: "All resources for the workload" + networking: + - "Single VCN" + - "Public and private subnets" + - "Internet Gateway + NAT Gateway" + - "Service Gateway" + identity: + - "Basic IAM policies" + - "Instance principals for compute" + security: + - "Cloud Guard (default)" + - "Default encryption" + +selection_criteria: + - condition: "regulatory_compliance OR enterprise_customer" + recommendation: "cis_landing_zone" + rationale: "CIS compliance provides strong security baseline and meets audit requirements" + + - condition: "multi_environment AND large_enterprise" + recommendation: "oelz" + rationale: "Full environment isolation with centralized governance" + + - condition: "poc OR small_workload OR single_application" + recommendation: "minimal_lz" + rationale: "Fast deployment without over-engineering; can upgrade to CIS later" diff --git a/kb/well-architected/operational-efficiency.yaml b/kb/well-architected/operational-efficiency.yaml new file mode 100644 index 0000000..2d8caff --- /dev/null +++ b/kb/well-architected/operational-efficiency.yaml @@ -0,0 +1,174 @@ +# OCI Well-Architected Framework — Pillar 4: Operational Efficiency +# Source: https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html + +pillar: + name: "Operational Efficiency" + id: operational_efficiency + wa_reference: "https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html" + reference: "https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html" + +categories: + - name: "Deployment Strategy" + id: deployment_strategy + checks: + - id: OPS-DEP-001 + name: "Infrastructure as Code" + description: "Infrastructure as Code (Terraform, Resource Manager) for all resources" + auto_detect: + pass_if: "Terraform or Resource Manager specified for infrastructure provisioning" + gap_if: "no IaC strategy specified" + severity: HIGH + applies_when: always + + - id: OPS-DEP-002 + name: "CI/CD pipeline" + description: "CI/CD pipeline defined for application deployment" + auto_detect: + pass_if: "CI/CD pipeline specified (OCI DevOps, Jenkins, GitLab, etc.)" + gap_if: "no CI/CD pipeline defined" + severity: MEDIUM + applies_when: "has_application_workloads" + + - id: OPS-DEP-003 + name: "Deployment strategy" + description: "Blue/green or canary deployment strategy for production changes" + auto_detect: + pass_if: "deployment strategy (blue/green, canary, rolling) defined" + gap_if: "no deployment strategy for production changes" + severity: MEDIUM + applies_when: "has_production_applications" + + - id: OPS-DEP-004 + name: "Environment parity" + description: "Environment parity (dev/staging/prod use same IaC templates)" + auto_detect: + pass_if: "same IaC templates used across environments with variable overrides" + gap_if: "different provisioning methods across environments" + severity: MEDIUM + applies_when: "has_multiple_environments" + + - name: "Workload Monitoring" + id: workload_monitoring + checks: + - id: OPS-MON-001 + name: "OCI Monitoring alarms" + description: "OCI Monitoring alarms configured for key metrics" + auto_detect: + pass_if: "monitoring alarms defined for CPU, memory, disk, network" + gap_if: "no monitoring alarms configured" + severity: HIGH + applies_when: always + + - id: OPS-MON-002 + name: "Custom application metrics" + description: "Custom metrics for application-specific KPIs" + auto_detect: + pass_if: "custom metrics defined for application performance" + gap_if: "no application-level monitoring beyond infrastructure metrics" + severity: MEDIUM + applies_when: "has_application_workloads" + + - id: OPS-MON-003 + name: "Logging Analytics" + description: "Logging Analytics for centralized log analysis" + auto_detect: + pass_if: "OCI Logging Analytics configured" + gap_if: "no centralized log analysis solution" + severity: MEDIUM + applies_when: always + + - id: OPS-MON-004 + name: "APM configured" + description: "APM configured for application tracing" + auto_detect: + pass_if: "OCI APM or third-party APM configured" + gap_if: "no application performance monitoring" + severity: LOW + applies_when: "has_application_workloads" + + - id: OPS-MON-005 + name: "Database Management" + description: "Database Management enabled for DB performance monitoring" + auto_detect: + pass_if: "OCI Database Management service enabled" + gap_if: "databases without performance monitoring" + severity: MEDIUM + applies_when: "has_databases" + + - id: OPS-MON-006 + name: "Ops Insights" + description: "Ops Insights for capacity planning" + auto_detect: + pass_if: "Ops Insights configured for capacity planning" + gap_if: "no capacity planning solution" + severity: LOW + applies_when: "has_production_workloads" + + - name: "OS Management" + id: os_management + checks: + - id: OPS-OSM-001 + name: "OS Management Hub" + description: "OS Management Hub for patch management" + auto_detect: + pass_if: "OS Management Hub configured for compute fleet" + gap_if: "no automated OS patch management" + severity: MEDIUM + applies_when: "has_compute_instances" + + - id: OPS-OSM-002 + name: "Automated patching schedule" + description: "Automated patching schedule for compute instances" + auto_detect: + pass_if: "patching schedule defined with maintenance windows" + gap_if: "no patching schedule for compute instances" + severity: MEDIUM + applies_when: "has_compute_instances" + + - id: OPS-OSM-003 + name: "Vulnerability remediation SLA" + description: "Vulnerability remediation SLA defined" + auto_detect: + pass_if: "vulnerability remediation timelines defined by severity" + gap_if: "no vulnerability remediation SLA" + severity: MEDIUM + applies_when: "has_compute_instances" + + - name: "Operations Support" + id: operations_support + checks: + - id: OPS-SUP-001 + name: "Support plan level" + description: "Support plan level appropriate for workload criticality" + auto_detect: + pass_if: "Oracle support plan level specified and matches criticality" + gap_if: "no support plan consideration" + severity: LOW + applies_when: always + + - id: OPS-SUP-002 + name: "Notification topics" + description: "Notification topics configured for operational events" + auto_detect: + pass_if: "OCI Notifications configured for operational events" + gap_if: "no notification configuration" + severity: MEDIUM + applies_when: always + + - id: OPS-SUP-003 + name: "Operational runbooks" + description: "Runbooks documented for common operational procedures" + auto_detect: + pass_if: "operational runbooks planned or documented" + gap_if: "no operational runbooks" + severity: LOW + applies_when: "has_production_workloads" + + - id: OPS-SUP-004 + name: "Automated remediation" + description: "OCI Events + Functions for automated remediation" + auto_detect: + pass_if: "event-driven automation configured for common issues" + gap_if: "no automated remediation" + severity: LOW + applies_when: "has_production_workloads" diff --git a/kb/well-architected/performance-cost.yaml b/kb/well-architected/performance-cost.yaml new file mode 100644 index 0000000..5a2b249 --- /dev/null +++ b/kb/well-architected/performance-cost.yaml @@ -0,0 +1,156 @@ +# OCI Well-Architected Framework — Pillar 3: Performance and Cost Optimization +# Source: https://docs.oracle.com/en/solutions/oci-best-practices/index.html + +pillar: + name: "Performance and Cost Optimization" + id: performance_cost + wa_reference: "https://docs.oracle.com/en/solutions/oci-best-practices/index.html" + reference: "https://docs.oracle.com/en/solutions/oci-best-practices/index.html" + +categories: + - name: "Compute Sizing" + id: compute_sizing + checks: + - id: PERF-CMP-001 + name: "Right-sized shapes" + description: "Shapes selected based on workload profile (not over-provisioned)" + auto_detect: + pass_if: "compute shapes justified by workload requirements" + gap_if: "shapes selected without workload sizing analysis" + severity: MEDIUM + applies_when: "has_compute_instances" + + - id: PERF-CMP-002 + name: "Flex shapes used" + description: "Flex shapes used where possible (pay for what you use)" + auto_detect: + pass_if: "flex shapes specified for variable workloads" + gap_if: "fixed shapes used where flex would be more cost-effective" + severity: LOW + applies_when: "has_compute_instances" + + - id: PERF-CMP-003 + name: "Burstable for dev/test" + description: "Burstable instances considered for dev/test" + auto_detect: + pass_if: "burstable or preemptible instances for non-production" + gap_if: "production-grade shapes used for dev/test" + severity: LOW + applies_when: "has_dev_test_environments" + + - id: PERF-CMP-004 + name: "ARM (Ampere) considered" + description: "ARM (Ampere) considered for compatible workloads (better price/performance)" + auto_detect: + pass_if: "Ampere (A1/A2) shapes evaluated for compatible workloads" + gap_if: "x86 shapes used where ARM would provide better price/performance" + severity: LOW + applies_when: "has_compute_instances" + + - name: "Storage Strategy" + id: storage_strategy + checks: + - id: PERF-STR-001 + name: "Storage tier matches access pattern" + description: "Storage tier matches access pattern (Standard, Infrequent Access, Archive)" + auto_detect: + pass_if: "storage tiers aligned with data access frequency" + gap_if: "all data on Standard tier without tiering analysis" + severity: MEDIUM + applies_when: "has_object_storage" + + - id: PERF-STR-002 + name: "Block Volume performance tier" + description: "Block Volume performance tier matches IOPS needs" + auto_detect: + pass_if: "block volume performance tier matches documented IOPS requirements" + gap_if: "block volume tier not aligned with workload IOPS needs" + severity: MEDIUM + applies_when: "has_block_volumes" + + - id: PERF-STR-003 + name: "Object Storage auto-tiering" + description: "Auto-tiering enabled for Object Storage where applicable" + auto_detect: + pass_if: "auto-tiering configured for applicable buckets" + gap_if: "large object storage without auto-tiering evaluation" + severity: LOW + applies_when: "has_large_object_storage" + + - name: "Network Tuning" + id: network_tuning + checks: + - id: PERF-NET-001 + name: "Right-sized bandwidth" + description: "Bandwidth provisioned matches actual needs (not over-provisioned FastConnect)" + auto_detect: + pass_if: "FastConnect bandwidth justified by traffic analysis" + gap_if: "bandwidth provisioned without traffic analysis" + severity: MEDIUM + applies_when: "has_fastconnect" + + - id: PERF-NET-002 + name: "LB type justified" + description: "Network Load Balancer vs. Flexible Load Balancer decision justified" + auto_detect: + pass_if: "load balancer type selection documented with rationale" + gap_if: "load balancer type chosen without justification" + severity: LOW + applies_when: "has_load_balancers" + + - name: "Cost Management" + id: cost_management + checks: + - id: PERF-CST-001 + name: "BYOL analysis" + description: "BYOL vs. License Included analysis done" + auto_detect: + pass_if: "BYOL vs. License Included comparison documented" + gap_if: "licensing model chosen without analysis" + severity: HIGH + applies_when: "has_oracle_licensed_services" + + - id: PERF-CST-002 + name: "Reserved capacity considered" + description: "Reserved capacity considered for stable workloads (1-year or 3-year)" + auto_detect: + pass_if: "reserved capacity evaluated for stable production workloads" + gap_if: "all on-demand pricing for stable workloads" + severity: MEDIUM + applies_when: "has_stable_production_workloads" + + - id: PERF-CST-003 + name: "Budgets and cost alerts" + description: "Budgets and cost alerts configured per compartment" + auto_detect: + pass_if: "OCI Budgets configured with alert thresholds" + gap_if: "no budget alerts configured" + severity: MEDIUM + applies_when: always + + - id: PERF-CST-004 + name: "Cost attribution tagging" + description: "Tagging strategy enables cost attribution" + auto_detect: + pass_if: "cost-tracking tags defined and enforced" + gap_if: "no cost attribution tagging strategy" + severity: MEDIUM + applies_when: always + + - id: PERF-CST-005 + name: "Auto-scaling cost controls" + description: "Auto-scaling min/max configured to prevent runaway costs" + auto_detect: + pass_if: "auto-scaling policies include max limits" + gap_if: "auto-scaling without upper bounds" + severity: MEDIUM + applies_when: "has_auto_scaling" + + - id: PERF-CST-006 + name: "Non-prod optimization" + description: "Non-production environments use smaller shapes or are scheduled off" + auto_detect: + pass_if: "non-prod environments use reduced shapes or have schedules" + gap_if: "non-prod environments running same shapes as production 24/7" + severity: LOW + applies_when: "has_dev_test_environments" diff --git a/kb/well-architected/personas.yaml b/kb/well-architected/personas.yaml new file mode 100644 index 0000000..9a53005 --- /dev/null +++ b/kb/well-architected/personas.yaml @@ -0,0 +1,156 @@ +# OCI Well-Architected Framework — Personas and Role-to-Pillar Mapping +# Used to target Deal Accelerator outputs to the right audience + +personas: + - name: "Cloud Architect" + id: cloud_architect + description: "Responsible for overall cloud architecture design and implementation" + primary_pillars: + - reliability_resilience + - security_compliance + - operational_efficiency + output_emphasis: + - "Full architecture diagram with all service interactions" + - "ADRs for all major design decisions" + - "Reliability and HA patterns" + - "Operational readiness checklist" + scorecard_focus: + lead_with: "Overall architecture health" + highlight_gaps: ["HIGH severity across all pillars"] + de_emphasize: ["LOW severity cost optimizations"] + + - name: "Security Architect" + id: security_architect + description: "Responsible for security posture, compliance, and risk management" + primary_pillars: + - security_compliance + output_emphasis: + - "Security scorecard with all checks detailed" + - "IAM policy structure and federation design" + - "Encryption strategy (at-rest, in-transit, key management)" + - "Cloud Guard configuration and detector recipes" + - "Data Safe assessment and recommendations" + - "Network security architecture (NSGs, WAF, Firewall)" + - "Compliance mapping to regulatory requirements" + scorecard_focus: + lead_with: "Security and Compliance pillar" + highlight_gaps: ["All security gaps regardless of severity"] + de_emphasize: ["Performance optimization", "Cost management"] + + - name: "Enterprise Architect" + id: enterprise_architect + description: "Responsible for strategic alignment, cost optimization, and enterprise governance" + primary_pillars: + - performance_cost + - distributed_cloud + - operational_efficiency + output_emphasis: + - "TCO analysis with BYOL and reserved capacity" + - "Business case with competitive positioning" + - "Distributed cloud strategy if applicable" + - "Governance model and tagging strategy" + - "Migration roadmap and phasing" + scorecard_focus: + lead_with: "Cost optimization and business alignment" + highlight_gaps: ["Cost management gaps", "Governance gaps"] + de_emphasize: ["Technical implementation details"] + + - name: "Network Architect" + id: network_architect + description: "Responsible for network design, connectivity, and security" + primary_pillars: + - security_compliance + - reliability_resilience + output_emphasis: + - "VCN topology with CIDR ranges and subnet design" + - "FastConnect/VPN connectivity with redundancy" + - "DRG routing and transit networking" + - "NSG rules and WAF configuration" + - "DNS and Traffic Management design" + - "Network Firewall placement and rules" + scorecard_focus: + lead_with: "Network security and reliability checks" + highlight_gaps: ["Network-related gaps across all pillars"] + de_emphasize: ["Database security", "Application monitoring"] + + - name: "Database Administrator" + id: dba + description: "Responsible for database architecture, performance, and operations" + primary_pillars: + - security_compliance + - reliability_resilience + - performance_cost + output_emphasis: + - "Database service selection rationale" + - "Data Guard / ADG configuration" + - "Backup and recovery strategy" + - "Data Safe configuration" + - "Performance tuning and monitoring" + - "TDE and encryption key management" + scorecard_focus: + lead_with: "Database security and reliability checks" + highlight_gaps: ["Database-related gaps across all pillars"] + de_emphasize: ["Network design", "Compute sizing"] + + - name: "DevOps Engineer" + id: devops_engineer + description: "Responsible for CI/CD, IaC, and operational automation" + primary_pillars: + - operational_efficiency + output_emphasis: + - "Terraform module structure and Resource Manager stacks" + - "CI/CD pipeline design (OCI DevOps or third-party)" + - "Deployment strategy (blue/green, canary)" + - "Monitoring and alerting configuration" + - "Automated remediation with Events + Functions" + - "OS Management Hub setup" + scorecard_focus: + lead_with: "Operational Efficiency pillar" + highlight_gaps: ["IaC gaps", "CI/CD gaps", "Monitoring gaps"] + de_emphasize: ["Cost optimization", "Distributed cloud"] + + - name: "CTO / VP Engineering" + id: cto + description: "Executive decision maker focused on risk, timeline, and strategic value" + primary_pillars: + - security_compliance + - reliability_resilience + - performance_cost + output_emphasis: + - "Executive summary (1-page)" + - "Risk register with mitigation strategies" + - "Competitive positioning vs AWS/Azure" + - "Implementation timeline with milestones" + - "Cost summary with optimization opportunities" + - "Well-Architected scorecard summary" + scorecard_focus: + lead_with: "Overall status with HIGH severity gaps" + highlight_gaps: ["HIGH severity only"] + de_emphasize: ["Technical implementation details", "LOW severity items"] + + - name: "Finance / Procurement" + id: finance + description: "Focused on cost, licensing, and commercial terms" + primary_pillars: + - performance_cost + output_emphasis: + - "Detailed cost breakdown by service" + - "BYOL vs. License Included analysis" + - "Reserved capacity savings (1yr vs 3yr)" + - "Monthly and annual cost projections" + - "Budget configuration per compartment" + - "Comparison with current spend (on-prem or competitor)" + scorecard_focus: + lead_with: "Performance and Cost Optimization pillar" + highlight_gaps: ["Cost-related gaps only"] + de_emphasize: ["All technical pillars"] + +# Mapping from political decision drivers to personas +decision_driver_mapping: + technical_leadership: [cloud_architect, cto] + security_focused: [security_architect] + cost_conscious: [enterprise_architect, finance] + operations_focused: [devops_engineer] + database_centric: [dba] + network_focused: [network_architect] + executive_sponsorship: [cto, finance] diff --git a/kb/well-architected/reliability-resilience.yaml b/kb/well-architected/reliability-resilience.yaml new file mode 100644 index 0000000..d32c23f --- /dev/null +++ b/kb/well-architected/reliability-resilience.yaml @@ -0,0 +1,213 @@ +# OCI Well-Architected Framework — Pillar 2: Reliability and Resilience +# Source: https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html + +pillar: + name: "Reliability and Resilience" + id: reliability_resilience + wa_reference: "https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html" + reference: "https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html" + +categories: + - name: "Scalability" + id: scalability + checks: + - id: REL-SCL-001 + name: "Auto-scaling configured" + description: "Auto-scaling configured for variable workloads" + auto_detect: + pass_if: "auto-scaling policies defined for compute or OKE" + gap_if: "variable workloads without auto-scaling" + severity: MEDIUM + applies_when: "has_variable_workloads" + + - id: REL-SCL-002 + name: "Service limits reviewed" + description: "Service limits reviewed and increased where needed" + auto_detect: + pass_if: "service limit review mentioned in deployment plan" + gap_if: "no service limit review planned" + severity: LOW + applies_when: always + + - id: REL-SCL-003 + name: "Capacity reservations" + description: "Capacity reservations for critical workloads" + auto_detect: + pass_if: "capacity reservations specified for critical compute" + gap_if: "critical production workloads without capacity reservations" + severity: MEDIUM + applies_when: "has_critical_workloads" + + - name: "Fault-Tolerant Networking" + id: fault_tolerant_networking + checks: + - id: REL-NET-001 + name: "Redundant FastConnect" + description: "Redundant FastConnect circuits (2 virtual circuits, different physical paths)" + auto_detect: + pass_if: "dual FastConnect circuits on diverse paths" + gap_if: "single FastConnect circuit without redundancy" + severity: HIGH + applies_when: "has_fastconnect" + + - id: REL-NET-002 + name: "VPN backup for FastConnect" + description: "VPN as backup if FastConnect is primary" + auto_detect: + pass_if: "IPSec VPN configured as FastConnect backup" + gap_if: "FastConnect without VPN backup" + severity: MEDIUM + applies_when: "has_fastconnect" + + - id: REL-NET-003 + name: "Redundant DRG tunnels" + description: "DRG with redundant tunnels" + auto_detect: + pass_if: "DRG configured with redundant IPSec tunnels" + gap_if: "DRG with single tunnel" + severity: MEDIUM + applies_when: "has_drg" + + - id: REL-NET-004 + name: "Load Balancer health checks" + description: "Load Balancer health checks configured" + auto_detect: + pass_if: "health check policies defined on load balancers" + gap_if: "load balancers without health checks" + severity: HIGH + applies_when: "has_load_balancers" + + - id: REL-NET-005 + name: "Multi-AD/FD compute placement" + description: "Multiple ADs or FDs used for compute placement" + auto_detect: + pass_if: "compute instances distributed across ADs or FDs" + gap_if: "all compute in single AD/FD" + severity: HIGH + applies_when: "has_production_compute" + + - name: "Data Backup" + id: data_backup + checks: + - id: REL-BAK-001 + name: "Automated database backups" + description: "Automated backups enabled for all databases" + auto_detect: + pass_if: "automated backup configured for all database services" + gap_if: "any database without automated backup" + severity: HIGH + applies_when: "has_databases" + + - id: REL-BAK-002 + name: "Backup retention policy" + description: "Backup retention policy defined and documented" + auto_detect: + pass_if: "backup retention periods specified" + gap_if: "no backup retention policy defined" + severity: MEDIUM + applies_when: "has_databases" + + - id: REL-BAK-003 + name: "Block Volume backups" + description: "Block Volume backups scheduled" + auto_detect: + pass_if: "block volume backup policies configured" + gap_if: "block volumes without backup schedules" + severity: MEDIUM + applies_when: "has_block_volumes" + + - id: REL-BAK-004 + name: "Cross-region backup copy" + description: "Cross-region backup copy for critical data" + auto_detect: + pass_if: "cross-region backup replication configured" + gap_if: "critical data without cross-region backup" + severity: MEDIUM + applies_when: "has_critical_data" + + - id: REL-BAK-005 + name: "Boot volume backups" + description: "Boot volume backups for compute instances" + auto_detect: + pass_if: "boot volume backup policies configured" + gap_if: "compute instances without boot volume backups" + severity: LOW + applies_when: "has_compute_instances" + + - name: "Data Replication" + id: data_replication + checks: + - id: REL-REP-001 + name: "Data Guard / ADG" + description: "Data Guard (or ADG for ADB) configured for HA databases" + auto_detect: + pass_if: "Data Guard or Autonomous Data Guard configured" + gap_if: "production databases without HA replication" + severity: HIGH + applies_when: "has_production_databases" + + - id: REL-REP-002 + name: "RPO/RTO documented" + description: "RPO/RTO documented and validated with actual testing" + auto_detect: + pass_if: "RPO and RTO targets defined with validation plan" + gap_if: "no RPO/RTO targets documented" + severity: HIGH + applies_when: "has_production_workloads" + + - id: REL-REP-003 + name: "Cross-region Object Storage replication" + description: "Cross-region replication for Object Storage" + auto_detect: + pass_if: "Object Storage replication configured to DR region" + gap_if: "critical object storage without cross-region replication" + severity: MEDIUM + applies_when: "has_critical_object_storage" + + - name: "Disaster Recovery" + id: disaster_recovery + checks: + - id: REL-DR-001 + name: "DR region identified" + description: "DR region identified and provisioned" + auto_detect: + pass_if: "DR region specified in architecture" + gap_if: "no DR region identified" + severity: HIGH + applies_when: "requires_dr" + + - id: REL-DR-002 + name: "DR architecture documented" + description: "DR architecture documented with switchover/failover procedures" + auto_detect: + pass_if: "DR runbook with switchover/failover procedures defined" + gap_if: "no DR procedures documented" + severity: HIGH + applies_when: "requires_dr" + + - id: REL-DR-003 + name: "DR drill schedule" + description: "DR drill schedule defined (quarterly minimum)" + auto_detect: + pass_if: "DR drill schedule defined" + gap_if: "no DR drill schedule" + severity: MEDIUM + applies_when: "requires_dr" + + - id: REL-DR-004 + name: "DNS failover" + description: "DNS failover or traffic management configured" + auto_detect: + pass_if: "OCI Traffic Management or DNS failover configured" + gap_if: "no automated DNS failover for DR" + severity: MEDIUM + applies_when: "requires_dr" + + - id: REL-DR-005 + name: "Application tier DR" + description: "Application tier DR strategy (pre-provisioned, Terraform-on-demand, or pilot light)" + auto_detect: + pass_if: "application DR strategy defined and documented" + gap_if: "no application tier DR strategy" + severity: HIGH + applies_when: "requires_dr" diff --git a/kb/well-architected/security-compliance.yaml b/kb/well-architected/security-compliance.yaml new file mode 100644 index 0000000..ab56f8d --- /dev/null +++ b/kb/well-architected/security-compliance.yaml @@ -0,0 +1,324 @@ +# OCI Well-Architected Framework — Pillar 1: Security and Compliance +# Source: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html + +pillar: + name: "Security and Compliance" + id: security_compliance + wa_reference: "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html" + reference: "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html" + +categories: + - name: "Identity & Access" + id: identity_access + checks: + - id: SEC-IAM-001 + name: "Least-privilege IAM policies" + description: "IAM policies follow least-privilege (not using broad 'manage all-resources')" + auto_detect: + pass_if: "architecture specifies granular IAM policies per compartment/service" + gap_if: "no IAM policy strategy defined or broad permissions used" + severity: HIGH + applies_when: always + + - id: SEC-IAM-002 + name: "MFA for human users" + description: "MFA enabled for all human users" + auto_detect: + pass_if: "MFA is specified in identity configuration" + gap_if: "no MFA mentioned in identity setup" + severity: HIGH + applies_when: always + + - id: SEC-IAM-003 + name: "Tenancy admin restricted" + description: "Tenancy admin account NOT used for day-to-day operations" + auto_detect: + pass_if: "separate admin groups defined for operations" + gap_if: "no mention of admin account restrictions" + severity: HIGH + applies_when: always + + - id: SEC-IAM-004 + name: "Instance/resource principals" + description: "Service accounts use instance principals or resource principals, not stored credentials" + auto_detect: + pass_if: "instance principals or resource principals specified for service-to-service auth" + gap_if: "stored credentials or API keys used for service accounts" + severity: HIGH + applies_when: always + + - id: SEC-IAM-005 + name: "Multiple Identity Domains" + description: "Multiple Identity Domains if multi-tenant or multi-environment" + auto_detect: + pass_if: "multiple identity domains defined" + gap_if: "single identity domain for multi-tenant setup" + severity: MEDIUM + applies_when: "multi_tenant or multi_environment" + + - id: SEC-IAM-006 + name: "IdP Federation" + description: "Federation configured with customer's IdP (AD, Okta, SAML)" + auto_detect: + pass_if: "federation with external IdP configured" + gap_if: "no IdP federation mentioned when enterprise customer" + severity: MEDIUM + applies_when: "enterprise_customer" + + - name: "Resource Isolation" + id: resource_isolation + checks: + - id: SEC-ISO-001 + name: "Compartment organization" + description: "Compartments organized by workload/environment (not flat)" + auto_detect: + pass_if: "hierarchical compartment structure defined" + gap_if: "flat compartment structure or no compartment strategy" + severity: HIGH + applies_when: always + + - id: SEC-ISO-002 + name: "Tagging strategy" + description: "Tags defined for cost tracking, environment, owner" + auto_detect: + pass_if: "defined tag namespaces with cost, environment, and owner tags" + gap_if: "no tagging strategy defined" + severity: MEDIUM + applies_when: always + + - id: SEC-ISO-003 + name: "Security Zones" + description: "Security Zones enabled for production compartments" + auto_detect: + pass_if: "Security Zones configured for production compartments" + gap_if: "no Security Zones for production workloads" + severity: MEDIUM + applies_when: "has_production_workloads" + + - id: SEC-ISO-004 + name: "Resource principal for cross-resource access" + description: "Cross-resource access uses resource principals, not user credentials" + auto_detect: + pass_if: "resource principals used for cross-service access" + gap_if: "user credentials used for cross-resource access" + severity: HIGH + applies_when: always + + - name: "Database Security" + id: database_security + checks: + - id: SEC-DB-001 + name: "Databases in private subnets" + description: "Databases in private subnets only" + auto_detect: + pass_if: "all database services placed in private subnets" + gap_if: "any database accessible from public subnet" + severity: HIGH + applies_when: "has_databases" + + - id: SEC-DB-002 + name: "TDE with customer-managed keys" + description: "TDE enabled with customer-managed keys (OCI Vault)" + auto_detect: + pass_if: "TDE with OCI Vault customer-managed keys specified" + gap_if: "Oracle-managed keys or no encryption key management specified" + severity: HIGH + applies_when: "has_databases" + + - id: SEC-DB-003 + name: "Key rotation policy" + description: "Key rotation < 90 days" + auto_detect: + pass_if: "key rotation policy defined with < 90 day interval" + gap_if: "no key rotation policy or > 90 day interval" + severity: MEDIUM + applies_when: "has_databases" + + - id: SEC-DB-004 + name: "Data Safe enabled" + description: "Data Safe enabled (audit, masking, VPD assessment)" + auto_detect: + pass_if: "Data Safe configured for database security assessment" + gap_if: "Data Safe not included in architecture" + severity: MEDIUM + applies_when: "has_databases" + + - id: SEC-DB-005 + name: "Private endpoints for ADB" + description: "Private endpoints for Autonomous Database" + auto_detect: + pass_if: "Autonomous Database configured with private endpoints" + gap_if: "Autonomous Database using public endpoints" + severity: HIGH + applies_when: "has_autonomous_database" + + - id: SEC-DB-006 + name: "Restricted DELETE permissions" + description: "DELETE permissions restricted to minimal users/groups" + auto_detect: + pass_if: "explicit restriction on DELETE permissions documented" + gap_if: "no mention of DELETE permission restrictions" + severity: MEDIUM + applies_when: "has_databases" + + - id: SEC-DB-007 + name: "Database security patches" + description: "Database security patches applied (CPU/PSU)" + auto_detect: + pass_if: "patching strategy defined for databases" + gap_if: "no database patching strategy" + severity: HIGH + applies_when: "has_databases" + + - name: "Data Protection" + id: data_protection + checks: + - id: SEC-DAT-001 + name: "Encryption at rest" + description: "Encryption at rest for all storage (Block, File, Object)" + auto_detect: + pass_if: "encryption at rest specified for all storage services" + gap_if: "any storage service without encryption at rest" + severity: HIGH + applies_when: always + + - id: SEC-DAT-002 + name: "OCI Vault key management" + description: "Keys managed in OCI Vault (not Oracle-managed)" + auto_detect: + pass_if: "OCI Vault specified for key management" + gap_if: "using Oracle-managed keys or no key management strategy" + severity: MEDIUM + applies_when: always + + - id: SEC-DAT-003 + name: "No public Object Storage" + description: "Object Storage buckets not publicly accessible" + auto_detect: + pass_if: "Object Storage buckets configured as private" + gap_if: "any Object Storage bucket publicly accessible without justification" + severity: HIGH + applies_when: "has_object_storage" + + - id: SEC-DAT-004 + name: "Retention rules" + description: "Retention rules on critical buckets" + auto_detect: + pass_if: "retention rules configured for critical data buckets" + gap_if: "no retention rules on critical data" + severity: MEDIUM + applies_when: "has_critical_data_in_object_storage" + + - name: "Network Security" + id: network_security + checks: + - id: SEC-NET-001 + name: "NSGs over Security Lists" + description: "NSGs preferred over Security Lists for fine-grained control" + auto_detect: + pass_if: "NSGs used as primary network security mechanism" + gap_if: "only Security Lists used without NSGs" + severity: MEDIUM + applies_when: always + + - id: SEC-NET-002 + name: "No default SSH from anywhere" + description: "Default security list modified (no SSH from 0.0.0.0/0)" + auto_detect: + pass_if: "SSH access restricted to specific CIDR ranges or bastion" + gap_if: "SSH open to 0.0.0.0/0" + severity: HIGH + applies_when: always + + - id: SEC-NET-003 + name: "WAF on internet-facing endpoints" + description: "WAF on all internet-facing endpoints" + auto_detect: + pass_if: "WAF configured for all internet-facing services" + gap_if: "internet-facing endpoints without WAF" + severity: HIGH + applies_when: "has_internet_facing_services" + + - id: SEC-NET-004 + name: "Service Gateway" + description: "Service Gateway for OCI service access (no internet traversal)" + auto_detect: + pass_if: "Service Gateway configured in VCN" + gap_if: "OCI services accessed via internet gateway" + severity: MEDIUM + applies_when: always + + - id: SEC-NET-005 + name: "Network Firewall" + description: "Network Firewall for hub VCN if required" + auto_detect: + pass_if: "Network Firewall deployed in hub VCN" + gap_if: "no Network Firewall in hub-spoke topology requiring deep packet inspection" + severity: LOW + applies_when: "hub_spoke_topology" + + - id: SEC-NET-006 + name: "TLS on Load Balancers" + description: "Load Balancers with TLS termination" + auto_detect: + pass_if: "TLS termination configured on load balancers" + gap_if: "load balancers without TLS" + severity: HIGH + applies_when: "has_load_balancers" + + - id: SEC-NET-007 + name: "Zero Trust Packet Routing" + description: "Zero Trust Packet Routing considered for sensitive workloads" + auto_detect: + pass_if: "ZTPR evaluated or configured" + gap_if: "ZTPR not considered for sensitive workloads" + severity: LOW + applies_when: "has_sensitive_workloads" + + - name: "Monitoring & Audit" + id: monitoring_audit + checks: + - id: SEC-MON-001 + name: "Cloud Guard enabled" + description: "Cloud Guard enabled with detector and responder recipes" + auto_detect: + pass_if: "Cloud Guard configured with appropriate recipes" + gap_if: "Cloud Guard not included in architecture" + severity: HIGH + applies_when: always + + - id: SEC-MON-002 + name: "OCI Audit enabled" + description: "OCI Audit service enabled" + auto_detect: + pass_if: "Audit service enabled (enabled by default, verify retention)" + gap_if: "audit service not mentioned or retention not configured" + severity: MEDIUM + applies_when: always + + - id: SEC-MON-003 + name: "VCN Flow Logs" + description: "VCN Flow Logs enabled" + auto_detect: + pass_if: "VCN Flow Logs enabled for critical subnets" + gap_if: "no VCN Flow Logs configured" + severity: MEDIUM + applies_when: always + + - id: SEC-MON-004 + name: "Vulnerability Scanning" + description: "Vulnerability Scanning enabled" + auto_detect: + pass_if: "OCI Vulnerability Scanning configured for compute instances" + gap_if: "no vulnerability scanning in architecture" + severity: MEDIUM + applies_when: "has_compute_instances" + + - id: SEC-MON-005 + name: "SIEM integration" + description: "Logs aggregated to SIEM if enterprise requirement" + auto_detect: + pass_if: "SIEM integration configured or Logging Analytics used" + gap_if: "no centralized log aggregation for enterprise workload" + severity: MEDIUM + applies_when: "enterprise_customer" diff --git a/prompts/deal-accelerator.md b/prompts/deal-accelerator.md new file mode 100644 index 0000000..eaaf937 --- /dev/null +++ b/prompts/deal-accelerator.md @@ -0,0 +1,170 @@ +# OCI Deal Accelerator + +You are the **OCI Deal Accelerator**, an AI skill that helps Oracle/OCI Solutions Architects compress the cycle from customer discovery to architecture proposal from days to hours. + +You take unstructured discovery notes from a customer meeting and produce a complete, costed, defensible OCI architecture — with diagrams, decision records, cost estimates, risk register, and a Well-Architected scorecard. + +--- + +## Your Principles + +1. **Empirical over theoretical.** Every recommendation must be justifiable with real metrics, benchmarks, or field experience — never "best practice because Oracle says so." +2. **Simplicity first.** Start with the simplest architecture that meets requirements. Complexity must be earned by evidence of need. +3. **Honest about limitations.** Acknowledge what OCI cannot do, where competitors have an edge, and where there are gotchas. Architect credibility depends on honesty. +4. **Composable, not monolithic.** Architectures are assembled from pattern blocks that combine, not from monolithic reference architecture templates. + +--- + +## Your Workflow + +You operate in three phases. You may run all three in sequence or be asked to start at any phase. + +### Phase 1: Discovery Capture → Workload Profile + +**Input:** Unstructured notes — messy, incomplete, mixed languages, abbreviations, half-sentences. This is how architects actually capture information. + +**Your job:** Parse and structure into a **Workload Profile** (YAML). Identify gaps and state reasonable defaults. Tell the architect: "I have enough to start, but I'm missing X, Y, Z. I'll assume [defaults] — correct me if wrong." + +The Workload Profile covers: +- **Current state**: databases (engine, version, size, features, HA), compute, middleware, messaging, storage, networking, identity, compliance, NoSQL/other data stores, integration +- **Requirements**: RTO/RPO, SLA target, performance (P95 latency, TPS, concurrent users), scalability (growth, peak multiplier), migration (downtime tolerance, timeline) +- **Decision drivers**: primary motivation, budget sensitivity, licensing (BYOL/ULA), team skills, political constraints + +Refer to `kb/` files for service details, sizing ratios, and pattern applicability. + +### Phase 2: Architecture Composition + +Given the Workload Profile, compose a complete architecture: + +1. **Select services** across the full OCI catalog — not just database, but compute, networking, security, observability, messaging, integration, AI/ML, migration tooling, governance. + +2. **Dimension each service** using sizing rules from `kb/sizing/`. For Oracle databases, use AWR/CloudWatch metrics if available. Apply conversion ratios (vCPU→OCPU by processor family). For ADB-S, size base OCPUs for P75 (not P50) because auto-scaling activation takes 2-3 minutes. + +3. **Compose the topology** from pattern blocks in `kb/patterns/`. Apply composition rules: + - Check for conflicts between patterns + - Add implied dependencies (ADB-S → Service Gateway for backup; FastConnect → DRG) + - Apply compliance overlays (PCI, HIPAA, SOC2) if required + +4. **Estimate costs** with explicit assumptions. Compare BYOL vs. License Included. Compare reserved vs. PAYG for stable workloads. Break down monthly by component. + +5. **Validate against the Well-Architected Framework** — run the architecture through the 5 pillars (Security, Reliability, Performance/Cost, Operational Efficiency, Distributed Cloud) using checklists from `kb/well-architected/`. Flag gaps automatically. Do NOT ask the architect 50 questions — infer answers from the composed architecture. + +### Phase 3: Output Generation + +Produce these deliverables: + +1. **Architecture Summary** — one page, executive-readable +2. **Architecture Decision Records (ADRs)** — for each significant choice: what, why, alternatives rejected +3. **Architecture Diagram** — generate a `.drawio` file using the diagram generator (see Diagram Generation section below) +4. **Cost Estimate** — line-item breakdown with assumptions +5. **Risk Register** — technical, migration, operational risks +6. **Well-Architected Scorecard** — 5-pillar validation with gaps and recommendations +7. **Competitive Positioning** (if competitor identified) — genuine advantages and honest gaps +8. **Migration High-Level Plan** — phases, dependencies, estimated effort + +--- + +## Service Categorization + +When discussing or diagramming services, use these categories for color coding and grouping: + +| Category | Color | Services | +|----------|-------|----------| +| **Infrastructure** | Teal `#2D5967` | Compute (VM, BM, Flex, Burstable), OKE, Functions, Load Balancer, Gateways (IGW, NAT, SGW), WAF, Bastion, API Gateway, Vault, Data Safe, Cloud Guard, Object/Block/File Storage, Monitoring, Logging, DB Management, Ops Insights, Notifications, Events | +| **Database** | Copper `#AA643B` | ADB-S, ADB-D, DBCS, ExaCS, Exadata, MySQL, PostgreSQL, NoSQL, OpenSearch, OCI Cache (Redis), GoldenGate | +| **Integration** | Purple `#804998` | DRG, Streaming (Kafka), OCI Queue, Oracle Integration Cloud (OIC), FastConnect, Service Connector Hub | +| **Dormant** | Light gray `#DFDCD8` | Standby/inactive resources (DR app tier, pre-provisioned but not running) | +| **Legacy** | Medium gray `#70665E` | Non-OCI systems (MQ Series, legacy middleware, 3rd party) | + +--- + +## Diagram Generation + +When asked to generate a diagram, produce a `.drawio` XML file using the **OCI official container styles** from the OCI Style Guide for Draw.io Toolkit v24.2. + +### Container Style Rules (MANDATORY) + +| Container | Border | Fill | Text Color | Key Attributes | +|-----------|--------|------|------------|----------------| +| **Tenancy** | Dashed `#9E9892` | None | `#312D2A` | `strokeWidth=1;dashed=1` | +| **Region** | Solid `#9E9892` | `#F5F4F2` | `#312D2A` | `rounded=1;arcSize=10` — ONLY container with fill | +| **VCN** | Dashed `#AE562C` | None | `#AE562C` | `strokeWidth=2;dashed=1` — SIGNATURE ORACLE VISUAL | +| **Subnet** | Dashed `#AE562C` | `#FCFBFA` | `#AE562C` | `strokeWidth=1;dashed=1` — thinner than VCN | +| **Compartment** | Dashed `#9E9892` | None | `#312D2A` | Same as Tenancy | + +### Service Block Style +All service blocks: `rounded=1;strokeColor=none;fontColor=#FFFFFF;fontSize=8;fontFamily=Oracle Sans;arcSize=8;` — vary only the `fillColor` per category. + +### Connection Styles +- **Standard**: solid `#706E6F` gray +- **Database flow**: solid `#AA643B` copper, strokeWidth=1.5 +- **Data Guard/Replication**: dashed `#AE562C` burnt orange, strokeWidth=2 +- **FastConnect**: solid `#804998` purple, bidirectional arrows, strokeWidth=2 +- **Migration**: dashed `#706E6F` gray, strokeWidth=1.5 +- **ETL/event-driven**: dashed `#804998` purple + +### Typography +- Font: `Oracle Sans` (fallback: Segoe UI, Helvetica Neue, Arial) +- Text is NEVER pure black — always `#312D2A` +- Container labels: 11-12px +- Service labels: 8-9px white on colored background +- Title: 10px italic `#70665E` + +### Python Generator + +Use the `tools/oci_diagram_gen.py` module to generate `.drawio` files programmatically. It accepts either: +- **Programmatic API**: `gen.add_region(...)`, `gen.add_vcn(...)`, `gen.add_service(...)`, `gen.save("output.drawio")` +- **YAML spec**: `gen = OCIDiagramGenerator.from_spec(yaml_dict)` — declarative, generated from the architecture composition + +The generator produces valid `.drawio` XML with correct nesting (`parent` attributes), proper container hierarchy, and official styles. Service blocks are colored placeholders. For full OCI stencil icons, the architect loads `OCI Library.xml` and drags icons onto placeholders. + +--- + +## Knowledge Base Structure + +``` +kb/ +├── services/ # One YAML per OCI service — what, when to use, when NOT, gotchas, limits +├── patterns/ # Composable architecture blocks with pre/post conditions, conflicts +│ ├── database-ha/ +│ ├── database-dr/ +│ ├── compute-scaling/ +│ ├── networking-hub-spoke/ +│ ├── security-baseline/ +│ ├── compliance-pci/ +│ └── ... +├── sizing/ # CPU conversion ratios, storage IOPS, ADB scaling behavior +├── pricing/ # Simplified pricing models for estimation +├── competitive/ # Service-to-service mapping vs AWS/Azure/GCP with real differences +├── well-architected/ # 5-pillar validation checklists +│ ├── security-compliance.yaml +│ ├── reliability-resilience.yaml +│ ├── performance-cost.yaml +│ ├── operational-efficiency.yaml +│ └── distributed-cloud.yaml +├── diagram/ # Diagram styles, color palette, reference layouts +│ ├── oci-toolkit-styles.yaml +│ └── reference-layouts/ +└── field-knowledge/ # Gotchas, real-world limits, lessons learned +``` + +--- + +## Interaction Style + +- The architect may communicate in **Spanish** but all deliverables are in **English**. +- Be direct and technical. No marketing language. +- When you don't know something, say so. Don't fabricate. +- When a simpler architecture would work, recommend it. Don't over-engineer. +- Present trade-offs explicitly. Let the architect decide. +- When generating outputs, produce the **minimum needed** — don't pad with supplementary docs unless asked. + +--- + +## What You Do NOT Do + +- You do NOT execute infrastructure changes. You design and recommend. +- You do NOT replace the architect's judgment. You accelerate it. +- You do NOT generate pixel-perfect diagrams. You generate 80% drafts the architect refines. +- You do NOT make up pricing. If you don't have current pricing, say so and estimate ranges. +- You do NOT claim features exist if you're unsure. Check the KB first. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b3fbb17 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +pyyaml>=6.0 +python-pptx>=1.0 +python-docx>=1.0 +openpyxl>=3.1 +drawpyo>=0.2.4 diff --git a/scripts/validate-architecture.py b/scripts/validate-architecture.py new file mode 100644 index 0000000..cb94387 --- /dev/null +++ b/scripts/validate-architecture.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python3 +""" +OCI Well-Architected Framework Validation Engine + +Validates an OCI architecture against the 5 Well-Architected pillars +and generates a scorecard with auto-detected gaps and recommendations. + +Usage: + python validate-architecture.py --profile workload-profile.yaml --architecture architecture.yaml + +The validation is automatic — it infers check results from the composed +architecture and workload profile flags. No manual checklist required. +""" + +import yaml +import sys +import os +import json +from datetime import datetime +from pathlib import Path +from typing import Any, List + + +# Pillar YAML files +PILLAR_FILES = [ + "security-compliance.yaml", + "reliability-resilience.yaml", + "performance-cost.yaml", + "operational-efficiency.yaml", + "distributed-cloud.yaml", +] + +KB_DIR = Path(__file__).parent.parent / "kb" / "well-architected" + + +def load_yaml(filepath: str) -> dict: + """Load a YAML file and return its contents.""" + with open(filepath, "r") as f: + return yaml.safe_load(f) + + +def check_applies(applies_when: str, flags: dict) -> bool: + """Determine if a check applies based on the workload profile flags.""" + if applies_when == "always": + return True + + # Handle OR conditions + if " OR " in applies_when: + conditions = [c.strip() for c in applies_when.split(" OR ")] + return any(flags.get(c, False) for c in conditions) + + # Handle AND conditions + if " AND " in applies_when: + conditions = [c.strip() for c in applies_when.split(" AND ")] + return all(flags.get(c, False) for c in conditions) + + # Handle 'or' conditions (lowercase) + if " or " in applies_when: + conditions = [c.strip() for c in applies_when.split(" or ")] + return any(flags.get(c, False) for c in conditions) + + # Single flag check + return flags.get(applies_when, False) + + +def evaluate_check(check: dict, architecture: dict, flags: dict) -> dict: + """ + Evaluate a single check against the architecture. + + Returns a result dict with status (pass/gap/not_applicable) and details. + """ + check_id = check["id"] + applies_when = check.get("applies_when", "always") + + # Check if this check applies to the workload + if not check_applies(applies_when, flags): + return { + "check_id": check_id, + "status": "not_applicable", + "name": check["name"], + } + + # Look for the check_id or related keywords in the architecture + arch_str = json.dumps(architecture).lower() + auto_detect = check.get("auto_detect", {}) + pass_keywords = _extract_keywords(auto_detect.get("pass_if", "")) + + # Check if architecture satisfies the check + matched = any(kw in arch_str for kw in pass_keywords if kw) + + if matched: + return { + "check_id": check_id, + "status": "pass", + "name": check["name"], + } + else: + return { + "check_id": check_id, + "status": "gap", + "name": check["name"], + "severity": check.get("severity", "MEDIUM"), + "finding": auto_detect.get("gap_if", check["description"]), + "recommendation": auto_detect.get("pass_if", ""), + "description": check["description"], + } + + +def _extract_keywords(text: str) -> List[str]: + """Extract searchable keywords from auto_detect conditions.""" + if not text: + return [] + # Extract key service/feature names from the condition text + keywords = [] + key_terms = [ + # Identity & Access + "iam", "mfa", "instance principal", "resource principal", + "instance_principal", "resource_principal", + "identity domain", "identity_domain", "federation", "idp", + "compartment", "least-privilege", "least_privilege", + "admin_restriction", "break-glass", "break_glass", + # Resource Isolation + "tag", "tags", "defined_namespace", "cost_center", "costcenter", + "security zone", "security_zone", + # Database Security + "private subnet", "private_subnet", "private", "tde", + "vault", "vault_key", "key_management", "customer-managed", + "data safe", "data_safe", "private endpoint", "private_endpoint", + "key_rotation", "rotation", "patching", "patch", + # Network Security + "nsg", "waf", "service gateway", "service_gateway", + "network firewall", "network_firewall", + "tls", "ssl", "ztpr", "zero trust", "zero_trust", + "bastion", "ssh_restricted", + # Monitoring + "cloud guard", "cloud_guard", "audit", "flow log", "flow_log", + "vulnerability scan", "vulnerability_scanning", "siem", + "logging_analytics", "logging analytics", + # Scalability + "auto-scaling", "autoscaling", "auto_scaling", + "capacity reservation", "capacity_reservation", + # Networking + "fastconnect", "fast_connect", "vpn", "vpn_backup", + "drg", "health check", "health_check", + "fault domain", "fault_domain", "fault domains", + "availability domain", "availability_domain", + "redundant", "diverse_path", + # Backup & DR + "backup", "automated_backup", "data guard", "data_guard", + "replication", "cross_region", "cross-region", + "rpo", "rto", "dr region", "dr_region", + "disaster recovery", "disaster_recovery", + "traffic management", "traffic_management", + "dns failover", "dns_failover", "drill", + # Operations + "terraform", "resource manager", "resource_manager", + "ci/cd", "cicd", "ci_cd", "devops", + "blue/green", "blue_green", "canary", "rolling", + "monitoring", "alarm", "alarms", "custom_metric", + "apm", "database management", "database_management", + "ops insights", "ops_insights", + "os management", "os_management", "os_management_hub", + "notification", "runbook", "event", "function", + "environment_parity", "parity", + # Compute + "flex", "flex shape", "flex_shape", + "ampere", "arm", "burstable", "preemptible", + # Cost + "reserved capacity", "reserved_capacity", + "budget", "budgets", "byol", "alert_threshold", + "scheduling", "scheduled", + # Storage + "auto_tiering", "auto-tiering", "tiering", + "retention", "retention_rule", + ] + text_lower = text.lower() + for term in key_terms: + if term in text_lower: + keywords.append(term) + return keywords + + +def validate_pillar(pillar_file: str, architecture: dict, flags: dict) -> dict: + """Validate architecture against a single pillar.""" + pillar_data = load_yaml(str(KB_DIR / pillar_file)) + pillar_info = pillar_data["pillar"] + categories = pillar_data.get("categories", []) + + # Check if pillar is conditional (e.g., Distributed Cloud) + if pillar_info.get("conditional", False): + applies = pillar_info.get("applies_when", "") + if not check_applies(applies, flags): + return { + "pillar_id": pillar_info["id"], + "pillar_name": pillar_info["name"], + "status": "NOT_APPLICABLE", + "reason": "Conditions not met for this pillar", + "checks_passed": 0, + "checks_total": 0, + "categories": {}, + "gaps": [], + } + + all_gaps = [] + category_results = {} + total_checks = 0 + total_passed = 0 + + for category in categories: + cat_id = category["id"] + cat_passed = 0 + cat_total = 0 + cat_gaps = [] + + for check in category.get("checks", []): + result = evaluate_check(check, architecture, flags) + + if result["status"] == "not_applicable": + continue + + cat_total += 1 + if result["status"] == "pass": + cat_passed += 1 + else: + gap = { + "check_id": result["check_id"], + "area": category["name"], + "finding": result.get("finding", ""), + "severity": result.get("severity", "MEDIUM"), + "recommendation": result.get("recommendation", ""), + "wa_reference": pillar_info.get("reference", ""), + } + cat_gaps.append(gap) + all_gaps.append(gap) + + category_results[cat_id] = { + "passed": cat_passed, + "total": cat_total, + "gaps": cat_gaps, + } + total_checks += cat_total + total_passed += cat_passed + + # Determine pillar status + high_gaps = sum(1 for g in all_gaps if g["severity"] == "HIGH") + if high_gaps > 0: + status = "GAPS_IDENTIFIED" + elif all_gaps: + status = "PASS_WITH_RECOMMENDATIONS" + else: + status = "PASS" + + return { + "pillar_id": pillar_info["id"], + "pillar_name": pillar_info["name"], + "status": status, + "checks_passed": total_passed, + "checks_total": total_checks, + "categories": category_results, + "gaps": all_gaps, + } + + +def generate_scorecard( + architecture: dict, + profile: dict, + customer_name: str = "", + arch_name: str = "", +) -> dict: + """Generate a complete Well-Architected scorecard.""" + flags = profile.get("workload_profile", {}).get("flags", {}) + + pillar_results = {} + total_checks = 0 + total_passed = 0 + all_gaps = [] + + for pillar_file in PILLAR_FILES: + result = validate_pillar(pillar_file, architecture, flags) + pillar_results[result["pillar_id"]] = result + total_checks += result["checks_total"] + total_passed += result["checks_passed"] + all_gaps.extend(result.get("gaps", [])) + + # Overall status + high_gaps = [g for g in all_gaps if g["severity"] == "HIGH"] + medium_gaps = [g for g in all_gaps if g["severity"] == "MEDIUM"] + low_gaps = [g for g in all_gaps if g["severity"] == "LOW"] + + if high_gaps: + overall_status = "GAPS_IDENTIFIED" + elif medium_gaps: + overall_status = "PASS_WITH_RECOMMENDATIONS" + else: + overall_status = "PASS" + + # Build action items + action_items = { + "high_priority": [ + { + "check_id": g["check_id"], + "pillar": g.get("area", ""), + "finding": g["finding"], + "recommendation": g["recommendation"], + } + for g in high_gaps + ], + "medium_priority": [ + { + "check_id": g["check_id"], + "pillar": g.get("area", ""), + "finding": g["finding"], + "recommendation": g["recommendation"], + } + for g in medium_gaps + ], + "low_priority": [ + { + "check_id": g["check_id"], + "pillar": g.get("area", ""), + "finding": g["finding"], + "recommendation": g["recommendation"], + } + for g in low_gaps + ], + } + + scorecard = { + "well_architected_scorecard": { + "overall_status": overall_status, + "generated_date": datetime.now().isoformat(), + "architecture_name": arch_name, + "customer": customer_name, + "summary": { + "total_checks": total_checks, + "total_passed": total_passed, + "total_gaps": len(all_gaps), + "high_severity_gaps": len(high_gaps), + "medium_severity_gaps": len(medium_gaps), + "low_severity_gaps": len(low_gaps), + }, + "pillars": { + pid: { + "status": p["status"], + "checks_passed": p["checks_passed"], + "checks_total": p["checks_total"], + "categories": p["categories"], + } + for pid, p in pillar_results.items() + }, + "action_items": action_items, + "references": { + "framework": "https://docs.oracle.com/en/solutions/oci-best-practices/index.html", + "security": "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html", + "reliability": "https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html", + "operations": "https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html", + "distributed": "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-distributed-cloud-implementation1.html", + }, + } + } + + return scorecard + + +def main(): + """CLI entry point.""" + import argparse + + parser = argparse.ArgumentParser( + description="Validate OCI architecture against Well-Architected Framework" + ) + parser.add_argument( + "--profile", + required=True, + help="Path to workload profile YAML", + ) + parser.add_argument( + "--architecture", + required=True, + help="Path to architecture YAML", + ) + parser.add_argument( + "--output", + default="scorecard-output.yaml", + help="Output scorecard file path", + ) + parser.add_argument( + "--format", + choices=["yaml", "json"], + default="yaml", + help="Output format", + ) + + args = parser.parse_args() + + profile = load_yaml(args.profile) + architecture = load_yaml(args.architecture) + + customer = ( + profile.get("workload_profile", {}) + .get("business_context", {}) + .get("customer_name", "Unknown") + ) + + scorecard = generate_scorecard( + architecture=architecture, + profile=profile, + customer_name=customer, + arch_name=args.architecture, + ) + + if args.format == "json": + output = json.dumps(scorecard, indent=2) + else: + output = yaml.dump(scorecard, default_flow_style=False, sort_keys=False) + + with open(args.output, "w") as f: + f.write(output) + + # Print summary + sc = scorecard["well_architected_scorecard"] + print(f"\nOCI Well-Architected Scorecard") + print(f"{'=' * 50}") + print(f"Overall Status: {sc['overall_status']}") + print(f"Checks: {sc['summary']['total_passed']}/{sc['summary']['total_checks']} passed") + print(f"Gaps: {sc['summary']['total_gaps']} " + f"(HIGH: {sc['summary']['high_severity_gaps']}, " + f"MEDIUM: {sc['summary']['medium_severity_gaps']}, " + f"LOW: {sc['summary']['low_severity_gaps']})") + print(f"\nPillar Results:") + for pid, p in sc["pillars"].items(): + print(f" {pid}: {p['status']} ({p['checks_passed']}/{p['checks_total']})") + print(f"\nScorecard written to: {args.output}") + + +if __name__ == "__main__": + main() diff --git a/templates/adr-template.md b/templates/adr-template.md new file mode 100644 index 0000000..504fa57 --- /dev/null +++ b/templates/adr-template.md @@ -0,0 +1,43 @@ +# ADR-{NUMBER}: {TITLE} + +**Status**: Accepted | Proposed | Superseded +**Date**: {DATE} +**Deciders**: {STAKEHOLDERS} + +## Context + +{Why this decision was needed. What problem are we solving? What constraints exist?} + +## Decision + +{What was chosen and how it will be implemented.} + +## Alternatives Considered + +### Option A: {NAME} +- **Pros**: {benefits} +- **Cons**: {drawbacks} + +### Option B: {NAME} +- **Pros**: {benefits} +- **Cons**: {drawbacks} + +## Rationale + +{Why this option was selected over alternatives. Reference workload profile requirements, Well-Architected Framework checks, and commercial considerations.} + +## Consequences + +- **Positive**: {benefits gained} +- **Negative**: {trade-offs accepted} +- **Risks**: {what could go wrong and mitigations} + +## Well-Architected Alignment + +| Pillar | Impact | Notes | +|--------|--------|-------| +| Security & Compliance | {PASS/GAP} | {details} | +| Reliability & Resilience | {PASS/GAP} | {details} | +| Performance & Cost | {PASS/GAP} | {details} | +| Operational Efficiency | {PASS/GAP} | {details} | +| Distributed Cloud | {N/A/PASS/GAP} | {details} | diff --git a/templates/business-case.yaml b/templates/business-case.yaml new file mode 100644 index 0000000..853a49e --- /dev/null +++ b/templates/business-case.yaml @@ -0,0 +1,153 @@ +# Business Case Template — DEFINE Phase +# Generates the business justification for OCI adoption. +# Target audience: Customer's internal decision-makers (CxO, finance, procurement). +# Output: Slide deck (.pptx) + YAML spec for reuse. + +business_case: + # --- Header --- + customer_name: "" + prepared_by: "" + date: "" + version: "1.0" + status: draft # draft | review | approved + confidentiality: "Oracle Confidential" + + # --- Executive Summary (1 paragraph for CxO) --- + executive_summary: "" + # e.g., "Migrating 5 Oracle databases from on-prem to ADB-S reduces + # annual infrastructure costs by 47% ($1.2M/year), eliminates 4h/month + # of unplanned downtime, and positions Acme for PCI compliance by Q3." + + # --- Business Drivers --- + drivers: + primary: "" # cost_reduction | modernization | performance | compliance | hw_eol | consolidation | competitive_pressure + secondary: [] # Additional drivers + urgency: "" # Why now? e.g., "Hardware EOL in 6 months" + cost_of_inaction: + financial: "" # "$X/year in maintenance + $Y risk exposure" + operational: "" # "4h monthly downtime, 3 FTEs on manual patching" + strategic: "" # "Competitor already on cloud, losing talent" + urgency_deadline: "" # Absolute date if applicable (e.g., "2026-09-30") + + # --- Current State Summary --- + current_state: + infrastructure: "" # Brief: "3 Exadata racks on-prem, 5 years old" + pain_points: [] # ["Manual patching takes 2 weekends/quarter", "No DR tested in 18 months"] + annual_spend: 0 # Current total annual IT spend for in-scope workloads + team_size: 0 # FTEs dedicated to operations + + # --- TCO Comparison --- + tco: + horizon_years: 3 # 3 or 5 + current_state: + annual_infrastructure: 0 # HW depreciation, DC, power, cooling, network + annual_licensing: 0 # SW licenses + annual support + annual_operations: 0 # FTEs × average cost (DBAs, sysadmins, etc.) + annual_downtime_cost: 0 # Revenue loss + recovery effort + annual_compliance_cost: 0 # Audit prep, remediation, tooling + total_annual: 0 + total_over_horizon: 0 + proposed_oci: + migration_one_time: 0 # Migration effort (consulting, tools, overtime) + annual_cloud_consumption: 0 # OCI UCM or PAYG + annual_licensing: 0 # BYOL savings or License Included delta + annual_operations: 0 # Reduced FTEs (managed services effect) + annual_downtime_cost: 0 # Improved SLA + annual_compliance_cost: 0 # Automated compliance (Data Safe, etc.) + total_annual: 0 + total_over_horizon: 0 # Includes migration one-time + savings: + annual: 0 + over_horizon: 0 + percentage: 0 + assumptions: + - "730 hours/month for always-on workloads" + - "BYOL applied for existing EE licenses with active support" + - "Migration duration: X months" + notes: "" + + # --- ROI Analysis --- + roi: + total_investment: 0 # Migration cost + Year 1 cloud premium + annual_net_benefit: 0 # Annual savings + quantified value + payback_months: 0 + three_year_roi_pct: 0 # (total_benefit - total_investment) / total_investment × 100 + five_year_roi_pct: 0 # Optional + + # --- Value Drivers (quantified where possible) --- + value_drivers: + - category: cost_reduction + title: "" + description: "" + quantified: "" # "$X/year" or "X% reduction" + evidence: "" # Source: benchmark, customer data, field experience + - category: risk_reduction + title: "" + description: "" + quantified: "" + evidence: "" + - category: agility + title: "" + description: "" + quantified: "" + evidence: "" + - category: innovation + title: "" + description: "" + quantified: "" + evidence: "" + + # --- Risk Assessment --- + risks: + migration_risks: + - risk: "" + probability: medium # low | medium | high + impact: high # low | medium | high + mitigation: "" + residual_risk: low + do_nothing_risks: + - risk: "" + probability: high + impact: "" + timeline: "" # When does this risk materialize? + + # --- Competitive Context --- + competitive: + alternatives_considered: [] # ["AWS RDS", "Azure SQL MI", "Stay on-prem"] + why_oci: [] # Differentiators for THIS workload + honest_gaps: [] # Where competitors have an edge + oci_unique: [] # Features only OCI offers (ADB, Exadata, BYOL depth) + + # --- Implementation Roadmap --- + roadmap: + total_duration: "" # e.g., "6 months" + phases: + - name: "Phase 1: Foundation" + duration: "" + deliverables: [] + quick_wins: [] # Early visible value + - name: "Phase 2: Migration" + duration: "" + deliverables: [] + - name: "Phase 3: Optimization" + duration: "" + deliverables: [] + + # --- Success Criteria (post go-live KPIs) --- + success_criteria: + - metric: "" + baseline: "" # Current value + target: "" # Expected value post-migration + measurement: "" # How to measure + timeline: "" # When to measure + + # --- The Ask / Recommendation --- + recommendation: + summary: "" # 1-sentence: "We recommend X" + commitment_type: "" # UCM | PAYG | hybrid | PoC_first + commitment_amount: "" # "$X/year for Y years" + next_steps: + - action: "" + owner: "" # Oracle | Customer | Joint + deadline: "" + escalation_path: "" # If no decision by [date], what happens? diff --git a/templates/customer-profile.yaml b/templates/customer-profile.yaml new file mode 100644 index 0000000..b8298cc --- /dev/null +++ b/templates/customer-profile.yaml @@ -0,0 +1,100 @@ +# ═══════════════════════════════════════════════════════════════ +# ECAL Customer Profile Template +# ═══════════════════════════════════════════════════════════════ +# Source: ECAL 3.1 - Ideate Step (Define Phase) +# Purpose: Strategic profiling of the customer beyond tech details +# Note: This is an INTERNAL document — not shared directly with customer +# ═══════════════════════════════════════════════════════════════ + +metadata: + customer: "" + account_team: "" + date: "" + version: "1.0" + status: "draft" # draft → reviewed → validated + +# ─────────────────────────────────────────────────────────────── +# 1. COMPANY OVERVIEW +# ─────────────────────────────────────────────────────────────── +company: + name: "" + industry: "" + sub_industry: "" # e.g., "retail banking", "life sciences" + headquarters: "" + annual_revenue: "" + employees: "" + fiscal_year_end: "" + stock_ticker: "" # if public + recent_events: [] # M&A, IPO, restructuring, leadership change + +# ─────────────────────────────────────────────────────────────── +# 2. STRATEGIC GOALS & DRIVERS +# ─────────────────────────────────────────────────────────────── +strategy: + published_goals: [] # from annual report, press releases, CEO keynotes + # Example: + # - "Double digital revenue by 2027" + # - "Reduce operating costs by 15% over 3 years" + # - "Expand to 3 new APAC markets" + + technology_strategy: "" # cloud-first? multi-cloud? hybrid? on-prem? + digital_transformation: "" # where are they on the journey? + competitive_position: "" # leader, challenger, follower in their market? + competitor_threats: [] # cloud-native disruptors, adjacent players + +# ─────────────────────────────────────────────────────────────── +# 3. ORACLE FOOTPRINT +# ─────────────────────────────────────────────────────────────── +oracle_footprint: + products_in_use: [] + # Example: + # - { product: "Oracle Database EE", version: "19c", count: 350, licensing: "Processor" } + # - { product: "WebLogic", version: "14c", count: 12, licensing: "NUP" } + licensing: + type: "" # ULA, PULA, Processor, NUP, Named User + expiry: "" # when does ULA/PULA expire? + byol_eligible: "" + support_spend: "" # annual Oracle support spend + existing_oci_usage: "" # any OCI tenancy already? what's running? + existing_cloud_usage: [] # AWS, Azure, GCP — what workloads? + +# ─────────────────────────────────────────────────────────────── +# 4. INDUSTRY ANALYSIS +# ─────────────────────────────────────────────────────────────── +industry_context: + market_trends: [] # relevant industry trends + regulatory_landscape: [] # regulations affecting IT decisions + peer_comparison: "" # how does this customer compare to industry peers? + # Example: + # market_trends: + # - "Open banking APIs mandated by 2025" + # - "DORA regulation requiring operational resilience testing" + # regulatory_landscape: + # - "EBA guidelines on ICT risk management" + # - "GDPR data residency requirements" + +# ─────────────────────────────────────────────────────────────── +# 5. RELATIONSHIP CONTEXT +# ─────────────────────────────────────────────────────────────── +relationship: + account_manager: "" + current_engagement_status: "" # active deal, renewal, new, expansion + previous_engagements: [] # past ECAL/consulting engagements + customer_sentiment: "" # positive, neutral, at-risk + executive_sponsors: + oracle: "" # Oracle exec sponsor + customer: "" # Customer exec sponsor + key_contacts: + - { name: "", role: "", influence: "", relationship: "" } + +# ─────────────────────────────────────────────────────────────── +# 6. INITIAL HYPOTHESIS +# ─────────────────────────────────────────────────────────────── +# Based on the profile, what Point of View could resonate? +hypotheses: + - hypothesis: "" + business_driver: "" # link to strategy.published_goals + pillar: "" # strategic / financial / business_ops / it_ops + confidence: "" # high / medium / low + evidence: "" # what supports this hypothesis? + risks: "" # what could invalidate it? diff --git a/templates/discovery-questionnaire.yaml b/templates/discovery-questionnaire.yaml new file mode 100644 index 0000000..bdb8bb2 --- /dev/null +++ b/templates/discovery-questionnaire.yaml @@ -0,0 +1,174 @@ +# ═══════════════════════════════════════════════════════════════ +# ECAL Discovery Questionnaire +# ═══════════════════════════════════════════════════════════════ +# Source: ECAL 3.1 - Current Step (Design Phase) +# Purpose: Structured collection of customer IT landscape info +# Usage: Send to customer pre-workshop or fill collaboratively +# ═══════════════════════════════════════════════════════════════ + +metadata: + customer: "" + engagement: "" + date: "" + completed_by: "" + version: "1.0" + +# ─────────────────────────────────────────────────────────────── +# 1. BUSINESS CONTEXT +# ─────────────────────────────────────────────────────────────── +business_context: + industry: "" + annual_revenue: "" + employees: "" + geographies: [] # regions/countries with IT presence + business_drivers: # what is motivating this initiative? + strategic: [] # cloud strategy, digital transformation, M&A + financial: [] # cost reduction, CAPEX→OPEX, license optimization + operational: [] # efficiency, time-to-market, agility + compliance: [] # regulatory, audit, data sovereignty + urgency: + deadline: "" # hard deadline if any + trigger: "" # contract renewal, EOL, compliance, competitive + key_stakeholders: + sponsor: "" # executive sponsor name/role + technical_lead: "" # customer technical DRI + decision_makers: [] # who signs off + +# ─────────────────────────────────────────────────────────────── +# 2. CURRENT STATE — APPLICATIONS & SYSTEMS +# ─────────────────────────────────────────────────────────────── +applications: + # Repeat this block for each application in scope + - name: "" + description: "" + business_criticality: "" # Critical / High / Medium / Low + current_platform: "" # e.g., "On-prem VMware", "AWS EC2", "bare metal" + database: + type: "" # Oracle, PostgreSQL, MySQL, SQL Server, etc. + version: "" + size_gb: "" + instance_count: "" + ha_current: "" # RAC, Data Guard, none, etc. + dr_current: "" # cross-site DG, backup only, none + compute: + type: "" # VM, bare metal, container + cpu_cores: "" + memory_gb: "" + os: "" + storage: + type: "" # SAN, NAS, local, S3 + capacity_gb: "" + iops_required: "" + integrations: + - target: "" # what does it integrate with? + method: "" # API, DB link, file transfer, messaging + frequency: "" # real-time, batch daily, batch weekly + data_volume: "" # GB/day or records/day + users: + concurrent: "" + total: "" + locations: [] # where are users located? + sla: + uptime_requirement: "" # e.g., 99.9%, 99.99% + rto: "" # recovery time objective + rpo: "" # recovery point objective + compliance: + frameworks: [] # PCI-DSS, HIPAA, GDPR, SOX, banking regs + data_classification: "" # public, internal, confidential, restricted + data_residency: "" # must stay in specific country/region? + migration_preference: "" # lift-and-shift, re-platform, re-architect, retain + notes: "" + +# ─────────────────────────────────────────────────────────────── +# 3. CURRENT STATE — INFRASTRUCTURE +# ─────────────────────────────────────────────────────────────── +infrastructure: + data_centers: + - location: "" + type: "" # owned, colo, cloud + provider: "" # if cloud: AWS, Azure, GCP, OCI + role: "" # primary, DR, dev/test + networking: + wan_type: "" # MPLS, SD-WAN, internet VPN + bandwidth_to_cloud: "" # if already connected + latency_requirements: "" # between sites, to users + dns_provider: "" + load_balancing: "" # F5, Citrix, cloud-native + identity: + idp: "" # Active Directory, Okta, Azure AD, Oracle IAM + sso: "" # yes/no, which protocol (SAML, OIDC) + mfa: "" # yes/no, which product + security: + firewall: "" # product/vendor + waf: "" # yes/no, product + siem: "" # product (Splunk, QRadar, etc.) + encryption_at_rest: "" # yes/no, method + encryption_in_transit: "" # yes/no, method + key_management: "" # HSM, KMS, manual + monitoring: + tools: [] # e.g., [Datadog, Nagios, OEM, CloudWatch] + alerting: "" # PagerDuty, email, custom + log_aggregation: "" # ELK, Splunk, OCI Logging + +# ─────────────────────────────────────────────────────────────── +# 4. CURRENT STATE — PEOPLE & PROCESS +# ─────────────────────────────────────────────────────────────── +people_and_process: + team: + total_it_staff: "" + dba_count: "" + cloud_engineers: "" + devops_engineers: "" + security_staff: "" + cloud_experience: "" # none, basic, intermediate, advanced + oracle_experience: "" # none, basic, intermediate, advanced + certifications: [] # OCI, AWS, Azure, etc. + operations: + change_management: "" # CAB, lightweight approval, none + deployment_process: "" # manual, CI/CD, IaC + iac_tools: [] # Terraform, Ansible, CloudFormation, ARM + ci_cd_tools: [] # Jenkins, GitLab CI, GitHub Actions, OCI DevOps + patching_cadence: "" # monthly, quarterly, ad-hoc + backup_testing: "" # frequency of restore tests + incident_process: "" # NOC, on-call, outsourced + itsm_tool: "" # ServiceNow, Jira, BMC + managed_services: + current_provider: "" # self-managed, outsourced to whom? + preference: "" # self-managed, co-managed, fully managed + budget_for_managed: "" # yes/no, approximate + +# ─────────────────────────────────────────────────────────────── +# 5. CONSTRAINTS & ASSUMPTIONS +# ─────────────────────────────────────────────────────────────── +constraints: + budget: + capex_available: "" + opex_monthly_target: "" + existing_oracle_licenses: "" # ULA, PULA, NUP, Processor + byol_eligible: "" # yes/no, which products + timeline: + project_start: "" + hard_deadline: "" + phased_approach_ok: "" # yes/no + technical: + - "" # e.g., "Must keep Oracle RAC", "Cannot change app code" + regulatory: + - "" # e.g., "Data must stay in EU", "FCA audit requirements" + organizational: + - "" # e.g., "Change freeze Dec-Jan", "Union approval needed" + +# ─────────────────────────────────────────────────────────────── +# 6. PRIORITIZATION (fill after workshop) +# ─────────────────────────────────────────────────────────────── +prioritization: + # Score each application 1-5 on these criteria + # Then sort by weighted score to determine migration order + criteria: + - { name: "Business Criticality", weight: 0.25 } + - { name: "Functional Fit (cloud-ready)", weight: 0.20 } + - { name: "Transformation Potential", weight: 0.20 } + - { name: "Technology Fit (OCI alignment)", weight: 0.20 } + - { name: "Risk Profile (migration risk)", weight: 0.15 } # inverted: lower risk = higher score + scores: [] + # Example: + # - { app: "Core ERP", criticality: 5, functional_fit: 3, transformation: 4, tech_fit: 4, risk: 2, weighted: 3.7 } diff --git a/templates/go-live-checklist.yaml b/templates/go-live-checklist.yaml new file mode 100644 index 0000000..1a78a06 --- /dev/null +++ b/templates/go-live-checklist.yaml @@ -0,0 +1,192 @@ +# Go-Live Checklist Template — DELIVER/Operate Phase +# Pre-go-live verification items. Check ALL before cutover. + +go_live_checklist: + customer_name: "" + project_name: "" + target_go_live_date: "" + architect: "" + + # --- Infrastructure Readiness --- + infrastructure: + - item: "All OCI resources provisioned per architecture design" + status: "" # done, in_progress, blocked, not_applicable + owner: "" + notes: "" + + - item: "Network connectivity verified (FastConnect/VPN throughput and latency)" + status: "" + owner: "" + notes: "" + + - item: "DNS cutover plan documented and tested" + status: "" + owner: "" + notes: "" + + - item: "SSL/TLS certificates provisioned and configured" + status: "" + owner: "" + notes: "" + + - item: "Non-production environments operational" + status: "" + owner: "" + notes: "" + + # --- Security Readiness --- + security: + - item: "IAM policies configured per least-privilege design" + status: "" + owner: "" + notes: "" + + - item: "Network security (NSGs, WAF, Bastion) configured and tested" + status: "" + owner: "" + notes: "" + + - item: "Encryption at rest and in transit verified" + status: "" + owner: "" + notes: "" + + - item: "Cloud Guard enabled with appropriate recipes" + status: "" + owner: "" + notes: "" + + - item: "Audit logging enabled and retention configured" + status: "" + owner: "" + notes: "" + + - item: "Compliance controls verified (PCI/HIPAA/SOC2 if applicable)" + status: "" + owner: "" + notes: "" + + # --- Data Readiness --- + data: + - item: "Data migration completed and validated" + status: "" + owner: "" + notes: "" + + - item: "Data integrity checks passed (row counts, checksums)" + status: "" + owner: "" + notes: "" + + - item: "Replication lag within acceptable RPO" + status: "" + owner: "" + notes: "" + + - item: "Backup and recovery tested (full restore to point-in-time)" + status: "" + owner: "" + notes: "" + + # --- Application Readiness --- + application: + - item: "Application functional testing passed in OCI environment" + status: "" + owner: "" + notes: "" + + - item: "Performance testing completed (meets P95 latency targets)" + status: "" + owner: "" + notes: "" + + - item: "Connection strings and endpoints updated" + status: "" + owner: "" + notes: "" + + - item: "Rollback procedure documented and tested" + status: "" + owner: "" + notes: "" + + # --- Monitoring & Operations Readiness --- + operations: + - item: "Monitoring dashboards configured and validated" + status: "" + owner: "" + notes: "" + + - item: "Alerting rules active and notification channels verified" + status: "" + owner: "" + notes: "" + + - item: "Runbooks documented for common failure scenarios" + status: "" + owner: "" + notes: "" + + - item: "On-call rotation established" + status: "" + owner: "" + notes: "" + + - item: "Ops team trained on OCI console and tools" + status: "" + owner: "" + notes: "" + + # --- HA/DR Readiness --- + ha_dr: + - item: "HA failover tested (automatic recovery verified)" + status: "" + owner: "" + notes: "" + + - item: "DR switchover tested (meets RTO/RPO targets)" + status: "" + owner: "" + notes: "" + + - item: "Auto-scaling tested under load" + status: "" + owner: "" + notes: "" + + # --- Cutover Plan --- + cutover: + - item: "Cutover window communicated to all stakeholders" + status: "" + owner: "" + notes: "" + + - item: "Cutover runbook reviewed and rehearsed" + status: "" + owner: "" + notes: "" + + - item: "Rollback criteria defined (when to roll back)" + status: "" + owner: "" + notes: "" + + - item: "War room / communication channel established for go-live" + status: "" + owner: "" + notes: "" + + # --- Sign-off --- + sign_off: + - role: "Solutions Architect" + name: "" + approved: false + date: "" + - role: "Customer Technical Lead" + name: "" + approved: false + date: "" + - role: "Customer Business Sponsor" + name: "" + approved: false + date: "" diff --git a/templates/handover-document.yaml b/templates/handover-document.yaml new file mode 100644 index 0000000..0f04cd7 --- /dev/null +++ b/templates/handover-document.yaml @@ -0,0 +1,104 @@ +# Handover Document Template — DELIVER/Adopt Phase +# Single-source summary for implementation team handover + +handover: + # --- Header --- + customer_name: "" + project_name: "" + date: "" + architect: "" + implementation_partner: "" # Oracle Consulting, partner name, customer self-implement + + # --- Value Story Summary --- + business_driver: "" + desired_outcome: "" + success_criteria_reference: "" # pointer to success-criteria artifact + + # --- Scope --- + scope: + in_scope: + - "" + out_of_scope: + - "" + mvp_scope: + - "" # What ships in Phase 1 + + # --- Architecture Summary --- + architecture: + diagram_reference: "" # path to .drawio file + key_decisions: + - decision: "" + rationale: "" + adr_reference: "" # e.g., "ADR-001" + services_used: + - service: "" + purpose: "" + configuration: "" # e.g., "ADB-S, 4 base OCPU, auto-scaling to 12" + + # --- Environment Strategy --- + environments: + - name: "Production" + region: "" + sizing: "" # e.g., "Full sizing per proposal" + - name: "UAT/Staging" + region: "" + sizing: "" # e.g., "50% of production" + - name: "Development" + region: "" + sizing: "" # e.g., "25% of production" + + # --- Migration Plan Summary --- + migration: + strategy: "" # e.g., "Phased migration over 8 weeks" + phases: + - phase: 1 + description: "" + duration: "" + components: [] + dependencies: [] + - phase: 2 + description: "" + duration: "" + components: [] + dependencies: [] + tooling: [] # e.g., [DMS, GoldenGate, Data Pump] + rollback_plan: "" + + # --- Open Items --- + open_items: + - item: "" + owner: "" + due_date: "" + impact_if_unresolved: "" # e.g., "Cannot proceed with Phase 2" + + # --- Assumptions to Verify --- + assumptions: + - assumption: "" # e.g., "AWR data represents typical workload" + status: "" # confirmed, pending, at_risk + verification_method: "" + + # --- Risk Register Reference --- + top_risks: + - risk: "" + severity: "" # critical, high, medium + mitigation: "" + owner: "" + + # --- Contacts --- + contacts: + oracle: + - role: "" + name: "" + availability: "" # e.g., "Available for questions during implementation" + customer: + - role: "" + name: "" + partner: + - role: "" + name: "" + + # --- Governance --- + governance: + steering_cadence: "" # e.g., "Bi-weekly" + escalation_path: "" + change_control: "" # e.g., "Changes via JIRA, approved by steering committee" diff --git a/templates/joint-engagement-plan.yaml b/templates/joint-engagement-plan.yaml new file mode 100644 index 0000000..d73991e --- /dev/null +++ b/templates/joint-engagement-plan.yaml @@ -0,0 +1,79 @@ +# Joint Engagement Plan Template — DEFINE Phase +# Scopes the DESIGN phase: timebox, resources, milestones, success criteria + +engagement_plan: + # --- Header --- + customer_name: "" + project_name: "" + date: "" + architect: "" + engagement_tier: "" # small, standard, complex + + # --- Value Story Reference --- + value_story_summary: "" # 1-2 sentence recap of the hypothesis + business_driver: "" + + # --- Scope --- + scope: + in_scope: + - "" # e.g., "3 Oracle OLTP databases (PROD, UAT, DR)" + out_of_scope: + - "" # e.g., "Reporting data warehouse (Phase 2)" + assumptions: + - "" # e.g., "AWR reports will be provided within 1 week" + + # --- Timeline --- + timeline: + design_start: "" # e.g., "2026-03-20" + design_end: "" # e.g., "2026-04-17" + proposal_date: "" # When the solution proposal will be presented + decision_date: "" # When the customer commits (or iterates) + + # --- Milestones --- + milestones: + - name: "Current State Review" + date: "" + deliverable: "Validated Workload Profile" + attendees: [] + + - name: "Architecture Workshop" + date: "" + deliverable: "Draft architecture diagram + key decisions" + attendees: [] + + - name: "Solution Proposal" + date: "" + deliverable: "Complete proposal deck + cost estimate" + attendees: [] + + # --- Resources --- + resources: + oracle_team: + - role: "Solutions Architect" + name: "" + commitment: "" # e.g., "40% for 4 weeks" + - role: "" # e.g., "Database Specialist", "Security Architect" + name: "" + commitment: "" + + customer_team: + - role: "" # e.g., "Technical Lead", "DBA", "Security" + name: "" + commitment: "" # e.g., "Available for workshops + async questions" + + # --- Success Criteria for DESIGN Phase --- + design_success_criteria: + - "Architecture validated against Well-Architected Framework" + - "Cost estimate within customer's budget range" + - "Migration plan achievable within stated timeline" + - "" # Add engagement-specific criteria + + # --- Risks to the Engagement --- + engagement_risks: + - risk: "" # e.g., "Customer may not provide AWR data in time" + impact: "" # high, medium, low + mitigation: "" # e.g., "Use sizing estimates with safety margin, flag assumptions" + + # --- Artifacts to Produce --- + # Auto-populated based on engagement_tier — see docs/engagement-tiers.md + planned_artifacts: [] diff --git a/templates/lessons-learned.yaml b/templates/lessons-learned.yaml new file mode 100644 index 0000000..1c5b8f7 --- /dev/null +++ b/templates/lessons-learned.yaml @@ -0,0 +1,53 @@ +# Lessons Learned Template — DELIVER/Improve Phase +# Captures what worked, what didn't, and improvements for future engagements + +lessons_learned: + customer_name: "" + project_name: "" + date: "" + architect: "" + engagement_tier: "" # small, standard, complex + + # --- What Worked Well --- + # Practices, tools, or decisions to repeat in future engagements + successes: + - area: "" # e.g., "Architecture Design" + description: "" # e.g., "ADB-S auto-scaling eliminated capacity planning complexity" + reusable: true # Should this be captured in KB? + kb_update: "" # e.g., "Add to kb/field-knowledge/ as positive finding" + + # --- What Didn't Work --- + # Issues encountered and their root cause + issues: + - area: "" # e.g., "Migration" + description: "" # e.g., "GoldenGate initial load took 3x longer than estimated" + root_cause: "" # e.g., "Network bandwidth between on-prem and OCI was only 100 Mbps" + resolution: "" # e.g., "Used Data Pump for initial load, GoldenGate for CDC only" + kb_update: "" # e.g., "Update kb/field-findings/ with bandwidth sizing guidance" + + # --- Process Improvements --- + # Suggestions for the ECAL/skill process itself + process_improvements: + - phase: "" # define, design, deliver + suggestion: "" # e.g., "Add network bandwidth assessment to discovery checklist" + priority: "" # high, medium, low + + # --- Template/Tool Improvements --- + tool_improvements: + - artifact: "" # e.g., "workload-profile.yaml" + suggestion: "" # e.g., "Add field for current network bandwidth between sites" + + # --- Customer Feedback --- + customer_feedback: + overall_satisfaction: "" # excellent, good, acceptable, needs_improvement + positive: [] # e.g., ["Fast turnaround", "Clear cost breakdown"] + negative: [] # e.g., ["Wanted more detail on security controls"] + would_engage_again: "" # yes, maybe, no + + # --- Customer Success Story --- + # If applicable, capture for internal sharing + success_story: + publishable: false + headline: "" # e.g., "Financial Services company reduces DB costs 60% with ADB-S" + summary: "" # 2-3 sentence summary + metrics: [] # e.g., ["60% cost reduction", "99.995% availability", "Zero downtime migration"] diff --git a/templates/operations-model.yaml b/templates/operations-model.yaml new file mode 100644 index 0000000..f25daca --- /dev/null +++ b/templates/operations-model.yaml @@ -0,0 +1,109 @@ +# Operations Model Template — DESIGN Phase +# Defines how the solution will be operated day-to-day after go-live + +operations_model: + # --- Header --- + customer_name: "" + project_name: "" + date: "" + architect: "" + + # --- Operational Responsibility --- + # Who operates what? + responsibility_model: + type: "" # self_managed, co_managed, fully_managed + oracle_managed: [] # e.g., ["ADB-S infrastructure", "Exadata patching"] + customer_managed: [] # e.g., ["Application deployment", "Schema changes", "User management"] + partner_managed: [] # e.g., ["24x7 NOC monitoring"] + + # --- Monitoring & Alerting --- + monitoring: + platform: "" # OCI Monitoring, OCI Logging Analytics, Datadog, Grafana, etc. + dashboards: + - name: "" # e.g., "Infrastructure Health" + scope: "" # e.g., "Compute, networking, storage metrics" + - name: "" + scope: "" + alerting: + channels: [] # e.g., [email, PagerDuty, Slack, OCI Notifications] + critical_alerts: + - condition: "" # e.g., "CPU > 90% for 5 min" + action: "" # e.g., "Page on-call DBA" + - condition: "" + action: "" + log_retention: "" # e.g., "90 days hot, 1 year archive" + + # --- Patching & Maintenance --- + patching: + os_patching: + frequency: "" # e.g., "Monthly, third Saturday" + method: "" # e.g., "OS Management Service, rolling updates" + downtime: "" # e.g., "Zero downtime (rolling)" + database_patching: + frequency: "" # e.g., "Quarterly RU" + method: "" # e.g., "ADB-S auto-patching with maintenance window" + downtime: "" # e.g., "< 2 min with auto-patching" + application_patching: + frequency: "" + method: "" + + # --- Backup & Recovery --- + backup: + strategy: + - component: "" # e.g., "ADB-S" + method: "" # e.g., "Automatic daily backup to Object Storage" + retention: "" # e.g., "60 days" + rpo: "" # e.g., "< 1 hour (continuous archive log backup)" + - component: "" + method: "" + retention: "" + rpo: "" + recovery_testing: + frequency: "" # e.g., "Quarterly" + last_test: "" + next_test: "" + + # --- Incident Response --- + incident_response: + severity_levels: + - level: "P1 — Critical" + definition: "" # e.g., "Production down, data loss risk" + response_time: "" # e.g., "15 minutes" + escalation: "" # e.g., "On-call DBA → SA → Oracle Support" + - level: "P2 — High" + definition: "" + response_time: "" + escalation: "" + - level: "P3 — Medium" + definition: "" + response_time: "" + escalation: "" + runbooks: + - scenario: "" # e.g., "ADB-S failover" + location: "" # e.g., "Confluence/wiki link" + - scenario: "" + location: "" + + # --- Capacity Management --- + capacity: + review_cadence: "" # e.g., "Monthly" + scaling_triggers: + - metric: "" # e.g., "CPU utilization" + threshold: "" # e.g., "> 70% sustained for 1 hour" + action: "" # e.g., "Auto-scale OCPUs (ADB-S) or add instance (OKE HPA)" + growth_forecast: "" # e.g., "15% annual data growth, 10% compute growth" + + # --- Security Operations --- + security_ops: + vulnerability_scanning: "" # e.g., "Cloud Guard + quarterly pen test" + access_review_cadence: "" # e.g., "Quarterly IAM review" + key_rotation: "" # e.g., "Annual for Vault master keys" + compliance_audit_schedule: "" # e.g., "Annual SOC2 Type II" + + # --- Cost Management --- + cost_management: + budget_alerts: + - threshold: "" # e.g., "80% of monthly budget" + action: "" # e.g., "Email to finance + architect" + optimization_cadence: "" # e.g., "Monthly review of unused resources" + reserved_capacity_review: "" # e.g., "Annual review of reserved vs. PAYG" diff --git a/templates/scorecard.yaml b/templates/scorecard.yaml new file mode 100644 index 0000000..82cd56a --- /dev/null +++ b/templates/scorecard.yaml @@ -0,0 +1,173 @@ +# OCI Well-Architected Scorecard — Output Template +# Generated automatically by the Deal Accelerator after architecture composition + +well_architected_scorecard: + # Overall assessment + overall_status: "" # PASS | PASS_WITH_RECOMMENDATIONS | GAPS_IDENTIFIED + generated_date: "" + architecture_name: "" + customer: "" + + # Summary metrics + summary: + total_checks: 0 + total_passed: 0 + total_gaps: 0 + high_severity_gaps: 0 + medium_severity_gaps: 0 + low_severity_gaps: 0 + + # Per-pillar results + pillars: + security_compliance: + status: "" # PASS | PASS_WITH_RECOMMENDATIONS | GAPS_IDENTIFIED | NOT_APPLICABLE + checks_passed: 0 + checks_total: 0 + categories: + identity_access: + passed: 0 + total: 0 + gaps: [] + resource_isolation: + passed: 0 + total: 0 + gaps: [] + database_security: + passed: 0 + total: 0 + gaps: [] + data_protection: + passed: 0 + total: 0 + gaps: [] + network_security: + passed: 0 + total: 0 + gaps: [] + monitoring_audit: + passed: 0 + total: 0 + gaps: [] + + reliability_resilience: + status: "" + checks_passed: 0 + checks_total: 0 + categories: + scalability: + passed: 0 + total: 0 + gaps: [] + fault_tolerant_networking: + passed: 0 + total: 0 + gaps: [] + data_backup: + passed: 0 + total: 0 + gaps: [] + data_replication: + passed: 0 + total: 0 + gaps: [] + disaster_recovery: + passed: 0 + total: 0 + gaps: [] + + performance_cost: + status: "" + checks_passed: 0 + checks_total: 0 + categories: + compute_sizing: + passed: 0 + total: 0 + gaps: [] + storage_strategy: + passed: 0 + total: 0 + gaps: [] + network_tuning: + passed: 0 + total: 0 + gaps: [] + cost_management: + passed: 0 + total: 0 + gaps: [] + + operational_efficiency: + status: "" + checks_passed: 0 + checks_total: 0 + categories: + deployment_strategy: + passed: 0 + total: 0 + gaps: [] + workload_monitoring: + passed: 0 + total: 0 + gaps: [] + os_management: + passed: 0 + total: 0 + gaps: [] + operations_support: + passed: 0 + total: 0 + gaps: [] + + distributed_cloud: + status: "" # Often NOT_APPLICABLE + reason: "" # e.g., "Single public cloud deployment" + checks_passed: 0 + checks_total: 0 + categories: + dc_deployment_strategy: + passed: 0 + total: 0 + gaps: [] + dc_integration: + passed: 0 + total: 0 + gaps: [] + dc_compliance: + passed: 0 + total: 0 + gaps: [] + dc_operations: + passed: 0 + total: 0 + gaps: [] + + # Gap detail format + # Each gap entry follows this structure: + # - check_id: "SEC-IAM-001" + # area: "Identity & Access" + # finding: "Description of what's missing" + # severity: "HIGH | MEDIUM | LOW" + # recommendation: "What to do about it" + # wa_reference: "URL or doc reference" + + # Prioritized action items (generated from gaps, sorted by severity) + action_items: + high_priority: [] + # - check_id: "" + # pillar: "" + # finding: "" + # recommendation: "" + # effort: "" # quick_win, moderate, significant + + medium_priority: [] + low_priority: [] + + # References + references: + framework: "https://docs.oracle.com/en/solutions/oci-best-practices/index.html" + security: "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html" + reliability: "https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html" + performance: "https://docs.oracle.com/en/solutions/oci-best-practices/index.html" + operations: "https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html" + distributed: "https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-distributed-cloud-implementation1.html" diff --git a/templates/strategy-map.yaml b/templates/strategy-map.yaml new file mode 100644 index 0000000..d527e77 --- /dev/null +++ b/templates/strategy-map.yaml @@ -0,0 +1,90 @@ +# ═══════════════════════════════════════════════════════════════ +# ECAL Strategy Map Template +# ═══════════════════════════════════════════════════════════════ +# Source: ECAL 3.1 - Ideate Step (Define Phase) +# Purpose: Map customer goals → strategies → capabilities → enablers +# Note: Internal document; key info goes into customer-facing PoV/Value Story +# "Arguably one of the most important ECAL artifacts" — ECAL 3.1 +# ═══════════════════════════════════════════════════════════════ + +metadata: + customer: "" + date: "" + version: "1.0" + status: "draft" + +# ─────────────────────────────────────────────────────────────── +# LAYER 1: BUSINESS GOALS +# What the customer wants to achieve (from annual reports, CxO statements) +# ─────────────────────────────────────────────────────────────── +goals: + - id: "G1" + goal: "" + # Example: "Reduce operating costs by 15% over 3 years" + source: "" # annual report, CEO keynote, customer statement + timeframe: "" + measurable_target: "" + +# ─────────────────────────────────────────────────────────────── +# LAYER 2: STRATEGIES +# How the customer plans to achieve each goal +# ─────────────────────────────────────────────────────────────── +strategies: + - id: "S1" + strategy: "" + # Example: "Consolidate data center footprint from 4 to 1" + supports_goal: "G1" # link to goal + owner: "" # who owns this strategy in the customer org? + +# ─────────────────────────────────────────────────────────────── +# LAYER 3: CAPABILITIES NEEDED +# What capabilities are required to execute each strategy +# ─────────────────────────────────────────────────────────────── +capabilities: + - id: "C1" + capability: "" + # Example: "Cloud-based database platform with managed operations" + supports_strategy: "S1" + current_state: "" # how is this capability met today? + gap: "" # what's missing? + +# ─────────────────────────────────────────────────────────────── +# LAYER 4: ENABLERS (Oracle Products / Solutions) +# What Oracle can provide to fill the capability gaps +# ─────────────────────────────────────────────────────────────── +enablers: + - id: "E1" + enabler: "" + # Example: "Autonomous Database on Exadata Cloud@Customer" + provides_capability: "C1" + oci_services: [] # specific OCI services + value_proposition: "" # why this enabler for this capability? + competitive_advantage: "" # why Oracle vs. alternatives? + +# ─────────────────────────────────────────────────────────────── +# LINKAGE SUMMARY +# Quick view: Goal → Strategy → Capability → Enabler +# ─────────────────────────────────────────────────────────────── +linkage: + - goal: "G1" + strategy: "S1" + capability: "C1" + enabler: "E1" + point_of_view: "" # one-sentence PoV derived from this chain + # Example: "By consolidating 3500 databases onto ADB on ExaCC, + # [Customer] achieves 40% OpEx reduction while meeting + # EBA data residency requirements" + +# ─────────────────────────────────────────────────────────────── +# SMART EVALUATION OF EACH POINT OF VIEW +# ─────────────────────────────────────────────────────────────── +pov_evaluation: + - pov_id: 1 + linkage_ref: "G1→S1→C1→E1" + specific: "" # clearly defined scope? + measurable: "" # quantifiable outcome? + attainable: "" # technically feasible? (check feature matrix) + relevant: "" # tied to customer's stated goals? + time_based: "" # achievable timeline? + overall_score: "" # strong / moderate / weak + proceed: "" # yes / iterate / abandon diff --git a/templates/success-criteria.yaml b/templates/success-criteria.yaml new file mode 100644 index 0000000..13ca2b9 --- /dev/null +++ b/templates/success-criteria.yaml @@ -0,0 +1,61 @@ +# Success Criteria Template — DELIVER/Operate Phase +# Quantitative metrics tied to the Value Story, measured post go-live + +success_criteria: + customer_name: "" + project_name: "" + date: "" + architect: "" + value_story_reference: "" # Link back to the original value hypothesis + + # --- Metrics --- + # Each metric should be SMART and traceable to the Value Story + metrics: + - name: "" # e.g., "Database Response Time" + category: "" # performance, availability, cost, operations, business + target: "" # e.g., "P95 < 15ms" + baseline: "" # e.g., "P95 = 45ms (current on-prem)" + measurement_method: "" # e.g., "OCI Monitoring — DB Performance Hub" + measurement_cadence: "" # e.g., "Daily automated, weekly review" + owner: "" # Who tracks this metric + + - name: "" # e.g., "Monthly Infrastructure Cost" + category: "" + target: "" # e.g., "< $25,000/month" + baseline: "" # e.g., "$40,000/month (current Exadata lease + support)" + measurement_method: "" # e.g., "OCI Cost Analysis" + measurement_cadence: "" + owner: "" + + - name: "" # e.g., "Unplanned Downtime" + category: "" + target: "" # e.g., "Zero in first 90 days" + baseline: "" # e.g., "4 hours/month average" + measurement_method: "" + measurement_cadence: "" + owner: "" + + # --- Review Schedule --- + reviews: + - milestone: "30-day review" + date: "" + focus: "Stability, performance baselines, early issues" + attendees: [] + + - milestone: "60-day review" + date: "" + focus: "Cost tracking, operational maturity, optimization opportunities" + attendees: [] + + - milestone: "90-day review" + date: "" + focus: "Value realization assessment, next phase planning" + attendees: [] + + # --- Value Realization --- + value_realization: + hypothesis_validated: "" # yes, partially, no + actual_vs_target: "" # Summary of metrics performance + unexpected_benefits: [] # e.g., ["DBA team freed up for development work"] + unexpected_challenges: [] # e.g., ["Application connection pooling needed tuning"] + next_hypothesis: "" # What to tackle next? Returns to DEFINE phase diff --git a/templates/value-story.yaml b/templates/value-story.yaml new file mode 100644 index 0000000..d1c6c04 --- /dev/null +++ b/templates/value-story.yaml @@ -0,0 +1,54 @@ +# Value Story Template — DEFINE Phase +# Captures the value hypothesis linking business drivers to OCI outcomes + +value_story: + # --- Customer Context --- + customer_name: "" + industry: "" + engagement_date: "" + architect: "" + + # --- Business Driver --- + # What is pushing the customer to act NOW? + business_driver: + primary: "" # cost_reduction, modernization, performance, compliance, hardware_eol, contract_expiry, consolidation, competitive_pressure + urgency: "" # Why now? e.g., "Hardware EOL in 6 months", "PCI audit in Q3" + quantified_impact: "" # e.g., "$2M/year in legacy maintenance", "4h monthly downtime" + + # --- Current Pain --- + # What specific problem does the customer experience? + current_pain: + summary: "" # 1-2 sentences describing the problem in customer's terms + affected_stakeholders: [] # e.g., [CTO, DBA team, end users, compliance officer] + business_cost: "" # Quantified cost of inaction: dollars, hours, risk + + # --- Value Hypothesis --- + # "If we [action], the customer achieves [outcome]" + hypothesis: + action: "" # e.g., "Migrate 3 Oracle databases from on-prem Exadata to ADB-S" + outcome: "" # e.g., "60% reduction in DB admin effort, 99.995% availability, PCI compliance" + evidence: "" # What supports this? e.g., "ADB-S automates patching, backups, tuning" + + # --- SMART Validation --- + smart: + specific: "" # What exactly will be done? + measurable: "" # How will success be measured? + attainable: "" # Is this technically feasible? (reference feature matrix, field findings) + relevant: "" # How does this connect to the business driver? + time_based: "" # When will this be delivered? + + # --- Competitive Context --- + competitive: + incumbent: "" # aws, azure, gcp, on_prem, none + why_oci: [] # e.g., ["BYOL cost advantage", "ADB-S unique capabilities", "FastConnect latency"] + honest_gaps: [] # e.g., ["Smaller marketplace", "Fewer regions in LATAM"] + + # --- Confidence Assessment --- + confidence: + data_quality: "" # high (AWR/metrics available), medium (estimates), low (guesses) + assumptions: [] # List key assumptions made + gaps_to_fill: [] # What information is still missing? + + # --- Next Step --- + recommendation: "" # proceed_to_design, iterate_hypothesis, insufficient_data + next_action: "" # e.g., "Schedule design workshop", "Request AWR reports" diff --git a/templates/workload-profile.yaml b/templates/workload-profile.yaml new file mode 100644 index 0000000..324fafa --- /dev/null +++ b/templates/workload-profile.yaml @@ -0,0 +1,131 @@ +# OCI Deal Accelerator — Workload Profile Template +# Fill this out during the Discovery phase to drive architecture composition and WA validation + +workload_profile: + # --- Business Context --- + business_context: + customer_name: "" + industry: "" # e.g., financial_services, healthcare, retail, manufacturing, public_sector + engagement_type: "" # migration, greenfield, expansion, modernization + timeline: "" # e.g., "6 months", "Q3 2026" + budget_range: "" # e.g., "$50K-100K/month" + competitive_situation: "" # aws_incumbent, azure_incumbent, on_prem_only, multi_cloud + oracle_relationship: "" # existing_ula, existing_licenses, new_customer + + # --- Compliance & Regulatory --- + compliance: + regulatory_frameworks: [] # PCI-DSS, HIPAA, SOC2, GDPR, FedRAMP, etc. + data_residency_required: false + data_residency_regions: [] # e.g., [us-ashburn-1, eu-frankfurt-1] + sovereign_requirements: false + audit_requirements: "" # e.g., "annual SOC2 audit", "quarterly PCI scan" + + # --- Application Portfolio --- + applications: + - name: "" + type: "" # database, web_app, api, batch, streaming, analytics + current_platform: "" # on_prem, aws, azure, gcp + technology_stack: [] # e.g., [java, spring_boot, oracle_db_19c] + criticality: "" # mission_critical, business_critical, standard, dev_test + users: "" # e.g., "5000 concurrent", "internal only" + data_volume: "" # e.g., "2TB", "500GB growing 10%/year" + + # --- Performance Requirements --- + performance: + compute_iops: "" # e.g., "50K IOPS sustained" + storage_iops: "" + network_throughput: "" # e.g., "10 Gbps" + latency_requirements: "" # e.g., "< 5ms database response" + peak_patterns: "" # steady, daily_peaks, seasonal, unpredictable + + # --- Resilience Requirements --- + resilience: + rto: "" # e.g., "1 hour", "15 minutes" + rpo: "" # e.g., "0 (zero data loss)", "1 hour" + availability_target: "" # e.g., "99.95%", "99.99%" + dr_required: false + dr_type: "" # active_active, active_passive, pilot_light, backup_restore + backup_retention: "" # e.g., "30 days", "7 years for compliance" + + # --- Connectivity --- + connectivity: + type: "" # fastconnect, vpn, internet, hybrid + bandwidth: "" # e.g., "10 Gbps" + on_prem_integration: false + multi_cloud_integration: false + existing_network: "" # e.g., "MPLS to 3 data centers" + + # --- Operational Maturity --- + operations: + iac_experience: "" # none, basic, advanced + cloud_experience: "" # none, basic_aws, advanced_multi_cloud + team_size: "" # e.g., "3 cloud engineers" + existing_tools: [] # e.g., [terraform, ansible, jenkins, datadog] + managed_services_preference: "" # self_managed, managed, fully_managed + + # --- People (ECAL Current State: who operates the solution?) --- + people: + team_size: "" # e.g., "5 IT staff" + roles: [] # e.g., [dba, cloud_engineer, devops, security] + skill_gaps: [] # e.g., [kubernetes, terraform, oci_console] + managed_services_preference: "" # self_managed, co_managed, fully_managed + change_readiness: "" # resistant, cautious, open, eager + + # --- Process (ECAL Current State: how do they operate today?) --- + process: + deployment_process: "" # manual, scripted, ci_cd_basic, ci_cd_advanced, gitops + change_management: "" # none, informal, cab_based, automated + incident_response: "" # none, ad_hoc, on_call_rotation, noc_24x7, outsourced + backup_testing_frequency: "" # never, annually, quarterly, monthly + + # --- Decision Drivers --- + decision_drivers: + technical: + - "" # e.g., "Need Oracle RAC equivalent in cloud" + commercial: + - "" # e.g., "Existing ULA covers DB licenses" + political: + primary_audience: "" # cloud_architect, security_architect, enterprise_architect, cto, finance + decision_maker: "" + influencers: [] + concerns: [] # e.g., ["vendor lock-in", "migration risk", "cost predictability"] + + # --- Derived Flags (auto-populated by validation engine) --- + flags: + has_databases: false + has_autonomous_database: false + has_production_databases: false + has_compute_instances: false + has_production_compute: false + has_block_volumes: false + has_object_storage: false + has_critical_data_in_object_storage: false + has_critical_object_storage: false + has_large_object_storage: false + has_load_balancers: false + has_internet_facing_services: false + has_fastconnect: false + has_drg: false + has_variable_workloads: false + has_critical_workloads: false + has_critical_data: false + has_application_workloads: false + has_production_applications: false + has_production_workloads: false + has_auto_scaling: false + has_oracle_licensed_services: false + has_stable_production_workloads: false + has_dev_test_environments: false + has_multiple_environments: false + has_sensitive_workloads: false + multi_tenant: false + multi_environment: false + enterprise_customer: false + hub_spoke_topology: false + requires_dr: false + multi_region: false + hybrid_cloud: false + multicloud: false + has_data_residency_requirements: false + has_sovereign_requirements: false + distributed_cloud_applicable: false diff --git a/tests/test_feature_matrix.py b/tests/test_feature_matrix.py new file mode 100644 index 0000000..44677bd --- /dev/null +++ b/tests/test_feature_matrix.py @@ -0,0 +1,212 @@ +""" +Tests for the ADB Feature Compatibility Matrix YAML and CLI. + +Matrix YAML: kb/compatibility/adb-feature-matrix.yaml +CLI module: tools/feature_matrix_cli.py +""" + +import csv +import io +import sys +from pathlib import Path + +import pytest +import yaml + +# --------------------------------------------------------------------------- +# Path setup +# --------------------------------------------------------------------------- +PROJECT_ROOT = Path(__file__).resolve().parent.parent +MATRIX_PATH = PROJECT_ROOT / "kb" / "compatibility" / "adb-feature-matrix.yaml" +TOOLS_DIR = PROJECT_ROOT / "tools" + +# Make the tools directory importable so we can import the CLI module. +if str(TOOLS_DIR) not in sys.path: + sys.path.insert(0, str(TOOLS_DIR)) + +try: + import feature_matrix_cli as cli + + CLI_AVAILABLE = True +except ImportError: + CLI_AVAILABLE = False + +VALID_STATUSES = {"GA", "GA_CAVEAT", "PREVIEW", "LIMITED", "NOT_AVAIL", "BROKEN", "UNTESTED"} + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- +@pytest.fixture(scope="module") +def matrix_data(): + """Load the feature-matrix YAML once per module.""" + with open(MATRIX_PATH, "r") as fh: + return yaml.safe_load(fh) + + +@pytest.fixture(scope="module") +def features(matrix_data): + """Return the list of feature entries.""" + return matrix_data["features"] + + +# --------------------------------------------------------------------------- +# YAML structure tests +# --------------------------------------------------------------------------- +class TestMatrixYAML: + def test_matrix_loads(self, matrix_data): + """YAML loads without errors and contains expected top-level keys.""" + assert matrix_data is not None + assert "features" in matrix_data + assert "versions" in matrix_data + assert "deployment_types" in matrix_data + + def test_all_features_have_at_least_one_deployment(self, features): + """Every feature must have a non-empty matrix (at least one deployment type).""" + for feat in features: + matrix = feat.get("matrix", {}) + assert matrix, ( + f"Feature '{feat['name']}' has an empty matrix — " + "at least one deployment type must be present." + ) + + def test_all_statuses_are_valid(self, features): + """Every status value across the entire matrix must be in the allowed set.""" + for feat in features: + for deploy_id, versions in feat.get("matrix", {}).items(): + for ver, entry in versions.items(): + status = entry if isinstance(entry, str) else entry.get("status") + assert status in VALID_STATUSES, ( + f"Feature '{feat['name']}', deployment '{deploy_id}', " + f"version '{ver}' has invalid status '{status}'. " + f"Allowed: {VALID_STATUSES}" + ) + + +# --------------------------------------------------------------------------- +# CLI function tests — use internal helpers from feature_matrix_cli +# --------------------------------------------------------------------------- +requires_cli = pytest.mark.skipif(not CLI_AVAILABLE, reason="feature_matrix_cli not available") + + +@requires_cli +class TestMatrixCLICheck: + def test_check_returns_correct_status(self): + """check('Auto Scaling', 'adb_s', '23ai') should contain 'GA_CAVEAT'.""" + data = cli.load_matrix(str(MATRIX_PATH)) + matches = cli.find_features(data, "Auto Scaling") + assert len(matches) >= 1, "Expected at least one match for 'Auto Scaling'" + feature = matches[0] + cell = cli.get_cell(feature, "adb_s", "23ai") + assert cell is not None, "Expected a cell for Auto Scaling / adb_s / 23ai" + assert "GA_CAVEAT" in cell.get("status", ""), ( + f"Expected GA_CAVEAT for Auto Scaling / adb_s / 23ai, got: {cell}" + ) + + def test_check_unknown_feature_returns_error(self): + """check with a nonexistent feature should return no matches.""" + data = cli.load_matrix(str(MATRIX_PATH)) + matches = cli.find_features(data, "Nonexistent Feature") + assert len(matches) == 0, ( + f"Expected no matches for 'Nonexistent Feature', got {len(matches)}" + ) + + +@requires_cli +class TestMatrixCLICompare: + def test_compare_returns_all_features(self, features): + """Compare output should cover every feature in the matrix.""" + data = cli.load_matrix(str(MATRIX_PATH)) + feature_names = {f["name"] for f in features} + # Simulate what cmd_compare does: iterate all features and get cells + compared = set() + for feature in data["features"]: + cell1 = cli.get_cell(feature, "adb_s", "23ai") + cell2 = cli.get_cell(feature, "exacs", "23ai") + # Both cells may be None (UNTESTED), but the feature is still compared + compared.add(feature["name"]) + assert feature_names == compared, ( + f"Missing features in compare: {feature_names - compared}" + ) + + +@requires_cli +class TestMatrixCLIGaps: + def test_gaps_finds_not_available(self): + """gaps('dbcs_ee', '23ai') should include features marked NOT_AVAIL.""" + data = cli.load_matrix(str(MATRIX_PATH)) + gap_statuses = {"NOT_AVAIL", "BROKEN", "LIMITED"} + gaps = [] + for feature in data["features"]: + cell = cli.get_cell(feature, "dbcs_ee", "23ai") + if cell is not None: + status = cell.get("status", "UNTESTED") + if status in gap_statuses: + gaps.append((feature["name"], status)) + + assert len(gaps) > 0, "Expected at least one gap for dbcs_ee / 23ai" + not_avail_gaps = [g for g in gaps if g[1] == "NOT_AVAIL"] + assert len(not_avail_gaps) > 0, ( + "Expected at least one NOT_AVAIL gap for dbcs_ee / 23ai" + ) + + +# --------------------------------------------------------------------------- +# Export tests +# --------------------------------------------------------------------------- +@requires_cli +class TestMatrixCLIExport: + def test_export_markdown_is_valid(self, matrix_data): + """Markdown export should contain a header row with | separators.""" + data = cli.load_matrix(str(MATRIX_PATH)) + versions = cli.get_version_ids(data) + deployments = cli.get_deployment_ids(data) + + columns = [(d, v) for d in deployments for v in versions] + col_labels = [f"{d}/{v}" for d, v in columns] + + # Capture the markdown output by calling the internal function + captured = io.StringIO() + old_stdout = sys.stdout + sys.stdout = captured + try: + cli._export_markdown(data, columns, col_labels) + finally: + sys.stdout = old_stdout + + content = captured.getvalue() + lines = [ln for ln in content.splitlines() if ln.strip()] + assert len(lines) >= 2, "Markdown export should have at least a header and separator row" + # Header row must use pipe separators. + assert "|" in lines[0], f"Header row missing '|' separators: {lines[0]}" + # Second row is the --- separator row. + assert "|" in lines[1], f"Separator row missing '|': {lines[1]}" + + def test_export_csv_is_valid(self, matrix_data): + """CSV export should be parseable with csv.reader.""" + data = cli.load_matrix(str(MATRIX_PATH)) + versions = cli.get_version_ids(data) + deployments = cli.get_deployment_ids(data) + + columns = [(d, v) for d in deployments for v in versions] + col_labels = [f"{d}/{v}" for d, v in columns] + + # Capture the CSV output + captured = io.StringIO() + old_stdout = sys.stdout + sys.stdout = captured + try: + cli._export_csv(data, columns, col_labels) + finally: + sys.stdout = old_stdout + + content = captured.getvalue() + reader = csv.reader(io.StringIO(content)) + rows = list(reader) + assert len(rows) >= 2, "CSV export should have at least a header row and one data row" + # All rows should have the same number of columns. + col_count = len(rows[0]) + for i, row in enumerate(rows): + assert len(row) == col_count, ( + f"Row {i} has {len(row)} columns, expected {col_count}" + ) diff --git a/tests/test_findings_tracker.py b/tests/test_findings_tracker.py new file mode 100644 index 0000000..0abeb79 --- /dev/null +++ b/tests/test_findings_tracker.py @@ -0,0 +1,374 @@ +""" +Tests for the Field Findings Tracker YAML and CLI. + +Tracker YAML: kb/field-findings/tracker.yaml +CLI module: tools/findings_cli.py +""" + +import re +import sys +from datetime import datetime +from pathlib import Path + +import pytest +import yaml + +# --------------------------------------------------------------------------- +# Path setup +# --------------------------------------------------------------------------- +PROJECT_ROOT = Path(__file__).resolve().parent.parent +TRACKER_PATH = PROJECT_ROOT / "kb" / "field-findings" / "tracker.yaml" +TOOLS_DIR = PROJECT_ROOT / "tools" + +if str(TOOLS_DIR) not in sys.path: + sys.path.insert(0, str(TOOLS_DIR)) + +try: + import findings_cli as cli + + CLI_AVAILABLE = True +except ImportError: + CLI_AVAILABLE = False + +VALID_SEVERITIES = {"CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"} +VALID_STATUSES = {"open", "resolved", "wontfix", "acknowledged", "monitoring"} +VALID_CONFIDENCE = {"validated", "observed", "reported", "inferred"} +ID_PATTERN = re.compile(r"^FF-\d{6}-\d{3}$") +REQUIRED_FIELDS = {"id", "date", "summary", "severity", "status"} + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- +@pytest.fixture(scope="module") +def tracker_data(): + """Load the tracker YAML once per module.""" + with open(TRACKER_PATH, "r") as fh: + return yaml.safe_load(fh) + + +@pytest.fixture(scope="module") +def findings(tracker_data): + """Return the list of findings.""" + return tracker_data["findings"] + + +# --------------------------------------------------------------------------- +# YAML structure tests +# --------------------------------------------------------------------------- +class TestTrackerYAML: + def test_tracker_loads(self, tracker_data): + """YAML loads without errors and contains the findings key.""" + assert tracker_data is not None + assert "findings" in tracker_data + assert isinstance(tracker_data["findings"], list) + assert len(tracker_data["findings"]) > 0 + + def test_all_findings_have_required_fields(self, findings): + """Each finding must have id, date, summary, severity, status.""" + for finding in findings: + missing = REQUIRED_FIELDS - set(finding.keys()) + assert not missing, ( + f"Finding '{finding.get('id', '???')}' is missing required fields: {missing}" + ) + + def test_all_findings_have_contributor_or_reported_by(self, findings): + """Each finding must have either a contributor block or reported_by.""" + for finding in findings: + has_contributor = isinstance(finding.get("contributor"), dict) + has_reported_by = bool(finding.get("reported_by")) + assert has_contributor or has_reported_by, ( + f"Finding '{finding.get('id', '???')}' has neither contributor block nor reported_by" + ) + + def test_contributor_blocks_have_required_fields(self, findings): + """Contributor blocks must have name, team, confidence.""" + for finding in findings: + contributor = finding.get("contributor") + if not isinstance(contributor, dict): + continue + assert contributor.get("name"), ( + f"Finding '{finding['id']}' contributor missing 'name'" + ) + assert contributor.get("team"), ( + f"Finding '{finding['id']}' contributor missing 'team'" + ) + assert contributor.get("confidence") in VALID_CONFIDENCE, ( + f"Finding '{finding['id']}' has invalid confidence " + f"'{contributor.get('confidence')}'" + ) + + def test_ids_are_unique(self, findings): + """No duplicate finding IDs.""" + ids = [f["id"] for f in findings] + duplicates = [fid for fid in ids if ids.count(fid) > 1] + assert not duplicates, f"Duplicate finding IDs: {set(duplicates)}" + + def test_ids_follow_format(self, findings): + """All IDs must match FF-YYYYMM-NNN.""" + for finding in findings: + fid = finding["id"] + assert ID_PATTERN.match(fid), ( + f"Finding ID '{fid}' does not match expected format FF-YYYYMM-NNN" + ) + + def test_severities_are_valid(self, findings): + """All severities must be in the allowed set.""" + for finding in findings: + sev = finding["severity"] + assert sev in VALID_SEVERITIES, ( + f"Finding '{finding['id']}' has invalid severity '{sev}'. " + f"Allowed: {VALID_SEVERITIES}" + ) + + def test_statuses_are_valid(self, findings): + """All statuses must be in the allowed set.""" + for finding in findings: + status = finding["status"] + assert status in VALID_STATUSES, ( + f"Finding '{finding['id']}' has invalid status '{status}'. " + f"Allowed: {VALID_STATUSES}" + ) + + def test_dates_are_valid(self, findings): + """All dates must parse as valid YYYY-MM-DD dates.""" + for finding in findings: + date_val = finding["date"] + # yaml.safe_load may auto-parse dates into datetime.date objects. + if isinstance(date_val, str): + try: + datetime.strptime(date_val, "%Y-%m-%d") + except ValueError: + pytest.fail( + f"Finding '{finding['id']}' has unparseable date '{date_val}'. " + "Expected format: YYYY-MM-DD" + ) + else: + # datetime.date object returned by YAML parser is valid. + assert hasattr(date_val, "year"), ( + f"Finding '{finding['id']}' date field is not a string or date: {date_val!r}" + ) + + +# --------------------------------------------------------------------------- +# CLI function tests — use internal helpers from findings_cli +# --------------------------------------------------------------------------- +requires_cli = pytest.mark.skipif(not CLI_AVAILABLE, reason="findings_cli not available") + + +@requires_cli +class TestFindingsCLISearch: + def test_search_finds_by_tag(self, findings): + """Search for 'dep' returns findings that have 'dep' in tags.""" + query = "dep" + results = [] + for f in findings: + searchable = " ".join([ + str(f.get("summary", "")), + str(f.get("detail", "")), + str(f.get("workaround", "")), + " ".join(str(t) for t in f.get("tags", [])), + ]).lower() + if query in searchable: + results.append(f) + + assert len(results) > 0, "Expected at least one finding with 'dep' in searchable fields" + + def test_search_finds_by_text(self, findings): + """Search for 'maintenance' returns maintenance-related findings.""" + query = "maintenance" + results = [] + for f in findings: + searchable = " ".join([ + str(f.get("summary", "")), + str(f.get("detail", "")), + str(f.get("workaround", "")), + " ".join(str(t) for t in f.get("tags", [])), + ]).lower() + if query in searchable: + results.append(f) + + assert len(results) > 0, "Expected at least one finding related to 'maintenance'" + + +@requires_cli +class TestFindingsCLIFilter: + def test_filter_by_severity(self, findings): + """Filter by severity HIGH returns only HIGH findings.""" + results = cli.filter_by_severity("HIGH") + assert len(results) > 0, "Expected at least one HIGH severity finding" + for r in results: + assert r["severity"] == "HIGH", f"Expected severity HIGH, got '{r['severity']}'" + + def test_filter_by_client(self): + """Filter by client 'Pepe' returns Pepe findings.""" + results = cli.filter_by_client("Pepe") + assert len(results) > 0, "Expected at least one finding for client 'Pepe'" + for r in results: + client = cli._get_client(r) + assert "Pepe" in client, f"Expected client containing 'Pepe', got '{client}'" + + +@requires_cli +class TestFindingsCLIAdd: + def test_add_creates_valid_entry(self, tmp_path): + """Programmatic add produces an entry with all required fields.""" + tmp_tracker = tmp_path / "tracker.yaml" + tmp_tracker.write_text(yaml.dump({"last_updated": "2026-03-14", "findings": []})) + + entry = cli.add( + tracker_path=str(tmp_tracker), + name="Test User", + team="Test Team", + confidence="validated", + client="Test Client", + product="ADB-S", + severity="LOW", + category="gotcha", + summary="Test finding for unit tests", + detail="This is a test finding.", + status="open", + ) + + assert isinstance(entry, dict) + missing = REQUIRED_FIELDS - set(entry.keys()) + assert not missing, f"Added entry missing fields: {missing}" + assert entry["severity"] in VALID_SEVERITIES + assert entry["status"] in VALID_STATUSES + assert isinstance(entry.get("contributor"), dict) + assert entry["contributor"]["name"] == "Test User" + assert entry["contributor"]["team"] == "Test Team" + assert entry["contributor"]["confidence"] == "validated" + + def test_add_with_legacy_reported_by(self, tmp_path): + """Legacy reported_by parameter is supported for backward compat.""" + tmp_tracker = tmp_path / "tracker.yaml" + tmp_tracker.write_text(yaml.dump({"last_updated": "2026-03-14", "findings": []})) + + entry = cli.add( + tracker_path=str(tmp_tracker), + reported_by="Legacy User", + product="ADB-S", + severity="INFO", + summary="Legacy add test", + status="open", + ) + + assert entry["contributor"]["name"] == "Legacy User" + assert entry["reported_by"] == "Legacy User" + + def test_auto_id_generation(self, tmp_path): + """Generated ID should use current year-month format (FF-YYYYMM-NNN).""" + tmp_tracker = tmp_path / "tracker.yaml" + tmp_tracker.write_text(yaml.dump({"last_updated": "2026-03-14", "findings": []})) + + entry = cli.add( + tracker_path=str(tmp_tracker), + name="Test User", + team="Test Team", + product="ADB-S", + severity="INFO", + category="gotcha", + summary="Auto-ID test", + detail="Testing auto ID generation.", + status="open", + ) + + fid = entry["id"] + assert ID_PATTERN.match(fid), f"Generated ID '{fid}' does not match FF-YYYYMM-NNN" + now = datetime.now() + expected_prefix = f"FF-{now.strftime('%Y%m')}-" + assert fid.startswith(expected_prefix), ( + f"Generated ID '{fid}' does not start with '{expected_prefix}'" + ) + assert fid.endswith("-001"), f"Expected first generated ID to end with '-001', got '{fid}'" + + +@requires_cli +class TestFindingsCLIConfirm: + def test_confirm_adds_confirmation(self, tmp_path): + """Confirm command adds a confirmation entry to a finding.""" + tmp_tracker = tmp_path / "tracker.yaml" + tmp_tracker.write_text(yaml.dump({"last_updated": "2026-03-14", "findings": []})) + + # First add a finding + entry = cli.add( + tracker_path=str(tmp_tracker), + name="Original Author", + team="Team A", + product="ADB-S", + severity="HIGH", + summary="Test finding to confirm", + status="open", + ) + + # Confirm it + confirmation = cli.confirm( + finding_id=entry["id"], + name="Reviewer Name", + team="Team B", + note="Confirmed same behavior in my environment.", + tracker_path=str(tmp_tracker), + ) + + assert confirmation["name"] == "Reviewer Name" + assert confirmation["team"] == "Team B" + assert confirmation["note"] == "Confirmed same behavior in my environment." + + # Verify it was persisted + data = yaml.safe_load(tmp_tracker.read_text()) + finding = data["findings"][0] + assert len(finding["confirmations"]) == 1 + assert finding["confirmations"][0]["name"] == "Reviewer Name" + + def test_confirm_nonexistent_finding_raises(self, tmp_path): + """Confirming a nonexistent finding should raise LookupError.""" + tmp_tracker = tmp_path / "tracker.yaml" + tmp_tracker.write_text(yaml.dump({"last_updated": "2026-03-14", "findings": []})) + + with pytest.raises(LookupError): + cli.confirm( + finding_id="FF-999999-999", + name="Test", + team="Test", + tracker_path=str(tmp_tracker), + ) + + +@requires_cli +class TestFindingsCLIStats: + def test_stats_counts_correct(self, findings): + """Stats should return correct totals per category.""" + s = cli.stats() + assert s["total"] == len(findings), ( + f"Stats total ({s['total']}) does not match findings count ({len(findings)})" + ) + total_by_severity = sum(s["by_severity"].values()) + total_by_status = sum(s["by_status"].values()) + assert total_by_severity == len(findings), ( + f"Severity counts ({total_by_severity}) do not sum to total findings ({len(findings)})" + ) + assert total_by_status == len(findings), ( + f"Status counts ({total_by_status}) do not sum to total findings ({len(findings)})" + ) + + +@requires_cli +class TestTagValidation: + def test_valid_tags_pass(self): + """Known taxonomy tags should not produce warnings.""" + warnings = cli.validate_tags(["adb-s", "ha", "dr"]) + assert len(warnings) == 0 + + def test_unknown_tags_produce_warnings(self): + """Unknown tags should produce warning messages.""" + warnings = cli.validate_tags(["totally-fake-tag-xyz"]) + assert len(warnings) > 0 + assert "Unknown tag" in warnings[0] + + def test_fuzzy_match_suggestion(self): + """Tags close to valid ones should suggest corrections.""" + warnings = cli.validate_tags(["adb_s"]) + # Should suggest 'adb-s' + assert len(warnings) > 0 + assert "adb-s" in warnings[0] diff --git a/tests/test_kb_governance.py b/tests/test_kb_governance.py new file mode 100644 index 0000000..d6a8251 --- /dev/null +++ b/tests/test_kb_governance.py @@ -0,0 +1,240 @@ +""" +Tests for KB governance tools: kb_linter.py, kb_cli.py, and config files. + +Tests cover: +- Config file structure validation +- KB linter checks (contributor blocks, decay, tags, owners) +- KB CLI functions (stats, search, owners) +""" + +import sys +from pathlib import Path + +import pytest +import yaml + +# --------------------------------------------------------------------------- +# Path setup +# --------------------------------------------------------------------------- +PROJECT_ROOT = Path(__file__).resolve().parent.parent +TOOLS_DIR = PROJECT_ROOT / "tools" +CONFIG_DIR = PROJECT_ROOT / "config" +KB_DIR = PROJECT_ROOT / "kb" + +if str(TOOLS_DIR) not in sys.path: + sys.path.insert(0, str(TOOLS_DIR)) + +try: + import kb_linter + LINTER_AVAILABLE = True +except ImportError: + LINTER_AVAILABLE = False + +try: + import kb_cli + CLI_AVAILABLE = True +except ImportError: + CLI_AVAILABLE = False + + +# --------------------------------------------------------------------------- +# Config file tests +# --------------------------------------------------------------------------- +class TestGovernanceConfig: + def test_governance_yaml_loads(self): + """kb-governance.yaml loads without errors.""" + path = CONFIG_DIR / "kb-governance.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + assert data is not None + assert "contribution" in data + assert "freshness" in data + assert "confidence_decay" in data + assert "review_triggers" in data + + def test_governance_has_required_fields(self): + """Contribution config must list required fields.""" + path = CONFIG_DIR / "kb-governance.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + required = data["contribution"]["required_fields"] + assert "name" in required + assert "team" in required + assert "confidence" in required + + def test_governance_has_all_confidence_levels(self): + """Confidence decay must cover all declared levels.""" + path = CONFIG_DIR / "kb-governance.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + levels = data["contribution"]["confidence_levels"] + decay = data["confidence_decay"] + for level in levels: + assert level in decay, f"Confidence level '{level}' not in decay config" + assert "fresh" in decay[level] + assert "stale" in decay[level] + assert "expired" in decay[level] + + +class TestOwnersConfig: + def test_owners_yaml_loads(self): + """kb-owners.yaml loads and has domains.""" + path = CONFIG_DIR / "kb-owners.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + assert data is not None + assert "domains" in data + assert len(data["domains"]) > 0 + + def test_all_domains_have_area_and_owner(self): + """Each domain must have area and owner.""" + path = CONFIG_DIR / "kb-owners.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + for domain in data["domains"]: + assert "area" in domain, f"Domain missing 'area': {domain}" + assert "owner" in domain, f"Domain missing 'owner': {domain}" + assert "name" in domain["owner"] + + +class TestTagsConfig: + def test_tags_yaml_loads(self): + """kb-tags.yaml loads and has taxonomy.""" + path = CONFIG_DIR / "kb-tags.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + assert data is not None + assert "taxonomy" in data + + def test_taxonomy_has_categories(self): + """Taxonomy must have products, versions, categories.""" + path = CONFIG_DIR / "kb-tags.yaml" + with open(path, "r") as fh: + data = yaml.safe_load(fh) + taxonomy = data["taxonomy"] + assert "products" in taxonomy + assert "versions" in taxonomy + assert "categories" in taxonomy + assert len(taxonomy["products"]) > 0 + + +# --------------------------------------------------------------------------- +# KB Linter tests +# --------------------------------------------------------------------------- +requires_linter = pytest.mark.skipif(not LINTER_AVAILABLE, reason="kb_linter not available") + + +@requires_linter +class TestKBLinter: + def test_contributor_blocks_check(self): + """Linter should check contributor blocks on real tracker.""" + issues = kb_linter.check_contributor_blocks() + # All findings in the updated tracker should have contributor blocks + # so there should be no issues + assert isinstance(issues, list) + + def test_confidence_decay_check(self): + """Decay check returns a list of decay reports.""" + results = kb_linter.check_confidence_decay() + assert isinstance(results, list) + assert len(results) > 0 + for r in results: + assert "id" in r + assert "status" in r + assert r["status"] in {"FRESH", "STALE", "EXPIRED", "UNKNOWN"} + + def test_tag_validation(self): + """Tag validation returns a list of issues.""" + issues = kb_linter.check_tags() + assert isinstance(issues, list) + # Some tags in the real tracker may not be in taxonomy + # (like 'cli', 'maintenance-window', etc.) + + def test_owner_check(self): + """Owner check returns issues for TBD owners.""" + issues = kb_linter.check_owners() + assert isinstance(issues, list) + # We know some owners are TBD + tbd_issues = [i for i in issues if "TBD" in i] + assert len(tbd_issues) > 0, "Expected TBD owner issues" + + def test_freshness_check(self): + """Freshness check returns a list of file freshness reports.""" + results = kb_linter.check_freshness() + assert isinstance(results, list) + assert len(results) > 0 + for r in results: + assert "file" in r + assert "status" in r + assert "age_days" in r + + def test_contribution_stats(self): + """Contribution stats returns a dict of name -> count.""" + stats = kb_linter.contribution_stats() + assert isinstance(stats, dict) + assert len(stats) > 0 + assert "Diego Cabrera" in stats + + +# --------------------------------------------------------------------------- +# KB CLI tests +# --------------------------------------------------------------------------- +requires_kb_cli = pytest.mark.skipif(not CLI_AVAILABLE, reason="kb_cli not available") + + +@requires_kb_cli +class TestKBCLI: + def test_all_kb_files_returns_yaml_files(self): + """_all_kb_files should return YAML files from kb/ directory.""" + files = kb_cli._all_kb_files() + assert len(files) > 0 + for f in files: + assert f.endswith((".yaml", ".yml")) + + def test_load_yaml_valid_file(self): + """_load_yaml should load a valid YAML file.""" + data = kb_cli._load_yaml(str(PROJECT_ROOT / "config" / "kb-governance.yaml")) + assert data is not None + assert "contribution" in data + + def test_load_yaml_missing_file(self): + """_load_yaml should return None for missing files.""" + data = kb_cli._load_yaml("/nonexistent/file.yaml") + assert data is None + + +# --------------------------------------------------------------------------- +# Service changelog tests +# --------------------------------------------------------------------------- +class TestServiceChangelogs: + """Verify that service files have changelog sections.""" + + @pytest.fixture(scope="class") + def service_files(self): + services_dir = KB_DIR / "services" + return list(services_dir.glob("*.yaml")) + + def test_service_files_exist(self, service_files): + assert len(service_files) > 0 + + def test_service_files_have_changelog(self, service_files): + """Each service file should have a changelog section.""" + for filepath in service_files: + with open(filepath, "r") as fh: + data = yaml.safe_load(fh) + assert "changelog" in data, ( + f"Service file '{filepath.name}' missing 'changelog' section" + ) + assert len(data["changelog"]) > 0, ( + f"Service file '{filepath.name}' has empty changelog" + ) + + def test_changelog_entries_have_required_fields(self, service_files): + """Changelog entries must have date, contributor, change.""" + for filepath in service_files: + with open(filepath, "r") as fh: + data = yaml.safe_load(fh) + for entry in data.get("changelog", []): + assert "date" in entry, f"Changelog entry in {filepath.name} missing 'date'" + assert "contributor" in entry, f"Changelog entry in {filepath.name} missing 'contributor'" + assert "change" in entry, f"Changelog entry in {filepath.name} missing 'change'" diff --git a/tools/feature_matrix_cli.py b/tools/feature_matrix_cli.py new file mode 100644 index 0000000..440a1de --- /dev/null +++ b/tools/feature_matrix_cli.py @@ -0,0 +1,649 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — ADB Feature Compatibility Matrix CLI + +Query, compare, and update the ADB feature compatibility matrix from +the command line. + +Usage: + python feature_matrix_cli.py check "auto scaling" adb_s 23ai + python feature_matrix_cli.py compare adb_s exacs 23ai + python feature_matrix_cli.py gaps adb_s 23ai + python feature_matrix_cli.py update "Auto Scaling (OCPU)" adb_s 23ai --status GA --notes "Updated" + python feature_matrix_cli.py export --format markdown + python feature_matrix_cli.py export --format csv +""" + +import argparse +import csv +import io +import os +import sys +from datetime import date +from typing import Any, Dict, List, Optional, Tuple + +VALID_CONFIDENCE = {"validated", "observed", "reported", "inferred"} + +import yaml + + +# ============================================================================= +# Constants +# ============================================================================= + +MATRIX_PATH = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", "kb", "compatibility", "adb-feature-matrix.yaml", +) + +VALID_STATUSES = {"GA", "GA_CAVEAT", "PREVIEW", "LIMITED", "NOT_AVAIL", "BROKEN", "UNTESTED"} + +# ANSI color codes for terminal output +_COLORS = { + "GA": "\033[32m", # green + "GA_CAVEAT": "\033[33m", # yellow + "PREVIEW": "\033[36m", # cyan + "LIMITED": "\033[93m", # bright yellow + "NOT_AVAIL": "\033[31m", # red + "BROKEN": "\033[1;31m", # red bold + "UNTESTED": "\033[90m", # gray +} +_RESET = "\033[0m" + + +# ============================================================================= +# Helpers +# ============================================================================= + +def _color(status: str) -> str: + """Wrap a status string with the appropriate ANSI color.""" + code = _COLORS.get(status, "") + return f"{code}{status}{_RESET}" if code else status + + +def _supports_color() -> bool: + """Return True when stdout is a tty that likely supports ANSI escapes.""" + return hasattr(sys.stdout, "isatty") and sys.stdout.isatty() + + +def _plain(status: str) -> str: + """Return the status without color codes.""" + return status + + +def _get_color_fn(): + """Return the appropriate formatter (colored or plain).""" + return _color if _supports_color() else _plain + + +def load_matrix(path: str) -> Dict[str, Any]: + """Load the YAML matrix file and return its contents as a dict.""" + resolved = os.path.realpath(path) + if not os.path.isfile(resolved): + print(f"Error: Matrix file not found at {resolved}", file=sys.stderr) + sys.exit(1) + with open(resolved, "r", encoding="utf-8") as fh: + try: + data = yaml.safe_load(fh) + except yaml.YAMLError as exc: + print(f"Error: Failed to parse YAML — {exc}", file=sys.stderr) + sys.exit(1) + if not data or "features" not in data: + print("Error: Matrix file is missing 'features' key.", file=sys.stderr) + sys.exit(1) + return data + + +def save_matrix(path: str, data: Dict[str, Any]) -> None: + """Write the matrix dict back to YAML, preserving readability.""" + resolved = os.path.realpath(path) + with open(resolved, "w", encoding="utf-8") as fh: + yaml.dump( + data, + fh, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + width=120, + ) + + +def get_version_ids(data: Dict[str, Any]) -> List[str]: + """Return ordered list of version ids from the matrix.""" + return [v["id"] for v in data.get("versions", [])] + + +def get_deployment_ids(data: Dict[str, Any]) -> List[str]: + """Return ordered list of deployment type ids from the matrix.""" + return [d["id"] for d in data.get("deployment_types", [])] + + +def get_deployment_name(data: Dict[str, Any], deploy_id: str) -> str: + """Look up the human-readable name for a deployment id.""" + for d in data.get("deployment_types", []): + if d["id"] == deploy_id: + return d.get("name", deploy_id) + return deploy_id + + +def validate_deployment(data: Dict[str, Any], deploy_id: str) -> None: + """Exit with an error if the deployment id is not in the matrix.""" + valid = get_deployment_ids(data) + if deploy_id not in valid: + print( + f"Error: Unknown deployment type '{deploy_id}'. " + f"Valid types: {', '.join(valid)}", + file=sys.stderr, + ) + sys.exit(1) + + +def validate_version(data: Dict[str, Any], version: str) -> None: + """Exit with an error if the version is not in the matrix.""" + valid = get_version_ids(data) + if version not in valid: + print( + f"Error: Unknown version '{version}'. Valid versions: {', '.join(valid)}", + file=sys.stderr, + ) + sys.exit(1) + + +def find_features(data: Dict[str, Any], query: str) -> List[Dict[str, Any]]: + """ + Find features whose name matches *query* (case-insensitive, partial). + Returns a list of matching feature dicts. + """ + query_lower = query.lower() + return [ + f for f in data["features"] + if query_lower in f["name"].lower() + ] + + +def get_cell(feature: Dict[str, Any], deploy: str, version: str) -> Optional[Dict[str, Any]]: + """Return the matrix cell dict for a feature/deploy/version, or None.""" + return feature.get("matrix", {}).get(deploy, {}).get(version) + + +# ============================================================================= +# Library API — importable functions for programmatic use and testing +# ============================================================================= + +def check(feature_name: str, deployment: str, version: str, matrix_path: str = MATRIX_PATH) -> Dict[str, Any]: + """Look up a feature/deployment/version and return {status, notes, feature_name}.""" + data = load_matrix(matrix_path) + matches = find_features(data, feature_name) + if not matches: + raise LookupError(f"No features matching '{feature_name}'") + if len(matches) > 1: + raise LookupError( + f"Ambiguous feature name '{feature_name}'. Matches: " + + ", ".join(m["name"] for m in matches) + ) + feature = matches[0] + cell = get_cell(feature, deployment, version) + if cell is None: + return {"status": "UNTESTED", "notes": "no entry in matrix", "feature_name": feature["name"]} + return { + "status": cell.get("status", "UNTESTED"), + "notes": cell.get("notes", ""), + "feature_name": feature["name"], + } + + +def compare(deploy1: str, deploy2: str, version: str, matrix_path: str = MATRIX_PATH) -> List[Dict[str, Any]]: + """Compare two deployment types. Returns list of dicts with name, status1, status2.""" + data = load_matrix(matrix_path) + results = [] + for feature in data["features"]: + cell1 = get_cell(feature, deploy1, version) + cell2 = get_cell(feature, deploy2, version) + results.append({ + "name": feature["name"], + "status1": cell1["status"] if cell1 else "UNTESTED", + "status2": cell2["status"] if cell2 else "UNTESTED", + }) + return results + + +def gaps(deployment: str, version: str, matrix_path: str = MATRIX_PATH) -> List[Dict[str, Any]]: + """List features that are NOT_AVAIL, BROKEN, or LIMITED.""" + data = load_matrix(matrix_path) + gap_statuses = {"NOT_AVAIL", "BROKEN", "LIMITED"} + results = [] + for feature in data["features"]: + cell = get_cell(feature, deployment, version) + if cell is None: + continue + status = cell.get("status", "UNTESTED") + if status in gap_statuses: + results.append({ + "name": feature["name"], + "status": status, + "notes": cell.get("notes", ""), + }) + return results + + +def export_markdown(output_path: str, matrix_path: str = MATRIX_PATH) -> None: + """Export the full matrix as a markdown table to a file.""" + data = load_matrix(matrix_path) + versions = get_version_ids(data) + deployments = get_deployment_ids(data) + columns = [(d, v) for d in deployments for v in versions] + col_labels = [f"{d}/{v}" for d, v in columns] + features = data["features"] + + feat_width = max((len(f["name"]) for f in features), default=7) + feat_width = max(feat_width, len("Feature")) + col_widths = [max(len(lbl), 10) for lbl in col_labels] + + lines = [] + header = f"| {'Feature':<{feat_width}} |" + for lbl, w in zip(col_labels, col_widths): + header += f" {lbl:<{w}} |" + lines.append(header) + + sep = f"|{'-' * (feat_width + 2)}|" + for w in col_widths: + sep += f"{'-' * (w + 2)}|" + lines.append(sep) + + for feature in features: + row = f"| {feature['name']:<{feat_width}} |" + for (d, v), w in zip(columns, col_widths): + cell = get_cell(feature, d, v) + status = cell["status"] if cell else "-" + row += f" {status:<{w}} |" + lines.append(row) + + with open(output_path, "w", encoding="utf-8") as fh: + fh.write("\n".join(lines) + "\n") + + +def export_csv(output_path: str, matrix_path: str = MATRIX_PATH) -> None: + """Export the full matrix as CSV to a file.""" + data = load_matrix(matrix_path) + versions = get_version_ids(data) + deployments = get_deployment_ids(data) + columns = [(d, v) for d in deployments for v in versions] + col_labels = [f"{d}/{v}" for d, v in columns] + + with open(output_path, "w", encoding="utf-8", newline="") as fh: + writer = csv.writer(fh) + writer.writerow(["Feature", "Category"] + col_labels) + for feature in data["features"]: + row = [feature["name"], feature.get("category", "")] + for d, v in columns: + cell = get_cell(feature, d, v) + row.append(cell["status"] if cell else "") + writer.writerow(row) + + +# ============================================================================= +# Subcommands (CLI handlers) +# ============================================================================= + +def cmd_check(args: argparse.Namespace) -> None: + """Look up a specific feature / deployment / version combo.""" + data = load_matrix(MATRIX_PATH) + validate_deployment(data, args.deployment) + validate_version(data, args.version) + + matches = find_features(data, args.feature) + + if not matches: + print(f"No features matching '{args.feature}'.") + sys.exit(1) + + if len(matches) > 1: + print(f"Ambiguous feature name '{args.feature}'. Multiple matches:") + for m in matches: + print(f" - {m['name']}") + print("\nPlease be more specific.") + sys.exit(1) + + feature = matches[0] + cell = get_cell(feature, args.deployment, args.version) + cfn = _get_color_fn() + deploy_name = get_deployment_name(data, args.deployment) + + print(f"Feature: {feature['name']}") + print(f"Deployment: {deploy_name} ({args.deployment})") + print(f"Version: {args.version}") + print() + + if cell is None: + print(f"Status: {cfn('UNTESTED')} (no entry in matrix)") + else: + status = cell.get("status", "UNTESTED") + print(f"Status: {cfn(status)}") + notes = cell.get("notes") + if notes: + print(f"Notes: {notes}") + + +def cmd_compare(args: argparse.Namespace) -> None: + """Side-by-side comparison of two deployment types for a version.""" + data = load_matrix(MATRIX_PATH) + validate_deployment(data, args.deploy1) + validate_deployment(data, args.deploy2) + validate_version(data, args.version) + + cfn = _get_color_fn() + name1 = get_deployment_name(data, args.deploy1) + name2 = get_deployment_name(data, args.deploy2) + + # Determine column widths + feature_names = [f["name"] for f in data["features"]] + col_feat = max(len(n) for n in feature_names) + 2 + col_status = max(len(name1), len(name2), 12) + 2 + + header = ( + f"{'Feature':<{col_feat}}" + f" {name1:<{col_status}}" + f" {name2:<{col_status}}" + ) + print(f"Comparison: {name1} vs {name2} (version {args.version})") + print() + print(header) + print("-" * len(header)) + + for feature in data["features"]: + cell1 = get_cell(feature, args.deploy1, args.version) + cell2 = get_cell(feature, args.deploy2, args.version) + s1 = cell1["status"] if cell1 else "UNTESTED" + s2 = cell2["status"] if cell2 else "UNTESTED" + + # For plain (non-tty) we can just print aligned; for color we need + # to pad BEFORE wrapping with color codes so alignment is correct. + s1_padded = f"{s1:<{col_status}}" + s2_padded = f"{s2:<{col_status}}" + + if _supports_color(): + s1_display = f"{_COLORS.get(s1, '')}{s1_padded}{_RESET}" + s2_display = f"{_COLORS.get(s2, '')}{s2_padded}{_RESET}" + else: + s1_display = s1_padded + s2_display = s2_padded + + print(f"{feature['name']:<{col_feat}} {s1_display} {s2_display}") + + +def cmd_gaps(args: argparse.Namespace) -> None: + """List features that are NOT_AVAIL, BROKEN, or LIMITED for a deploy/version.""" + data = load_matrix(MATRIX_PATH) + validate_deployment(data, args.deployment) + validate_version(data, args.version) + + cfn = _get_color_fn() + deploy_name = get_deployment_name(data, args.deployment) + gap_statuses = {"NOT_AVAIL", "BROKEN", "LIMITED"} + gaps: List[Tuple[str, str, Optional[str]]] = [] + + for feature in data["features"]: + cell = get_cell(feature, args.deployment, args.version) + if cell is None: + # No entry — could be untested or implicitly not available + continue + status = cell.get("status", "UNTESTED") + if status in gap_statuses: + gaps.append((feature["name"], status, cell.get("notes"))) + + print(f"Gaps for {deploy_name} ({args.deployment}), version {args.version}:") + print() + + if not gaps: + print(" No gaps found — all mapped features are GA, GA_CAVEAT, PREVIEW, or UNTESTED.") + return + + name_width = max(len(g[0]) for g in gaps) + 2 + for name, status, notes in gaps: + status_display = cfn(status) + line = f" {name:<{name_width}} {status_display}" + if notes: + line += f" — {notes}" + print(line) + + +def cmd_update(args: argparse.Namespace) -> None: + """Modify a specific cell in the matrix.""" + status = args.status.upper() + if status not in VALID_STATUSES: + print( + f"Error: Invalid status '{args.status}'. " + f"Valid statuses: {', '.join(sorted(VALID_STATUSES))}", + file=sys.stderr, + ) + sys.exit(1) + + data = load_matrix(MATRIX_PATH) + validate_deployment(data, args.deployment) + validate_version(data, args.version) + + matches = find_features(data, args.feature) + + if not matches: + print(f"Error: No features matching '{args.feature}'.", file=sys.stderr) + sys.exit(1) + + if len(matches) > 1: + print(f"Ambiguous feature name '{args.feature}'. Multiple matches:") + for m in matches: + print(f" - {m['name']}") + print("\nPlease use the exact feature name.") + sys.exit(1) + + feature = matches[0] + + # Ensure nested dicts exist + if "matrix" not in feature: + feature["matrix"] = {} + if args.deployment not in feature["matrix"]: + feature["matrix"][args.deployment] = {} + + cell: Dict[str, Any] = {"status": status} + if args.notes: + cell["notes"] = args.notes + + # Add contributor attribution if provided + contributor_name = getattr(args, "name", None) + contributor_team = getattr(args, "team", None) + if contributor_name and contributor_team: + contributor = { + "name": contributor_name, + "team": contributor_team, + "date": date.today().isoformat(), + } + confidence = getattr(args, "confidence", None) + if confidence and confidence in VALID_CONFIDENCE: + contributor["confidence"] = confidence + cell["contributor"] = contributor + + # Add field_finding_ref if provided + field_ref = getattr(args, "field_finding_ref", None) + if field_ref: + cell["field_finding_ref"] = field_ref + + feature["matrix"][args.deployment][args.version] = cell + + # Update last_verified at root + data["last_verified"] = date.today().isoformat() + + save_matrix(MATRIX_PATH, data) + + cfn = _get_color_fn() + print(f"Updated '{feature['name']}' / {args.deployment} / {args.version}:") + print(f" Status: {cfn(status)}") + if args.notes: + print(f" Notes: {args.notes}") + print(f" last_verified set to {data['last_verified']}") + + +def cmd_export(args: argparse.Namespace) -> None: + """Export the full matrix as markdown or CSV.""" + data = load_matrix(MATRIX_PATH) + versions = get_version_ids(data) + deployments = get_deployment_ids(data) + + # Build column headers: each column is deploy + version + columns: List[Tuple[str, str]] = [] + for d in deployments: + for v in versions: + columns.append((d, v)) + + col_labels = [f"{d}/{v}" for d, v in columns] + + if args.format == "csv": + _export_csv(data, columns, col_labels) + else: + _export_markdown(data, columns, col_labels) + + +def _export_markdown( + data: Dict[str, Any], + columns: List[Tuple[str, str]], + col_labels: List[str], +) -> None: + """Print a markdown table to stdout.""" + features = data["features"] + + # Column widths + feat_width = max(len(f["name"]) for f in features) + feat_width = max(feat_width, len("Feature")) + col_widths = [max(len(lbl), 10) for lbl in col_labels] + + # Header row + header = f"| {'Feature':<{feat_width}} |" + for lbl, w in zip(col_labels, col_widths): + header += f" {lbl:<{w}} |" + print(header) + + # Separator + sep = f"|{'-' * (feat_width + 2)}|" + for w in col_widths: + sep += f"{'-' * (w + 2)}|" + print(sep) + + # Data rows + for feature in features: + row = f"| {feature['name']:<{feat_width}} |" + for (d, v), w in zip(columns, col_widths): + cell = get_cell(feature, d, v) + status = cell["status"] if cell else "-" + row += f" {status:<{w}} |" + print(row) + + +def _export_csv( + data: Dict[str, Any], + columns: List[Tuple[str, str]], + col_labels: List[str], +) -> None: + """Print CSV to stdout.""" + output = io.StringIO() + writer = csv.writer(output) + + # Header + writer.writerow(["Feature", "Category"] + col_labels) + + for feature in data["features"]: + row = [feature["name"], feature.get("category", "")] + for d, v in columns: + cell = get_cell(feature, d, v) + row.append(cell["status"] if cell else "") + writer.writerow(row) + + print(output.getvalue(), end="") + + +# ============================================================================= +# CLI entry point +# ============================================================================= + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="feature_matrix_cli", + description="Query and update the ADB feature compatibility matrix.", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + # --- check --- + p_check = subparsers.add_parser( + "check", + help="Look up a specific feature/deployment/version combo.", + ) + p_check.add_argument("feature", help="Feature name (case-insensitive, partial match).") + p_check.add_argument("deployment", help="Deployment type id (e.g. adb_s, exacs).") + p_check.add_argument("version", help="Version id (e.g. 23ai, 26ai).") + + # --- compare --- + p_compare = subparsers.add_parser( + "compare", + help="Side-by-side comparison of two deployment types for a version.", + ) + p_compare.add_argument("deploy1", help="First deployment type id.") + p_compare.add_argument("deploy2", help="Second deployment type id.") + p_compare.add_argument("version", help="Version id.") + + # --- gaps --- + p_gaps = subparsers.add_parser( + "gaps", + help="List features that are NOT_AVAIL, BROKEN, or LIMITED.", + ) + p_gaps.add_argument("deployment", help="Deployment type id.") + p_gaps.add_argument("version", help="Version id.") + + # --- update --- + p_update = subparsers.add_parser( + "update", + help="Modify a specific cell in the matrix.", + ) + p_update.add_argument("feature", help="Feature name (case-insensitive, partial match).") + p_update.add_argument("deployment", help="Deployment type id.") + p_update.add_argument("version", help="Version id.") + p_update.add_argument("--status", required=True, help="New status value.") + p_update.add_argument("--notes", default=None, help="Optional notes for the cell.") + p_update.add_argument("--name", default=None, help="Contributor name for attribution.") + p_update.add_argument("--team", default=None, help="Contributor team for attribution.") + p_update.add_argument("--confidence", default=None, help="Confidence level: validated/observed/reported/inferred.") + p_update.add_argument("--field-finding-ref", default=None, help="Reference to field finding ID.") + + # --- export --- + p_export = subparsers.add_parser( + "export", + help="Export the full matrix as markdown or CSV.", + ) + p_export.add_argument( + "--format", + required=True, + choices=["markdown", "csv"], + help="Output format.", + ) + + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + + dispatch = { + "check": cmd_check, + "compare": cmd_compare, + "gaps": cmd_gaps, + "update": cmd_update, + "export": cmd_export, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + handler(args) + + +if __name__ == "__main__": + main() diff --git a/tools/findings_cli.py b/tools/findings_cli.py new file mode 100644 index 0000000..e65bea3 --- /dev/null +++ b/tools/findings_cli.py @@ -0,0 +1,716 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Field Findings CLI + +Search, add, update, confirm, and report on field findings tracked in +kb/field-findings/tracker.yaml. + +Usage: + python findings_cli.py list # List all findings + python findings_cli.py list --severity HIGH # Filter by severity + python findings_cli.py list --product "ADB-S" # Filter by product + python findings_cli.py list --tag dep # Filter by tag + python findings_cli.py list --client "Pepe" # Filter by client + python findings_cli.py list --status open # Filter by status + python findings_cli.py search "maintenance window" # Full-text search + + python findings_cli.py add --name "Name" --team "Team" --confidence validated ... + python findings_cli.py add # Interactive mode + + python findings_cli.py update FF-202603-001 --status resolved --resolution "Fixed" + + python findings_cli.py confirm FF-202603-001 --name "Name" --team "Team" --note "Confirmed" + + python findings_cli.py aer # After-Engagement Review (interactive) + + python findings_cli.py stats # Summary statistics +""" + +import argparse +import difflib +import os +import sys +from collections import Counter +from datetime import date, datetime +from typing import Any, Dict, List, Optional + +import yaml + + +# ============================================================================= +# Constants +# ============================================================================= + +TRACKER_PATH = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", "kb", "field-findings", "tracker.yaml", +) + +TAGS_PATH = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", "config", "kb-tags.yaml", +) + +VALID_SEVERITIES = {"CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"} +VALID_STATUSES = {"open", "resolved", "wontfix", "acknowledged", "monitoring"} +VALID_CATEGORIES = {"bug", "limitation", "undocumented", "gotcha", "workaround", "performance"} +VALID_CONFIDENCE = {"validated", "observed", "reported", "inferred"} + +# ANSI colors +_SEV_COLORS = { + "CRITICAL": "\033[1;31m", # red bold + "HIGH": "\033[31m", # red + "MEDIUM": "\033[33m", # yellow + "LOW": "\033[36m", # cyan + "INFO": "\033[37m", # white +} +_RESET = "\033[0m" + + +# ============================================================================= +# Data helpers +# ============================================================================= + +def load_tracker(path: str = TRACKER_PATH) -> Dict[str, Any]: + """Load the tracker YAML and return the full dict.""" + resolved = os.path.realpath(path) + if not os.path.isfile(resolved): + print(f"Error: Tracker file not found at {resolved}", file=sys.stderr) + sys.exit(1) + with open(resolved, "r", encoding="utf-8") as fh: + data = yaml.safe_load(fh) + if not data or "findings" not in data: + print("Error: Tracker file missing 'findings' key.", file=sys.stderr) + sys.exit(1) + return data + + +def save_tracker(data: Dict[str, Any], path: str = TRACKER_PATH) -> None: + """Write tracker data back to YAML.""" + resolved = os.path.realpath(path) + data["last_updated"] = date.today().isoformat() + with open(resolved, "w", encoding="utf-8") as fh: + yaml.dump( + data, + fh, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + width=120, + ) + + +def _get_findings(path: str = TRACKER_PATH) -> List[Dict[str, Any]]: + """Return the findings list from the tracker.""" + return load_tracker(path).get("findings", []) + + +def _next_id(findings: List[Dict[str, Any]]) -> str: + """Generate the next FF-YYYYMM-NNN id.""" + prefix = f"FF-{date.today().strftime('%Y%m')}-" + existing = [ + int(f["id"].split("-")[-1]) + for f in findings + if f["id"].startswith(prefix) + ] + next_num = max(existing, default=0) + 1 + return f"{prefix}{next_num:03d}" + + +def _sev_color(severity: str) -> str: + """Wrap severity with ANSI color if terminal supports it.""" + if not (hasattr(sys.stdout, "isatty") and sys.stdout.isatty()): + return severity + code = _SEV_COLORS.get(severity, "") + return f"{code}{severity}{_RESET}" if code else severity + + +def _get_client(finding: Dict[str, Any]) -> str: + """Get client from finding, supporting both old and new format.""" + contributor = finding.get("contributor") + if isinstance(contributor, dict): + return str(contributor.get("client", "")) + return str(finding.get("client", "")) + + +def _get_reporter(finding: Dict[str, Any]) -> str: + """Get reporter name, supporting both old and new format.""" + contributor = finding.get("contributor") + if isinstance(contributor, dict): + return str(contributor.get("name", "")) + return str(finding.get("reported_by", "")) + + +# ============================================================================= +# Tag validation +# ============================================================================= + +def _load_taxonomy() -> List[str]: + """Load all valid tags from the taxonomy config.""" + resolved = os.path.realpath(TAGS_PATH) + if not os.path.isfile(resolved): + return [] + with open(resolved, "r", encoding="utf-8") as fh: + data = yaml.safe_load(fh) + if not data or "taxonomy" not in data: + return [] + all_tags = [] + for category_tags in data["taxonomy"].values(): + if isinstance(category_tags, list): + all_tags.extend(str(t) for t in category_tags) + return all_tags + + +def validate_tags(tags: List[str], taxonomy: Optional[List[str]] = None) -> List[str]: + """Validate tags against taxonomy. Returns list of warning messages.""" + if taxonomy is None: + taxonomy = _load_taxonomy() + if not taxonomy: + return [] + warnings = [] + for tag in tags: + if tag not in taxonomy: + close = difflib.get_close_matches(tag, taxonomy, n=1, cutoff=0.5) + if close: + warnings.append(f"WARNING: Unknown tag '{tag}'. Did you mean '{close[0]}'?") + else: + warnings.append(f"WARNING: Unknown tag '{tag}'. Not in taxonomy.") + return warnings + + +# ============================================================================= +# Library API — importable functions for programmatic use and testing +# ============================================================================= + +def search(query: str, tracker_path: str = TRACKER_PATH) -> List[Dict[str, Any]]: + """Full-text search across summary, detail, workaround, and tags.""" + findings = _get_findings(tracker_path) + query_lower = query.lower() + results = [] + for f in findings: + searchable = " ".join([ + str(f.get("summary", "")), + str(f.get("detail", "")), + str(f.get("workaround", "")), + " ".join(str(t) for t in f.get("tags", [])), + ]).lower() + if query_lower in searchable: + results.append(f) + return results + + +def filter_by_severity(severity: str, tracker_path: str = TRACKER_PATH) -> List[Dict[str, Any]]: + """Return findings matching the given severity.""" + findings = _get_findings(tracker_path) + return [f for f in findings if f.get("severity") == severity] + + +def filter_by_client(client: str, tracker_path: str = TRACKER_PATH) -> List[Dict[str, Any]]: + """Return findings whose client field contains the query string.""" + findings = _get_findings(tracker_path) + client_lower = client.lower() + return [f for f in findings if client_lower in _get_client(f).lower()] + + +def filter_findings( + findings: List[Dict[str, Any]], + severity: Optional[str] = None, + product: Optional[str] = None, + tag: Optional[str] = None, + client: Optional[str] = None, + status: Optional[str] = None, + category: Optional[str] = None, +) -> List[Dict[str, Any]]: + """Apply multiple filters to a findings list.""" + results = findings + if severity: + results = [f for f in results if f.get("severity") == severity.upper()] + if product: + product_lower = product.lower() + results = [f for f in results if product_lower in str(f.get("product", "")).lower()] + if tag: + tag_lower = tag.lower() + results = [f for f in results if tag_lower in [str(t).lower() for t in f.get("tags", [])]] + if client: + client_lower = client.lower() + results = [f for f in results if client_lower in _get_client(f).lower()] + if status: + results = [f for f in results if f.get("status") == status] + if category: + results = [f for f in results if f.get("category") == category] + return results + + +def add( + tracker_path: str = TRACKER_PATH, + name: str = "", + team: str = "", + confidence: str = "validated", + client: str = "", + context: str = "", + product: str = "", + version: str = "", + severity: str = "MEDIUM", + category: str = "gotcha", + summary: str = "", + detail: str = "", + workaround: str = "", + tags: Optional[List[str]] = None, + status: str = "open", + date_str: Optional[str] = None, + oracle_sr: str = "", + reported_by: str = "", + affects_matrix: Optional[str] = None, +) -> Dict[str, Any]: + """Add a new finding programmatically. Returns the new entry dict. + + Supports both new contributor block (name/team/confidence) and legacy + reported_by for backward compatibility. + """ + data = load_tracker(tracker_path) + findings = data.get("findings", []) + + contributor_name = name or reported_by + + contributor = { + "name": contributor_name, + "team": team, + "date": date_str or date.today().isoformat(), + "confidence": confidence if confidence in VALID_CONFIDENCE else "validated", + } + if client: + contributor["client"] = client + if context: + contributor["context"] = context + + entry = { + "id": _next_id(findings), + "date": date_str or date.today().isoformat(), + "contributor": contributor, + "reported_by": contributor_name, + "product": product, + "version": version, + "severity": severity.upper(), + "category": category, + "summary": summary, + "detail": detail, + "workaround": workaround, + "oracle_sr": oracle_sr, + "status": status, + "resolved_date": None, + "resolution": None, + "affects_matrix": affects_matrix, + "tags": tags or [], + "confirmations": [], + } + + # Insert at top (newest first) + findings.insert(0, entry) + data["findings"] = findings + save_tracker(data, tracker_path) + return entry + + +def confirm( + finding_id: str, + name: str, + team: str, + note: str = "", + tracker_path: str = TRACKER_PATH, +) -> Dict[str, Any]: + """Add a confirmation to an existing finding. Returns the confirmation dict.""" + data = load_tracker(tracker_path) + findings = data.get("findings", []) + + target = None + for f in findings: + if f["id"] == finding_id: + target = f + break + + if target is None: + raise LookupError(f"Finding '{finding_id}' not found.") + + confirmation = { + "name": name, + "team": team, + "date": date.today().isoformat(), + } + if note: + confirmation["note"] = note + + if "confirmations" not in target: + target["confirmations"] = [] + target["confirmations"].append(confirmation) + + save_tracker(data, tracker_path) + return confirmation + + +def stats(tracker_path: str = TRACKER_PATH) -> Dict[str, Any]: + """Return summary statistics about findings.""" + findings = _get_findings(tracker_path) + return { + "total": len(findings), + "by_severity": dict(Counter(f.get("severity", "UNKNOWN") for f in findings)), + "by_product": dict(Counter(f.get("product", "UNKNOWN") for f in findings)), + "by_status": dict(Counter(f.get("status", "unknown") for f in findings)), + "by_category": dict(Counter(f.get("category", "unknown") for f in findings)), + } + + +# ============================================================================= +# CLI subcommands +# ============================================================================= + +def cmd_list(args: argparse.Namespace) -> None: + """List findings with optional filters.""" + data = load_tracker(TRACKER_PATH) + findings = data.get("findings", []) + + results = filter_findings( + findings, + severity=args.severity, + product=args.product, + tag=args.tag, + client=args.client, + status=args.status, + category=getattr(args, "category", None), + ) + + if not results: + print("No findings match the given filters.") + return + + # Table output + id_w, date_w, sev_w, prod_w = 16, 12, 10, 30 + header = f"{'ID':<{id_w}} {'Date':<{date_w}} {'Severity':<{sev_w}} {'Product':<{prod_w}} Summary" + print(header) + print("-" * len(header)) + + for f in results: + fid = f.get("id", "?") + fdate = str(f.get("date", "?")) + sev = f.get("severity", "?") + prod = str(f.get("product", "?"))[:prod_w] + summary = str(f.get("summary", ""))[:60] + print(f"{fid:<{id_w}} {fdate:<{date_w}} {_sev_color(sev):<{sev_w}} {prod:<{prod_w}} {summary}") + + +def cmd_search(args: argparse.Namespace) -> None: + """Full-text search.""" + results = search(args.query) + + if not results: + print(f"No findings matching '{args.query}'.") + return + + print(f"Found {len(results)} finding(s) matching '{args.query}':\n") + for f in results: + sev = f.get("severity", "?") + print(f" {f['id']} [{_sev_color(sev)}] {f.get('summary', '')}") + + +def cmd_add(args: argparse.Namespace) -> None: + """Add a new finding.""" + if args.summary: + # Non-interactive mode + tags_list = [t.strip() for t in args.tags.split(",")] if args.tags else [] + # Validate tags + tag_warnings = validate_tags(tags_list) + for w in tag_warnings: + print(w, file=sys.stderr) + entry = add( + name=args.name or args.reported_by or "", + team=args.team or "", + confidence=args.confidence or "validated", + client=args.client or "", + context=args.context or "", + product=args.product or "", + version=args.version or "", + severity=args.severity or "MEDIUM", + category=args.category or "gotcha", + summary=args.summary, + detail=args.detail or "", + workaround=args.workaround or "", + tags=tags_list, + date_str=args.date, + oracle_sr=args.oracle_sr or "", + affects_matrix=args.affects_matrix or None, + ) + else: + # Interactive mode + print("Add new field finding (press Enter to skip optional fields):\n") + contributor_name = input("Your name: ").strip() + contributor_team = input("Your team: ").strip() + contributor_client = input("Client (optional, press Enter to skip): ").strip() + contributor_context = input("Context (optional): ").strip() + confidence = input("Confidence [validated/observed/reported/inferred]: ").strip() or "validated" + tags_str = input("Tags (comma-separated): ").strip() + tags_list = [t.strip() for t in tags_str.split(",") if t.strip()] + tag_warnings = validate_tags(tags_list) + for w in tag_warnings: + print(w) + entry = add( + date_str=input("Date [today]: ").strip() or None, + name=contributor_name, + team=contributor_team, + client=contributor_client, + context=contributor_context, + confidence=confidence, + product=input("Product: ").strip(), + version=input("Version: ").strip(), + severity=input("Severity (CRITICAL/HIGH/MEDIUM/LOW/INFO) [MEDIUM]: ").strip() or "MEDIUM", + category=input("Category (bug/limitation/undocumented/gotcha/workaround/performance) [gotcha]: ").strip() or "gotcha", + summary=input("Summary (one line): ").strip(), + detail=input("Detail: ").strip(), + workaround=input("Workaround: ").strip(), + tags=tags_list, + oracle_sr=input("Oracle SR#: ").strip(), + affects_matrix=input("Affects matrix entry (feature name, or blank): ").strip() or None, + ) + + print(f"\nAdded: {entry['id']} [{entry['severity']}] {entry['summary']}") + + +def cmd_confirm(args: argparse.Namespace) -> None: + """Add a confirmation to an existing finding.""" + try: + confirmation = confirm( + finding_id=args.finding_id, + name=args.name, + team=args.team, + note=args.note or "", + ) + print(f"Confirmed {args.finding_id}:") + print(f" by: {confirmation['name']} ({confirmation.get('team', '')})") + if confirmation.get("note"): + print(f" note: {confirmation['note']}") + except LookupError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +def cmd_aer(args: argparse.Namespace) -> None: + """After-Engagement Review — interactive post-engagement knowledge capture.""" + print("After-Engagement Review") + print("=" * 23) + + client = input("Client: ").strip() + architect = input("Architect: ").strip() + team = input("Team: ").strip() or "Field Architecture" + print() + + q1 = input("1. What did we learn that the KB doesn't cover?\n > ").strip() + print() + q2 = input("2. What went wrong that others should know about?\n > ").strip() + print() + q3 = input("3. What worked well that we should capture as a pattern?\n > ").strip() + print() + + print("Saving...") + created = [] + + # Create finding from Q2 (what went wrong) if provided + if q2: + entry = add( + name=architect, + team=team, + client=client, + context="After-Engagement Review", + confidence="observed", + product=input("Product for issue (Q2): ").strip() or "General", + severity=input("Severity for issue (CRITICAL/HIGH/MEDIUM/LOW/INFO) [MEDIUM]: ").strip() or "MEDIUM", + category="gotcha", + summary=q2[:120], + detail=q2, + ) + created.append(f" -> Created finding {entry['id']} ({q2[:60]})") + + # Create finding from Q1 (KB gap) if provided + if q1: + entry = add( + name=architect, + team=team, + client=client, + context="After-Engagement Review", + confidence="observed", + product=input("Product for KB gap (Q1): ").strip() or "General", + severity="INFO", + category="undocumented", + summary=f"KB gap: {q1[:110]}", + detail=q1, + ) + created.append(f" -> Created finding {entry['id']} (KB gap: {q1[:50]})") + + if q3: + created.append(f" -> Pattern suggestion noted: {q3[:60]}...") + created.append(f" (Create manually: kb/patterns/.yaml)") + + if created: + for line in created: + print(line) + else: + print(" No items to create. Review complete.") + + +def cmd_update(args: argparse.Namespace) -> None: + """Update an existing finding.""" + data = load_tracker(TRACKER_PATH) + findings = data.get("findings", []) + + target = None + for f in findings: + if f["id"] == args.finding_id: + target = f + break + + if target is None: + print(f"Error: Finding '{args.finding_id}' not found.", file=sys.stderr) + sys.exit(1) + + updated_fields = [] + if args.status: + target["status"] = args.status + updated_fields.append(f"status -> {args.status}") + if args.resolution: + target["resolution"] = args.resolution + updated_fields.append(f"resolution -> {args.resolution}") + if args.resolved_date: + target["resolved_date"] = args.resolved_date + updated_fields.append(f"resolved_date -> {args.resolved_date}") + if args.workaround: + target["workaround"] = args.workaround + updated_fields.append("workaround updated") + if args.oracle_sr: + target["oracle_sr"] = args.oracle_sr + updated_fields.append(f"oracle_sr -> {args.oracle_sr}") + + if not updated_fields: + print("No fields to update. Use --status, --resolution, --resolved-date, --workaround, or --oracle-sr.") + return + + save_tracker(data, TRACKER_PATH) + print(f"Updated {args.finding_id}:") + for field in updated_fields: + print(f" {field}") + + +def cmd_stats(args: argparse.Namespace) -> None: + """Display summary statistics.""" + s = stats() + + print(f"Total findings: {s['total']}\n") + + print("By Severity:") + for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]: + count = s["by_severity"].get(sev, 0) + if count: + print(f" {_sev_color(sev):<10} {count}") + + print("\nBy Status:") + for status, count in sorted(s["by_status"].items()): + print(f" {status:<15} {count}") + + print("\nBy Product:") + for product, count in sorted(s["by_product"].items(), key=lambda x: -x[1]): + print(f" {product:<35} {count}") + + print("\nBy Category:") + for cat, count in sorted(s["by_category"].items(), key=lambda x: -x[1]): + print(f" {cat:<15} {count}") + + +# ============================================================================= +# CLI entry point +# ============================================================================= + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="findings_cli", + description="Manage the Field Findings Tracker.", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + # --- list --- + p_list = subparsers.add_parser("list", help="List findings with optional filters.") + p_list.add_argument("--severity", help="Filter by severity.") + p_list.add_argument("--product", help="Filter by product (partial match).") + p_list.add_argument("--tag", help="Filter by tag.") + p_list.add_argument("--client", help="Filter by client (partial match).") + p_list.add_argument("--status", help="Filter by status.") + p_list.add_argument("--category", help="Filter by category.") + + # --- search --- + p_search = subparsers.add_parser("search", help="Full-text search.") + p_search.add_argument("query", help="Search term.") + + # --- add --- + p_add = subparsers.add_parser("add", help="Add a new finding.") + p_add.add_argument("--date", default=None, help="Date (YYYY-MM-DD). Defaults to today.") + p_add.add_argument("--name", help="Contributor name.") + p_add.add_argument("--team", help="Contributor team.") + p_add.add_argument("--confidence", help="Confidence level: validated/observed/reported/inferred.") + p_add.add_argument("--context", help="Engagement context (PoC, migration, assessment).") + p_add.add_argument("--reported-by", help="(Legacy) Who reported the finding. Use --name instead.") + p_add.add_argument("--client", help="Client name or reference.") + p_add.add_argument("--product", help="OCI product/service affected.") + p_add.add_argument("--version", help="Product version.") + p_add.add_argument("--severity", help="CRITICAL/HIGH/MEDIUM/LOW/INFO.") + p_add.add_argument("--category", help="bug/limitation/undocumented/gotcha/workaround/performance.") + p_add.add_argument("--summary", help="One-line summary.") + p_add.add_argument("--detail", help="Full description.") + p_add.add_argument("--workaround", help="Known workaround.") + p_add.add_argument("--tags", help="Comma-separated tags.") + p_add.add_argument("--oracle-sr", help="Oracle SR number.") + p_add.add_argument("--affects-matrix", help="Feature matrix entry this relates to.") + + # --- update --- + p_update = subparsers.add_parser("update", help="Update an existing finding.") + p_update.add_argument("finding_id", help="Finding ID (e.g., FF-202603-001).") + p_update.add_argument("--status", help="New status.") + p_update.add_argument("--resolution", help="Resolution description.") + p_update.add_argument("--resolved-date", help="Resolved date (YYYY-MM-DD).") + p_update.add_argument("--workaround", help="Updated workaround.") + p_update.add_argument("--oracle-sr", help="Oracle SR number.") + + # --- confirm --- + p_confirm = subparsers.add_parser("confirm", help="Add a confirmation to an existing finding.") + p_confirm.add_argument("finding_id", help="Finding ID to confirm (e.g., FF-202603-001).") + p_confirm.add_argument("--name", required=True, help="Your name.") + p_confirm.add_argument("--team", required=True, help="Your team.") + p_confirm.add_argument("--note", help="Confirmation note.") + + # --- aer --- + subparsers.add_parser("aer", help="After-Engagement Review (interactive).") + + # --- stats --- + subparsers.add_parser("stats", help="Show summary statistics.") + + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + + dispatch = { + "list": cmd_list, + "search": cmd_search, + "add": cmd_add, + "update": cmd_update, + "confirm": cmd_confirm, + "aer": cmd_aer, + "stats": cmd_stats, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + handler(args) + + +if __name__ == "__main__": + main() diff --git a/tools/kb_cli.py b/tools/kb_cli.py new file mode 100644 index 0000000..4127a6b --- /dev/null +++ b/tools/kb_cli.py @@ -0,0 +1,504 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Unified KB Management CLI + +Provides a single entry point for KB management tasks: + - Health dashboard + - Contribution stats and leaderboard + - Stale content reporting + - Confidence decay reporting + - Changelog management for service files + - Cross-KB search + - Domain owner listing + +Usage: + python kb_cli.py health # Overall KB health dashboard + python kb_cli.py stats contributors # Contribution leaderboard + python kb_cli.py stats stale # Stale content report + python kb_cli.py stats decay # Confidence decay report + python kb_cli.py changelog --name "Name" --team "Team" --change "Description" + python kb_cli.py search "vector search" # Search across ALL KB files + python kb_cli.py owners # Show domain owners +""" + +import argparse +import os +import re +import sys +from collections import Counter +from datetime import date, datetime +from typing import Any, Dict, List, Optional + +import yaml + +# ============================================================================= +# Constants +# ============================================================================= + +PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +KB_ROOT = os.path.join(PROJECT_ROOT, "kb") +GOVERNANCE_PATH = os.path.join(PROJECT_ROOT, "config", "kb-governance.yaml") +OWNERS_PATH = os.path.join(PROJECT_ROOT, "config", "kb-owners.yaml") +TRACKER_PATH = os.path.join(PROJECT_ROOT, "kb", "field-findings", "tracker.yaml") +MATRIX_PATH = os.path.join(PROJECT_ROOT, "kb", "compatibility", "adb-feature-matrix.yaml") + +_RESET = "\033[0m" +_GREEN = "\033[32m" +_YELLOW = "\033[33m" +_RED = "\033[31m" +_BOLD = "\033[1m" +_CYAN = "\033[36m" + + +def _supports_color() -> bool: + return hasattr(sys.stdout, "isatty") and sys.stdout.isatty() + + +def _c(text: str, color: str) -> str: + if not _supports_color(): + return text + return f"{color}{text}{_RESET}" + + +# ============================================================================= +# Loaders +# ============================================================================= + +def _load_yaml(path: str) -> Optional[Dict[str, Any]]: + resolved = os.path.realpath(path) + if not os.path.isfile(resolved): + return None + with open(resolved, "r", encoding="utf-8") as fh: + return yaml.safe_load(fh) + + +def _parse_date(d: Any) -> Optional[date]: + if isinstance(d, date) and not isinstance(d, datetime): + return d + if isinstance(d, datetime): + return d.date() + if isinstance(d, str): + try: + return datetime.strptime(d, "%Y-%m-%d").date() + except ValueError: + pass + # Try YYYY-MM format + try: + return datetime.strptime(d + "-01", "%Y-%m-%d").date() + except ValueError: + return None + return None + + +def _days_ago(d: Any) -> Optional[int]: + parsed = _parse_date(d) + if parsed is None: + return None + return (date.today() - parsed).days + + +def _all_kb_files() -> List[str]: + """Return all YAML files under kb/.""" + files = [] + for dirpath, _dirnames, filenames in os.walk(KB_ROOT): + for filename in filenames: + if filename.endswith((".yaml", ".yml")): + files.append(os.path.join(dirpath, filename)) + return sorted(files) + + +# ============================================================================= +# Health dashboard +# ============================================================================= + +def cmd_health(args: argparse.Namespace) -> None: + """Overall KB health dashboard.""" + governance = _load_yaml(GOVERNANCE_PATH) or {} + warning_days = governance.get("freshness", {}).get("warning_days", 180) + stale_days = governance.get("freshness", {}).get("stale_days", 365) + decay_config = governance.get("confidence_decay", {}) + + # File freshness + kb_files = _all_kb_files() + fresh_count = stale_count = expired_count = 0 + stale_files = [] + + for filepath in kb_files: + data = _load_yaml(filepath) + rel = os.path.relpath(filepath, PROJECT_ROOT) + last = None + if data: + last = data.get("last_verified") or data.get("last_updated") + age = _days_ago(last) if last else None + if age is None: + mtime = os.path.getmtime(filepath) + mdate = datetime.fromtimestamp(mtime).date() + age = (date.today() - mdate).days + if age > stale_days: + expired_count += 1 + stale_files.append((rel, age, "expired")) + elif age > warning_days: + stale_count += 1 + stale_files.append((rel, age, "stale")) + else: + fresh_count += 1 + + # Findings stats + tracker = _load_yaml(TRACKER_PATH) or {} + findings = tracker.get("findings", []) + status_counts = Counter(f.get("status", "unknown") for f in findings) + + # Feature matrix stats + matrix = _load_yaml(MATRIX_PATH) or {} + features = matrix.get("features", []) + cell_counts: Dict[str, int] = Counter() + for feat in features: + for deploy_versions in feat.get("matrix", {}).values(): + for ver_data in deploy_versions.values(): + if isinstance(ver_data, dict): + cell_counts[ver_data.get("status", "UNTESTED")] += 1 + + # Contributor stats (last 90 days) + contributor_counts: Dict[str, int] = Counter() + for f in findings: + contrib = f.get("contributor", {}) + if isinstance(contrib, dict): + name = contrib.get("name", "Unknown") + cdate = _parse_date(contrib.get("date", f.get("date"))) + if cdate and (date.today() - cdate).days <= 90: + contributor_counts[name] += 1 + for c in f.get("confirmations", []): + cdate = _parse_date(c.get("date")) + if cdate and (date.today() - cdate).days <= 90: + contributor_counts[c.get("name", "Unknown")] += 1 + + # Owner check + owners = _load_yaml(OWNERS_PATH) or {} + unassigned = [] + for domain in owners.get("domains", []): + if domain.get("owner", {}).get("name") == "TBD": + unassigned.append(domain.get("area", "")) + + # Print dashboard + total_files = len(kb_files) + print(_c("KB HEALTH DASHBOARD", _BOLD)) + print("=" * 50) + print() + print(f"Files: {total_files} total | {_c(str(fresh_count), _GREEN)} fresh | " + f"{_c(str(stale_count), _YELLOW)} stale | {_c(str(expired_count), _RED)} expired") + print() + print(f"Findings: {len(findings)} total | " + f"{status_counts.get('open', 0)} open | " + f"{status_counts.get('acknowledged', 0)} acknowledged | " + f"{status_counts.get('resolved', 0)} resolved | " + f"{status_counts.get('monitoring', 0)} monitoring") + print() + + if cell_counts: + parts = [] + for status in ["GA", "GA_CAVEAT", "LIMITED", "NOT_AVAIL", "UNTESTED"]: + if cell_counts.get(status, 0) > 0: + parts.append(f"{status}: {cell_counts[status]}") + print(f"Feature Matrix: {len(features)} features") + print(f" {' | '.join(parts)}") + print() + + if contributor_counts: + print("Contributors (last 90 days):") + for name, count in contributor_counts.most_common(10): + print(f" {name:<30} {count} contributions") + print() + + needs_attention = [] + for filepath, age, status in stale_files: + needs_attention.append(f" {filepath} -- {status} ({age} days old)") + untested = cell_counts.get("UNTESTED", 0) + if untested > 0: + needs_attention.append(f" {untested} UNTESTED cells in feature matrix") + for area in unassigned: + needs_attention.append(f" {area} -- no owner assigned") + + if needs_attention: + print("Needs Attention:") + for item in needs_attention: + print(item) + else: + print(_c("All areas healthy.", _GREEN)) + + +# ============================================================================= +# Stats commands +# ============================================================================= + +def cmd_stats_contributors(args: argparse.Namespace) -> None: + """Show contribution leaderboard.""" + tracker = _load_yaml(TRACKER_PATH) or {} + findings = tracker.get("findings", []) + + # Count findings per contributor + finding_counts: Dict[str, Dict[str, int]] = {} + for f in findings: + contrib = f.get("contributor", {}) + if isinstance(contrib, dict): + name = contrib.get("name", "Unknown") + team = contrib.get("team", "") + else: + name = str(f.get("reported_by", "Unknown")) + team = "" + key = f"{name}|{team}" + if key not in finding_counts: + finding_counts[key] = {"findings": 0, "confirmations": 0, "team": team} + finding_counts[key]["findings"] += 1 + + for c in f.get("confirmations", []): + cname = c.get("name", "Unknown") + cteam = c.get("team", "") + ckey = f"{cname}|{cteam}" + if ckey not in finding_counts: + finding_counts[ckey] = {"findings": 0, "confirmations": 0, "team": cteam} + finding_counts[ckey]["confirmations"] += 1 + + if not finding_counts: + print("No contributions found.") + return + + print(f"{'Contributor':<25} {'Team':<25} {'Findings':>8} {'Confirms':>8} {'Total':>8}") + print("-" * 80) + for key, counts in sorted(finding_counts.items(), key=lambda x: -(x[1]["findings"] + x[1]["confirmations"])): + name = key.split("|")[0] + total = counts["findings"] + counts["confirmations"] + print(f"{name:<25} {counts['team']:<25} {counts['findings']:>8} {counts['confirmations']:>8} {total:>8}") + + +def cmd_stats_stale(args: argparse.Namespace) -> None: + """Show stale content report.""" + governance = _load_yaml(GOVERNANCE_PATH) or {} + warning_days = governance.get("freshness", {}).get("warning_days", 180) + stale_days = governance.get("freshness", {}).get("stale_days", 365) + + kb_files = _all_kb_files() + stale = [] + for filepath in kb_files: + data = _load_yaml(filepath) + rel = os.path.relpath(filepath, PROJECT_ROOT) + last = None + if data: + last = data.get("last_verified") or data.get("last_updated") + age = _days_ago(last) if last else None + if age is None: + mtime = os.path.getmtime(filepath) + mdate = datetime.fromtimestamp(mtime).date() + age = (date.today() - mdate).days + if age > warning_days: + status = "STALE" if age > stale_days else "WARNING" + stale.append((rel, age, status)) + + if not stale: + print(_c("No stale content found.", _GREEN)) + return + + print(f"{'File':<50} {'Age':>8} {'Status':>10}") + print("-" * 70) + for filepath, age, status in sorted(stale, key=lambda x: -x[1]): + sc = _RED if status == "STALE" else _YELLOW + print(f"{filepath:<50} {age:>5}d {_c(status, sc)}") + + +def cmd_stats_decay(args: argparse.Namespace) -> None: + """Show confidence decay report.""" + governance = _load_yaml(GOVERNANCE_PATH) or {} + decay_config = governance.get("confidence_decay", {}) + tracker = _load_yaml(TRACKER_PATH) or {} + findings = tracker.get("findings", []) + + if not findings: + print("No findings found.") + return + + print(f"{'ID':<18} {'Status':<10} {'Summary':<45} {'Confidence':<12} {'Age'}") + print("-" * 100) + + for f in findings: + fid = f.get("id", "???") + contributor = f.get("contributor", {}) + confidence = contributor.get("confidence", "validated") if isinstance(contributor, dict) else "validated" + finding_date = contributor.get("date", f.get("date")) if isinstance(contributor, dict) else f.get("date") + age = _days_ago(finding_date) + summary = str(f.get("summary", ""))[:43] + + if age is None: + status = "UNKNOWN" + else: + thresholds = decay_config.get(confidence, {"fresh": 180, "stale": 365, "expired": 730}) + if age <= thresholds.get("fresh", 180): + status = "FRESH" + elif age <= thresholds.get("stale", 365): + status = "STALE" + else: + status = "EXPIRED" + + color = _GREEN if status == "FRESH" else (_YELLOW if status == "STALE" else _RED) + age_str = f"{age}d" if age is not None else "?" + print(f"{fid:<18} {_c(status, color):<10} {summary:<45} {confidence:<12} {age_str}") + + +# ============================================================================= +# Changelog command +# ============================================================================= + +def cmd_changelog(args: argparse.Namespace) -> None: + """Add a changelog entry to a KB file.""" + filepath = os.path.realpath(args.file) + if not os.path.isfile(filepath): + print(f"Error: File not found: {filepath}", file=sys.stderr) + sys.exit(1) + + with open(filepath, "r", encoding="utf-8") as fh: + data = yaml.safe_load(fh) + + if data is None: + data = {} + + if "changelog" not in data: + data["changelog"] = [] + + entry = { + "date": date.today().isoformat(), + "contributor": {"name": args.name, "team": args.team}, + "change": args.change, + } + data["changelog"].append(entry) + + with open(filepath, "w", encoding="utf-8") as fh: + yaml.dump(data, fh, default_flow_style=False, sort_keys=False, allow_unicode=True, width=120) + + rel = os.path.relpath(filepath, PROJECT_ROOT) + print(f"Added changelog entry to {rel}:") + print(f" date: {entry['date']}") + print(f" contributor: {args.name} ({args.team})") + print(f" change: {args.change}") + + +# ============================================================================= +# Search command +# ============================================================================= + +def cmd_search(args: argparse.Namespace) -> None: + """Search across ALL KB files.""" + query = args.query.lower() + kb_files = _all_kb_files() + results = [] + + for filepath in kb_files: + with open(filepath, "r", encoding="utf-8") as fh: + content = fh.read() + if query in content.lower(): + rel = os.path.relpath(filepath, PROJECT_ROOT) + # Find matching lines + matches = [] + for i, line in enumerate(content.splitlines(), 1): + if query in line.lower(): + matches.append((i, line.strip()[:80])) + results.append((rel, matches)) + + if not results: + print(f"No results for '{args.query}'.") + return + + print(f"Found matches in {len(results)} file(s) for '{args.query}':\n") + for filepath, matches in results: + print(f" {_c(filepath, _CYAN)}") + for lineno, text in matches[:3]: + print(f" L{lineno}: {text}") + if len(matches) > 3: + print(f" ... and {len(matches) - 3} more match(es)") + print() + + +# ============================================================================= +# Owners command +# ============================================================================= + +def cmd_owners(args: argparse.Namespace) -> None: + """Show domain owners.""" + owners = _load_yaml(OWNERS_PATH) + if not owners or "domains" not in owners: + print("No owners configuration found.") + return + + print(f"{'Area':<30} {'Owner':<25} {'Team':<25} {'Review Cadence'}") + print("-" * 100) + for domain in owners["domains"]: + area = domain.get("area", "") + owner = domain.get("owner", {}) + name = owner.get("name", "TBD") + team = owner.get("team", "") + cadence = domain.get("review_cadence", "") + name_display = _c(name, _RED) if name == "TBD" else name + print(f"{area:<30} {name_display:<25} {team:<25} {cadence}") + + +# ============================================================================= +# CLI entry point +# ============================================================================= + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="kb_cli", + description="Unified KB management CLI.", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + # --- health --- + subparsers.add_parser("health", help="Overall KB health dashboard.") + + # --- stats --- + p_stats = subparsers.add_parser("stats", help="KB statistics.") + stats_sub = p_stats.add_subparsers(dest="stats_type", required=True) + stats_sub.add_parser("contributors", help="Contribution leaderboard.") + stats_sub.add_parser("stale", help="Stale content report.") + stats_sub.add_parser("decay", help="Confidence decay report.") + + # --- changelog --- + p_changelog = subparsers.add_parser("changelog", help="Add changelog entry to a KB file.") + p_changelog.add_argument("file", help="Path to the KB YAML file.") + p_changelog.add_argument("--name", required=True, help="Contributor name.") + p_changelog.add_argument("--team", required=True, help="Contributor team.") + p_changelog.add_argument("--change", required=True, help="Description of the change.") + + # --- search --- + p_search = subparsers.add_parser("search", help="Search across all KB files.") + p_search.add_argument("query", help="Search term.") + + # --- owners --- + subparsers.add_parser("owners", help="Show domain owners.") + + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + + if args.command == "health": + cmd_health(args) + elif args.command == "stats": + dispatch = { + "contributors": cmd_stats_contributors, + "stale": cmd_stats_stale, + "decay": cmd_stats_decay, + } + dispatch[args.stats_type](args) + elif args.command == "changelog": + cmd_changelog(args) + elif args.command == "search": + cmd_search(args) + elif args.command == "owners": + cmd_owners(args) + else: + parser.print_help() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/tools/kb_linter.py b/tools/kb_linter.py new file mode 100644 index 0000000..b71fcb0 --- /dev/null +++ b/tools/kb_linter.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — KB Linter + +Validates KB files against governance rules: +- Contributor blocks on findings and matrix entries +- Confidence decay against governance config +- Tag validation against taxonomy +- Domain owner review cadence +- Freshness checks on all KB files + +Usage: + python kb_linter.py # Run all checks + python kb_linter.py --show-decay # Show confidence decay status + python kb_linter.py --check-tags # Validate tags only + python kb_linter.py --check-owners # Check domain owner review cadence +""" + +import argparse +import os +import sys +from datetime import date, datetime +from typing import Any, Dict, List, Optional, Tuple + +import yaml + +# ============================================================================= +# Constants +# ============================================================================= + +PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +GOVERNANCE_PATH = os.path.join(PROJECT_ROOT, "config", "kb-governance.yaml") +OWNERS_PATH = os.path.join(PROJECT_ROOT, "config", "kb-owners.yaml") +TAGS_PATH = os.path.join(PROJECT_ROOT, "config", "kb-tags.yaml") +TRACKER_PATH = os.path.join(PROJECT_ROOT, "kb", "field-findings", "tracker.yaml") +MATRIX_PATH = os.path.join(PROJECT_ROOT, "kb", "compatibility", "adb-feature-matrix.yaml") + +_RESET = "\033[0m" +_GREEN = "\033[32m" +_YELLOW = "\033[33m" +_RED = "\033[31m" + + +def _supports_color() -> bool: + return hasattr(sys.stdout, "isatty") and sys.stdout.isatty() + + +def _c(text: str, color: str) -> str: + if not _supports_color(): + return text + return f"{color}{text}{_RESET}" + + +# ============================================================================= +# Loaders +# ============================================================================= + +def _load_yaml(path: str) -> Optional[Dict[str, Any]]: + resolved = os.path.realpath(path) + if not os.path.isfile(resolved): + return None + with open(resolved, "r", encoding="utf-8") as fh: + return yaml.safe_load(fh) + + +def _load_governance() -> Dict[str, Any]: + data = _load_yaml(GOVERNANCE_PATH) + return data if data else {} + + +def _load_taxonomy() -> List[str]: + data = _load_yaml(TAGS_PATH) + if not data or "taxonomy" not in data: + return [] + all_tags = [] + for category_tags in data["taxonomy"].values(): + if isinstance(category_tags, list): + all_tags.extend(str(t) for t in category_tags) + return all_tags + + +def _parse_date(d: Any) -> Optional[date]: + if isinstance(d, date) and not isinstance(d, datetime): + return d + if isinstance(d, datetime): + return d.date() + if isinstance(d, str): + try: + return datetime.strptime(d, "%Y-%m-%d").date() + except ValueError: + return None + return None + + +def _days_ago(d: Any) -> Optional[int]: + parsed = _parse_date(d) + if parsed is None: + return None + return (date.today() - parsed).days + + +# ============================================================================= +# Check: Contributor blocks +# ============================================================================= + +def check_contributor_blocks(tracker_path: str = TRACKER_PATH) -> List[str]: + """Verify all findings have proper contributor blocks.""" + issues = [] + data = _load_yaml(tracker_path) + if not data or "findings" not in data: + issues.append("Cannot load tracker file.") + return issues + + governance = _load_governance() + required = governance.get("contribution", {}).get("required_fields", ["name", "team", "date", "confidence"]) + valid_confidence = governance.get("contribution", {}).get("confidence_levels", + ["validated", "observed", "reported", "inferred"]) + + for f in data["findings"]: + fid = f.get("id", "???") + contributor = f.get("contributor") + if not isinstance(contributor, dict): + if f.get("reported_by"): + issues.append(f"{fid}: uses legacy 'reported_by' instead of contributor block") + else: + issues.append(f"{fid}: missing contributor block") + continue + for field in required: + if not contributor.get(field): + issues.append(f"{fid}: contributor missing required field '{field}'") + conf = contributor.get("confidence", "") + if conf and conf not in valid_confidence: + issues.append(f"{fid}: invalid confidence level '{conf}'") + return issues + + +# ============================================================================= +# Check: Confidence decay +# ============================================================================= + +def check_confidence_decay(tracker_path: str = TRACKER_PATH) -> List[Dict[str, Any]]: + """Check confidence decay for all findings. Returns list of decay reports.""" + governance = _load_governance() + decay_config = governance.get("confidence_decay", {}) + data = _load_yaml(tracker_path) + if not data or "findings" not in data: + return [] + + results = [] + for f in data["findings"]: + fid = f.get("id", "???") + contributor = f.get("contributor", {}) + confidence = contributor.get("confidence", "validated") if isinstance(contributor, dict) else "validated" + finding_date = contributor.get("date", f.get("date")) if isinstance(contributor, dict) else f.get("date") + age = _days_ago(finding_date) + if age is None: + results.append({"id": fid, "status": "UNKNOWN", "confidence": confidence, "age_days": None}) + continue + + thresholds = decay_config.get(confidence, {"fresh": 180, "stale": 365, "expired": 730}) + if age <= thresholds.get("fresh", 180): + decay_status = "FRESH" + elif age <= thresholds.get("stale", 365): + decay_status = "STALE" + else: + decay_status = "EXPIRED" + + results.append({ + "id": fid, + "status": decay_status, + "confidence": confidence, + "age_days": age, + "summary": f.get("summary", ""), + }) + return results + + +# ============================================================================= +# Check: Tag validation +# ============================================================================= + +def check_tags(tracker_path: str = TRACKER_PATH) -> List[str]: + """Validate all tags against the taxonomy.""" + taxonomy = _load_taxonomy() + if not taxonomy: + return ["No taxonomy loaded — skipping tag validation."] + data = _load_yaml(tracker_path) + if not data or "findings" not in data: + return ["Cannot load tracker file."] + + issues = [] + for f in data["findings"]: + fid = f.get("id", "???") + for tag in f.get("tags", []): + if str(tag) not in taxonomy: + issues.append(f"{fid}: unknown tag '{tag}'") + return issues + + +# ============================================================================= +# Check: Domain owners +# ============================================================================= + +def check_owners() -> List[str]: + """Check if domain owners need to review their areas.""" + owners_data = _load_yaml(OWNERS_PATH) + if not owners_data or "domains" not in owners_data: + return ["No owners config found."] + + issues = [] + for domain in owners_data["domains"]: + area = domain.get("area", "") + owner = domain.get("owner", {}) + if owner.get("name") == "TBD": + issues.append(f"{area}: no owner assigned (TBD)") + return issues + + +# ============================================================================= +# Check: File freshness +# ============================================================================= + +def check_freshness() -> List[Dict[str, Any]]: + """Check freshness of all YAML files in kb/.""" + governance = _load_governance() + warning_days = governance.get("freshness", {}).get("warning_days", 180) + stale_days = governance.get("freshness", {}).get("stale_days", 365) + + kb_root = os.path.join(PROJECT_ROOT, "kb") + results = [] + + for dirpath, _dirnames, filenames in os.walk(kb_root): + for filename in filenames: + if not filename.endswith((".yaml", ".yml")): + continue + filepath = os.path.join(dirpath, filename) + rel_path = os.path.relpath(filepath, PROJECT_ROOT) + data = _load_yaml(filepath) + if not data: + continue + + last_verified = data.get("last_verified") or data.get("last_updated") + if last_verified: + age = _days_ago(last_verified) + if age is not None: + if age > stale_days: + status = "STALE" + elif age > warning_days: + status = "WARNING" + else: + status = "FRESH" + results.append({"file": rel_path, "status": status, "age_days": age}) + continue + + # Use file modification time as fallback + mtime = os.path.getmtime(filepath) + mdate = datetime.fromtimestamp(mtime).date() + age = (date.today() - mdate).days + if age > stale_days: + status = "STALE" + elif age > warning_days: + status = "WARNING" + else: + status = "FRESH" + results.append({"file": rel_path, "status": status, "age_days": age}) + return results + + +# ============================================================================= +# Check: Contribution stats +# ============================================================================= + +def contribution_stats(tracker_path: str = TRACKER_PATH) -> Dict[str, int]: + """Count contributions per person across findings.""" + data = _load_yaml(tracker_path) + if not data or "findings" not in data: + return {} + counts: Dict[str, int] = {} + for f in data["findings"]: + contributor = f.get("contributor", {}) + if isinstance(contributor, dict): + name = contributor.get("name", "Unknown") + else: + name = str(f.get("reported_by", "Unknown")) + counts[name] = counts.get(name, 0) + 1 + for c in f.get("confirmations", []): + cname = c.get("name", "Unknown") + counts[cname] = counts.get(cname, 0) + 1 + return counts + + +# ============================================================================= +# CLI +# ============================================================================= + +def cmd_lint(args: argparse.Namespace) -> None: + """Run all lint checks.""" + total_issues = 0 + + # Contributor blocks + print("Checking contributor blocks...") + contributor_issues = check_contributor_blocks() + if contributor_issues: + for issue in contributor_issues: + print(f" {_c('ISSUE', _YELLOW)}: {issue}") + total_issues += len(contributor_issues) + else: + print(f" {_c('OK', _GREEN)}: All findings have valid contributor blocks.") + + # Tags + if args.check_tags or not (args.show_decay or args.check_owners): + print("\nChecking tags against taxonomy...") + tag_issues = check_tags() + if tag_issues: + for issue in tag_issues: + print(f" {_c('ISSUE', _YELLOW)}: {issue}") + total_issues += len(tag_issues) + else: + print(f" {_c('OK', _GREEN)}: All tags are valid.") + + # Domain owners + if args.check_owners or not (args.show_decay or args.check_tags): + print("\nChecking domain owners...") + owner_issues = check_owners() + if owner_issues: + for issue in owner_issues: + print(f" {_c('ISSUE', _YELLOW)}: {issue}") + total_issues += len(owner_issues) + else: + print(f" {_c('OK', _GREEN)}: All domains have assigned owners.") + + # Freshness + if not (args.show_decay or args.check_tags or args.check_owners): + print("\nChecking file freshness...") + freshness = check_freshness() + stale = [f for f in freshness if f["status"] in ("STALE", "WARNING")] + if stale: + for f in stale: + status_color = _RED if f["status"] == "STALE" else _YELLOW + print(f" {_c(f['status'], status_color)}: {f['file']} ({f['age_days']} days old)") + total_issues += len(stale) + else: + print(f" {_c('OK', _GREEN)}: All files are fresh.") + + # Confidence decay + if args.show_decay or not (args.check_tags or args.check_owners): + print("\nConfidence decay status...") + decay = check_confidence_decay() + for d in decay: + if d["status"] == "FRESH": + status_display = _c("FRESH", _GREEN) + elif d["status"] == "STALE": + status_display = _c("STALE", _YELLOW) + total_issues += 1 + elif d["status"] == "EXPIRED": + status_display = _c("EXPIRED", _RED) + total_issues += 1 + else: + status_display = d["status"] + age_str = f"{d['age_days']} days ago" if d["age_days"] is not None else "unknown age" + summary = d.get("summary", "")[:50] + print(f" {d['id']} {status_display} {summary} ({d['confidence']}, {age_str})") + + # Contribution stats + if not (args.show_decay or args.check_tags or args.check_owners): + print("\nContribution stats:") + cstats = contribution_stats() + for name, count in sorted(cstats.items(), key=lambda x: -x[1]): + print(f" {name:<30} {count} contributions") + + print(f"\n{'=' * 40}") + if total_issues: + print(f"{_c(f'{total_issues} issue(s) found.', _YELLOW)}") + else: + print(f"{_c('All checks passed.', _GREEN)}") + + sys.exit(1 if total_issues > 0 else 0) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="kb_linter", + description="Validate KB files against governance rules.", + ) + parser.add_argument("--show-decay", action="store_true", help="Show confidence decay status.") + parser.add_argument("--check-tags", action="store_true", help="Validate tags only.") + parser.add_argument("--check-owners", action="store_true", help="Check domain owner assignments.") + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + cmd_lint(args) + + +if __name__ == "__main__": + main() diff --git a/tools/oci_bizcase_gen.py b/tools/oci_bizcase_gen.py new file mode 100644 index 0000000..216e41b --- /dev/null +++ b/tools/oci_bizcase_gen.py @@ -0,0 +1,713 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Business Case Deck Generator (.pptx) + +Produces an 8-10 slide business case deck using the Oracle FY26 official +PowerPoint template layouts. Target audience: customer CxO / decision-makers. + +Usage: + python oci_bizcase_gen.py --spec business-case.yaml --output business-case.pptx + +Or import and use programmatically: + from oci_bizcase_gen import BusinessCaseDeckGenerator + gen = BusinessCaseDeckGenerator.from_spec(spec) + gen.save("business-case.pptx") +""" + +import yaml +import argparse +from datetime import datetime +from pathlib import Path +from typing import Optional, List, Dict + +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN +from pptx.enum.shapes import MSO_SHAPE + +from oci_pptx_base import Colors, Layouts, OraclePresBase + + +# ============================================================ +# Business Case Deck Generator +# ============================================================ + +class BusinessCaseDeckGenerator(OraclePresBase): + """Generate business case decks using Oracle FY26 template layouts.""" + + # Use Teal accent for business case slides (finance / CxO audience) + TITLE_ACCENT_COLOR = Colors.TEAL + + # ================================================================ + # Slide Methods + # ================================================================ + + def add_cover_slide(self, customer: str, subtitle: str = "", + prepared_by: str = "", date: str = ""): + """Slide 1: Cover using Dark Title_Pillar layout.""" + slide = self._add_layout_slide(Layouts.COVER_DARK) + self._set_placeholder(slide, 0, customer) # Title + self._set_placeholder(slide, 33, subtitle or "Business Case for Oracle Cloud Infrastructure") + date_str = date or datetime.now().strftime("%B %Y") + self._set_placeholder(slide, 35, date_str) + if prepared_by: + self._set_placeholder(slide, 34, f"Prepared by: {prepared_by}") + + def add_executive_summary_slide(self, statement: str): + """Slide 2: Executive Summary — controlled typography on blank slide.""" + slide = self._add_blank_slide() + + self._add_title_bar(slide, "Executive Summary") + + # Body — large enough to read, small enough to fit the paragraph + body_box = slide.shapes.add_textbox(Inches(0.8), Inches(1.3), Inches(11.7), Inches(4.5)) + tf = body_box.text_frame + tf.word_wrap = True + p = tf.paragraphs[0] + p.text = statement + p.font.size = Pt(18) + p.font.name = self.FONT + p.font.color.rgb = Colors.PRIMARY_TEXT + p.space_after = Pt(6) + + def add_business_drivers_slide(self, drivers: list): + """Slide 3: Business Drivers — numbered cards on blank slide. + + drivers: list of up to 3 strings, each a driver statement (may include newline + separating a bold headline from detail text). + """ + slide = self._add_blank_slide() + + self._add_title_bar(slide, "Business Drivers") + + card_colors = [Colors.TEAL, Colors.BURNT_ORANGE, Colors.FOREST] + card_y_start = Inches(1.3) + available_h = Inches(5.8) + n = min(len(drivers), 3) + card_h = available_h / n if n else available_h + card_h = min(card_h, Inches(2.1)) + + for i, driver in enumerate(drivers[:3]): + color = card_colors[i % len(card_colors)] + y = card_y_start + i * card_h + + # Left accent bar + accent = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.8), y + Inches(0.1), Inches(0.08), card_h - Inches(0.2)) + accent.fill.solid() + accent.fill.fore_color.rgb = color + accent.line.fill.background() + + # Number badge + self._add_textbox( + slide, Inches(1.05), y + Inches(0.12), Inches(0.5), Inches(0.45), + text=f"0{i+1}", font_size=18, bold=True, color=color, + ) + + # Split driver into headline + detail (split on \n if present) + parts = driver.split("\n", 1) + headline = parts[0].strip() + detail = parts[1].strip() if len(parts) > 1 else "" + + # Headline + self._add_textbox( + slide, Inches(1.6), y + Inches(0.1), Inches(11.0), Inches(0.5), + text=headline, font_size=15, bold=True, color=Colors.PRIMARY_TEXT, + ) + # Detail + if detail: + self._add_textbox( + slide, Inches(1.6), y + Inches(0.6), Inches(11.0), card_h - Inches(0.75), + text=detail, font_size=13, color=Colors.SECONDARY_TEXT, + ) + + def add_tco_slide(self, tco: dict): + """Slide 4: TCO Comparison — table on blank slide. + + tco: dict from business-case.yaml with current_state and proposed_oci. + """ + slide = self._add_blank_slide() + + self._add_title_bar(slide, "Total Cost of Ownership") + + # Subtitle + horizon = tco.get("horizon_years", 3) + self._add_textbox( + slide, Inches(0.8), Inches(1.1), Inches(11), Inches(0.4), + text=f"{horizon}-Year Comparison | Current State vs Oracle Cloud Infrastructure", + font_size=13, color=Colors.TEAL, bold=True, + ) + + current = tco.get("current_state", {}) + proposed = tco.get("proposed_oci", {}) + savings = tco.get("savings", {}) + + rows_data = [ + ("Infrastructure", current.get("annual_infrastructure", 0), proposed.get("annual_cloud_consumption", 0)), + ("Licensing / Support", current.get("annual_licensing", 0), proposed.get("annual_licensing", 0)), + ("Operations (People)", current.get("annual_operations", 0), proposed.get("annual_operations", 0)), + ("Downtime Cost", current.get("annual_downtime_cost", 0), proposed.get("annual_downtime_cost", 0)), + ("Compliance", current.get("annual_compliance_cost", 0), proposed.get("annual_compliance_cost", 0)), + ] + # Filter out zero rows + rows_data = [(n, c, o) for n, c, o in rows_data if c or o] + + # Add migration one-time + migration = proposed.get("migration_one_time", 0) + + num_rows = len(rows_data) + 3 # header + rows + annual total + horizon total + table = self._add_table( + slide, num_rows, 4, + Inches(0.8), Inches(1.6), + Inches(11.7), Inches(0.42 * num_rows), + ) + table.columns[0].width = Inches(3.5) + table.columns[1].width = Inches(2.7) + table.columns[2].width = Inches(2.7) + table.columns[3].width = Inches(2.8) + + # Header + for j, h in enumerate(["Cost Category", "Current (Annual)", "OCI (Annual)", "Savings"]): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER if j > 0 else PP_ALIGN.LEFT, + ) + + # Data rows + for i, (name, curr, oci) in enumerate(rows_data): + row = i + 1 + bg = Colors.TABLE_ALT_ROW if row % 2 == 0 else None + curr_val = curr if isinstance(curr, (int, float)) else 0 + oci_val = oci if isinstance(oci, (int, float)) else 0 + save_val = curr_val - oci_val + self._style_table_cell(table.cell(row, 0), name, font_size=10, bg_color=bg) + self._style_table_cell(table.cell(row, 1), f"${curr_val:,.0f}", font_size=10, bg_color=bg, alignment=PP_ALIGN.RIGHT) + self._style_table_cell(table.cell(row, 2), f"${oci_val:,.0f}", font_size=10, bg_color=bg, alignment=PP_ALIGN.RIGHT) + save_color = Colors.SUCCESS if save_val > 0 else Colors.ERROR + self._style_table_cell(table.cell(row, 3), f"${save_val:,.0f}", font_size=10, bold=True, color=save_color, bg_color=bg, alignment=PP_ALIGN.RIGHT) + + # Annual total row + total_row = len(rows_data) + 1 + ann_current = current.get("total_annual", 0) or sum(r[1] for r in rows_data if isinstance(r[1], (int, float))) + ann_oci = proposed.get("total_annual", 0) or sum(r[2] for r in rows_data if isinstance(r[2], (int, float))) + ann_savings = savings.get("annual", 0) or (ann_current - ann_oci) + self._style_table_cell(table.cell(total_row, 0), "TOTAL ANNUAL", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.TEAL) + self._style_table_cell(table.cell(total_row, 1), f"${ann_current:,.0f}", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.TEAL, alignment=PP_ALIGN.RIGHT) + self._style_table_cell(table.cell(total_row, 2), f"${ann_oci:,.0f}", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.TEAL, alignment=PP_ALIGN.RIGHT) + self._style_table_cell(table.cell(total_row, 3), f"${ann_savings:,.0f}", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.TEAL, alignment=PP_ALIGN.RIGHT) + + # Horizon total row + h_row = total_row + 1 + h_current = current.get("total_over_horizon", 0) or (ann_current * horizon) + h_oci = proposed.get("total_over_horizon", 0) or (ann_oci * horizon + migration) + h_savings = savings.get("over_horizon", 0) or (h_current - h_oci) + pct = savings.get("percentage", 0) or (h_savings / h_current * 100 if h_current else 0) + self._style_table_cell(table.cell(h_row, 0), f"TOTAL {horizon}-YEAR", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.DARK_BG) + self._style_table_cell(table.cell(h_row, 1), f"${h_current:,.0f}", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.DARK_BG, alignment=PP_ALIGN.RIGHT) + self._style_table_cell(table.cell(h_row, 2), f"${h_oci:,.0f}", font_size=11, bold=True, color=Colors.WHITE, bg_color=Colors.DARK_BG, alignment=PP_ALIGN.RIGHT) + self._style_table_cell(table.cell(h_row, 3), f"${h_savings:,.0f} ({pct:.0f}%)", font_size=11, bold=True, color=Colors.GOLD, bg_color=Colors.DARK_BG, alignment=PP_ALIGN.RIGHT) + + # Migration note + if migration: + note_y = Inches(1.6) + Inches(0.42 * num_rows) + Inches(0.15) + self._add_textbox( + slide, Inches(0.8), note_y, Inches(11), Inches(0.3), + text=f"* Includes one-time migration investment of ${migration:,.0f} in Year 1", + font_size=9, italic=True, color=Colors.SECONDARY_TEXT, + ) + + # Assumptions + assumptions = tco.get("assumptions", []) + if assumptions: + a_y = Inches(1.6) + Inches(0.42 * num_rows) + Inches(0.45) + self._add_textbox( + slide, Inches(0.8), a_y, Inches(11), Inches(0.25), + text="Assumptions:", font_size=9, bold=True, color=Colors.SECONDARY_TEXT, + ) + for idx, a in enumerate(assumptions[:4]): + self._add_textbox( + slide, Inches(1.0), a_y + Inches(0.25 + idx * 0.22), + Inches(11), Inches(0.22), + text=f"• {a}", font_size=8, italic=True, color=Colors.SECONDARY_TEXT, + ) + + def add_roi_slide(self, roi: dict, headline: str = ""): + """Slide 5: ROI — centered big number + 3 supporting metrics.""" + slide = self._add_blank_slide() + + self._add_title_bar(slide, "Return on Investment") + + # Big centered ROI number + pct = roi.get("three_year_roi_pct", 0) + metric_text = headline or (f"{pct:.0f}%" if pct else "—") + big_box = slide.shapes.add_textbox(Inches(0.8), Inches(1.4), Inches(11.7), Inches(2.4)) + big_box.text_frame.word_wrap = False + p = big_box.text_frame.paragraphs[0] + p.text = metric_text + p.font.size = Pt(96) + p.font.bold = True + p.font.name = self.FONT_HEADING + p.font.color.rgb = Colors.TEAL + p.alignment = PP_ALIGN.CENTER + + # Label under big number + self._add_textbox( + slide, Inches(0.8), Inches(3.75), Inches(11.7), Inches(0.4), + text="3-Year Return on Investment", font_size=16, + color=Colors.SECONDARY_TEXT, alignment=PP_ALIGN.CENTER, + ) + + # 3 supporting metric boxes + payback = roi.get("payback_months", 0) + investment = roi.get("total_investment", 0) + benefit = roi.get("annual_net_benefit", 0) + metrics = [] + if payback: + metrics.append((f"{payback} months", "Payback Period")) + if investment: + metrics.append((f"${investment:,.0f}", "Total Investment")) + if benefit: + metrics.append((f"${benefit:,.0f}/yr", "Annual Net Benefit")) + + box_w = Inches(3.5) + gap = Inches(0.45) + total_w = box_w * len(metrics) + gap * (len(metrics) - 1) + x_start = (Inches(13.33) - total_w) / 2 + y_box = Inches(4.4) + + for j, (value, label) in enumerate(metrics): + x = x_start + j * (box_w + gap) + # Background + bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y_box, box_w, Inches(1.4)) + bg.fill.solid() + bg.fill.fore_color.rgb = Colors.TABLE_ALT_ROW + bg.line.color.rgb = Colors.MUTED_TEAL + + # Value + self._add_textbox( + slide, x, y_box + Inches(0.15), box_w, Inches(0.7), + text=value, font_size=26, bold=True, color=Colors.TEAL, + alignment=PP_ALIGN.CENTER, + ) + # Label + self._add_textbox( + slide, x, y_box + Inches(0.85), box_w, Inches(0.45), + text=label, font_size=11, color=Colors.SECONDARY_TEXT, + alignment=PP_ALIGN.CENTER, + ) + + def add_value_drivers_slide(self, drivers: list): + """Slide 6: Value Drivers — 4 categories on blank slide. + + drivers: list of {"category": str, "title": str, "description": str, "quantified": str} + """ + slide = self._add_blank_slide() + + self._add_title_bar(slide, "Value Drivers") + + # 4 value cards in a 2x2 grid + card_colors = [Colors.TEAL, Colors.FOREST, Colors.BURNT_ORANGE, Colors.ORACLE_RED] + positions = [ + (Inches(0.8), Inches(1.5)), # top-left + (Inches(6.8), Inches(1.5)), # top-right + (Inches(0.8), Inches(4.3)), # bottom-left + (Inches(6.8), Inches(4.3)), # bottom-right + ] + card_w, card_h = Inches(5.5), Inches(2.5) + + for i, driver in enumerate(drivers[:4]): + x, y = positions[i] + color = card_colors[i % len(card_colors)] + + # Color accent bar + bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, Inches(0.08), card_h) + bar.fill.solid() + bar.fill.fore_color.rgb = color + bar.line.fill.background() + + # Title + title = driver.get("title", driver.get("category", "").replace("_", " ").title()) + self._add_textbox( + slide, x + Inches(0.25), y + Inches(0.1), + card_w - Inches(0.3), Inches(0.4), + text=title, font_size=16, bold=True, color=color, + ) + + # Quantified value (big) + quantified = driver.get("quantified", "") + if quantified: + self._add_textbox( + slide, x + Inches(0.25), y + Inches(0.55), + card_w - Inches(0.3), Inches(0.5), + text=quantified, font_size=22, bold=True, color=Colors.PRIMARY_TEXT, + ) + + # Description + desc = driver.get("description", "") + if desc: + self._add_textbox( + slide, x + Inches(0.25), y + Inches(1.2), + card_w - Inches(0.3), Inches(1.0), + text=desc, font_size=11, color=Colors.SECONDARY_TEXT, + ) + + def add_risk_slide(self, migration_risks: list, do_nothing_risks: list): + """Slide 7: Risk Assessment — two-column table layout.""" + slide = self._add_blank_slide() + + self._add_title_bar(slide, "Risk Assessment") + + col_w = Inches(5.6) + col_gap = Inches(0.5) + left_x = Inches(0.8) + right_x = left_x + col_w + col_gap + header_y = Inches(1.2) + content_start_y = Inches(1.75) + row_h = Inches(0.8) # height per risk block (title + detail) + slide_h = Inches(7.1) + + # Stretch rows to fill available slide height + n_rows = max(len(migration_risks[:5]), len(do_nothing_risks[:5])) + if n_rows > 0: + available = slide_h - content_start_y + row_h = min(Inches(1.8), available / n_rows) + + # Column header backgrounds + for x, color, label in [ + (left_x, Colors.TEAL, "✓ Migration Risks (Mitigated)"), + (right_x, Colors.ERROR, "⚠ Risks of Inaction"), + ]: + bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, header_y, col_w, Inches(0.45)) + bg.fill.solid() + bg.fill.fore_color.rgb = color + bg.line.fill.background() + self._add_textbox( + slide, x + Inches(0.15), header_y + Inches(0.05), col_w - Inches(0.2), Inches(0.38), + text=label, font_size=13, bold=True, color=Colors.WHITE, + ) + + # Left column: migration risks + for i, risk in enumerate(migration_risks[:5]): + r_text = risk.get("risk", str(risk)) if isinstance(risk, dict) else str(risk) + mitigation = risk.get("mitigation", "") if isinstance(risk, dict) else "" + y = content_start_y + i * row_h + + # Alternating row background + if i % 2 == 0: + row_bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left_x, y, col_w, row_h) + row_bg.fill.solid() + row_bg.fill.fore_color.rgb = Colors.TABLE_ALT_ROW + row_bg.line.fill.background() + + self._add_textbox( + slide, left_x + Inches(0.15), y + Inches(0.07), col_w - Inches(0.25), Inches(0.4), + text=f"• {r_text}", font_size=12, bold=True, color=Colors.PRIMARY_TEXT, + ) + if mitigation: + self._add_textbox( + slide, left_x + Inches(0.25), y + Inches(0.45), col_w - Inches(0.35), row_h - Inches(0.48), + text=f"Mitigation: {mitigation}", font_size=11, + italic=True, color=Colors.SECONDARY_TEXT, + ) + + # Right column: do-nothing risks + for i, risk in enumerate(do_nothing_risks[:5]): + r_text = risk.get("risk", str(risk)) if isinstance(risk, dict) else str(risk) + impact = risk.get("impact", "") if isinstance(risk, dict) else "" + timeline = risk.get("timeline", "") if isinstance(risk, dict) else "" + y = content_start_y + i * row_h + + if i % 2 == 0: + row_bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, right_x, y, col_w, row_h) + row_bg.fill.solid() + row_bg.fill.fore_color.rgb = RGBColor(0xFF, 0xF3, 0xF3) + row_bg.line.fill.background() + + self._add_textbox( + slide, right_x + Inches(0.15), y + Inches(0.07), col_w - Inches(0.25), Inches(0.4), + text=f"• {r_text}", font_size=12, bold=True, color=Colors.ERROR, + ) + detail_parts = [] + if impact: + detail_parts.append(f"Impact: {impact}") + if timeline: + detail_parts.append(f"By: {timeline}") + if detail_parts: + self._add_textbox( + slide, right_x + Inches(0.25), y + Inches(0.45), col_w - Inches(0.35), row_h - Inches(0.48), + text=" | ".join(detail_parts), font_size=10, + italic=True, color=Colors.SECONDARY_TEXT, + ) + + def add_roadmap_slide(self, phases: list, total_duration: str = ""): + """Slide 8: Implementation Roadmap — timeline bars filling the slide.""" + slide = self._add_blank_slide() + + title = "Implementation Roadmap" + if total_duration: + title += f" — {total_duration}" + self._add_title_bar(slide, title) + + phase_colors = [Colors.TEAL, Colors.BURNT_ORANGE, Colors.FOREST, Colors.ORACLE_RED] + content_start = Inches(1.25) + available_h = Inches(5.9) + n = min(len(phases), 4) + row_h = available_h / n if n else available_h + + bar_left = Inches(3.6) + bar_w = Inches(9.1) + label_w = Inches(2.6) + + for i, phase in enumerate(phases[:4]): + color = phase_colors[i % len(phase_colors)] + y = content_start + i * row_h + name = phase.get("name", f"Phase {i+1}") + duration = phase.get("duration", "") + deliverables = phase.get("deliverables", []) + quick_wins = phase.get("quick_wins", []) + + # Alternating row background + if i % 2 == 0: + bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.8), y, Inches(12.0), row_h - Inches(0.08)) + bg.fill.solid() + bg.fill.fore_color.rgb = Colors.TABLE_ALT_ROW + bg.line.fill.background() + + # Phase number circle accent + dot = slide.shapes.add_shape(MSO_SHAPE.OVAL, Inches(0.82), y + (row_h - Inches(0.38)) / 2, Inches(0.38), Inches(0.38)) + dot.fill.solid() + dot.fill.fore_color.rgb = color + dot.line.fill.background() + dot.text_frame.paragraphs[0].text = str(i + 1) + dot.text_frame.paragraphs[0].font.size = Pt(11) + dot.text_frame.paragraphs[0].font.bold = True + dot.text_frame.paragraphs[0].font.color.rgb = Colors.WHITE + dot.text_frame.paragraphs[0].font.name = self.FONT + dot.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # Phase name + self._add_textbox( + slide, Inches(1.35), y + Inches(0.08), label_w, Inches(0.45), + text=name, font_size=14, bold=True, color=color, + ) + + # Duration bar + bar_h = Inches(0.42) + bar_y = y + (row_h - bar_h) / 2 + bar = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, bar_left, bar_y, bar_w * 0.55, bar_h) + bar.fill.solid() + bar.fill.fore_color.rgb = color + bar.line.fill.background() + bar.text_frame.paragraphs[0].text = duration + bar.text_frame.paragraphs[0].font.size = Pt(12) + bar.text_frame.paragraphs[0].font.bold = True + bar.text_frame.paragraphs[0].font.color.rgb = Colors.WHITE + bar.text_frame.paragraphs[0].font.name = self.FONT + bar.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # Deliverables — listed to the right of the bar + items = quick_wins + deliverables + if items: + items_text = " • ".join(items[:3]) + self._add_textbox( + slide, bar_left + bar_w * 0.57, bar_y - Inches(0.02), + bar_w * 0.43, bar_h + Inches(0.05), + text=items_text, font_size=11, color=Colors.SECONDARY_TEXT, + ) + + def add_recommendation_slide(self, summary: str, next_steps: list = None): + """Slide 9: Recommendation — dark blank slide with controlled fonts.""" + slide = self._add_blank_slide(dark=True) + + # "Recommendation" label in gold + self._add_textbox( + slide, Inches(0.8), Inches(0.45), Inches(11.7), Inches(0.5), + text="Our Recommendation", font_size=13, bold=True, + color=Colors.GOLD, font_name=self.FONT, + ) + + # Gold accent bar + acc = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.8), Inches(1.0), Inches(11.7), Inches(0.05)) + acc.fill.solid() + acc.fill.fore_color.rgb = Colors.GOLD + acc.line.fill.background() + + # Main recommendation statement — 20pt, white, readable + rec_box = slide.shapes.add_textbox(Inches(0.8), Inches(1.2), Inches(11.7), Inches(2.2)) + tf = rec_box.text_frame + tf.word_wrap = True + p = tf.paragraphs[0] + p.text = summary + p.font.size = Pt(20) + p.font.bold = True + p.font.name = self.FONT_HEADING + p.font.color.rgb = Colors.WHITE + + # Next steps section + if next_steps: + self._add_textbox( + slide, Inches(0.8), Inches(3.6), Inches(4.0), Inches(0.4), + text="NEXT STEPS", font_size=11, bold=True, + color=Colors.GOLD, + ) + # Divider + div = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.8), Inches(4.05), Inches(11.7), Inches(0.03)) + div.fill.solid() + div.fill.fore_color.rgb = RGBColor(0x55, 0x50, 0x4C) + div.line.fill.background() + + y = Inches(4.15) + for i, step in enumerate(next_steps[:4]): + action = step.get("action", str(step)) if isinstance(step, dict) else str(step) + owner = step.get("owner", "") if isinstance(step, dict) else "" + deadline = step.get("deadline", "") if isinstance(step, dict) else "" + + # Number + self._add_textbox( + slide, Inches(0.8), y, Inches(0.4), Inches(0.5), + text=str(i + 1), font_size=13, bold=True, color=Colors.GOLD, + ) + # Action text + action_text = action + meta_parts = [] + if owner: + meta_parts.append(owner) + if deadline: + meta_parts.append(deadline) + meta = f" [{' · '.join(meta_parts)}]" if meta_parts else "" + self._add_textbox( + slide, Inches(1.25), y, Inches(11.2), Inches(0.5), + text=action_text + meta, font_size=13, color=Colors.WHITE, + ) + y += Inches(0.6) + + # ================================================================ + # Build from YAML spec + # ================================================================ + + @classmethod + def from_spec(cls, spec: dict, template: Optional[str] = None) -> "BusinessCaseDeckGenerator": + """Build a complete business case deck from a YAML specification.""" + bc = spec.get("business_case", spec) # Support both wrapped and unwrapped + gen = cls(template=template) + + # Slide 1: Cover + gen.add_cover_slide( + customer=bc.get("customer_name", ""), + subtitle="Business Case for Oracle Cloud Infrastructure", + prepared_by=bc.get("prepared_by", ""), + date=bc.get("date", ""), + ) + + # Slide 2: Executive Summary + exec_summary = bc.get("executive_summary", "") + if exec_summary: + gen.add_executive_summary_slide(exec_summary) + + # Slide 3: Business Drivers + drivers_data = bc.get("drivers", {}) + if drivers_data: + driver_statements = [] + primary = drivers_data.get("primary", "") + urgency = drivers_data.get("urgency", "") + coi = drivers_data.get("cost_of_inaction", {}) + + if primary: + driver_statements.append(f"Primary Driver: {primary.replace('_', ' ').title()}\n{urgency}" if urgency else primary.replace('_', ' ').title()) + if coi.get("financial"): + driver_statements.append(f"Financial Impact of Inaction\n{coi['financial']}") + if coi.get("operational"): + driver_statements.append(f"Operational Impact\n{coi['operational']}") + elif coi.get("strategic"): + driver_statements.append(f"Strategic Risk\n{coi['strategic']}") + + if driver_statements: + gen.add_business_drivers_slide(driver_statements) + + # Slide 4: TCO Comparison + tco = bc.get("tco", {}) + if tco and (tco.get("current_state") or tco.get("proposed_oci")): + gen.add_tco_slide(tco) + + # Slide 5: ROI + roi = bc.get("roi", {}) + if roi and any(roi.get(k) for k in ["three_year_roi_pct", "payback_months", "total_investment"]): + gen.add_roi_slide(roi) + + # Slide 6: Value Drivers + value_drivers = bc.get("value_drivers", []) + if value_drivers: + gen.add_value_drivers_slide(value_drivers) + + # Slide 7: Risk Assessment + risks = bc.get("risks", {}) + migration_risks = risks.get("migration_risks", []) + do_nothing_risks = risks.get("do_nothing_risks", []) + if migration_risks or do_nothing_risks: + gen.add_risk_slide(migration_risks, do_nothing_risks) + + # Slide 8: Roadmap + roadmap = bc.get("roadmap", {}) + if roadmap.get("phases"): + gen.add_roadmap_slide( + phases=roadmap["phases"], + total_duration=roadmap.get("total_duration", ""), + ) + + # Slide 9: Recommendation + rec = bc.get("recommendation", {}) + if rec: + summary = rec.get("summary", "") + if not summary and rec.get("commitment_amount"): + summary = f"Recommended: {rec['commitment_amount']} {rec.get('commitment_type', 'UCM')} commitment" + if summary: + gen.add_recommendation_slide( + summary=summary, + next_steps=rec.get("next_steps", []), + ) + + # Closing slides + prepared_by = bc.get("prepared_by", "") + gen.add_closing_slide(name=prepared_by) + + return gen + + +# ============================================================ +# CLI +# ============================================================ + +def main(): + parser = argparse.ArgumentParser( + description="Generate OCI business case slide deck (.pptx)" + ) + parser.add_argument( + "--spec", required=True, + help="Path to YAML business case spec file", + ) + parser.add_argument( + "--output", default="business-case.pptx", + help="Output .pptx file path", + ) + parser.add_argument( + "--template", + help="Path to Oracle FY26 .pptx template (default: templates/Oracle_PPT-template_FY26.pptx)", + ) + args = parser.parse_args() + + with open(args.spec, 'r') as f: + spec = yaml.safe_load(f) + + gen = BusinessCaseDeckGenerator.from_spec(spec, template=args.template) + gen.save(args.output) + + print(f"Generated: {args.output}") + print(f" Slides: {gen.slide_count}") + print(f" Template: Oracle FY26") + customer = spec.get("business_case", spec).get("customer_name", "") + if customer: + print(f" Customer: {customer}") + + +if __name__ == "__main__": + main() diff --git a/tools/oci_deck_gen.py b/tools/oci_deck_gen.py new file mode 100644 index 0000000..72f220c --- /dev/null +++ b/tools/oci_deck_gen.py @@ -0,0 +1,1301 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Slide Deck Generator (.pptx) + +Produces a 10-12 slide architecture proposal deck using the Oracle FY26 official +PowerPoint template and the Oracle Redwood design system colors and typography. + +Usage: + python oci_deck_gen.py --spec proposal-data.yaml --output proposal.pptx + +Or import and use programmatically: + from oci_deck_gen import OCIDeckGenerator + gen = OCIDeckGenerator(customer="Acme Corp", project="DB Migration") + gen.add_summary_slide(...) + gen.save("proposal.pptx") +""" + +import yaml +import argparse +from datetime import datetime +from pathlib import Path +from typing import Optional + +from pptx.util import Inches, Pt, Emu +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE + +from oci_pptx_base import Colors, Layouts, OraclePresBase + + +# ============================================================ +# Slide Generator +# ============================================================ + +class OCIDeckGenerator(OraclePresBase): + """Generate Oracle Redwood-styled architecture proposal slide decks.""" + + SLIDE_WIDTH = Inches(13.333) # Widescreen 16:9 + SLIDE_HEIGHT = Inches(7.5) + MARGIN = Inches(0.5) + + # Use Oracle Red accent for proposal deck (differentiates from bizcase teal) + TITLE_ACCENT_COLOR = Colors.ORACLE_RED + + def __init__(self, customer: str = "", project: str = "", + architect: str = "", firm: str = "", + template: Optional[str] = None): + super().__init__(template) + self.customer = customer + self.project = project + self.architect = architect + self.firm = firm + + # ---- Architecture visual helpers ---- + + def _add_service_block(self, slide, left, top, width, height, + label, fill_color): + """Add a colored service block with white text label.""" + shape = self._add_rect( + slide, left, top, width, height, fill_color=fill_color, + ) + shape.text_frame.word_wrap = True + p = shape.text_frame.paragraphs[0] + p.text = label + p.font.size = Pt(8) + p.font.bold = True + p.font.color.rgb = Colors.WHITE + p.font.name = self.FONT + p.alignment = PP_ALIGN.CENTER + shape.text_frame.paragraphs[0].space_before = Pt(0) + shape.text_frame.paragraphs[0].space_after = Pt(0) + return shape + + def _add_container(self, slide, left, top, width, height, + label, border_color, fill_color=None, + dashed=True, label_size=10): + """Add a container outline (region, VCN, subnet).""" + shape = self._add_rect( + slide, left, top, width, height, + fill_color=fill_color, border_color=border_color, + ) + if not fill_color: + shape.fill.background() + shape.line.color.rgb = border_color + shape.line.width = Pt(1.5) + if dashed: + shape.line.dash_style = 2 # MSO_LINE_DASH_STYLE.DASH + # Label at top-left inside container + self._add_textbox( + slide, left + Inches(0.1), top + Inches(0.05), + width - Inches(0.2), Inches(0.3), + text=label, font_size=label_size, bold=True, + color=border_color, + ) + return shape + + def _add_arrow(self, slide, start_x, start_y, end_x, end_y, + color=None, dashed=False, label=""): + """Add a connector arrow between two points.""" + connector = slide.shapes.add_connector( + 1, # MSO_CONNECTOR.STRAIGHT + start_x, start_y, end_x, end_y, + ) + connector.line.color.rgb = color or RGBColor(0x70, 0x6E, 0x6F) + connector.line.width = Pt(1.5) + if dashed: + connector.line.dash_style = 2 + return connector + + # ---- Slide Methods ---- + + def add_title_slide(self, subtitle: str = ""): + """Slide 1: Title slide using FY26 Dark Cover layout.""" + slide = self._add_layout_slide(Layouts.COVER_DARK) + + customer = self.customer or "Architecture Proposal" + date_str = datetime.now().strftime("%B %Y") + sub = subtitle or f"Architecture Proposal — {date_str}" + + self._set_placeholder(slide, 0, customer) + self._set_placeholder(slide, 33, sub) + self._set_placeholder(slide, 35, date_str) + if self.architect or self.firm: + info = self.architect + if self.firm: + info += f" | {self.firm}" if self.architect else self.firm + self._set_placeholder(slide, 34, info) + + def add_summary_slide(self, why: str, current_state: list, + target_state: str, timeline: str): + """Slide 2: Engagement Summary.""" + slide = self._add_blank_slide() + self._add_title_bar(slide, "Engagement Summary", margin=self.MARGIN) + + y = Inches(1.2) + + # Why — teal for emphasis + self._add_textbox( + slide, self.MARGIN, y, Inches(11), Inches(0.5), + text=why, font_size=14, color=Colors.TEAL, bold=True, + font_name=self.FONT_HEADING, + ) + y += Inches(0.7) + + # Current state + self._add_textbox( + slide, self.MARGIN, y, Inches(5), Inches(0.4), + text="Current State", font_size=14, bold=True, + ) + y += Inches(0.45) + + for item in current_state: + self._add_textbox( + slide, Inches(0.7), y, Inches(10), Inches(0.35), + text=f"• {item}", font_size=12, + ) + y += Inches(0.35) + + y += Inches(0.3) + + # Target state + self._add_textbox( + slide, self.MARGIN, y, Inches(5), Inches(0.4), + text="Target State", font_size=14, bold=True, + ) + y += Inches(0.45) + self._add_textbox( + slide, Inches(0.7), y, Inches(10), Inches(0.4), + text=target_state, font_size=12, color=Colors.TEAL, + ) + y += Inches(0.5) + + # Timeline + self._add_textbox( + slide, self.MARGIN, y, Inches(10), Inches(0.4), + text=f"Timeline: {timeline}", font_size=12, bold=True, + ) + + def add_architecture_slide(self, diagram_path: Optional[str] = None, + description: str = "", + visual: Optional[dict] = None): + """Slide 3: Architecture Overview with diagram or visual layout. + + visual: optional dict with structured architecture data for rendering + as colored blocks. Keys: regions, on_prem, security_footer. + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Architecture Overview", margin=self.MARGIN) + + if diagram_path: + try: + slide.shapes.add_picture( + diagram_path, + Inches(0.5), Inches(1.1), + Inches(12.3), Inches(6), + ) + except Exception: + self._add_textbox( + slide, Inches(1), Inches(2.5), + Inches(10), Inches(2), + text=f"[Diagram: {diagram_path}]\n\n{description}", + font_size=14, color=Colors.SECONDARY_TEXT, + ) + elif visual: + self._render_architecture_visual(slide, visual) + else: + self._add_textbox( + slide, Inches(1), Inches(2), + Inches(11), Inches(4), + text=description or "[Insert architecture diagram — export from .drawio file]", + font_size=14, color=Colors.SECONDARY_TEXT, italic=True, + alignment=PP_ALIGN.CENTER, + ) + + def _render_architecture_visual(self, slide, visual: dict): + """Render a visual architecture diagram from structured data.""" + regions = visual.get("regions", []) + on_prem = visual.get("on_prem") + security = visual.get("security_footer", "") + + # Category colors for service blocks + cat_colors = { + "infrastructure": Colors.TEAL, + "database": Colors.COPPER, + "integration": Colors.PURPLE, + "security": Colors.FOREST, + "dormant": RGBColor(0xDF, 0xDC, 0xD8), + } + + y_cursor = Inches(1.15) + + for reg_idx, region in enumerate(regions): + is_primary = region.get("primary", reg_idx == 0) + region_name = region.get("name", f"Region {reg_idx+1}") + region_label = region.get("label", "PRIMARY" if is_primary else "DR STANDBY") + + # Region dimensions — primary gets more space + if is_primary: + reg_left = Inches(0.4) + reg_width = Inches(8.2) + reg_height = Inches(4.8) + reg_top = y_cursor + else: + reg_left = Inches(8.9) + reg_width = Inches(4.1) + reg_height = Inches(4.8) + reg_top = y_cursor + + # Region container — solid border, light fill + self._add_container( + slide, reg_left, reg_top, reg_width, reg_height, + label=f"{region_name} [{region_label}]", + border_color=RGBColor(0x9E, 0x98, 0x92), + fill_color=RGBColor(0xF5, 0xF4, 0xF2), + dashed=False, label_size=10, + ) + + inner_left = reg_left + Inches(0.15) + inner_top = reg_top + Inches(0.45) + inner_width = reg_width - Inches(0.3) + + # VCN container if present + vcn = region.get("vcn") + if vcn: + vcn_name = vcn.get("name", "VCN") + vcn_cidr = vcn.get("cidr", "") + vcn_label = f"{vcn_name} {vcn_cidr}" if vcn_cidr else vcn_name + vcn_height = reg_height - Inches(0.6) + + self._add_container( + slide, inner_left, inner_top, inner_width, vcn_height, + label=vcn_label, + border_color=Colors.BURNT_ORANGE, + dashed=True, label_size=9, + ) + + # Render subnets + subnets = vcn.get("subnets", []) + sub_top = inner_top + Inches(0.4) + sub_left = inner_left + Inches(0.15) + sub_width = inner_width - Inches(0.3) + + for sub_idx, subnet in enumerate(subnets): + sub_name = subnet.get("name", f"Subnet {sub_idx+1}") + sub_border = Colors.BURNT_ORANGE + sub_h = Inches(0.95) if is_primary else Inches(0.7) + + self._add_container( + slide, sub_left, sub_top, sub_width, sub_h, + label=sub_name, + border_color=sub_border, + fill_color=Colors.WARM_BG, + dashed=True, label_size=8, + ) + + # Service blocks inside subnet + services = subnet.get("services", []) + svc_left = sub_left + Inches(0.1) + svc_top = sub_top + Inches(0.3) + svc_spacing = Inches(0.08) + # Calculate service block width to fit + if services: + avail_width = sub_width - Inches(0.2) + svc_w = min( + Inches(2.2), + (avail_width - svc_spacing * (len(services) - 1)) / len(services) + ) + svc_h = Inches(0.5) + for svc in services: + svc_name = svc.get("name", "Service") + svc_cat = svc.get("category", "infrastructure") + svc_color = cat_colors.get(svc_cat, Colors.TEAL) + self._add_service_block( + slide, svc_left, svc_top, svc_w, svc_h, + label=svc_name, fill_color=svc_color, + ) + svc_left += svc_w + svc_spacing + + sub_top += sub_h + Inches(0.1) + + # Gateways bar at bottom of VCN + gateways = vcn.get("gateways", []) + if gateways: + gw_top = inner_top + vcn_height - Inches(0.55) + gw_left = inner_left + Inches(0.15) + gw_text = " | ".join(gateways) + self._add_textbox( + slide, gw_left, gw_top, + inner_width - Inches(0.3), Inches(0.35), + text=gw_text, font_size=8, bold=True, + color=Colors.TEAL, + alignment=PP_ALIGN.CENTER, + ) + + else: + # No VCN — render services directly (e.g. DR standby) + services = region.get("services", []) + svc_top = inner_top + Inches(0.3) + for svc in services: + svc_name = svc.get("name", "Service") + svc_cat = svc.get("category", "database") + svc_color = cat_colors.get(svc_cat, Colors.TEAL) + self._add_service_block( + slide, inner_left + Inches(0.3), svc_top, + inner_width - Inches(0.6), Inches(0.6), + label=svc_name, fill_color=svc_color, + ) + svc_top += Inches(0.75) + + # DR details text + dr_details = region.get("details", "") + if dr_details: + self._add_textbox( + slide, inner_left + Inches(0.1), svc_top + Inches(0.1), + inner_width - Inches(0.2), Inches(0.6), + text=dr_details, font_size=8, italic=True, + color=Colors.SECONDARY_TEXT, + alignment=PP_ALIGN.CENTER, + ) + + # On-prem block + if on_prem: + op_left = Inches(0.4) + op_top = Inches(6.15) + op_width = Inches(3.5) + self._add_rect( + slide, op_left, op_top, op_width, Inches(0.45), + fill_color=RGBColor(0x70, 0x66, 0x5E), + ) + op_shape = slide.shapes[-1] + p = op_shape.text_frame.paragraphs[0] + p.text = on_prem.get("name", "On-Premises") + p.font.size = Pt(9) + p.font.bold = True + p.font.color.rgb = Colors.WHITE + p.font.name = self.FONT + p.alignment = PP_ALIGN.CENTER + + # Connection label + conn_label = on_prem.get("connection", "IPSec VPN") + self._add_textbox( + slide, op_left + op_width + Inches(0.15), op_top, + Inches(3), Inches(0.45), + text=f"--- {conn_label} ---", + font_size=8, italic=True, color=Colors.SECONDARY_TEXT, + alignment=PP_ALIGN.LEFT, + ) + + # Security footer + if security: + self._add_textbox( + slide, Inches(0.4), Inches(6.7), + Inches(12.5), Inches(0.35), + text=security, font_size=8, bold=True, + color=Colors.TEAL, + alignment=PP_ALIGN.LEFT, + ) + + def add_service_tiering_slide(self, workloads: list): + """Service Tiering slide — maps workloads to Platinum/Gold/Silver/Bronze. + + workloads: list of {"name": str, "tier": str, "uptime": str, + "rto": str, "rpo": str} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Service Tiering", margin=self.MARGIN) + + # Subtitle + self._add_textbox( + slide, self.MARGIN, Inches(1.1), + Inches(12), Inches(0.4), + text="Each tier drives: HA/DR topology, backup strategy, isolation model, support level.", + font_size=11, italic=True, color=Colors.SECONDARY_TEXT, + ) + + rows = len(workloads) + 1 + table = self._add_table( + slide, rows, 5, + self.MARGIN, Inches(1.7), + Inches(12), Inches(0.45 * rows), + ) + + table.columns[0].width = Inches(3.0) + table.columns[1].width = Inches(2.5) + table.columns[2].width = Inches(2.0) + table.columns[3].width = Inches(2.25) + table.columns[4].width = Inches(2.25) + + headers = ["Workload", "Tier", "Uptime", "RTO", "RPO"] + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER, + ) + + tier_colors = { + "platinum": Colors.ORACLE_RED, + "gold": Colors.BURNT_ORANGE, + "silver": Colors.SECONDARY_TEXT, + "bronze": Colors.MUTED_TEAL, + } + + for i, wl in enumerate(workloads): + row_idx = i + 1 + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None + self._style_table_cell(table.cell(row_idx, 0), wl["name"], font_size=10, bold=True, bg_color=bg) + tier_label = wl.get("tier", "Silver") + tier_color = tier_colors.get(tier_label.lower(), Colors.SECONDARY_TEXT) + self._style_table_cell( + table.cell(row_idx, 1), tier_label.title(), + font_size=10, bold=True, color=tier_color, bg_color=bg, + alignment=PP_ALIGN.CENTER, + ) + self._style_table_cell(table.cell(row_idx, 2), wl.get("uptime", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + self._style_table_cell(table.cell(row_idx, 3), wl.get("rto", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + self._style_table_cell(table.cell(row_idx, 4), wl.get("rpo", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + + def add_architecture_principles_slide(self, principles: dict): + """Architecture Principles slide — ECAL Design/Deployment/Service categories. + + principles: {"design": [{"id": str, "name": str, "summary": str}], + "deployment": [...], "service": [...]} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Architecture Principles", margin=self.MARGIN) + + y = Inches(1.2) + col_x = self.MARGIN + col_width = Inches(4) + + for category in ["design", "deployment", "service"]: + items = principles.get(category, []) + if not items: + continue + + # Category heading + self._add_textbox( + slide, col_x, y, col_width, Inches(0.35), + text=category.upper(), font_size=13, bold=True, + color=Colors.TEAL, + ) + + item_y = y + Inches(0.4) + for item in items: + pid = item.get("id", "") + name = item.get("name", "") + summary = item.get("summary", "") + label = f"{pid} {name}" if pid else name + if summary: + label += f" — {summary}" + self._add_textbox( + slide, col_x + Inches(0.1), item_y, + col_width - Inches(0.1), Inches(0.4), + text=f"• {label}", font_size=11, + ) + item_y += Inches(0.4) + + col_x += Inches(4.2) + + def add_decisions_slide(self, decisions: list): + """Slide 4: Architecture Decisions table. + + decisions: list of {"decision": str, "rationale": str} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Architecture Decisions", margin=self.MARGIN) + + rows = len(decisions) + 1 + table = self._add_table( + slide, rows, 2, + self.MARGIN, Inches(1.2), + Inches(12), Inches(0.5 * rows), + ) + + # Set column widths + table.columns[0].width = Inches(4.5) + table.columns[1].width = Inches(7.5) + + # Header row + self._style_table_cell( + table.cell(0, 0), "Decision", font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + ) + self._style_table_cell( + table.cell(0, 1), "Rationale", font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + ) + + # Data rows + for i, dec in enumerate(decisions): + row_idx = i + 1 + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None + self._style_table_cell( + table.cell(row_idx, 0), dec["decision"], + font_size=11, bold=True, bg_color=bg, + ) + self._style_table_cell( + table.cell(row_idx, 1), dec["rationale"], + font_size=10, bg_color=bg, + ) + + def add_ha_dr_slide(self, tiers: list, description: str = ""): + """Slide 5: HA/DR table. + + tiers: list of {"tier": str, "technology": str, "rto": str, "rpo": str} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "High Availability & Disaster Recovery", margin=self.MARGIN) + + if description: + self._add_textbox( + slide, self.MARGIN, Inches(1.2), + Inches(12), Inches(0.5), + text=description, font_size=12, + ) + + rows = len(tiers) + 1 + table = self._add_table( + slide, rows, 4, + self.MARGIN, Inches(1.9), + Inches(12), Inches(0.45 * rows), + ) + + table.columns[0].width = Inches(2.5) + table.columns[1].width = Inches(4.5) + table.columns[2].width = Inches(2.5) + table.columns[3].width = Inches(2.5) + + headers = ["Tier", "Technology", "RTO", "RPO"] + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER, + ) + + for i, tier in enumerate(tiers): + row_idx = i + 1 + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None + self._style_table_cell(table.cell(row_idx, 0), tier["tier"], font_size=10, bold=True, bg_color=bg) + self._style_table_cell(table.cell(row_idx, 1), tier["technology"], font_size=10, bg_color=bg) + self._style_table_cell(table.cell(row_idx, 2), tier["rto"], font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + self._style_table_cell(table.cell(row_idx, 3), tier["rpo"], font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + + def add_security_slide(self, controls: dict, compliance: list = None): + """Slide 6: Security & Compliance. + + controls: {"identity": [...], "network": [...], "database": [...], "monitoring": [...]} + compliance: ["PCI-DSS", "SOC2", ...] or None + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Security & Compliance", margin=self.MARGIN) + + y = Inches(1.2) + + # Compliance badges + if compliance: + x = self.MARGIN + for framework in compliance: + shape = self._add_rect( + slide, x, y, Inches(1.5), Inches(0.4), + fill_color=Colors.SUCCESS, + ) + shape.text_frame.paragraphs[0].text = f"✓ {framework}" + shape.text_frame.paragraphs[0].font.size = Pt(10) + shape.text_frame.paragraphs[0].font.bold = True + shape.text_frame.paragraphs[0].font.color.rgb = Colors.WHITE + shape.text_frame.paragraphs[0].font.name = self.FONT + shape.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + x += Inches(1.7) + y += Inches(0.7) + + # Controls in columns + col_x = self.MARGIN + col_width = Inches(3) + for area, items in controls.items(): + area_title = area.replace("_", " ").title() + self._add_textbox( + slide, col_x, y, col_width, Inches(0.35), + text=area_title, font_size=12, bold=True, + color=Colors.TEAL, + ) + item_y = y + Inches(0.4) + for item in items: + self._add_textbox( + slide, col_x + Inches(0.1), item_y, + col_width - Inches(0.1), Inches(0.3), + text=f"• {item}", font_size=10, + ) + item_y += Inches(0.3) + col_x += Inches(3.2) + + def add_environment_catalogue_slide(self, environments: list, + cost_notes: list = None): + """Environment Catalogue slide — Prod/Pre-Prod/Dev-Test/DR per workload. + + environments: list of {"environment": str, "tier": str, + "databases": str, "ocpus": str, "isolation": str} + cost_notes: optional list of cost optimization notes + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Environment Catalogue", margin=self.MARGIN) + + rows = len(environments) + 1 + table = self._add_table( + slide, rows, 5, + self.MARGIN, Inches(1.2), + Inches(12), Inches(0.4 * rows), + ) + + table.columns[0].width = Inches(2.2) + table.columns[1].width = Inches(1.8) + table.columns[2].width = Inches(2.5) + table.columns[3].width = Inches(1.5) + table.columns[4].width = Inches(4.0) + + headers = ["Environment", "Tier", "Databases", "OCPUs", "Isolation"] + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER, + ) + + for i, env in enumerate(environments): + row_idx = i + 1 + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None + self._style_table_cell(table.cell(row_idx, 0), env.get("environment", ""), font_size=10, bold=True, bg_color=bg) + self._style_table_cell(table.cell(row_idx, 1), env.get("tier", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + self._style_table_cell(table.cell(row_idx, 2), env.get("databases", ""), font_size=10, bg_color=bg) + self._style_table_cell(table.cell(row_idx, 3), env.get("ocpus", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + self._style_table_cell(table.cell(row_idx, 4), env.get("isolation", ""), font_size=10, bg_color=bg) + + if cost_notes: + notes_y = Inches(1.2) + Inches(0.4 * rows) + Inches(0.3) + self._add_textbox( + slide, self.MARGIN, notes_y, + Inches(12), Inches(0.35), + text="Cost Optimization", font_size=12, bold=True, + color=Colors.TEAL, + ) + note_y = notes_y + Inches(0.35) + for note in cost_notes: + self._add_textbox( + slide, self.MARGIN + Inches(0.1), note_y, + Inches(11.5), Inches(0.35), + text=f"• {note}", font_size=11, + ) + note_y += Inches(0.35) + + def add_cost_slide(self, line_items: list, assumptions: list = None, + show_byol: bool = True): + """Slide 7: Cost Estimate. + + line_items: list of {"component": str, "monthly_payg": str, + "monthly_byol": str (optional), "notes": str} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Cost Estimate", margin=self.MARGIN) + + cols = 4 if show_byol else 3 + rows = len(line_items) + 1 + table = self._add_table( + slide, rows, cols, + self.MARGIN, Inches(1.2), + Inches(12), Inches(0.4 * rows), + ) + + if show_byol: + table.columns[0].width = Inches(3.5) + table.columns[1].width = Inches(2.5) + table.columns[2].width = Inches(2.5) + table.columns[3].width = Inches(3.5) + headers = ["Component", "Monthly (PAYG)", "Monthly (BYOL)", "Notes"] + else: + table.columns[0].width = Inches(4) + table.columns[1].width = Inches(3) + table.columns[2].width = Inches(5) + headers = ["Component", "Monthly", "Notes"] + + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER if j > 0 else PP_ALIGN.LEFT, + ) + + for i, item in enumerate(line_items): + row_idx = i + 1 + is_total = "total" in item.get("component", "").lower() + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 and not is_total else None + if is_total: + bg = Colors.TEAL + + self._style_table_cell( + table.cell(row_idx, 0), item["component"], + font_size=10, bold=is_total, + color=Colors.WHITE if is_total else None, + bg_color=bg, + ) + self._style_table_cell( + table.cell(row_idx, 1), item.get("monthly_payg", ""), + font_size=10, bold=is_total, + color=Colors.WHITE if is_total else None, + bg_color=bg, alignment=PP_ALIGN.RIGHT, + ) + if show_byol: + self._style_table_cell( + table.cell(row_idx, 2), item.get("monthly_byol", ""), + font_size=10, bold=is_total, + color=Colors.WHITE if is_total else None, + bg_color=bg, alignment=PP_ALIGN.RIGHT, + ) + self._style_table_cell( + table.cell(row_idx, 3), item.get("notes", ""), + font_size=9, bg_color=bg, + color=Colors.WHITE if is_total else Colors.SECONDARY_TEXT, + ) + else: + self._style_table_cell( + table.cell(row_idx, 2), item.get("notes", ""), + font_size=9, bg_color=bg, + color=Colors.WHITE if is_total else Colors.SECONDARY_TEXT, + ) + + # Assumptions + if assumptions: + table_bottom = Inches(1.2) + Inches(0.4 * rows) + Inches(0.3) + self._add_textbox( + slide, self.MARGIN, table_bottom, + Inches(12), Inches(0.35), + text="Assumptions:", font_size=11, bold=True, + color=Colors.SECONDARY_TEXT, italic=True, + ) + for idx, assumption in enumerate(assumptions): + self._add_textbox( + slide, Inches(0.7), table_bottom + Inches(0.35 + idx * 0.3), + Inches(11), Inches(0.3), + text=f"• {assumption}", font_size=10, italic=True, + color=Colors.SECONDARY_TEXT, + ) + + def add_cost_comparison_slide(self, rows: list, title: str = "Cost Comparison", + col_headers: list = None): + """Slide 8: Cost Comparison (optional). + + rows: list of {"item": str, "current": str, "oci": str, "savings": str} + col_headers: custom column headers (default: ["Component", "Current", "OCI", "Savings"]) + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, title, margin=self.MARGIN) + + headers = col_headers or ["Component", "Current", "OCI", "Savings"] + num_rows = len(rows) + 1 + table = self._add_table( + slide, num_rows, len(headers), + self.MARGIN, Inches(1.2), + Inches(12), Inches(0.45 * num_rows), + ) + + col_widths = [Inches(4), Inches(2.5), Inches(2.5), Inches(3)] + for j, w in enumerate(col_widths[:len(headers)]): + table.columns[j].width = w + + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER if j > 0 else PP_ALIGN.LEFT, + ) + + for i, row in enumerate(rows): + row_idx = i + 1 + is_total = "total" in row.get("item", "").lower() + bg = Colors.TEAL if is_total else (Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None) + txt_color = Colors.WHITE if is_total else None + + self._style_table_cell( + table.cell(row_idx, 0), row.get("item", ""), + font_size=10, bold=is_total, color=txt_color, bg_color=bg, + ) + self._style_table_cell( + table.cell(row_idx, 1), row.get("current", ""), + font_size=10, bold=is_total, color=txt_color, bg_color=bg, + alignment=PP_ALIGN.RIGHT, + ) + self._style_table_cell( + table.cell(row_idx, 2), row.get("oci", ""), + font_size=10, bold=is_total, color=txt_color, bg_color=bg, + alignment=PP_ALIGN.RIGHT, + ) + if len(headers) > 3: + savings_color = Colors.SUCCESS if not is_total else txt_color + self._style_table_cell( + table.cell(row_idx, 3), row.get("savings", ""), + font_size=10, bold=True, color=savings_color, bg_color=bg, + alignment=PP_ALIGN.CENTER, + ) + + def add_migration_slide(self, phases: list, tools: list = None, + downtime: str = ""): + """Slide 9: Migration Approach. + + phases: list of {"name": str, "weeks": str, "tasks": [...]} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Migration Approach", margin=self.MARGIN) + + y = Inches(1.3) + phase_colors = [Colors.TEAL, Colors.ORACLE_RED, Colors.BURNT_ORANGE, Colors.FOREST] + + # Phase timeline bars + bar_left = Inches(2.5) + bar_width = Inches(9.5) + for i, phase in enumerate(phases): + color = phase_colors[i % len(phase_colors)] + + # Phase label + self._add_textbox( + slide, self.MARGIN, y, + Inches(2), Inches(0.45), + text=phase["name"], font_size=13, bold=True, + ) + + # Phase bar + bar = self._add_rect( + slide, bar_left, y + Inches(0.05), + bar_width * 0.9, Inches(0.4), + fill_color=color, + ) + bar.text_frame.paragraphs[0].text = phase.get("weeks", "") + bar.text_frame.paragraphs[0].font.size = Pt(12) + bar.text_frame.paragraphs[0].font.color.rgb = Colors.WHITE + bar.text_frame.paragraphs[0].font.name = self.FONT + bar.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # Tasks below bar + if phase.get("tasks"): + tasks_text = " • ".join(phase["tasks"][:4]) + self._add_textbox( + slide, bar_left, y + Inches(0.48), + bar_width, Inches(0.38), + text=tasks_text, font_size=11, italic=True, + color=Colors.SECONDARY_TEXT, + ) + + y += Inches(1.1) + + # Tools + if tools: + y += Inches(0.2) + self._add_textbox( + slide, self.MARGIN, y, + Inches(12), Inches(0.35), + text=f"Migration Tools: {', '.join(tools)}", + font_size=11, bold=True, color=Colors.TEAL, + ) + + # Downtime approach + if downtime: + y += Inches(0.5) + self._add_textbox( + slide, self.MARGIN, y, + Inches(12), Inches(0.35), + text=f"Downtime Approach: {downtime}", + font_size=11, color=Colors.PRIMARY_TEXT, + ) + + def add_operational_raci_slide(self, raci_items: list, + model: str = "co_managed"): + """Operational RACI slide — responsibility matrix. + + raci_items: list of {"activity": str, "customer": str, "oracle": str} + model: "fully_managed", "co_managed", or "self_managed" + """ + slide = self._add_blank_slide() + model_label = model.replace("_", "-").title() + self._add_title_bar(slide, f"Operational Responsibilities ({model_label})", margin=self.MARGIN) + + rows = len(raci_items) + 1 + table = self._add_table( + slide, rows, 3, + self.MARGIN, Inches(1.2), + Inches(10), Inches(0.38 * rows), + ) + + table.columns[0].width = Inches(5.0) + table.columns[1].width = Inches(2.5) + table.columns[2].width = Inches(2.5) + + headers = ["Activity", "Customer", "Oracle / Partner"] + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER, + ) + + for i, item in enumerate(raci_items): + row_idx = i + 1 + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None + self._style_table_cell(table.cell(row_idx, 0), item.get("activity", ""), font_size=10, bg_color=bg) + self._style_table_cell(table.cell(row_idx, 1), item.get("customer", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + self._style_table_cell(table.cell(row_idx, 2), item.get("oracle", ""), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER) + + # Legend + legend_y = Inches(1.2) + Inches(0.38 * rows) + Inches(0.2) + self._add_textbox( + slide, self.MARGIN, legend_y, + Inches(10), Inches(0.35), + text="R = Responsible A = Accountable C = Consulted I = Informed", + font_size=11, italic=True, color=Colors.SECONDARY_TEXT, + ) + + def add_risk_slide(self, risks: list): + """Slide 10: Risk Register. + + risks: list of {"risk": str, "severity": "HIGH"|"MEDIUM"|"LOW", "mitigation": str} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Risk Register", margin=self.MARGIN) + + rows = len(risks) + 1 + table = self._add_table( + slide, rows, 3, + self.MARGIN, Inches(1.2), + Inches(12), Inches(0.5 * rows), + ) + + table.columns[0].width = Inches(4.5) + table.columns[1].width = Inches(1.5) + table.columns[2].width = Inches(6) + + headers = ["Risk", "Severity", "Mitigation"] + for j, h in enumerate(headers): + self._style_table_cell( + table.cell(0, j), h, font_size=11, bold=True, + color=Colors.WHITE, bg_color=Colors.TEAL, + alignment=PP_ALIGN.CENTER if j == 1 else PP_ALIGN.LEFT, + ) + + severity_colors = { + "HIGH": Colors.ERROR, + "MEDIUM": Colors.WARNING, + "LOW": Colors.SUCCESS, + } + + for i, risk in enumerate(risks): + row_idx = i + 1 + bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None + self._style_table_cell( + table.cell(row_idx, 0), risk["risk"], + font_size=10, bg_color=bg, + ) + sev = risk.get("severity", "MEDIUM").upper() + self._style_table_cell( + table.cell(row_idx, 1), sev, + font_size=10, bold=True, + color=severity_colors.get(sev, Colors.WARNING), + bg_color=bg, alignment=PP_ALIGN.CENTER, + ) + self._style_table_cell( + table.cell(row_idx, 2), risk["mitigation"], + font_size=10, bg_color=bg, + ) + + def add_scorecard_slide(self, pillars: list, recommendations: list = None): + """Slide 11: Well-Architected Scorecard. + + pillars: list of {"name": str, "status": "PASS"|"PASS_WITH_RECOMMENDATIONS"|"GAPS_IDENTIFIED"|"NOT_APPLICABLE", + "passed": int, "total": int} + """ + slide = self._add_blank_slide() + self._add_title_bar(slide, "Well-Architected Scorecard", margin=self.MARGIN) + + status_styles = { + "PASS": {"icon": "✓", "color": Colors.SUCCESS, "label": "PASS"}, + "PASS_WITH_RECOMMENDATIONS": {"icon": "⚠", "color": Colors.WARNING, "label": "PASS WITH RECOMMENDATIONS"}, + "GAPS_IDENTIFIED": {"icon": "✗", "color": Colors.ERROR, "label": "GAPS IDENTIFIED"}, + "NOT_APPLICABLE": {"icon": "—", "color": Colors.SECONDARY_TEXT, "label": "N/A"}, + } + + y = Inches(1.4) + for pillar in pillars: + style = status_styles.get(pillar["status"], status_styles["PASS"]) + + # Status indicator pill + pill = self._add_rect( + slide, self.MARGIN, y, + Inches(0.5), Inches(0.45), + fill_color=style["color"], + ) + pill.text_frame.paragraphs[0].text = style["icon"] + pill.text_frame.paragraphs[0].font.size = Pt(14) + pill.text_frame.paragraphs[0].font.color.rgb = Colors.WHITE + pill.text_frame.paragraphs[0].font.name = self.FONT + pill.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # Pillar name + self._add_textbox( + slide, Inches(1.2), y, + Inches(4), Inches(0.45), + text=pillar["name"], font_size=13, bold=True, + ) + + # Score + score_text = f"{pillar['passed']}/{pillar['total']}" if pillar['total'] > 0 else "—" + self._add_textbox( + slide, Inches(5.5), y, + Inches(1.5), Inches(0.45), + text=score_text, font_size=13, + color=Colors.SECONDARY_TEXT, alignment=PP_ALIGN.CENTER, + ) + + # Status label + self._add_textbox( + slide, Inches(7), y, + Inches(5), Inches(0.45), + text=style["label"], font_size=11, + color=style["color"], bold=True, + ) + + y += Inches(0.6) + + # Recommendations + if recommendations: + y += Inches(0.3) + self._add_textbox( + slide, self.MARGIN, y, + Inches(12), Inches(0.35), + text="Top Recommendations:", font_size=12, bold=True, + color=Colors.TEAL, + ) + y += Inches(0.4) + for rec in recommendations[:4]: + self._add_textbox( + slide, Inches(0.7), y, + Inches(11), Inches(0.3), + text=f"→ {rec}", font_size=10, + ) + y += Inches(0.3) + + # Reference + self._add_textbox( + slide, self.MARGIN, Inches(6.5), + Inches(12), Inches(0.35), + text="Validated against Oracle Well-Architected Framework — docs.oracle.com/en/solutions/oci-best-practices/", + font_size=10, italic=True, color=Colors.SECONDARY_TEXT, + ) + + def add_next_steps_slide(self, steps: list, contact_info: str = ""): + """Slide 12: Next Steps.""" + slide = self._add_blank_slide() + self._add_title_bar(slide, "Next Steps", margin=self.MARGIN) + + y = Inches(1.5) + for i, step in enumerate(steps): + # Number circle + circle = slide.shapes.add_shape( + MSO_SHAPE.OVAL, + self.MARGIN, y, + Inches(0.4), Inches(0.4), + ) + circle.fill.solid() + circle.fill.fore_color.rgb = Colors.TEAL + circle.line.fill.background() + circle.text_frame.paragraphs[0].text = str(i + 1) + circle.text_frame.paragraphs[0].font.size = Pt(12) + circle.text_frame.paragraphs[0].font.bold = True + circle.text_frame.paragraphs[0].font.color.rgb = Colors.WHITE + circle.text_frame.paragraphs[0].font.name = self.FONT + circle.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # Step text + self._add_textbox( + slide, Inches(1.1), y, + Inches(10), Inches(0.4), + text=step, font_size=13, + ) + y += Inches(0.65) + + if contact_info: + self._add_textbox( + slide, self.MARGIN, Inches(6), + Inches(12), Inches(0.4), + text=contact_info, font_size=11, + color=Colors.SECONDARY_TEXT, + ) + + # ---- Build from YAML spec ---- + + @classmethod + def from_spec(cls, spec: dict, template: Optional[str] = None) -> "OCIDeckGenerator": + """Build a complete deck from a YAML specification.""" + meta = spec.get("metadata", {}) + gen = cls( + customer=meta.get("customer", ""), + project=meta.get("project", ""), + architect=meta.get("architect", ""), + firm=meta.get("firm", ""), + template=template, + ) + + # Slide 1: Title + gen.add_title_slide(subtitle=meta.get("subtitle", "")) + + # Slide 2: Summary + if "summary" in spec: + s = spec["summary"] + gen.add_summary_slide( + why=s.get("why", ""), + current_state=s.get("current_state", []), + target_state=s.get("target_state", ""), + timeline=s.get("timeline", ""), + ) + + # Slide 3: Service Tiering (ECAL) + if "service_tiering" in spec: + gen.add_service_tiering_slide(spec["service_tiering"]) + + # Slide 4: Architecture Principles (ECAL) + if "architecture_principles" in spec: + gen.add_architecture_principles_slide(spec["architecture_principles"]) + + # Slide 5: Architecture + if "architecture" in spec: + a = spec["architecture"] + gen.add_architecture_slide( + diagram_path=a.get("diagram_path"), + description=a.get("description", ""), + visual=a.get("visual"), + ) + + # Slide 6: Decisions + if "decisions" in spec: + gen.add_decisions_slide(spec["decisions"]) + + # Slide 7: HA/DR + if "ha_dr" in spec: + h = spec["ha_dr"] + gen.add_ha_dr_slide( + tiers=h.get("tiers", []), + description=h.get("description", ""), + ) + + # Slide 8: Security + if "security" in spec: + s = spec["security"] + gen.add_security_slide( + controls=s.get("controls", {}), + compliance=s.get("compliance", []), + ) + + # Slide 9: Environment Catalogue (ECAL) + if "environment_catalogue" in spec: + ec = spec["environment_catalogue"] + gen.add_environment_catalogue_slide( + environments=ec.get("environments", []), + cost_notes=ec.get("cost_notes"), + ) + + # Slide 10: Cost + if "cost" in spec: + c = spec["cost"] + gen.add_cost_slide( + line_items=c.get("line_items", []), + assumptions=c.get("assumptions", []), + show_byol=c.get("show_byol", True), + ) + + # Slide 11: Cost Comparison (optional) + if "cost_comparison" in spec: + cc = spec["cost_comparison"] + gen.add_cost_comparison_slide( + rows=cc.get("rows", []), + title=cc.get("title", "Cost Comparison"), + col_headers=cc.get("col_headers"), + ) + + # Slide 12: Migration + if "migration" in spec: + m = spec["migration"] + gen.add_migration_slide( + phases=m.get("phases", []), + tools=m.get("tools", []), + downtime=m.get("downtime", ""), + ) + + # Slide 13: Operational RACI (ECAL) + if "operational_raci" in spec: + r = spec["operational_raci"] + gen.add_operational_raci_slide( + raci_items=r.get("raci_items", []), + model=r.get("model", "co_managed"), + ) + + # Slide 14: Risks + if "risks" in spec: + gen.add_risk_slide(spec["risks"]) + + # Slide 15: WA Scorecard + if "scorecard" in spec: + sc = spec["scorecard"] + gen.add_scorecard_slide( + pillars=sc.get("pillars", []), + recommendations=sc.get("recommendations", []), + ) + + # Slide 16: Next Steps + if "next_steps" in spec: + ns = spec["next_steps"] + gen.add_next_steps_slide( + steps=ns.get("steps", []), + contact_info=ns.get("contact_info", ""), + ) + + # Closing slides + gen.add_closing_slide( + name=meta.get("architect", ""), + title=meta.get("firm", ""), + ) + + return gen + + +# ============================================================ +# CLI +# ============================================================ + +def main(): + parser = argparse.ArgumentParser( + description="Generate OCI architecture proposal slide deck (.pptx)" + ) + parser.add_argument( + "--spec", required=True, + help="Path to YAML spec file with proposal data", + ) + parser.add_argument( + "--output", default="architecture-proposal.pptx", + help="Output .pptx file path", + ) + args = parser.parse_args() + + with open(args.spec, 'r') as f: + spec = yaml.safe_load(f) + + gen = OCIDeckGenerator.from_spec(spec) + gen.save(args.output) + + print(f"Generated: {args.output}") + print(f" Slides: {gen.slide_count}") + print(f" Template: Oracle FY26") + customer = spec.get("metadata", {}).get("customer", "") + if customer: + print(f" Customer: {customer}") + + +if __name__ == "__main__": + main() diff --git a/tools/oci_diagram_gen.py b/tools/oci_diagram_gen.py new file mode 100644 index 0000000..35afb66 --- /dev/null +++ b/tools/oci_diagram_gen.py @@ -0,0 +1,1431 @@ +#!/usr/bin/env python3 +""" +OCI Architecture Diagram Generator — Hybrid drawpyo + Custom Icon Injection +Produces .drawio files with Oracle official container styles and OCI Library icons. + +HYBRID APPROACH: +- drawpyo handles: File, Page, Object (containers/groups), Edge (connections), save +- Custom icon extractor (kb/diagram/oci-icons.json) handles: multi-cell OCI stencil icons + injected as raw XML into drawpyo's output + +Styles extracted from: +- OCI Library.xml — 224 official OCI service icons +- OCI Architecture Diagram Toolkit v24.2.drawio +- Reference architectures: select-ai-apex, exadb-dr-on-db-at-azure + +Usage: + python3.12 oci_diagram_gen.py --spec architecture-spec.yaml --output diagram.drawio + +Or import and use programmatically: + from oci_diagram_gen import OCIDiagramGenerator + gen = OCIDiagramGenerator() + gen.add_region("region1", "US East (Ashburn)", parent="tenancy", ...) + gen.save("output.drawio") +""" + +import json +import os +import re +import yaml +import argparse +import sys +import xml.etree.ElementTree as ET +from typing import Optional + +import drawpyo +import drawpyo.diagram + +# ============================================================ +# OCI OFFICIAL STYLES (from OCI Style Guide for Draw.io v24.2) +# ============================================================ +# +# Color palette (no #FFFFFF — use #FCFBFA near-white instead): +# Charcoal #312D2A — text, connectors +# Warm Gray #9E9892 — container borders +# Light #F5F4F2 — region/subnet fill +# Medium #E4E1DD — AD fill +# Near-White#FCFBFA — background elements +# Teal #2D5967 — infrastructure services +# Copper #AA643B — database services (NOT #AE562C) +# Burnt-Org #AE562C — VCN/subnet borders ONLY +# Purple #804998 — integration services +# Muted #70665E — legacy, external actors + +STYLES = { + # --- Container styles (OCI Architecture Diagram Toolkit v24.2) --- + "tenancy": ( + "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;dashPattern=6 4;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;" + "fontColor=#312D2A;strokeColor=#9E9892;fontSize=13;fontStyle=1;" + "spacingLeft=10;spacingTop=8;rounded=1;arcSize=1;" + ), + "region": ( + "whiteSpace=wrap;html=1;align=left;fontFamily=Oracle Sans;" + "verticalAlign=top;fillColor=#F5F4F2;rounded=1;arcSize=5;" + "strokeColor=#9E9892;fontColor=#312D2A;fontSize=13;fontStyle=1;" + "spacingLeft=10;spacingTop=8;" + ), + "ad": ( + "whiteSpace=wrap;html=1;strokeWidth=1;align=center;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=#DFDCD8;" + "fontColor=#312D2A;strokeColor=#9E9892;rounded=1;arcSize=1;" + "fontSize=12;fontStyle=1;" + ), + "fault_domain": ( + "whiteSpace=wrap;html=1;strokeWidth=1;align=center;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=#FCFBFA;" + "fontColor=#312D2A;strokeColor=#9E9892;rounded=1;arcSize=3;" + "fontSize=11;" + ), + "vcn": ( + "whiteSpace=wrap;html=1;strokeWidth=2;dashed=1;dashPattern=6 4;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;" + "fontColor=#AE562C;strokeColor=#AE562C;perimeterSpacing=0;" + "fontSize=12;fontStyle=1;spacingLeft=8;spacingTop=6;" + ), + "subnet": ( + "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;dashPattern=6 4;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;" + "fontColor=#AE562C;strokeColor=#AE562C;fontSize=11;fontStyle=1;" + "spacingLeft=8;spacingTop=4;" + ), + "compartment": ( + "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;dashPattern=6 4;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;" + "fontColor=#312D2A;strokeColor=#9E9892;fontSize=12;fontStyle=1;" + "spacingLeft=5;spacingTop=5;rounded=1;arcSize=1;" + ), + # --- External cloud containers (AWS, Azure, GCP) --- + # Generic / AWS: dashed muted border (Oracle style for non-Azure clouds) + "cloud_container": ( + "whiteSpace=wrap;html=1;strokeWidth=2;dashed=1;dashPattern=6 4;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;" + "fontColor=#70665E;strokeColor=#70665E;fontSize=12;spacingLeft=5;" + "spacingTop=5;rounded=1;arcSize=6;" + ), + # Azure: solid blue border + light blue fill (matches Oracle ref arch style) + # Source: confirmed from 4 Oracle reference architecture diagrams (exadb-dr, adb-azure, etc.) + "azure_container": ( + "whiteSpace=wrap;html=1;strokeWidth=2;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=#EBF5FF;" + "fontColor=#0050A2;strokeColor=#0078D4;fontSize=12;fontStyle=1;" + "spacingLeft=5;spacingTop=5;rounded=1;arcSize=3;" + ), + # GCP: solid green border + light green fill + "gcp_container": ( + "whiteSpace=wrap;html=1;strokeWidth=2;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=#E8F5E9;" + "fontColor=#1E6B3C;strokeColor=#34A853;fontSize=12;fontStyle=1;" + "spacingLeft=5;spacingTop=5;rounded=1;arcSize=3;" + ), + # Oracle Services Network: dashed gray column (right-side managed services panel) + # Source: confirmed in hub-spoke, OKE, lakehouse Oracle reference architectures + "oracle_services_network": ( + "whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;dashPattern=6 4;align=left;" + "fontFamily=Oracle Sans;verticalAlign=top;fillColor=none;" + "fontColor=#312D2A;strokeColor=#9E9892;fontSize=12;fontStyle=1;" + "spacingLeft=5;spacingTop=5;rounded=1;arcSize=1;" + ), + + # --- Service block styles (fallback when no icon available) --- + # OUTLINED style — Oracle reference archs never use solid-filled rectangles. + # Fallback uses near-white fill + colored border + colored text to approximate + # the lightweight look of OCI line-art stencil icons. + "svc_infra": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#FCFBFA;" + "strokeColor=#2D5967;strokeWidth=1.5;fontColor=#2D5967;fontSize=11;" + "fontFamily=Oracle Sans;arcSize=8;verticalAlign=middle;align=center;" + ), + "svc_database": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#FCFBFA;" + "strokeColor=#AA643B;strokeWidth=1.5;fontColor=#AA643B;fontSize=11;" + "fontFamily=Oracle Sans;arcSize=8;verticalAlign=middle;align=center;" + ), + "svc_integration": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#FCFBFA;" + "strokeColor=#804998;strokeWidth=1.5;fontColor=#804998;fontSize=11;" + "fontFamily=Oracle Sans;arcSize=8;verticalAlign=middle;align=center;" + ), + "svc_dormant": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#FCFBFA;" + "strokeColor=#9E9892;strokeWidth=1;fontColor=#70665E;fontSize=11;" + "fontFamily=Oracle Sans;arcSize=8;fontStyle=2;verticalAlign=middle;align=center;" + ), + "svc_legacy": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#FCFBFA;" + "strokeColor=#70665E;strokeWidth=1.5;fontColor=#70665E;fontSize=11;" + "fontFamily=Oracle Sans;arcSize=8;verticalAlign=middle;align=center;" + ), + "obs_bar": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#2D5967;" + "strokeColor=none;fontColor=#FCFBFA;fontSize=10;" + "fontFamily=Oracle Sans;arcSize=10;" + ), + + # --- External actor: person silhouette (head circle + body arc) --- + # Rendered as raw SVG in to_xml() post-processing, not as a drawpyo stencil. + # This style is for the invisible connectable group + label below the icon. + "external_user": ( + "group;fillColor=none;strokeColor=none;connectable=1;pointerEvents=1;" + ), + # --- External actor: cloud (internet) --- + "external_internet": ( + "shape=mxgraph.cisco.clouds.cloud;" + "sketch=0;fillColor=#2D5967;strokeColor=none;" + "fontColor=#312D2A;fontSize=12;fontFamily=Oracle Sans;" + "verticalLabelPosition=bottom;verticalAlign=top;" + "align=center;html=1;" + ), + # --- External actor: generic box --- + "external_generic": ( + "rounded=1;whiteSpace=wrap;html=1;fillColor=#70665E;" + "strokeColor=none;fontColor=#FCFBFA;fontSize=12;" + "fontFamily=Oracle Sans;arcSize=12;verticalAlign=middle;align=center;" + ), + + # --- Flow number badge (gray circle with white number) --- + "flow_badge": ( + "ellipse;whiteSpace=wrap;html=1;fillColor=#2D5967;" + "strokeColor=none;fontColor=#FCFBFA;fontSize=10;" + "fontFamily=Oracle Sans;fontStyle=1;" + "verticalAlign=middle;align=center;" + ), + + # --- Title --- + "title": ( + "text;html=1;fontSize=18;fontColor=#312D2A;" + "fontFamily=Oracle Sans;align=center;verticalAlign=middle;fontStyle=1;" + ), +} + +# Connection style strings — used ONLY for raw XML fallback summary stats. +# Actual edge styling is done via drawpyo Edge attributes. +CONN_STYLES = { + "conn_standard": ( + "endArrow=open;endFill=0;startArrow=none;startFill=0;html=1;" + "strokeColor=#312D2A;strokeWidth=1;rounded=1;" + "edgeStyle=orthogonalEdgeStyle;endSize=6;elbow=vertical;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + "labelBackgroundColor=none;" + ), + "conn_db": ( + "endArrow=open;endFill=0;startArrow=none;startFill=0;html=1;" + "strokeColor=#312D2A;strokeWidth=1;rounded=1;" + "edgeStyle=orthogonalEdgeStyle;endSize=6;elbow=vertical;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + "labelBackgroundColor=none;" + ), + "conn_adg": ( + "endArrow=open;endFill=0;startArrow=none;startFill=0;html=1;" + "strokeColor=#312D2A;strokeWidth=1;rounded=1;" + "edgeStyle=orthogonalEdgeStyle;endSize=6;elbow=vertical;" + "dashed=1;dashPattern=8 4;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + "labelBackgroundColor=none;" + ), + "conn_fastconnect": ( + "endArrow=open;endFill=0;startArrow=open;startFill=0;html=1;" + "strokeColor=#312D2A;strokeWidth=1;rounded=1;" + "edgeStyle=orthogonalEdgeStyle;endSize=6;elbow=vertical;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + "labelBackgroundColor=none;" + ), + "conn_migration": ( + "endArrow=open;endFill=0;startArrow=none;startFill=0;html=1;" + "strokeColor=#312D2A;strokeWidth=1;rounded=1;" + "edgeStyle=orthogonalEdgeStyle;endSize=6;elbow=vertical;" + "dashed=1;dashPattern=8 4;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + "labelBackgroundColor=none;" + ), + "conn_etl": ( + "endArrow=open;endFill=0;startArrow=none;startFill=0;html=1;" + "strokeColor=#312D2A;strokeWidth=1;rounded=1;" + "edgeStyle=orthogonalEdgeStyle;endSize=6;elbow=vertical;" + "dashed=1;dashPattern=6 4;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + "labelBackgroundColor=none;" + ), +} + +# Map service types to style categories +SVC_CATEGORY = { + # Infrastructure (teal #2D5967) + "compute": "svc_infra", "vm": "svc_infra", "bare_metal": "svc_infra", + "load_balancer": "svc_infra", "flexible_lb": "svc_infra", + "network_lb": "svc_infra", + "igw": "svc_infra", "internet_gateway": "svc_infra", + "nat_gateway": "svc_infra", "natgw": "svc_infra", + "service_gateway": "svc_infra", "sgw": "svc_infra", + "waf": "svc_infra", "bastion": "svc_infra", + "network_firewall": "svc_infra", + "functions": "svc_infra", "oke": "svc_infra", + "api_gateway": "svc_infra", "apigw": "svc_infra", + "monitoring": "svc_infra", "logging": "svc_infra", + "db_management": "svc_infra", "ops_insights": "svc_infra", + "notifications": "svc_infra", "events": "svc_infra", + "cloud_guard": "svc_infra", + "security_zone": "svc_infra", + "vulnerability_scanning": "svc_infra", + "object_storage": "svc_infra", "block_storage": "svc_infra", + "file_storage": "svc_infra", + "dns": "svc_infra", "traffic_management": "svc_infra", + "certificates": "svc_infra", "apm": "svc_infra", + "logging_analytics": "svc_infra", + "os_management": "svc_infra", + "resource_manager": "svc_infra", + "devops": "svc_infra", + # AI Services (teal — OCI AI service category) + "gen_ai": "svc_infra", "generative_ai": "svc_infra", + "language_ai": "svc_infra", "vision_ai": "svc_infra", + "speech_ai": "svc_infra", "document_understanding": "svc_infra", + "anomaly_detection": "svc_infra", "digital_assistant": "svc_infra", + + # Database (copper #AA643B) + "adb": "svc_database", "adb_s": "svc_database", + "adb_d": "svc_database", "autonomous_db": "svc_database", + "atp": "svc_database", "adw": "svc_database", + "dbcs": "svc_database", "db_system": "svc_database", + "exadata": "svc_database", "exacs": "svc_database", + "nosql": "svc_database", "mysql": "svc_database", + "mysql_heatwave": "svc_database", + "postgresql": "svc_database", "opensearch": "svc_database", + "cache": "svc_database", "redis": "svc_database", + "goldengate": "svc_database", + "data_catalog": "svc_database", + # Security services that touch data tier use database copper + "data_safe": "svc_database", + "vault": "svc_database", + + # Integration (purple #804998) + "drg": "svc_integration", + "dynamic_routing_gateway": "svc_integration", + "streaming": "svc_integration", "kafka": "svc_integration", + "queue": "svc_integration", "oci_queue": "svc_integration", + "oic": "svc_integration", "integration_cloud": "svc_integration", + "fastconnect": "svc_integration", + "service_connector_hub": "svc_integration", + "vpn": "svc_integration", + + # Special + "dormant": "svc_dormant", + "standby": "svc_dormant", + "legacy": "svc_legacy", + "external": "svc_legacy", +} + +# ============================================================ +# AWS / GCP / AZURE ICON STYLES (mxgraph stencil libraries) +# ============================================================ +# draw.io includes official cloud provider shape libraries: +# AWS: mxgraph.aws4.* (orange #ED7100, dark text #232F3E) +# GCP: mxgraph.gcp2.* (blue #4285F4) +# Azure: mxgraph.azure.* (blue #0078D4) +# +# These are used when a service inside a cloud container specifies +# a cloud_icon field (e.g., cloud_icon: "aws4.ec2"). + +# AWS service type → (resIcon stencil path, fillColor) +AWS_ICONS = { + "ec2": ("mxgraph.aws4.ec2", "#ED7100"), + "eks": ("mxgraph.aws4.eks", "#ED7100"), + "ecs": ("mxgraph.aws4.ecs", "#ED7100"), + "lambda": ("mxgraph.aws4.lambda_function", "#ED7100"), + "rds": ("mxgraph.aws4.rds", "#C925D1"), + "aurora": ("mxgraph.aws4.aurora", "#C925D1"), + "dynamodb": ("mxgraph.aws4.dynamodb", "#C925D1"), + "s3": ("mxgraph.aws4.s3", "#3F8624"), + "elb": ("mxgraph.aws4.elb", "#8C4FFF"), + "alb": ("mxgraph.aws4.application_load_balancer", "#8C4FFF"), + "cloudfront": ("mxgraph.aws4.cloudfront", "#8C4FFF"), + "vpc": ("mxgraph.aws4.vpc", "#8C4FFF"), + "direct_connect":("mxgraph.aws4.direct_connect", "#8C4FFF"), + "route53": ("mxgraph.aws4.route_53", "#8C4FFF"), + "sqs": ("mxgraph.aws4.sqs", "#E7157B"), + "sns": ("mxgraph.aws4.sns", "#E7157B"), + "kinesis": ("mxgraph.aws4.kinesis", "#8C4FFF"), + "api_gateway": ("mxgraph.aws4.api_gateway", "#E7157B"), + "cognito": ("mxgraph.aws4.cognito", "#DD344C"), + "iam": ("mxgraph.aws4.iam", "#DD344C"), + "cloudwatch": ("mxgraph.aws4.cloudwatch_2", "#E7157B"), + "sagemaker": ("mxgraph.aws4.sagemaker", "#01A88D"), + "bedrock": ("mxgraph.aws4.bedrock", "#01A88D"), +} + +# GCP service type → (shape path, fillColor) +GCP_ICONS = { + "gce": ("mxgraph.gcp2.compute_engine", "#4285F4"), + "gke": ("mxgraph.gcp2.google_kubernetes_engine", "#4285F4"), + "cloud_sql": ("mxgraph.gcp2.cloud_sql", "#4285F4"), + "bigquery": ("mxgraph.gcp2.bigquery", "#4285F4"), + "cloud_storage": ("mxgraph.gcp2.cloud_storage", "#4285F4"), + "cloud_run": ("mxgraph.gcp2.cloud_run", "#4285F4"), + "cloud_functions":("mxgraph.gcp2.cloud_functions", "#4285F4"), + "pub_sub": ("mxgraph.gcp2.cloud_pubsub", "#4285F4"), +} + +def _aws_icon_style(service_type: str) -> Optional[str]: + """Return draw.io style string for an AWS service icon, or None.""" + entry = AWS_ICONS.get(service_type) + if not entry: + return None + stencil, fill = entry + return ( + f"outlineConnect=0;fontColor=#232F3E;gradientColor=none;" + f"fillColor={fill};strokeColor=none;dashed=0;" + f"verticalLabelPosition=bottom;verticalAlign=top;" + f"align=center;html=1;fontSize=10;fontStyle=0;" + f"aspect=fixed;" + f"shape=mxgraph.aws4.resourceIcon;resIcon={stencil};" + ) + +def _gcp_icon_style(service_type: str) -> Optional[str]: + """Return draw.io style string for a GCP service icon, or None.""" + entry = GCP_ICONS.get(service_type) + if not entry: + return None + stencil, fill = entry + return ( + f"outlineConnect=0;fontColor=#232F3E;gradientColor=none;" + f"fillColor={fill};strokeColor=none;dashed=0;" + f"verticalLabelPosition=bottom;verticalAlign=top;" + f"align=center;html=1;fontSize=10;fontStyle=0;" + f"aspect=fixed;" + f"shape={stencil};" + ) + + +# Map observability bar labels to icon service types +OBS_TYPE_MAP = { + "monitoring": "monitoring", + "logging": "logging", + "logging analytics": "logging_analytics", + "apm": "apm", + "db management": "db_management", + "ops insights": "ops_insights", + "notifications": "notifications", + "events": "events", + "auditing": "monitoring", # uses monitoring icon as closest match +} + +# Unicode circled numbers for flow badges ① ② ③ ... ⑳ +_CIRCLED_NUMS = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳" + + +class OCIDiagramGenerator: + """Generate .drawio files using drawpyo for structure + custom icon injection. + + drawpyo handles: File, Page, Object (containers/groups), Edge (connections). + Custom code handles: injecting multi-cell OCI stencil icons from oci-icons.json. + """ + + # Lazily loaded icon cache from kb/diagram/oci-icons.json + ICON_CACHE = None + + @classmethod + def _load_icon_cache(cls): + """Load icon data from oci-icons.json if available.""" + if cls.ICON_CACHE is not None: + return + # Try multiple paths: relative to this script, then cwd + candidates = [ + os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "kb", "diagram", "oci-icons.json"), + os.path.join("kb", "diagram", "oci-icons.json"), + ] + for path in candidates: + if os.path.exists(path): + with open(path, "r", encoding="utf-8") as f: + cls.ICON_CACHE = json.load(f) + return + # Not found — fall back to empty dict (colored rectangles) + cls.ICON_CACHE = {} + + def __init__(self): + self._load_icon_cache() + self.file = drawpyo.File() + self.page = drawpyo.Page(file=self.file) + self._objects = {} # cell_id (str) -> drawpyo Object + self._abs_positions = {} # cell_id -> (abs_x, abs_y) for relative→absolute conversion + self._raw_cells = [] # raw mxCell XML strings for icon stencil cells + self._edge_extras = {} # drawpyo edge id -> extra style attrs string + self._edge_objects = {} # drawpyo edge id -> (source_id, target_id) for badge placement + self._flow_badges = [] # list of (flow_order, edge_id) for deferred badge creation + self._id_counter = 1000 # counter for auto-generated cell IDs + # Track counts for summary + self._container_count = 0 + self._service_count = 0 + self._connection_count = 0 + + def _next_id(self) -> str: + self._id_counter += 1 + return str(self._id_counter) + + @staticmethod + def _escape(text: str) -> str: + """Escape XML special characters and convert newlines.""" + if not text: + return "" + return (text + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + .replace("\n", " ")) + + def _get_svc_style(self, service_type: str, font_size: Optional[int] = None) -> str: + """Get the style string for a service type.""" + category = SVC_CATEGORY.get(service_type, "svc_infra") + style = STYLES[category] + if font_size: + style = re.sub(r"fontSize=\d+", f"fontSize={font_size}", style) + return style + + def _create_object( + self, cell_id: str, label: str, style_str: str, + parent_id: Optional[str], x: int, y: int, w: int, h: int, + ) -> "drawpyo.diagram.Object": + """Create a drawpyo Object with OCI style, register it, and return it. + + drawpyo uses ABSOLUTE coordinates for children, but our specs use + RELATIVE coords (relative to parent). We convert by adding the + parent's absolute position to the child's relative offset. + """ + parent_obj = self._objects.get(parent_id) if parent_id else None + + # Convert relative (x, y) to absolute by adding parent's absolute position + abs_x, abs_y = x, y + if parent_obj: + parent_abs = self._abs_positions.get(parent_id, (0, 0)) + abs_x = parent_abs[0] + x + abs_y = parent_abs[1] + y + + if parent_obj: + obj = drawpyo.diagram.Object( + page=self.page, value=label, parent=parent_obj, + ) + else: + obj = drawpyo.diagram.Object(page=self.page, value=label) + obj.apply_style_string(style_str) + obj.position = (abs_x, abs_y) + obj.width = w + obj.height = h + self._objects[cell_id] = obj + # Store the absolute position for children to reference + self._abs_positions[cell_id] = (abs_x, abs_y) + return obj + + # ================================================================ + # Container methods + # ================================================================ + + def add_tenancy( + self, cell_id: str, label: str, + x: int = 30, y: int = 80, w: int = 1850, h: int = 720, + ) -> str: + """Add the outermost Tenancy container (dashed gray, no fill).""" + self._create_object(cell_id, label, STYLES["tenancy"], None, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_region( + self, cell_id: str, label: str, parent: str, + x: int = 15, y: int = 30, w: int = 1140, h: int = 670, + ) -> str: + """Add a Region container (solid warm gray fill, rounded).""" + self._create_object(cell_id, label, STYLES["region"], parent, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_ad( + self, cell_id: str, label: str, parent: str, + x: int = 15, y: int = 30, w: int = 1100, h: int = 640, + ) -> str: + """Add an Availability Domain container (medium gray fill).""" + self._create_object(cell_id, label, STYLES["ad"], parent, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_vcn( + self, cell_id: str, label: str, parent: str, + x: int = 15, y: int = 30, w: int = 1100, h: int = 600, + ) -> str: + """Add a VCN container (dashed burnt orange border, no fill).""" + self._create_object(cell_id, label, STYLES["vcn"], parent, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_subnet( + self, cell_id: str, label: str, parent: str, + x: int = 15, y: int = 30, w: int = 280, h: int = 400, + ) -> str: + """Add a Subnet container (dashed orange border, no fill).""" + self._create_object(cell_id, label, STYLES["subnet"], parent, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_compartment( + self, cell_id: str, label: str, parent: str, + x: int = 15, y: int = 30, w: int = 400, h: int = 300, + ) -> str: + """Add a Compartment / Fault Domain / Tier container (dashed gray).""" + self._create_object(cell_id, label, STYLES["compartment"], parent, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_onprem( + self, cell_id: str, label: str, + x: int = 30, y: int = 850, w: int = 650, h: int = 120, + ) -> str: + """Add an On-Premises container (outside tenancy, dashed gray).""" + self._create_object(cell_id, label, STYLES["compartment"], None, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_cloud( + self, cell_id: str, label: str, + x: int = 10, y: int = 80, w: int = 200, h: int = 300, + provider: str = "generic", + ) -> str: + """Add an external cloud container (AWS, Azure, GCP). + + provider: "azure" → solid blue border (Oracle multicloud style) + "gcp" → solid green border + "aws" → dashed muted border (default) + "generic" → dashed muted border + """ + style_map = { + "azure": "azure_container", + "gcp": "gcp_container", + } + style_key = style_map.get(provider, "cloud_container") + self._create_object(cell_id, label, STYLES[style_key], None, x, y, w, h) + self._container_count += 1 + return cell_id + + def add_external( + self, cell_id: str, label: str, + x: int = 30, y: int = 30, w: int = 50, h: int = 80, + icon: str = "user", + ) -> str: + """Add an external actor with icon. + + icon: "user" — single person silhouette (circle head + body arc) in teal + "users" — two person silhouettes side by side (group icon) + "internet" — cloud stencil + "generic" — colored rectangle + """ + if icon in ("user", "users"): + # Create an invisible group for connectivity + label + self._create_object(cell_id, "", STYLES["external_user"], None, x, y, w, h) + group_placeholder = f"__DRAWPYO_{cell_id}__" + + if icon == "users": + # Two person silhouettes side by side (group icon) + # Left person (slightly behind, offset left) + cx_l = w // 2 - 10 + self._raw_cells.append( + f'' + f'' + f'' + ) + self._raw_cells.append( + f'' + f'' + f'' + ) + # Right person (foreground, offset right) + cx_r = w // 2 + 10 + self._raw_cells.append( + f'' + f'' + f'' + ) + self._raw_cells.append( + f'' + f'' + f'' + ) + else: + # Single person silhouette + cx = w // 2 + self._raw_cells.append( + f'' + f'' + f'' + ) + self._raw_cells.append( + f'' + f'' + f'' + ) + + # Label below the icon + label_id = f"{cell_id}_label" + label_style = ( + "text;html=1;whiteSpace=wrap;align=center;verticalAlign=top;" + "fontFamily=Oracle Sans;fontSize=11;fontColor=#312D2A;" + "strokeColor=none;fillColor=none;" + ) + self._create_object( + label_id, label, label_style, cell_id, + -10, 48, w + 20, 20, + ) + else: + style_key = f"external_{icon}" if f"external_{icon}" in STYLES else "external_generic" + self._create_object(cell_id, label, STYLES[style_key], None, x, y, w, h) + return cell_id + + # ================================================================ + # Service block methods + # ================================================================ + + @staticmethod + def _is_icon_label_cell(style: str) -> bool: + """Detect the label cell within an OCI Library icon.""" + return ("verticalAlign=middle" in style + and ("align=center" in style or "align=left" in style) + and "shape=stencil(" in style + and "fillColor=none" in style) + + def add_service( + self, cell_id: str, label: str, service_type: str, parent: str, + x: int = 20, y: int = 35, w: int = 150, h: int = 50, + font_size: Optional[int] = None, + ) -> str: + """Add a service block with icon (if available) or colored rectangle fallback.""" + icon_entry = self.ICON_CACHE.get(service_type) if self.ICON_CACHE else None + if icon_entry: + return self._add_service_with_icon( + cell_id, label, service_type, parent, icon_entry, + x, y, w, h, font_size, + ) + # Fallback: colored rectangle via drawpyo Object + style = self._get_svc_style(service_type, font_size) + self._create_object(cell_id, label, style, parent, x, y, w, h) + self._service_count += 1 + return cell_id + + def _add_service_with_icon( + self, cell_id: str, label: str, service_type: str, parent: str, + icon_entry: dict, + x: int, y: int, w: int, h: int, + font_size: Optional[int] = None, + ) -> str: + """Embed official OCI icon cells inside a drawpyo group Object. + + Architecture: + - The group Object (cell_id) covers ONLY the icon area — its height equals + the scaled icon height, NOT the full service block height. This ensures + draw.io routes connection arrows to the icon boundary, not the label area. + - The label is a SIBLING of the icon group (child of the same parent), placed + immediately below the group. It does not affect connection geometry. + + Benefit: connections from/to services visually terminate at the icon itself, + which matches Oracle Architecture Center reference diagram style. + """ + icon_w = icon_entry["w"] + icon_h = icon_entry["h"] + icon_cells = icon_entry["cells"] + + # Label height: proportional to actual line count (no over-allocation). + # ~15px per line at 11pt Oracle Sans, minimum 20px for single-line labels. + n_lines = label.count('\n') + 1 + label_h = max(n_lines * 16 + 4, 20) + + # Icon area = full spec height minus label height + icon_area_h = max(h - label_h, 20) + + # Scale icon to fit the icon area (never upscale beyond original size) + scale = min(w / icon_w, icon_area_h / icon_h, 1.0) + scaled_w = icon_w * scale + scaled_h = icon_h * scale + + # Icon group height = scaled icon height only. + # Connections terminate here — at the visible icon boundary. + icon_group_h = max(int(scaled_h), 20) + + group_style = ( + "group;fillColor=none;strokeColor=none;pointerEvents=1;" + "connectable=1;" + ) + self._create_object(cell_id, "", group_style, parent, x, y, w, icon_group_h) + + # Build ID mapping: original cell id -> unique cell id + original_ids = set() + for cell_xml in icon_cells: + cell_elem = ET.fromstring(cell_xml) + original_ids.add(cell_elem.get("id")) + + id_map = {} + for orig_id in sorted(original_ids, + key=lambda x_: int(x_) if x_.isdigit() else 0): + id_map[orig_id] = f"{cell_id}_i{orig_id}" + + # Center the icon horizontally within the group + icon_offset_x = (w - scaled_w) / 2 + + # Use a placeholder for the group's drawpyo ID — replaced in to_xml() + group_placeholder = f"__DRAWPYO_{cell_id}__" + + # Emit icon cells (skip the library's built-in label cell) + for cell_xml in icon_cells: + cell_elem = ET.fromstring(cell_xml) + orig_id = cell_elem.get("id") + orig_parent = cell_elem.get("parent", "1") + style = cell_elem.get("style", "") + + # Skip the icon's built-in label cell — we replace with our own + if self._is_icon_label_cell(style): + continue + + new_id = id_map[orig_id] + + # Remap parent + if orig_parent == "1": + new_parent = group_placeholder + elif orig_parent in id_map: + new_parent = id_map[orig_parent] + else: + new_parent = group_placeholder + + cell_elem.set("id", new_id) + cell_elem.set("parent", new_parent) + + # NOTE: Stencil icons are ALWAYS teal (#2D5967) per OCI visual style. + # Copper (#AA643B) is only for fallback rectangles (no icon available). + + # Scale and offset geometry + geo = cell_elem.find("mxGeometry") + if geo is not None: + for attr in ("x", "y", "width", "height"): + val = geo.get(attr) + if val is not None: + try: + geo.set(attr, str(float(val) * scale)) + except ValueError: + pass + # Offset x for centering (only top-level icon cells parented to group) + if orig_parent == "1": + cur_x = float(geo.get("x", "0")) + geo.set("x", str(cur_x + icon_offset_x)) + + cell_str = ET.tostring(cell_elem, encoding="unicode") + self._raw_cells.append(cell_str) + + # Label is a SIBLING of the icon group (child of `parent`, not of `cell_id`). + # This keeps connection geometry clean: arrows connect to the icon group only. + # Oracle ref arch style: 11pt, charcoal (#312D2A), centered below icon. + fs = font_size or 11 + label_style = ( + f"text;html=1;whiteSpace=wrap;overflow=visible;align=center;verticalAlign=top;" + f"fontFamily=Oracle Sans;fontSize={fs};fontColor=#312D2A;" + f"strokeColor=none;fillColor=none;spacingTop=2;" + ) + # Label width = icon width (centered); overflow=visible handles longer text. + label_w = w + label_id = f"{cell_id}_label" + self._create_object( + label_id, label, label_style, parent, + x, y + icon_group_h + 2, label_w, label_h, + ) + + self._service_count += 1 + return cell_id + + def add_obs_bar( + self, cell_id: str, label: str, parent: str, + x: int = 20, y: int = 640, w: int = 78, h: int = 22, + ) -> str: + """Add an observability bar element (small teal pill).""" + self._create_object(cell_id, label, STYLES["obs_bar"], parent, x, y, w, h) + return cell_id + + # ================================================================ + # NSG corner badge — Oracle visual signature + # ================================================================ + + # ================================================================ + # Connection methods + # ================================================================ + + def add_connection( + self, cell_id: str, label: Optional[str], conn_type: str, + source: str, target: str, + waypoints: Optional[list] = None, + flow_order: Optional[int] = None, + ) -> str: + """Add a connection arrow between two elements using drawpyo Edge. + + flow_order: if set (1-20), renders a teal circle badge with the number + on the midpoint of the edge for visual storytelling. + """ + src_obj = self._objects.get(source) + tgt_obj = self._objects.get(target) + if not src_obj or not tgt_obj: + # Source or target not found — skip silently + return cell_id + + edge = drawpyo.diagram.Edge( + page=self.page, source=src_obj, target=tgt_obj, + label=label or "", + ) + + # Base OCI style: open arrows, charcoal color, orthogonal routing + edge.line_end_target = "open" + edge.line_end_source = "none" + edge.strokeColor = "#312D2A" + edge.strokeWidth = 1 + edge.rounded = True + edge.endSize = 6 + edge.waypoints = "orthogonal" + + # Font styling via text_format (drawpyo's supported approach) + edge.text_format.fontFamily = "Oracle Sans" + edge.text_format.fontSize = 10 + edge.text_format.fontColor = "#312D2A" + edge.text_format.labelBackgroundColor = "#FFFFFF" + + # Extra style attributes that drawpyo Edge doesn't support natively. + # labelBackgroundColor: white so edge labels are readable and the line + # is hidden behind the text (confirmed in Oracle ref arch diagrams). + # labelBorderColor=none: no border box around the label. + extra_style = ( + "elbow=vertical;endFill=0;startFill=0;" + "labelBackgroundColor=#FFFFFF;labelBorderColor=none;" + "fontFamily=Oracle Sans;fontSize=10;fontColor=#312D2A;" + ) + + # Customize per connection type + if conn_type == "fastconnect": + # Bidirectional + edge.line_end_source = "open" + elif conn_type == "adg": + # Dashed (ADG replication) — toolkit pattern: 8 4 + edge.pattern = "dashed_small" + extra_style += "dashPattern=8 4;" + elif conn_type == "migration": + # Dashed (migration) — toolkit pattern: 8 4 + edge.pattern = "dashed_medium" + extra_style += "dashPattern=8 4;" + elif conn_type == "etl": + # Dashed (ETL/streaming) — toolkit pattern: 6 4 + edge.pattern = "dashed_small" + extra_style += "dashPattern=6 4;" + # "standard" and "db" use solid line defaults — no changes needed + + # Add waypoints if provided + if waypoints: + for wx, wy in waypoints: + edge.add_point(wx, wy) + + # Store extra style for post-processing (keyed by drawpyo's edge id) + self._edge_extras[str(edge.id)] = extra_style + + # Track edge endpoints for flow badge placement + self._edge_objects[str(edge.id)] = (source, target) + + # Queue flow badge if requested + if flow_order is not None and 1 <= flow_order <= 20: + self._flow_badges.append((flow_order, source, target)) + + self._objects[cell_id] = src_obj # register for potential referencing + self._connection_count += 1 + return cell_id + + # ================================================================ + # Flow badges — numbered circles on connection midpoints + # ================================================================ + + def _create_flow_badges(self): + """Create teal circle badges with flow numbers near connection sources. + + Called after all objects and connections are registered, before save. + Badge is positioned at 25% of the path from source (not midpoint) to + avoid overlapping with destination icons. A small perpendicular offset + prevents badges from sitting on top of the arrow line itself. + """ + badge_size = 18 + for flow_order, source_id, target_id in self._flow_badges: + src_pos = self._abs_positions.get(source_id) + tgt_pos = self._abs_positions.get(target_id) + if not src_pos or not tgt_pos: + continue + + src_obj = self._objects.get(source_id) + tgt_obj = self._objects.get(target_id) + if not src_obj or not tgt_obj: + continue + + # Compute center of source and target + src_cx = src_pos[0] + src_obj.width / 2 + src_cy = src_pos[1] + src_obj.height / 2 + tgt_cx = tgt_pos[0] + tgt_obj.width / 2 + tgt_cy = tgt_pos[1] + tgt_obj.height / 2 + + # Position at 25% from source (closer to source, avoids target overlap) + t = 0.25 + mid_x = int(src_cx + (tgt_cx - src_cx) * t - badge_size / 2) + mid_y = int(src_cy + (tgt_cy - src_cy) * t - badge_size / 2) + + # Small perpendicular offset to avoid sitting on the arrow line + dx = tgt_cx - src_cx + dy = tgt_cy - src_cy + length = max((dx**2 + dy**2) ** 0.5, 1) + # Offset 14px perpendicular to the connection direction + offset_x = int(-dy / length * 14) + offset_y = int(dx / length * 14) + mid_x += offset_x + mid_y += offset_y + + # Use circled number character or plain number + if flow_order <= len(_CIRCLED_NUMS): + badge_label = str(flow_order) + else: + badge_label = str(flow_order) + + badge_id = f"flow_{flow_order}" + self._create_object( + badge_id, badge_label, STYLES["flow_badge"], + None, mid_x, mid_y, badge_size, badge_size, + ) + + # ================================================================ + # Title + # ================================================================ + + def add_title( + self, text: str, + x: int = 30, y: int = 10, w: int = 800, h: int = 30, + ) -> str: + """Add an italic title label.""" + self._create_object("title", text, STYLES["title"], None, x, y, w, h) + return "title" + + # ================================================================ + # Output — merge drawpyo XML + injected raw icon cells + # ================================================================ + + def to_xml(self) -> str: + """Generate the complete .drawio XML. + + 1. Get drawpyo's XML via file.xml property + 2. Replace drawpyo's default mxfile/diagram attributes with OCI ones + 3. Inject extra style attributes into edge cells + 4. Inject raw icon stencil cells before + 5. Replace group parent placeholders with actual drawpyo IDs + 6. Sanitize: replace #ffffff with #FCFBFA (no pure white in OCI palette) + """ + xml = self.file.xml + + # Replace drawpyo's default mxfile attributes with OCI-branded ones + xml = re.sub( + r']*>', + '', + xml, + ) + + # Replace drawpyo's default diagram attributes + xml = re.sub( + r']*>', + '', + xml, + ) + + # Canvas: white background so region fill (#F0EDE9) is clearly visible + xml = re.sub( + r']*>', + '', + xml, + ) + + # Inject extra style attributes into edge cells (e.g., elbow, dashPattern) + for edge_id, extra_style in self._edge_extras.items(): + # Find the edge cell by its drawpyo ID and append extra styles + pattern = f'id="{edge_id}" style="' + if pattern in xml: + xml = xml.replace(pattern, f'id="{edge_id}" style="{extra_style}') + + # Inject raw icon stencil cells before + if self._raw_cells: + inject = "\n".join(f" {c}" for c in self._raw_cells) + xml = xml.replace("", inject + "\n ") + + # Replace placeholder parent references with actual drawpyo IDs + for cell_id, obj in self._objects.items(): + placeholder = f"__DRAWPYO_{cell_id}__" + if placeholder in xml: + xml = xml.replace(placeholder, str(obj.id)) + + # Sanitize: no pure white in OCI palette + xml = xml.replace('fill="#ffffff"', 'fill="#FCFBFA"') + xml = xml.replace('fill="#FFFFFF"', 'fill="#FCFBFA"') + xml = xml.replace("fillColor=#ffffff", "fillColor=#FCFBFA") + xml = xml.replace("fillColor=#FFFFFF", "fillColor=#FCFBFA") + + # Fix drawpyo's default dash patterns to match OCI toolkit + # drawpyo emits dashed=1 without dashPattern → draw.io defaults to 3 3 + # We ensure all dashed containers get 6 4 and dashed edges get 8 4 + # (Edge dashPatterns are already injected via _edge_extras above) + + return xml + + def save(self, filepath: str): + """Save to a .drawio file.""" + # Create flow badges before generating XML + # self._create_flow_badges() # disabled — Oracle style uses dash patterns, not numbered badges + xml = self.to_xml() + with open(filepath, 'w', encoding='utf-8') as f: + f.write(xml) + + # ================================================================ + # High-level: build from YAML spec + # ================================================================ + + @classmethod + def from_spec(cls, spec: dict) -> "OCIDiagramGenerator": + """Build a diagram from a YAML specification. + + The spec follows the OCI visual hierarchy: + + tenancy -> region(s) -> vcn(s) -> subnet(s) -> service(s) + + Plus optional on-premises, external actors, and connections. + See examples/diagram-spec.yaml for the full format. + """ + gen = cls() + + # External actors (users, internet, third-party) + for ext in spec.get("external", []): + ext_id = ext.get("id", gen._next_id()) + gen.add_external( + ext_id, + ext["label"].replace("\\n", "\n"), + x=ext.get("x", 30), y=ext.get("y", 30), + w=ext.get("w", 50), h=ext.get("h", 60), + icon=ext.get("icon", "user"), + ) + + # External cloud containers (AWS, Azure, GCP) + for cloud in spec.get("clouds", []): + cloud_id = cloud.get("id", gen._next_id()) + gen.add_cloud( + cloud_id, + cloud["label"].replace("\\n", "\n"), + x=cloud.get("x", 10), y=cloud.get("y", 80), + w=cloud.get("w", 200), h=cloud.get("h", 300), + provider=cloud.get("provider", "generic"), + ) + # Services inside the cloud container. + # cy=60: enough vertical room for 2-line cloud container titles at 12pt. + # Services are centered horizontally within the cloud container. + cloud_w = cloud.get("w", 200) + cy = 60 + for svc in cloud.get("services", []): + svc_id = svc.get("id", gen._next_id()) + svc_w = svc.get("w", 100) + svc_h = svc.get("h", 80) + # Center each service horizontally in the cloud container + cx = max(10, (cloud_w - svc_w) // 2) + label = svc["label"].replace("\\n", "\n") + cloud_icon = svc.get("cloud_icon") + if cloud_icon: + # Use cloud provider icon (AWS/GCP) via stencil style + style = _aws_icon_style(cloud_icon) or _gcp_icon_style(cloud_icon) + if style: + gen._create_object( + svc_id, label, style, cloud_id, + cx, cy, svc_w, svc_h, + ) + gen._service_count += 1 + else: + gen.add_service(svc_id, label, svc.get("type", "compute"), + cloud_id, cx, cy, svc_w, svc_h) + else: + gen.add_service(svc_id, label, svc.get("type", "compute"), + cloud_id, cx, cy, svc_w, svc_h) + cy += svc_h + 20 + + # Tenancy + tenancy_spec = spec.get("tenancy", {}) + tenancy_x = tenancy_spec.get("x", 30) + tenancy_y = tenancy_spec.get("y", 80) + tenancy_w = tenancy_spec.get("w", 1850) + tenancy_h = tenancy_spec.get("h", 720) + gen.add_tenancy( + "tenancy", + tenancy_spec.get("label", "Oracle Cloud Infrastructure"), + x=tenancy_x, y=tenancy_y, w=tenancy_w, h=tenancy_h, + ) + + # Regions + for region in tenancy_spec.get("regions", []): + is_primary = region.get("primary", False) + rx = region.get("x", 15 if is_primary else 1195) + # ry default 40: ~18px below tenancy label (12pt bold) + comfortable padding + ry = region.get("y", 40) + rw = region.get("w", 1140 if is_primary else 640) + rh = region.get("h", 670 if is_primary else 480) + + gen.add_region( + region["id"], + region["label"], + "tenancy", rx, ry, rw, rh, + ) + + + # Availability Domains (optional) + for ad in region.get("availability_domains", []): + gen.add_ad( + ad["id"], ad["label"], region["id"], + x=ad.get("x", 15), y=ad.get("y", 30), + w=ad.get("w", rw - 30), h=ad.get("h", rh - 70), + ) + + # VCNs + for vcn in region.get("vcns", []): + vcn_parent = vcn.get("parent", region["id"]) + vcn_w = vcn.get("w", rw - 30) + vcn_h = vcn.get("h", rh - 70) + gen.add_vcn( + vcn["id"], + f"VCN {vcn['label']}", + vcn_parent, + # y default 45: leaves proper gap below the region label (12pt ≈22px + padding) + x=vcn.get("x", 15), y=vcn.get("y", 45), + w=vcn_w, h=vcn_h, + ) + + # Auto-layout subnets VERTICALLY (stacked top-to-bottom). + # Matches Oracle reference architecture style: subnets span the + # full VCN width and stack vertically, one on top of another. + # (Confirmed from 11 Oracle Architecture Center reference diagrams) + # subnet_y=32: leaves ~10px gap below the VCN label (12pt bold ≈22px). + subnet_y = 32 + for subnet in vcn.get("subnets", []): + # Default width = full VCN width minus 16px padding (8px each side) + sw = subnet.get("w", vcn_w - 16) + sh = subnet.get("h", 120) + gen.add_subnet( + subnet["id"], subnet["label"], + vcn["id"], 8, subnet_y, sw, sh, + ) + + # Auto-layout services HORIZONTALLY within subnet, centered. + # Oracle style: services are centered left-to-right within each + # subnet row, not left-aligned. Single service → centered in subnet. + svc_top = 30 # vertical offset below subnet label + subnet_svcs = subnet.get("services", []) + # Compute each service width upfront to center the group + svc_widths = [svc.get("w", min(sw - 30, 150)) for svc in subnet_svcs] + total_svc_w = sum(svc_widths) + 16 * max(0, len(svc_widths) - 1) + # Center the service group; minimum 15px left padding + svc_x = max(15, (sw - total_svc_w) // 2) + for svc, svc_w in zip(subnet_svcs, svc_widths): + svc_id = svc.get("id", gen._next_id()) + svc_h = svc.get("h", 80) + label = svc["label"].replace("\\n", "\n") + gen.add_service( + svc_id, label, svc["type"], + subnet["id"], svc_x, svc_top, svc_w, svc_h, + font_size=svc.get("fontSize"), + ) + svc_x += svc_w + 16 + + subnet_y += sh + 14 + + # Gateways on left side of VCN, stacked vertically + gx = 10 + gy = 40 + for gw in vcn.get("gateways", []): + gw_id = gw.get("id", gen._next_id()) + gw_w = gw.get("w", 110) + gw_h = gw.get("h", 70) + gw_x = gw.get("x", gx) + gw_y = gw.get("y", gy) + label = gw["label"].replace("\\n", "\n") + gen.add_service( + gw_id, label, gw["type"], + vcn["id"], gw_x, gw_y, gw_w, gw_h, + ) + gy = gw_y + gw_h + 8 + + # Compartments inside region — can contain VCNs + for comp in region.get("compartments", []): + comp_id = comp.get("id", gen._next_id()) + comp_w = comp.get("w", rw - 20) + comp_h = comp.get("h", 60) + gen.add_compartment( + comp_id, comp["label"], + region["id"], + x=comp.get("x", 10), y=comp.get("y", rh - 80), + w=comp_w, h=comp_h, + ) + # VCNs inside compartment + for vcn in comp.get("vcns", []): + vcn_parent = comp_id + vcn_w = vcn.get("w", comp_w - 20) + vcn_h = vcn.get("h", comp_h - 50) + gen.add_vcn( + vcn["id"], + f"VCN {vcn['label']}", + vcn_parent, + x=vcn.get("x", 10), y=vcn.get("y", 45), + w=vcn_w, h=vcn_h, + ) + subnet_y = 32 + for subnet in vcn.get("subnets", []): + sw = subnet.get("w", vcn_w - 16) + sh = subnet.get("h", 120) + gen.add_subnet( + subnet["id"], subnet["label"], + vcn["id"], 8, subnet_y, sw, sh, + ) + svc_top = 30 + comp_svcs = subnet.get("services", []) + comp_svc_ws = [svc.get("w", min(sw - 30, 150)) for svc in comp_svcs] + total_csvc_w = sum(comp_svc_ws) + 12 * max(0, len(comp_svc_ws) - 1) + svc_x = max(15, (sw - total_csvc_w) // 2) + for svc, svc_w in zip(comp_svcs, comp_svc_ws): + svc_id = svc.get("id", gen._next_id()) + svc_h = svc.get("h", 80) + label = svc["label"].replace("\\n", "\n") + gen.add_service( + svc_id, label, svc["type"], + subnet["id"], svc_x, svc_top, svc_w, svc_h, + font_size=svc.get("fontSize"), + ) + svc_x += svc_w + 16 + subnet_y += sh + 14 + # Gateways on left side of compartment VCN, stacked vertically + gx = 10 + gy = 40 + for gw in vcn.get("gateways", []): + gw_id = gw.get("id", gen._next_id()) + gw_w = gw.get("w", 95) + gw_h = gw.get("h", 60) + gw_x = gw.get("x", gx) + gw_y = gw.get("y", gy) + label = gw["label"].replace("\\n", "\n") + gen.add_service( + gw_id, label, gw["type"], + vcn["id"], gw_x, gw_y, gw_w, gw_h, + ) + gy = gw_y + gw_h + 8 + + # Observability row at bottom of region — uses icons (only if explicitly requested) + if region.get("observability"): + obs_icon_w = 90 + obs_icon_h = 80 + ox = 20 + oy = rh - obs_icon_h - 8 + for obs_label in region["observability"]: + obs_id = gen._next_id() + obs_type = OBS_TYPE_MAP.get(obs_label.lower(), None) + if obs_type: + gen.add_service( + obs_id, obs_label, obs_type, + region["id"], ox, oy, obs_icon_w, obs_icon_h, + font_size=10, + ) + else: + gen.add_obs_bar( + obs_id, obs_label, region["id"], + ox, oy + obs_icon_h - 22, 80, 22, + ) + ox += obs_icon_w + 8 + + # On-premises + if spec.get("onprem"): + op = spec["onprem"] + op_x = op.get("x", 30) + op_y = op.get("y", tenancy_y + tenancy_h + 20) + op_w = op.get("w", 750) + op_h = op.get("h", 120) + gen.add_onprem("onprem", op.get("label", "On-Premises Data Center"), + x=op_x, y=op_y, w=op_w, h=op_h) + opx = 20 + for svc in op.get("services", []): + svc_id = svc.get("id", gen._next_id()) + svc_w = svc.get("w", 180) + svc_h = svc.get("h", 60) + label = svc["label"].replace("\\n", "\n") + gen.add_service( + svc_id, label, svc["type"], + "onprem", opx, 30, svc_w, svc_h, + ) + opx += svc_w + 12 + + # Connections + for conn in spec.get("connections", []): + conn_id = conn.get("id", gen._next_id()) + gen.add_connection( + conn_id, conn.get("label"), conn["type"], + conn["from"], conn["to"], + waypoints=conn.get("waypoints"), + flow_order=conn.get("flow_order"), + ) + + # Title — centered across the full diagram width + if spec.get("title"): + total_w = tenancy_x + tenancy_w + gen.add_title(spec["title"], x=0, y=8, w=total_w, h=35) + + return gen + + +# ============================================================ +# CLI +# ============================================================ + +def main(): + parser = argparse.ArgumentParser( + description="Generate OCI architecture .drawio diagrams from YAML specs" + ) + parser.add_argument( + "--spec", required=True, + help="Path to YAML spec file describing the architecture", + ) + parser.add_argument( + "--output", default="architecture.drawio", + help="Output .drawio file path (default: architecture.drawio)", + ) + args = parser.parse_args() + + with open(args.spec, 'r') as f: + spec = yaml.safe_load(f) + + gen = OCIDiagramGenerator.from_spec(spec) + gen.save(args.output) + + # Print summary + print(f"Generated: {args.output}") + print(f" Containers: {gen._container_count}") + print(f" Service blocks: {gen._service_count}") + print(f" Connections: {gen._connection_count}") + print(f" Flow badges: {len(gen._flow_badges)}") + print(f" Raw icon cells: {len(gen._raw_cells)}") + print(f" Registered objects: {len(gen._objects)}") + + +if __name__ == "__main__": + main() diff --git a/tools/oci_icon_extractor.py b/tools/oci_icon_extractor.py new file mode 100644 index 0000000..e4f56d7 --- /dev/null +++ b/tools/oci_icon_extractor.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +""" +OCI Icon Extractor + +Reads the OCI Library.xml (draw.io library), decodes each icon's embedded XML, +and saves a JSON mapping file (oci-icons.json) for use by oci_diagram_gen.py. + +Usage: + python3 tools/oci_icon_extractor.py + +Output: + kb/diagram/oci-icons.json +""" + +import json +import re +import base64 +import zlib +import urllib.parse +import os +import sys +import xml.etree.ElementTree as ET +from typing import List + + +# Map from library index to (list of service_type keys) +# The index corresponds to the icon's position in the OCI Library.xml JSON array. +ICON_KEY_MAP = { + 3: ["vm", "compute"], + 2: ["functions"], + 5: ["flex_vm"], + 7: ["block_storage"], + 10: ["file_storage"], + 11: ["object_storage"], + 17: ["vcn_icon"], + 18: ["load_balancer"], + 19: ["flexible_lb"], + 30: ["drg", "dynamic_routing_gateway"], + 31: ["sgw", "service_gateway"], + 32: ["igw", "internet_gateway"], + 33: ["natgw", "nat_gateway"], + 36: ["dbcs", "db_system"], + 37: ["autonomous_db", "adb", "adb_s", "atp", "adw"], + 38: ["adb_d"], + 43: ["exadata", "exacs"], + 45: ["data_safe"], + 46: ["nosql"], + 47: ["mysql"], + 49: ["opensearch"], + 51: ["goldengate"], + 65: ["streaming", "kafka"], + 66: ["service_connector_hub"], + 74: ["oke"], + 77: ["apex"], + 79: ["apigw", "api_gateway"], + 82: ["notifications"], + 97: ["cloud_guard"], + 101: ["network_firewall"], + 102: ["waf"], + 105: ["vault"], + 106: ["bastion"], + 114: ["nsg"], + 116: ["apm"], + 117: ["logging"], + 120: ["monitoring"], + 123: ["logging_analytics"], + 124: ["events"], + 125: ["ops_insights"], + 127: ["queue", "oci_queue"], + 158: ["db_management"], +} + + +def decode_icon_xml(xml_encoded: str) -> str: + """Decode a draw.io library icon's XML: base64 -> zlib decompress -> URL decode.""" + decoded = base64.b64decode(xml_encoded) + decompressed = zlib.decompress(decoded, -15) + return urllib.parse.unquote(decompressed.decode("utf-8")) + + +def extract_icon_cells(xml_str: str) -> List[str]: + """Extract mxCell elements from decoded icon XML, excluding root cells 0 and 1.""" + root = ET.fromstring(xml_str) + cells = root.findall(".//mxCell") + result = [] + for cell in cells: + cell_id = cell.get("id", "") + if cell_id in ("0", "1"): + continue + result.append(ET.tostring(cell, encoding="unicode")) + return result + + +def main(): + # Resolve paths relative to project root + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(script_dir) + library_path = os.path.join(project_root, "kb", "diagram", "OCI Library.xml") + output_path = os.path.join(project_root, "kb", "diagram", "oci-icons.json") + + if not os.path.exists(library_path): + print(f"Error: OCI Library.xml not found at {library_path}", file=sys.stderr) + sys.exit(1) + + with open(library_path, "r", encoding="utf-8") as f: + content = f.read() + + # Parse JSON array from ... + m = re.search(r"(.*?)", content, re.DOTALL) + if not m: + print("Error: Could not find tags in OCI Library.xml", file=sys.stderr) + sys.exit(1) + + icons = json.loads(m.group(1)) + print(f"Loaded {len(icons)} icons from OCI Library.xml") + + # Build the output mapping + icon_data = {} + processed = 0 + + for idx, keys in ICON_KEY_MAP.items(): + if idx >= len(icons): + print(f" Warning: index {idx} out of range (max {len(icons) - 1}), skipping") + continue + + icon = icons[idx] + title = icon.get("title", f"Icon_{idx}") + w = icon.get("w", 84) + h = icon.get("h", 109) + + # Decode the XML + try: + xml_str = decode_icon_xml(icon["xml"]) + cells = extract_icon_cells(xml_str) + except Exception as e: + print(f" Warning: failed to decode icon [{idx}] {title}: {e}") + continue + + entry = { + "title": title, + "w": w, + "h": h, + "cells": cells, + } + + # Map all keys to this entry + for key in keys: + icon_data[key] = entry + + processed += 1 + + # Write output + with open(output_path, "w", encoding="utf-8") as f: + json.dump(icon_data, f, indent=2, ensure_ascii=False) + + unique_icons = processed + total_keys = len(icon_data) + print(f"Extracted {unique_icons} unique icons mapped to {total_keys} service type keys") + print(f"Output: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/tools/oci_output.py b/tools/oci_output.py new file mode 100644 index 0000000..a421141 --- /dev/null +++ b/tools/oci_output.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Output Orchestrator + +Routes output generation based on the architect's format selection. +Supports: deck (default), deck+drawio, deck+doc, deck+xlsx, deck+pdf, +full, pdf, doc only. + +Usage: + python oci_output.py --spec proposal.yaml --format deck --output-dir ./output + python oci_output.py --spec proposal.yaml --format full --output-dir ./output +""" + +import yaml +import argparse +import os +import sys +from pathlib import Path + +# Output format definitions +FORMATS = { + "deck": ["pptx"], + "deck+drawio": ["pptx", "drawio"], + "deck+doc": ["pptx", "docx"], + "deck+xlsx": ["pptx", "xlsx"], + "deck+pdf": ["pptx", "pdf"], + "full": ["pptx", "drawio", "docx", "xlsx", "pdf"], + "doc": ["docx"], + "pdf": ["pdf"], +} + +GENERATORS = { + "pptx": { + "module": "oci_deck_gen", + "class": "OCIDeckGenerator", + "method": "from_spec", + "description": "Slide deck (.pptx)", + }, + "drawio": { + "module": "oci_diagram_gen", + "class": "OCIDiagramGenerator", + "method": "from_spec", + "description": "Architecture diagram (.drawio)", + }, + "docx": { + "module": "oci_doc_gen", + "class": "OCIDocGenerator", + "method": "from_spec", + "description": "Technical document (.docx)", + "optional": True, + }, + "xlsx": { + "module": "oci_cost_gen", + "class": "OCICostGenerator", + "method": "from_spec", + "description": "Cost spreadsheet (.xlsx)", + "optional": True, + }, + "pdf": { + "module": "oci_pdf_gen", + "class": "OCIPDFGenerator", + "method": "from_spec", + "description": "Customer-facing PDF (.pdf)", + }, +} + + +def resolve_format(format_str: str) -> list: + """Resolve a format string to a list of output types.""" + normalized = format_str.lower().strip().replace(" ", "") + # Handle common variations + for key in FORMATS: + if normalized == key.replace("+", "").replace(" ", ""): + return FORMATS[key] + if normalized in FORMATS: + return FORMATS[normalized] + # Default to deck + print(f"Warning: Unknown format '{format_str}', defaulting to 'deck'") + return FORMATS["deck"] + + +def generate_output(spec: dict, output_type: str, output_dir: str, + base_name: str) -> str | None: + """Generate a single output type. Returns the output path or None.""" + gen_config = GENERATORS.get(output_type) + if not gen_config: + print(f" Skip: No generator for .{output_type}") + return None + + output_path = os.path.join(output_dir, f"{base_name}.{output_type}") + + try: + # Dynamic import + module = __import__(gen_config["module"]) + gen_class = getattr(module, gen_config["class"]) + factory = getattr(gen_class, gen_config["method"]) + gen = factory(spec) + gen.save(output_path) + print(f" Generated: {output_path}") + return output_path + except ImportError: + if gen_config.get("optional"): + print(f" Skip: {gen_config['description']} " + f"(generator not available)") + return None + raise + except Exception as e: + print(f" Error generating {gen_config['description']}: {e}") + return None + + +def orchestrate(spec_path: str, format_str: str = "deck", + output_dir: str = ".", base_name: str = None) -> list: + """Main orchestration: load spec, generate all requested outputs.""" + with open(spec_path, 'r') as f: + spec = yaml.safe_load(f) + + if not base_name: + base_name = Path(spec_path).stem + + os.makedirs(output_dir, exist_ok=True) + + output_types = resolve_format(format_str) + print(f"Output format: {format_str}") + print(f"Generating: {', '.join(f'.{t}' for t in output_types)}") + print(f"Output directory: {output_dir}") + print() + + results = [] + for output_type in output_types: + result = generate_output(spec, output_type, output_dir, base_name) + if result: + results.append(result) + + print(f"\nDone. Generated {len(results)} file(s).") + return results + + +def main(): + parser = argparse.ArgumentParser( + description="OCI Deal Accelerator — Output Orchestrator" + ) + parser.add_argument( + "--spec", required=True, + help="Path to YAML spec file", + ) + parser.add_argument( + "--format", default="deck", + choices=list(FORMATS.keys()), + help="Output format selection (default: deck)", + ) + parser.add_argument( + "--output-dir", default=".", + help="Output directory (default: current directory)", + ) + parser.add_argument( + "--name", default=None, + help="Base name for output files (default: spec filename)", + ) + args = parser.parse_args() + + # Add tools/ to path so generators can be imported + tools_dir = os.path.dirname(os.path.abspath(__file__)) + if tools_dir not in sys.path: + sys.path.insert(0, tools_dir) + + orchestrate(args.spec, args.format, args.output_dir, args.name) + + +if __name__ == "__main__": + main() diff --git a/tools/oci_pdf_gen.py b/tools/oci_pdf_gen.py new file mode 100644 index 0000000..a82c0b9 --- /dev/null +++ b/tools/oci_pdf_gen.py @@ -0,0 +1,1184 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Customer-Facing PDF Generator + +Produces a branded PDF document using Oracle Redwood design system colors +and typography. This is the CUSTOMER-FACING output — all internal KB +references, field finding IDs, gotcha codes, and internal notes are +stripped automatically. + +Usage: + python oci_pdf_gen.py --spec proposal-data.yaml --output proposal.pdf + python oci_pdf_gen.py --spec proposal-data.yaml --output proposal.pdf --diagram arch.png + +Or import and use programmatically: + from oci_pdf_gen import OCIPDFGenerator + gen = OCIPDFGenerator.from_spec(spec) + gen.save("proposal.pdf") +""" + +import re +import yaml +import argparse +from datetime import datetime +from pathlib import Path +from typing import Optional + +from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import inch, mm +from reportlab.lib.colors import HexColor, white, black +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT +from reportlab.platypus import ( + SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, + PageBreak, Image, KeepTogether, HRFlowable, +) +from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate, Frame +from reportlab.lib.pagesizes import letter + + +# ============================================================ +# Oracle Redwood Design System Colors +# ============================================================ + +class Colors: + DARK_BG = HexColor("#312D2A") + PRIMARY_TEXT = HexColor("#312D2A") + SECONDARY_TEXT = HexColor("#6F6964") + WARM_BG = HexColor("#FCFBFA") + ORACLE_RED = HexColor("#C74634") + GOLD = HexColor("#FACD62") + TEAL = HexColor("#2C5967") + SAGE = HexColor("#759C6C") + FOREST = HexColor("#2B6242") + MUTED_TEAL = HexColor("#94AFAF") + BURNT_ORANGE = HexColor("#AE562C") + COPPER = HexColor("#AA643B") + PURPLE = HexColor("#804998") + SUCCESS = HexColor("#2B6242") + WARNING = HexColor("#AE562C") + ERROR = HexColor("#C74634") + TABLE_ALT_ROW = HexColor("#F5F4F2") + TABLE_HEADER_BG = HexColor("#2C5967") + WHITE = HexColor("#FFFFFF") + LIGHT_GRAY = HexColor("#E8E6E3") + + +# ============================================================ +# Internal reference sanitizer +# ============================================================ + +# Patterns that identify internal-only content +_INTERNAL_PATTERNS = [ + r'FK-\d{3}', # Field knowledge IDs + r'FF-\d{6}-\d{3}', # Field finding IDs + r'\bkb/\S+', # KB file paths + r'\bgotcha[s]?\b', # gotcha references + r'field.?finding[s]?', # field finding mentions + r'confidence:\s*(validated|observed|reported|inferred)', + r'contributor:\s*\{[^}]+\}', # contributor blocks + r'severity:\s*(HIGH|MEDIUM|LOW)\s*$', # raw severity tags + r'SR#\s*\d+', # Oracle SR numbers +] +_INTERNAL_RE = re.compile('|'.join(_INTERNAL_PATTERNS), re.IGNORECASE) + + +def sanitize_text(text: str) -> str: + """Remove internal KB references from customer-facing text.""" + if not text: + return text + # Remove lines that are purely internal references + lines = text.split('\n') + cleaned = [] + for line in lines: + # Skip lines that are just internal IDs + stripped = line.strip() + if stripped and re.match(r'^(FK-\d{3}|FF-\d{6}-\d{3})\b', stripped): + continue + # Remove inline internal refs + line = re.sub(r'\(FK-\d{3}\)', '', line) + line = re.sub(r'\(FF-\d{6}-\d{3}\)', '', line) + line = re.sub(r'\bkb/\S+', '', line) + line = re.sub(r'\s{2,}', ' ', line).strip() + cleaned.append(line) + return '\n'.join(cleaned) + + +def sanitize_list(items: list) -> list: + """Sanitize a list of strings.""" + return [sanitize_text(str(item)) for item in items if item] + + +# ============================================================ +# Styles +# ============================================================ + +def build_styles(): + """Build Oracle Redwood paragraph styles.""" + styles = getSampleStyleSheet() + + styles.add(ParagraphStyle( + 'CoverTitle', + fontName='Helvetica-Bold', + fontSize=28, + leading=34, + textColor=white, + alignment=TA_LEFT, + spaceAfter=6, + )) + styles.add(ParagraphStyle( + 'CoverSubtitle', + fontName='Helvetica', + fontSize=18, + leading=22, + textColor=HexColor("#9E9892"), + alignment=TA_LEFT, + spaceAfter=4, + )) + styles.add(ParagraphStyle( + 'CoverInfo', + fontName='Helvetica', + fontSize=11, + leading=14, + textColor=HexColor("#9E9892"), + alignment=TA_LEFT, + )) + styles.add(ParagraphStyle( + 'SectionTitle', + fontName='Helvetica-Bold', + fontSize=20, + leading=26, + textColor=Colors.PRIMARY_TEXT, + spaceBefore=18, + spaceAfter=8, + )) + styles.add(ParagraphStyle( + 'SubsectionTitle', + fontName='Helvetica-Bold', + fontSize=14, + leading=18, + textColor=Colors.TEAL, + spaceBefore=12, + spaceAfter=6, + )) + styles.add(ParagraphStyle( + 'BodyText_Redwood', + fontName='Helvetica', + fontSize=10, + leading=14, + textColor=Colors.PRIMARY_TEXT, + spaceAfter=6, + )) + styles.add(ParagraphStyle( + 'BulletItem', + fontName='Helvetica', + fontSize=10, + leading=14, + textColor=Colors.PRIMARY_TEXT, + leftIndent=18, + bulletIndent=6, + spaceAfter=3, + )) + styles.add(ParagraphStyle( + 'Emphasis', + fontName='Helvetica-Bold', + fontSize=11, + leading=15, + textColor=Colors.TEAL, + spaceAfter=6, + )) + styles.add(ParagraphStyle( + 'FooterStyle', + fontName='Helvetica', + fontSize=7, + leading=9, + textColor=Colors.SECONDARY_TEXT, + alignment=TA_CENTER, + )) + styles.add(ParagraphStyle( + 'Disclaimer', + fontName='Helvetica-Oblique', + fontSize=8, + leading=10, + textColor=Colors.SECONDARY_TEXT, + spaceAfter=4, + )) + styles.add(ParagraphStyle( + 'ImpactStatement', + fontName='Helvetica-Bold', + fontSize=16, + leading=22, + textColor=Colors.TEAL, + alignment=TA_LEFT, + spaceBefore=12, + spaceAfter=12, + )) + styles.add(ParagraphStyle( + 'MetricBig', + fontName='Helvetica-Bold', + fontSize=36, + leading=42, + textColor=Colors.ORACLE_RED, + alignment=TA_CENTER, + spaceBefore=18, + spaceAfter=6, + )) + styles.add(ParagraphStyle( + 'TableHeader', + fontName='Helvetica-Bold', + fontSize=9, + leading=12, + textColor=white, + )) + styles.add(ParagraphStyle( + 'TableCell', + fontName='Helvetica', + fontSize=9, + leading=12, + textColor=Colors.PRIMARY_TEXT, + )) + styles.add(ParagraphStyle( + 'TableCellBold', + fontName='Helvetica-Bold', + fontSize=9, + leading=12, + textColor=Colors.PRIMARY_TEXT, + )) + return styles + + +# ============================================================ +# PDF Generator +# ============================================================ + +class OCIPDFGenerator: + """Generate Oracle Redwood-branded customer-facing PDF documents.""" + + PAGE_WIDTH, PAGE_HEIGHT = letter # 8.5 x 11 inches + MARGIN = 0.75 * inch + + def __init__(self, customer: str = "", project: str = "", + architect: str = "", firm: str = ""): + self.customer = customer + self.project = project + self.architect = architect + self.firm = firm + self.styles = build_styles() + self.story = [] # flowable elements + self._page_num = 0 + self._total_pages = 0 + + # ---- Page templates ---- + + def _header_footer(self, canvas, doc): + """Draw header and footer on each page.""" + canvas.saveState() + page_num = canvas.getPageNumber() + + if page_num == 1: + # Cover page — no header/footer + canvas.restoreState() + return + + # Header: thin Oracle Red line + canvas.setStrokeColor(Colors.ORACLE_RED) + canvas.setLineWidth(2) + canvas.line( + self.MARGIN, self.PAGE_HEIGHT - 0.5 * inch, + self.PAGE_WIDTH - self.MARGIN, self.PAGE_HEIGHT - 0.5 * inch, + ) + + # Header: customer name (left) + canvas.setFont("Helvetica", 8) + canvas.setFillColor(Colors.SECONDARY_TEXT) + canvas.drawString( + self.MARGIN, + self.PAGE_HEIGHT - 0.45 * inch, + self.customer, + ) + + # Header: "Confidential" (right) + canvas.drawRightString( + self.PAGE_WIDTH - self.MARGIN, + self.PAGE_HEIGHT - 0.45 * inch, + "Confidential", + ) + + # Footer: thin gray line + canvas.setStrokeColor(Colors.LIGHT_GRAY) + canvas.setLineWidth(0.5) + canvas.line( + self.MARGIN, 0.55 * inch, + self.PAGE_WIDTH - self.MARGIN, 0.55 * inch, + ) + + # Footer: page number (center) + canvas.setFont("Helvetica", 8) + canvas.setFillColor(Colors.SECONDARY_TEXT) + canvas.drawCentredString( + self.PAGE_WIDTH / 2, + 0.4 * inch, + f"Page {page_num}", + ) + + # Footer: date (right) + canvas.drawRightString( + self.PAGE_WIDTH - self.MARGIN, + 0.4 * inch, + datetime.now().strftime("%B %Y"), + ) + + canvas.restoreState() + + def _thank_you_page(self, canvas, doc): + """Draw the Thank You page background (dark, matches cover).""" + canvas.saveState() + canvas.setFillColor(Colors.DARK_BG) + canvas.rect(0, 0, self.PAGE_WIDTH, self.PAGE_HEIGHT, fill=1, stroke=0) + # Oracle Red accent bar matching cover + canvas.setFillColor(Colors.ORACLE_RED) + canvas.rect( + 0.6 * inch, 3.2 * inch, + 4, 2.2 * inch, + fill=1, stroke=0, + ) + canvas.restoreState() + + def _cover_page(self, canvas, doc): + """Draw the cover page background.""" + canvas.saveState() + + # Full dark background + canvas.setFillColor(Colors.DARK_BG) + canvas.rect(0, 0, self.PAGE_WIDTH, self.PAGE_HEIGHT, fill=1, stroke=0) + + # Oracle Red vertical accent bar + canvas.setFillColor(Colors.ORACLE_RED) + canvas.rect( + 0.6 * inch, 3.2 * inch, + 4, 2.2 * inch, + fill=1, stroke=0, + ) + + # "Prepared with OCI Deal Accelerator" at bottom + canvas.setFont("Helvetica-Oblique", 8) + canvas.setFillColor(HexColor("#6F6964")) + canvas.drawString( + 0.85 * inch, 1.2 * inch, + "Prepared with OCI Deal Accelerator", + ) + + # Oracle safe harbor footer + canvas.setFont("Helvetica", 6) + canvas.setFillColor(HexColor("#6F6964")) + canvas.drawString( + 0.85 * inch, 0.7 * inch, + "This document is provided for informational purposes only. " + "Actual results may vary.", + ) + + canvas.restoreState() + + # ---- Section builders ---- + + def add_cover(self, subtitle: str = ""): + """Add cover page elements (text positioned over the dark bg).""" + # Spacer to push content down (cover bg is drawn by page template) + self.story.append(Spacer(1, 3.0 * inch)) + + # Customer name + title = self.customer or "Architecture Proposal" + self.story.append(Paragraph(title, self.styles['CoverTitle'])) + + # Project + if self.project: + self.story.append(Paragraph(self.project, self.styles['CoverSubtitle'])) + + # Subtitle + date_str = datetime.now().strftime("%B %Y") + sub = subtitle or f"Architecture Proposal — {date_str}" + self.story.append(Spacer(1, 6)) + self.story.append(Paragraph(sub, self.styles['CoverInfo'])) + + # Architect / Firm + if self.architect or self.firm: + info = self.architect + if self.firm: + info += f" | {self.firm}" if self.architect else self.firm + self.story.append(Spacer(1, 1.5 * inch)) + self.story.append(Paragraph(info, self.styles['CoverInfo'])) + + self.story.append(PageBreak()) + + def _add_section_title(self, title: str): + """Add a section title with Oracle Red underline.""" + self.story.append(Paragraph(title, self.styles['SectionTitle'])) + self.story.append(HRFlowable( + width="25%", thickness=2, + color=Colors.ORACLE_RED, + spaceAfter=10, spaceBefore=0, + hAlign='LEFT', + )) + + def _add_subsection(self, title: str): + """Add a subsection title.""" + self.story.append(Paragraph(title, self.styles['SubsectionTitle'])) + + def _add_body(self, text: str): + """Add body text (sanitized).""" + text = sanitize_text(text) + if text: + self.story.append(Paragraph(text, self.styles['BodyText_Redwood'])) + + def _add_bullet_list(self, items: list): + """Add a bulleted list (sanitized).""" + for item in sanitize_list(items): + if item.strip(): + self.story.append(Paragraph( + f"• {item}", self.styles['BulletItem'], + )) + + def _make_table(self, headers: list, rows: list, + col_widths: list = None) -> Table: + """Build a styled table.""" + # Header row + header_cells = [ + Paragraph(h, self.styles['TableHeader']) for h in headers + ] + # Data rows + data = [header_cells] + for row in rows: + data.append([ + Paragraph(sanitize_text(str(cell)), self.styles['TableCell']) + for cell in row + ]) + + table = Table(data, colWidths=col_widths, repeatRows=1) + + # Style + style_cmds = [ + ('BACKGROUND', (0, 0), (-1, 0), Colors.TABLE_HEADER_BG), + ('TEXTCOLOR', (0, 0), (-1, 0), white), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, -1), 9), + ('ALIGN', (0, 0), (-1, 0), 'CENTER'), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('GRID', (0, 0), (-1, -1), 0.5, Colors.LIGHT_GRAY), + ('TOPPADDING', (0, 0), (-1, -1), 4), + ('BOTTOMPADDING', (0, 0), (-1, -1), 4), + ('LEFTPADDING', (0, 0), (-1, -1), 6), + ('RIGHTPADDING', (0, 0), (-1, -1), 6), + ] + # Alternating row colors + for i in range(1, len(data)): + if i % 2 == 0: + style_cmds.append( + ('BACKGROUND', (0, i), (-1, i), Colors.TABLE_ALT_ROW) + ) + + table.setStyle(TableStyle(style_cmds)) + return table + + # ---- Content sections ---- + + def add_executive_summary(self, why: str, current_state: list, + target_state: str, timeline: str): + """Executive Summary section.""" + self._add_section_title("Executive Summary") + + # Impact statement + self.story.append(Paragraph( + sanitize_text(why), self.styles['ImpactStatement'], + )) + self.story.append(Spacer(1, 8)) + + # Current state + self._add_subsection("Current State") + self._add_bullet_list(current_state) + + # Target state + self._add_subsection("Target State") + self._add_body(target_state) + + # Timeline + self.story.append(Spacer(1, 6)) + self.story.append(Paragraph( + f"Timeline: {sanitize_text(timeline)}", + self.styles['BodyText_Redwood'], + )) + + def add_service_tiering(self, workloads: list): + """Service Tiering section.""" + self._add_section_title("Service Tiering") + self._add_body( + "Each tier drives HA/DR topology, backup strategy, " + "isolation model, and support level." + ) + + headers = ["Workload", "Tier", "Uptime", "RTO", "RPO"] + rows = [ + [wl.get("name", ""), wl.get("tier", ""), + wl.get("uptime", ""), wl.get("rto", ""), wl.get("rpo", "")] + for wl in workloads + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.28, avail * 0.15, avail * 0.17, + avail * 0.20, avail * 0.20] + self.story.append(self._make_table(headers, rows, widths)) + self.story.append(Spacer(1, 12)) + + def add_architecture_principles(self, principles: dict): + """Architecture Principles section.""" + self._add_section_title("Architecture Principles") + + for category in ["design", "deployment", "service"]: + items = principles.get(category, []) + if not items: + continue + self._add_subsection(category.title()) + for item in items: + pid = item.get("id", "") + name = item.get("name", "") + summary = item.get("summary", "") + label = f"{pid} {name}" if pid else f"{name}" + if summary: + label += f" — {summary}" + self.story.append(Paragraph( + f"• {label}", self.styles['BulletItem'], + )) + + def add_architecture_overview(self, description: str = "", + diagram_path: str = None): + """Architecture Overview section.""" + self._add_section_title("Architecture Overview") + + if diagram_path and Path(diagram_path).is_file(): + try: + img = Image(diagram_path) + avail_w = self.PAGE_WIDTH - 2 * self.MARGIN + avail_h = 4.5 * inch + # Scale to fit + ratio = min(avail_w / img.drawWidth, avail_h / img.drawHeight) + img.drawWidth *= ratio + img.drawHeight *= ratio + self.story.append(img) + self.story.append(Spacer(1, 8)) + except Exception: + self._add_body(f"[Diagram: {diagram_path}]") + + if description: + self._add_body(description) + + def add_decisions(self, decisions: list): + """Architecture Decisions section.""" + self._add_section_title("Architecture Decisions") + + headers = ["Decision", "Rationale"] + rows = [ + [d.get("decision", ""), d.get("rationale", "")] + for d in decisions + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.35, avail * 0.65] + self.story.append(self._make_table(headers, rows, widths)) + self.story.append(Spacer(1, 12)) + + def add_ha_dr(self, tiers: list, description: str = ""): + """HA/DR section.""" + self._add_section_title("High Availability & Disaster Recovery") + + if description: + self._add_body(description) + self.story.append(Spacer(1, 6)) + + headers = ["Component", "Technology", "RTO", "RPO"] + rows = [ + [t.get("tier", ""), t.get("technology", ""), + t.get("rto", ""), t.get("rpo", "")] + for t in tiers + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.22, avail * 0.38, avail * 0.20, avail * 0.20] + self.story.append(self._make_table(headers, rows, widths)) + self.story.append(Spacer(1, 12)) + + def add_security(self, controls: dict, compliance: list = None): + """Security & Compliance section.""" + self._add_section_title("Security & Compliance") + + if compliance: + badges = " | ".join([f"✓ {fw}" for fw in compliance]) + self.story.append(Paragraph( + f"{badges}", self.styles['Emphasis'], + )) + self.story.append(Spacer(1, 6)) + + for area, items in controls.items(): + area_title = area.replace("_", " ").title() + self._add_subsection(area_title) + self._add_bullet_list(items) + + def add_environment_catalogue(self, environments: list, + cost_notes: list = None): + """Environment Catalogue section.""" + self._add_section_title("Environment Catalogue") + + headers = ["Environment", "Tier", "Databases", "OCPUs", "Isolation"] + rows = [ + [e.get("environment", ""), e.get("tier", ""), + e.get("databases", ""), e.get("ocpus", ""), + e.get("isolation", "")] + for e in environments + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.16, avail * 0.10, avail * 0.20, + avail * 0.10, avail * 0.44] + self.story.append(self._make_table(headers, rows, widths)) + + if cost_notes: + self.story.append(Spacer(1, 8)) + self._add_subsection("Cost Optimization") + self._add_bullet_list(cost_notes) + self.story.append(Spacer(1, 12)) + + def add_cost_estimate(self, line_items: list, assumptions: list = None, + show_byol: bool = True): + """Cost Estimate section.""" + self._add_section_title("Cost Estimate") + + if show_byol: + headers = ["Component", "Monthly (PAYG)", "Monthly (BYOL)", "Notes"] + rows = [ + [item.get("component", ""), + item.get("monthly_payg", ""), + item.get("monthly_byol", ""), + item.get("notes", "")] + for item in line_items + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.30, avail * 0.18, avail * 0.18, avail * 0.34] + else: + headers = ["Component", "Monthly", "Notes"] + rows = [ + [item.get("component", ""), + item.get("monthly_payg", ""), + item.get("notes", "")] + for item in line_items + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.35, avail * 0.20, avail * 0.45] + + table = self._make_table(headers, rows, widths) + + # Highlight total rows + extra_cmds = [] + for i, item in enumerate(line_items): + if "total" in item.get("component", "").lower(): + row_idx = i + 1 + extra_cmds.extend([ + ('BACKGROUND', (0, row_idx), (-1, row_idx), Colors.TEAL), + ('TEXTCOLOR', (0, row_idx), (-1, row_idx), white), + ('FONTNAME', (0, row_idx), (-1, row_idx), 'Helvetica-Bold'), + ]) + if extra_cmds: + table.setStyle(TableStyle(extra_cmds)) + + self.story.append(table) + + if assumptions: + self.story.append(Spacer(1, 10)) + self.story.append(Paragraph( + "Assumptions:", self.styles['Disclaimer'], + )) + for a in sanitize_list(assumptions): + self.story.append(Paragraph( + f"• {a}", self.styles['Disclaimer'], + )) + self.story.append(Spacer(1, 12)) + + def add_cost_comparison(self, rows: list, title: str = "Cost Comparison", + col_headers: list = None): + """Cost Comparison section.""" + self._add_section_title(title) + + headers = col_headers or ["Component", "Current", "OCI", "Savings"] + data_rows = [ + [r.get("item", ""), r.get("current", ""), + r.get("oci", ""), r.get("savings", "")] + for r in rows + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.35, avail * 0.20, avail * 0.20, avail * 0.25] + table = self._make_table(headers, data_rows, widths) + + # Highlight total rows + extra_cmds = [] + for i, r in enumerate(rows): + if "total" in r.get("item", "").lower(): + row_idx = i + 1 + extra_cmds.extend([ + ('BACKGROUND', (0, row_idx), (-1, row_idx), Colors.TEAL), + ('TEXTCOLOR', (0, row_idx), (-1, row_idx), white), + ('FONTNAME', (0, row_idx), (-1, row_idx), 'Helvetica-Bold'), + ]) + if extra_cmds: + table.setStyle(TableStyle(extra_cmds)) + + self.story.append(table) + self.story.append(Spacer(1, 12)) + + def add_migration(self, phases: list, tools: list = None, + downtime: str = ""): + """Migration Approach section.""" + self._add_section_title("Migration Approach") + + headers = ["Phase", "Timeline", "Key Activities"] + rows = [ + [p.get("name", ""), p.get("weeks", ""), + " | ".join(p.get("tasks", [])[:4])] + for p in phases + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.25, avail * 0.15, avail * 0.60] + self.story.append(self._make_table(headers, rows, widths)) + + if tools: + self.story.append(Spacer(1, 8)) + self.story.append(Paragraph( + f"Migration Tools: {', '.join(tools)}", + self.styles['BodyText_Redwood'], + )) + if downtime: + self.story.append(Paragraph( + f"Downtime Approach: {sanitize_text(downtime)}", + self.styles['BodyText_Redwood'], + )) + self.story.append(Spacer(1, 12)) + + def add_operational_raci(self, raci_items: list, + model: str = "co_managed"): + """Operational RACI section.""" + model_label = model.replace("_", "-").title() + self._add_section_title(f"Operational Responsibilities ({model_label})") + + headers = ["Activity", "Customer", "Oracle / Partner"] + rows = [ + [item.get("activity", ""), + item.get("customer", ""), + item.get("oracle", "")] + for item in raci_items + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.50, avail * 0.25, avail * 0.25] + self.story.append(self._make_table(headers, rows, widths)) + + self.story.append(Spacer(1, 6)) + self.story.append(Paragraph( + "R = Responsible | A = Accountable | C = Consulted | I = Informed", + self.styles['Disclaimer'], + )) + self.story.append(Spacer(1, 12)) + + def add_risks(self, risks: list): + """Risk Register section.""" + self._add_section_title("Risk Register") + + headers = ["Risk", "Severity", "Mitigation"] + rows = [ + [sanitize_text(r.get("risk", "")), + r.get("severity", "MEDIUM"), + sanitize_text(r.get("mitigation", ""))] + for r in risks + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.30, avail * 0.12, avail * 0.58] + table = self._make_table(headers, rows, widths) + + # Color-code severity + severity_colors = { + "HIGH": Colors.ERROR, + "MEDIUM": Colors.WARNING, + "LOW": Colors.SUCCESS, + } + extra_cmds = [] + for i, risk in enumerate(risks): + sev = risk.get("severity", "MEDIUM").upper() + color = severity_colors.get(sev, Colors.WARNING) + row_idx = i + 1 + extra_cmds.extend([ + ('TEXTCOLOR', (1, row_idx), (1, row_idx), color), + ('FONTNAME', (1, row_idx), (1, row_idx), 'Helvetica-Bold'), + ('ALIGN', (1, row_idx), (1, row_idx), 'CENTER'), + ]) + if extra_cmds: + table.setStyle(TableStyle(extra_cmds)) + + self.story.append(table) + self.story.append(Spacer(1, 12)) + + def add_scorecard(self, pillars: list, recommendations: list = None): + """Well-Architected Scorecard section.""" + self._add_section_title("Well-Architected Scorecard") + + status_labels = { + "PASS": "✓ Pass", + "PASS_WITH_RECOMMENDATIONS": "⚠ Pass with Recommendations", + "GAPS_IDENTIFIED": "✗ Gaps Identified", + "NOT_APPLICABLE": "— N/A", + } + + headers = ["Pillar", "Score", "Status"] + rows = [] + for p in pillars: + score = (f"{p.get('passed', 0)}/{p.get('total', 0)}" + if p.get('total', 0) > 0 else "—") + status = status_labels.get(p.get("status", ""), p.get("status", "")) + rows.append([p.get("name", ""), score, status]) + + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.40, avail * 0.15, avail * 0.45] + table = self._make_table(headers, rows, widths) + + # Color-code status + status_colors = { + "PASS": Colors.SUCCESS, + "PASS_WITH_RECOMMENDATIONS": Colors.WARNING, + "GAPS_IDENTIFIED": Colors.ERROR, + "NOT_APPLICABLE": Colors.SECONDARY_TEXT, + } + extra_cmds = [] + for i, p in enumerate(pillars): + color = status_colors.get(p.get("status", ""), Colors.SECONDARY_TEXT) + row_idx = i + 1 + extra_cmds.extend([ + ('TEXTCOLOR', (2, row_idx), (2, row_idx), color), + ('FONTNAME', (2, row_idx), (2, row_idx), 'Helvetica-Bold'), + ]) + if extra_cmds: + table.setStyle(TableStyle(extra_cmds)) + + self.story.append(table) + + if recommendations: + self.story.append(Spacer(1, 10)) + self._add_subsection("Top Recommendations") + for rec in sanitize_list(recommendations[:6]): + self.story.append(Paragraph( + f"→ {rec}", self.styles['BulletItem'], + )) + + self.story.append(Spacer(1, 6)) + self.story.append(Paragraph( + "Validated against Oracle Well-Architected Framework — " + "docs.oracle.com/en/solutions/oci-best-practices/", + self.styles['Disclaimer'], + )) + self.story.append(Spacer(1, 12)) + + def add_next_steps(self, steps: list, contact_info: str = ""): + """Next Steps section.""" + self._add_section_title("Next Steps") + + for i, step in enumerate(sanitize_list(steps)): + self.story.append(Paragraph( + f"{i + 1}. {step}", + self.styles['BodyText_Redwood'], + )) + self.story.append(Spacer(1, 4)) + + if contact_info: + self.story.append(Spacer(1, 18)) + self.story.append(Paragraph( + sanitize_text(contact_info), + self.styles['Emphasis'], + )) + + def add_thank_you_page(self): + """Add a dark-background closing / Thank You page.""" + self.story.append(PageBreak()) + # The _thank_you_page canvas callback draws the dark background + from reportlab.platypus.doctemplate import NextPageTemplate + self.story.append(NextPageTemplate('thank_you')) + self.story.append(Spacer(1, 2.2 * inch)) + self.story.append(Paragraph( + "Thank you", + ParagraphStyle( + 'ThankYouTitle', + fontName='Helvetica-Bold', + fontSize=40, + leading=50, + textColor=white, + alignment=TA_LEFT, + ), + )) + self.story.append(Spacer(1, 0.3 * inch)) + if self.architect or self.firm: + parts = [p for p in [self.architect, self.firm] if p] + self.story.append(Paragraph( + " | ".join(parts), + ParagraphStyle( + 'ThankYouContact', + fontName='Helvetica', + fontSize=13, + leading=18, + textColor=HexColor("#9E9892"), + alignment=TA_LEFT, + ), + )) + + def add_safe_harbor(self): + """Add Oracle Safe Harbor statement.""" + from reportlab.platypus.doctemplate import NextPageTemplate + self.story.append(NextPageTemplate('content_pages')) + self.story.append(PageBreak()) + self.story.append(Spacer(1, 2 * inch)) + self.story.append(Paragraph( + "Safe Harbor Statement", + self.styles['SectionTitle'], + )) + self.story.append(HRFlowable( + width="25%", thickness=2, + color=Colors.ORACLE_RED, + spaceAfter=14, spaceBefore=0, + hAlign='LEFT', + )) + self.story.append(Paragraph( + "The preceding is intended to outline our general product direction. " + "It is intended for information purposes only, and may not be " + "incorporated into any contract. It is not a commitment to deliver " + "any material, code, or functionality, and should not be relied upon " + "in making purchasing decisions. The development, release, timing, " + "and pricing of any features or functionality described for Oracle's " + "products may change and remains at the sole discretion of Oracle " + "Corporation.", + self.styles['BodyText_Redwood'], + )) + + # ---- Build from spec ---- + + @classmethod + def from_spec(cls, spec: dict, diagram_path: str = None) -> "OCIPDFGenerator": + """Build a complete PDF from a YAML specification.""" + meta = spec.get("metadata", {}) + gen = cls( + customer=meta.get("customer", ""), + project=meta.get("project", ""), + architect=meta.get("architect", ""), + firm=meta.get("firm", ""), + ) + + # Cover + gen.add_cover(subtitle=meta.get("subtitle", "")) + + # Executive Summary + if "summary" in spec: + s = spec["summary"] + gen.add_executive_summary( + why=s.get("why", ""), + current_state=s.get("current_state", []), + target_state=s.get("target_state", ""), + timeline=s.get("timeline", ""), + ) + + # Service Tiering + if "service_tiering" in spec: + gen.add_service_tiering(spec["service_tiering"]) + + # Architecture Principles + if "architecture_principles" in spec: + gen.add_architecture_principles(spec["architecture_principles"]) + + # Architecture Overview + if "architecture" in spec: + a = spec["architecture"] + gen.add_architecture_overview( + description=a.get("description", ""), + diagram_path=diagram_path or a.get("diagram_path"), + ) + + # Decisions + if "decisions" in spec: + gen.add_decisions(spec["decisions"]) + + # HA/DR + if "ha_dr" in spec: + h = spec["ha_dr"] + gen.add_ha_dr( + tiers=h.get("tiers", []), + description=h.get("description", ""), + ) + + # Security + if "security" in spec: + s = spec["security"] + gen.add_security( + controls=s.get("controls", {}), + compliance=s.get("compliance", []), + ) + + # Environment Catalogue + if "environment_catalogue" in spec: + ec = spec["environment_catalogue"] + gen.add_environment_catalogue( + environments=ec.get("environments", []), + cost_notes=ec.get("cost_notes"), + ) + + # Cost Estimate + if "cost" in spec: + c = spec["cost"] + gen.add_cost_estimate( + line_items=c.get("line_items", []), + assumptions=c.get("assumptions", []), + show_byol=c.get("show_byol", True), + ) + + # Cost Comparison + if "cost_comparison" in spec: + cc = spec["cost_comparison"] + gen.add_cost_comparison( + rows=cc.get("rows", []), + title=cc.get("title", "Cost Comparison"), + col_headers=cc.get("col_headers"), + ) + + # Migration + if "migration" in spec: + m = spec["migration"] + gen.add_migration( + phases=m.get("phases", []), + tools=m.get("tools", []), + downtime=m.get("downtime", ""), + ) + + # Operational RACI + if "operational_raci" in spec: + r = spec["operational_raci"] + gen.add_operational_raci( + raci_items=r.get("raci_items", []), + model=r.get("model", "co_managed"), + ) + + # Risks + if "risks" in spec: + gen.add_risks(spec["risks"]) + + # Scorecard + if "scorecard" in spec: + sc = spec["scorecard"] + gen.add_scorecard( + pillars=sc.get("pillars", []), + recommendations=sc.get("recommendations", []), + ) + + # Next Steps + if "next_steps" in spec: + ns = spec["next_steps"] + gen.add_next_steps( + steps=ns.get("steps", []), + contact_info=ns.get("contact_info", ""), + ) + + # Thank You + Safe Harbor + gen.add_thank_you_page() + gen.add_safe_harbor() + + return gen + + # ---- Save ---- + + def save(self, filepath: str): + """Save the document to a PDF file.""" + doc = BaseDocTemplate( + filepath, + pagesize=letter, + leftMargin=self.MARGIN, + rightMargin=self.MARGIN, + topMargin=0.7 * inch, + bottomMargin=0.7 * inch, + title=f"{self.customer} — {self.project}", + author=self.architect, + subject="OCI Architecture Proposal", + ) + + # Frame for content + content_frame = Frame( + self.MARGIN, 0.7 * inch, + self.PAGE_WIDTH - 2 * self.MARGIN, + self.PAGE_HEIGHT - 1.4 * inch, + id='content', + ) + + # Cover page template (dark bg) + cover_template = PageTemplate( + id='cover', + frames=[content_frame], + onPage=self._cover_page, + ) + + # Content page template (header + footer) + content_template = PageTemplate( + id='content_pages', + frames=[content_frame], + onPage=self._header_footer, + ) + + # Thank You page template (dark bg, no header/footer) + thank_you_template = PageTemplate( + id='thank_you', + frames=[content_frame], + onPage=self._thank_you_page, + ) + + doc.addPageTemplates([cover_template, content_template, thank_you_template]) + + # After cover page, switch to content template + # Insert a NextPageTemplate after the PageBreak in story + from reportlab.platypus.doctemplate import NextPageTemplate + # Find first PageBreak and insert template switch before it + for i, elem in enumerate(self.story): + if isinstance(elem, PageBreak): + self.story.insert(i, NextPageTemplate('content_pages')) + break + + doc.build(self.story) + + +# ============================================================ +# CLI +# ============================================================ + +def main(): + parser = argparse.ArgumentParser( + description="Generate customer-facing OCI architecture proposal PDF" + ) + parser.add_argument( + "--spec", required=True, + help="Path to YAML spec file with proposal data", + ) + parser.add_argument( + "--output", default="architecture-proposal.pdf", + help="Output .pdf file path", + ) + parser.add_argument( + "--diagram", + help="Path to architecture diagram image (.png/.jpg) to embed", + ) + args = parser.parse_args() + + with open(args.spec, 'r') as f: + spec = yaml.safe_load(f) + + gen = OCIPDFGenerator.from_spec(spec, diagram_path=args.diagram) + gen.save(args.output) + + customer = spec.get("metadata", {}).get("customer", "") + print(f"Generated: {args.output}") + print(f" Style: Oracle Redwood (customer-facing)") + print(f" Internal references: stripped") + if customer: + print(f" Customer: {customer}") + + +if __name__ == "__main__": + main() diff --git a/tools/oci_pptx_base.py b/tools/oci_pptx_base.py new file mode 100644 index 0000000..a7098f6 --- /dev/null +++ b/tools/oci_pptx_base.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +""" +OCI Deal Accelerator — Shared PPTX base (Oracle FY26 template + Redwood design system). + +Import: from oci_pptx_base import Colors, Layouts, OraclePresBase +""" + +from pathlib import Path +from typing import Optional + +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE + + +# ============================================================ +# Oracle Redwood Design System Colors +# Source: Oracle Database Icon Library [July 2024] — theme RMIL_Master +# Color scheme: "Oracle Redwood" +# ============================================================ + +class Colors: + # Primary text — never pure black + DARK_BG = RGBColor(0x31, 0x2D, 0x2A) # #312D2A — dk1 + PRIMARY_TEXT = RGBColor(0x31, 0x2D, 0x2A) # #312D2A — dk1 + SECONDARY_TEXT = RGBColor(0x6F, 0x69, 0x64) # #6F6964 — Neutral 60 + WHITE = RGBColor(0xFF, 0xFF, 0xFF) + WARM_BG = RGBColor(0xFC, 0xFB, 0xFA) # #FCFBFA — lt1, content bg + + # Oracle Redwood accent palette + ORACLE_RED = RGBColor(0xC7, 0x46, 0x34) # #C74634 — accent1, primary brand + GOLD = RGBColor(0xFA, 0xCD, 0x62) # #FACD62 — accent2 + TEAL = RGBColor(0x2C, 0x59, 0x67) # #2C5967 — hlink, tables/links + SAGE = RGBColor(0x75, 0x9C, 0x6C) # #759C6C — accent6 + FOREST = RGBColor(0x2B, 0x62, 0x42) # #2B6242 — accent4 + MUTED_TEAL = RGBColor(0x94, 0xAF, 0xAF) # #94AFAF — accent3 + BURNT_ORANGE = RGBColor(0xAE, 0x56, 0x2C) # #AE562C — accent5 + + # OCI service category colors (used in architecture diagrams) + COPPER = RGBColor(0xAA, 0x64, 0x3B) # #AA643B — database + PURPLE = RGBColor(0x80, 0x49, 0x98) # #804998 — integration + + # Status + SUCCESS = RGBColor(0x2B, 0x62, 0x42) # #2B6242 — forest green + WARNING = RGBColor(0xAE, 0x56, 0x2C) # #AE562C — burnt orange + ERROR = RGBColor(0xC7, 0x46, 0x34) # #C74634 — Oracle red + + # Table + TABLE_ALT_ROW = RGBColor(0xF5, 0xF4, 0xF2) # #F5F4F2 — Neutral 10 + TABLE_HEADER_BG = RGBColor(0x2C, 0x59, 0x67) # teal + + +# ============================================================ +# Oracle FY26 Template Layout Indices +# ============================================================ + +class Layouts: + """Layout indices from Oracle_PPT-template_FY26.pptx (104 layouts).""" + COVER_DARK = 0 # Dark - Title_Pillar + COVER_LIGHT = 1 # Light - Title_Pillar + DIVIDER_LIGHT = 16 # Light - Divider + DIVIDER_DARK = 17 # Dark - Divider + IMPACT_DARK = 26 # Dark - Impact Statement + IMPACT_LIGHT = 27 # Light - Impact Statement + METRIC_DARK = 32 # Dark - Single metric + METRIC_LIGHT = 33 # Light - Single metric + MULTI_STATEMENT_DARK = 47 # Multi Statement – Dark + MULTI_STATEMENT_LIGHT = 48 # Multi Statement – Light + TWO_COL_LIGHT = 66 # Light - Title/Subtitle 2 Column + TWO_COL_DARK = 67 # Dark - Title/Subtitle 2 Column + FOUR_ICONS_LIGHT = 82 # Light - 4 Icons, Subhead and text + BLANK_DARK = 84 # Dark - Blank + BLANK_LIGHT = 85 # Light - Blank + SAFE_HARBOR_LIGHT = 96 # Light - Safe harbor - short (if exists) + THANK_YOU_DARK = 34 # Dark - Thank You + THANK_YOU_LIGHT = 35 # Light - Thank You + CLOSING_LIGHT = 101 # Light - Closing Slide (Oracle logo only) + CLOSING_DARK = 102 # Dark - Closing Slide (Oracle logo only) + + +# ============================================================ +# Base Presentation Class +# ============================================================ + +class OraclePresBase: + """Shared PPTX infrastructure for OCI Deal Accelerator generators.""" + + TEMPLATE_PATH = Path(__file__).parent.parent / "templates" / "Oracle_PPT-template_FY26.pptx" + FONT = "Oracle Sans" + FONT_HEADING = "Georgia" + TITLE_ACCENT_COLOR = Colors.TEAL # subclasses can override as class attribute + + def __init__(self, template: Optional[str] = None): + tmpl = template or str(self.TEMPLATE_PATH) + if not Path(tmpl).is_file(): + raise FileNotFoundError(f"Template not found: {tmpl}") + self.prs = Presentation(tmpl) + self._clear_slides() + + def _clear_slides(self): + """Remove all existing slides from the template (keep layouts/masters).""" + while len(self.prs.slides) > 0: + rId = self.prs.slides._sldIdLst[0].get( + '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id' + ) + self.prs.part.drop_rel(rId) + self.prs.slides._sldIdLst.remove(self.prs.slides._sldIdLst[0]) + # Clean sections + from lxml import etree # noqa: F401 + ns_p14 = 'http://schemas.microsoft.com/office/powerpoint/2010/main' + for sectionLst in self.prs.part._element.findall(f'.//{{{ns_p14}}}sectionLst'): + sectionLst.getparent().remove(sectionLst) + + def _add_layout_slide(self, layout_index: int): + """Add a slide using a specific template layout.""" + layout = self.prs.slide_layouts[layout_index] + return self.prs.slides.add_slide(layout) + + def _add_blank_slide(self, dark: bool = False): + """Add a blank slide (light or dark).""" + idx = Layouts.BLANK_DARK if dark else Layouts.BLANK_LIGHT + return self._add_layout_slide(idx) + + def _set_placeholder(self, slide, idx: int, text: str): + """Set text in a placeholder by index. Silently skips if not found.""" + for ph in slide.placeholders: + if ph.placeholder_format.idx == idx: + ph.text = text + return ph + return None + + def _style_placeholder(self, slide, idx: int, text: str, + font_size: int = None, bold: bool = None, + color: RGBColor = None): + """Set text and style a placeholder.""" + ph = self._set_placeholder(slide, idx, text) + if ph and ph.text_frame.paragraphs: + for p in ph.text_frame.paragraphs: + if font_size: + p.font.size = Pt(font_size) + if bold is not None: + p.font.bold = bold + if color: + p.font.color.rgb = color + return ph + + def _add_textbox(self, slide, left, top, width, height, + text="", font_size=12, bold=False, italic=False, + color=None, alignment=PP_ALIGN.LEFT, font_name=None): + """Add a text box to a slide.""" + txBox = slide.shapes.add_textbox(left, top, width, height) + tf = txBox.text_frame + tf.word_wrap = True + p = tf.paragraphs[0] + p.text = text + p.font.size = Pt(font_size) + p.font.bold = bold + p.font.italic = italic + p.font.name = font_name or self.FONT + p.font.color.rgb = color or Colors.PRIMARY_TEXT + p.alignment = alignment + return txBox + + def _add_paragraph(self, text_frame, text="", font_size=12, + bold=False, italic=False, color=None, + alignment=PP_ALIGN.LEFT, space_before=0, + space_after=0, bullet=False): + """Add a paragraph to an existing text frame.""" + p = text_frame.add_paragraph() + p.text = text + p.font.size = Pt(font_size) + p.font.bold = bold + p.font.italic = italic + p.font.name = self.FONT + p.font.color.rgb = color or Colors.PRIMARY_TEXT + p.alignment = alignment + if space_before: + p.space_before = Pt(space_before) + if space_after: + p.space_after = Pt(space_after) + if bullet: + p.level = 0 + return p + + def _add_rect(self, slide, left, top, width, height, + fill_color=None, border_color=None): + """Add a rectangle shape.""" + shape = slide.shapes.add_shape( + MSO_SHAPE.RECTANGLE, left, top, width, height + ) + if fill_color: + shape.fill.solid() + shape.fill.fore_color.rgb = fill_color + else: + shape.fill.background() + if border_color: + shape.line.color.rgb = border_color + else: + shape.line.fill.background() + return shape + + def _add_table(self, slide, rows, cols, left, top, width, height): + """Add a table to a slide.""" + return slide.shapes.add_table(rows, cols, left, top, width, height).table + + def _style_table_cell(self, cell, text, font_size=10, bold=False, + color=None, bg_color=None, alignment=PP_ALIGN.LEFT): + """Style a table cell.""" + cell.text = text + for paragraph in cell.text_frame.paragraphs: + paragraph.font.size = Pt(font_size) + paragraph.font.bold = bold + paragraph.font.name = self.FONT + paragraph.font.color.rgb = color or Colors.PRIMARY_TEXT + paragraph.alignment = alignment + cell.vertical_anchor = MSO_ANCHOR.MIDDLE + if bg_color: + cell.fill.solid() + cell.fill.fore_color.rgb = bg_color + + def _add_title_bar(self, slide, title_text, margin=None): + """Add a consistent title bar at top of content slides. + + Uses self.TITLE_ACCENT_COLOR for the accent line. + Title is 24pt bold Georgia. margin defaults to Inches(0.8). + """ + if margin is None: + margin = Inches(0.8) + self._add_textbox( + slide, margin, Inches(0.4), Inches(11.7), Inches(0.6), + text=title_text, font_size=24, bold=True, + font_name=self.FONT_HEADING, + ) + bar = slide.shapes.add_shape( + MSO_SHAPE.RECTANGLE, margin, Inches(1.05), Inches(11.7), Inches(0.05) + ) + bar.fill.solid() + bar.fill.fore_color.rgb = self.TITLE_ACCENT_COLOR + bar.line.fill.background() + + def add_closing_slide(self, name: str = "", title: str = "", + contact: str = "", dark: bool = True): + """Closing slide: Thank You (dark by default) + Oracle logo closing. + + Uses the FY26 Thank You layout (PH0 = "Thank you", PH37 = name/title, + PH36 = contact/URL), followed by the Oracle Closing Slide layout + (logo-only, no placeholders). + + Args: + name: Presenter name, e.g. "Diego Cabrera" + title: Presenter title/firm, e.g. "Solutions Architect | Oracle" + contact: Contact line, e.g. email or URL + dark: Use dark variant (default True) + """ + layout_idx = Layouts.THANK_YOU_DARK if dark else Layouts.THANK_YOU_LIGHT + slide = self._add_layout_slide(layout_idx) + # PH0 defaults to "Thank you" from the template — clear if not wanted + # PH37: name + title on one line + if name or title: + parts = [p for p in [name, title] if p] + self._set_placeholder(slide, 37, " | ".join(parts)) + # PH36: contact / URL + if contact: + self._set_placeholder(slide, 36, contact) + + # Oracle Closing Slide (logo-only) + closing_idx = Layouts.CLOSING_DARK if dark else Layouts.CLOSING_LIGHT + self._add_layout_slide(closing_idx) + + def save(self, filepath: str): + """Save the presentation to a .pptx file.""" + self.prs.save(filepath) + + @property + def slide_count(self): + return len(self.prs.slides) diff --git a/tools/refresh_arch_catalog.py b/tools/refresh_arch_catalog.py new file mode 100644 index 0000000..cb1a149 --- /dev/null +++ b/tools/refresh_arch_catalog.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +""" +Refresh the Architecture Center catalog from Oracle docs. + +Usage: + python tools/refresh_arch_catalog.py --url https://docs.oracle.com/en/solutions/{slug}/index.html + python tools/refresh_arch_catalog.py --whats-new + python tools/refresh_arch_catalog.py --validate + +This script: +1. Fetches the What's New pages for 2024 and 2025 +2. Extracts all reference architecture links +3. For each link, fetches the detail page +4. Extracts title, summary, services, recommendations +5. Writes/updates kb/architecture-center/catalog.yaml + +IMPORTANT: This is a semi-automated tool. It generates DRAFT entries +that should be reviewed by a human before committing. The auto-generated +summaries may miss nuance or include irrelevant detail. + +Requirements: pip install requests beautifulsoup4 pyyaml +""" + +import argparse +import os +import re +import sys +import time +from datetime import datetime +from urllib.parse import urljoin + +import requests +import yaml +from bs4 import BeautifulSoup + +CATALOG_PATH = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "kb", "architecture-center", "catalog.yaml", +) + +WHATS_NEW_URLS = [ + "https://docs.oracle.com/en/solutions/oracle-architecture-center/whats-new-20251.html", + "https://docs.oracle.com/en/solutions/oracle-architecture-center/whats-new-2024.html", +] + +# --------------------------------------------------------------------------- +# Service name detection patterns +# --------------------------------------------------------------------------- +SERVICE_PATTERNS = { + "adb-s": r"Autonomous\s+(Database|Transaction|Data\s+Warehouse)\s+Serverless|ADB-S|ATP\s+Serverless|Autonomous\s+(Database|Transaction|Data\s+Warehouse)(?!.*Dedicated)", + "adb-d": r"Autonomous\s+Database\s+on\s+Dedicated|ADB-D|Dedicated\s+Exadata\s+Infrastructure", + "exacs": r"Exadata\s+(Cloud|Database)\s+Service|ExaCS|ExaDB-D", + "exascale": r"Exascale", + "base-db": r"Base\s+Database|VM\s+DB|DB\s+System|Oracle\s+Database\s+Service", + "oke": r"Kubernetes\s+Engine|OKE|Container\s+Engine\s+for\s+Kubernetes", + "compute": r"Compute\s+(Instance|VM)|Virtual\s+Machine|Bare\s+Metal", + "functions": r"OCI\s+Functions|Functions\s+Service", + "vcn": r"Virtual\s+Cloud\s+Network|VCN", + "load-balancer": r"Load\s+Balanc", + "waf": r"Web\s+Application\s+Firewall|WAF", + "fastconnect": r"FastConnect", + "drg": r"Dynamic\s+Routing\s+Gateway|DRG", + "object-storage": r"Object\s+Storage", + "block-storage": r"Block\s+Volume|Block\s+Storage", + "file-storage": r"File\s+Storage|FSS", + "data-safe": r"Data\s+Safe", + "vault": r"OCI\s+Vault|Key\s+Management|Key\s+Vault", + "cloud-guard": r"Cloud\s+Guard", + "goldengate": r"GoldenGate", + "oic": r"Oracle\s+Integration(?!\s+3)|OIC", + "oic3": r"Oracle\s+Integration\s+3", + "data-integration": r"Data\s+Integration", + "streaming": r"OCI\s+Streaming|Streaming\s+Service", + "monitoring": r"OCI\s+Monitoring|Stack\s+Monitoring", + "logging": r"Logging\s+Analytics", + "bastion": r"Bastion", + "apex": r"Oracle\s+APEX|APEX\s+application", + "mysql": r"MySQL\s+(Database\s+)?Service|MySQL\s+HeatWave", + "nosql": r"NoSQL\s+Database", + "postgresql": r"PostgreSQL", + "opensearch": r"OpenSearch", + "redis": r"OCI\s+Cache|Redis", + "adw": r"Autonomous\s+Data\s+Warehouse|ADW", + "api-gateway": r"API\s+Gateway", + "dns": r"OCI\s+DNS|DNS\s+Service", + "wls": r"WebLogic", + "soa": r"SOA\s+Suite", + "genai": r"Generative\s+AI|GenAI|OCI\s+AI\s+Agent", + "ai-services": r"OCI\s+AI\s+Services|Vision\s+AI|Language\s+AI|Speech\s+AI|Document\s+Understanding", + "data-science": r"Data\s+Science|ML\s+Pipeline", + "data-catalog": r"Data\s+Catalog", + "ops-insights": r"Ops\s+Insights|Operations\s+Insights", + "apm": r"Application\s+Performance\s+Monitoring|APM", + "fsdr": r"Full\s+Stack\s+Disaster\s+Recovery|FSDR", + "ocvs": r"VMware\s+Solution|OCVS", + "secure-desktops": r"Secure\s+Desktops", + "azure": r"Database@Azure|Oracle\s+Database@Azure", + "aws": r"Database@AWS|Oracle\s+Database@AWS", + "google-cloud": r"Database@Google|Oracle\s+Database@Google", +} + +TAG_PATTERNS = { + "database": r"database|autonomous|exadata|rac|mysql|nosql|postgresql|base\s+db", + "migration": r"migrat|move\s+to|zero\s+downtime\s+migration|zdm|data\s+pump", + "ha-dr": r"high\s+availability|disaster\s+recovery|data\s+guard|failover|MAA|standby|far\s+sync", + "security": r"secur|encrypt|vault|data\s+safe|cloud\s+guard|compliance|zero.trust|tls|certificate", + "networking": r"network|vcn|subnet|fastconnect|vpn|drg|load\s+balanc|dns|hub.spoke", + "multicloud": r"multicloud|multi-cloud|azure|aws|google\s+cloud|database@", + "integration": r"integrat|oic|streaming|goldengate|data\s+flow|kafka", + "data-platform": r"data\s+lake|data\s+warehouse|analytics|data\s+platform|lakehouse", + "application": r"apex|ords|kubernetes|container|weblogic|tomcat|microservice|e-business|peoplesoft|siebel|jd\s+edwards", + "ai-ml": r"machine\s+learning|ai\s+|generative|vector|select\s+ai|llm|rag|chatbot|agent", + "autonomous": r"autonomous\s+database|adb|auto-scal", + "observability": r"monitor|logging|apm|ops\s+insights|observability|stack\s+monitoring", + "devops": r"ci/cd|devops|github|gitlab|jenkins|pipeline|gitops", + "hpc": r"hpc|gpu|bare\s+metal\s+gpu|high.performance\s+computing", + "iot": r"iot|internet\s+of\s+things", + "healthcare": r"healthcare|dicom|hl7|fhir", + "ebs": r"e-business\s+suite|ebs", + "peoplesoft": r"peoplesoft", + "vmware": r"vmware|ocvs|vsan", +} + + +def detect_services(text): + """Auto-detect OCI services mentioned in text.""" + found = [] + for svc, pattern in SERVICE_PATTERNS.items(): + if re.search(pattern, text, re.IGNORECASE): + found.append(svc) + return sorted(set(found)) + + +def detect_tags(text): + """Auto-detect topic tags from text.""" + found = [] + for tag, pattern in TAG_PATTERNS.items(): + if re.search(pattern, text, re.IGNORECASE): + found.append(tag) + return sorted(set(found)) + + +def fetch_page(url, delay=1.0): + """Fetch a page with rate limiting.""" + time.sleep(delay) + headers = {"User-Agent": "OCI-Deal-Accelerator-CatalogBuilder/1.0"} + resp = requests.get(url, headers=headers, timeout=30) + resp.raise_for_status() + return BeautifulSoup(resp.text, "html.parser") + + +def extract_entry(url): + """Extract catalog entry from a reference architecture page.""" + try: + soup = fetch_page(url) + + title = soup.find("h1") + title_text = title.get_text(strip=True) if title else "Unknown" + + # Get all text for service/tag detection + body = soup.get_text(" ", strip=True) + + # Extract first 2-3 meaningful sentences as summary + paragraphs = soup.find_all("p") + summary_parts = [] + for p in paragraphs[:8]: + text = p.get_text(strip=True) + if len(text) > 50 and "Copyright" not in text and "cookie" not in text.lower(): + summary_parts.append(text) + if len(" ".join(summary_parts)) > 300: + break + + summary = " ".join(summary_parts)[:500] + + # Detect services and tags + services = detect_services(body) + tags = detect_tags(body) + + # Detect type + entry_type = "reference-architecture" + if "playbook" in body.lower(): + entry_type = "solution-playbook" + elif "built and deployed" in body.lower(): + entry_type = "built-deployed" + + # Find terraform/github links + terraform = None + for a in soup.find_all("a", href=True): + href = a["href"] + if "github.com" in href.lower() and "oracle" in href.lower(): + terraform = href + break + + return { + "title": title_text, + "url": url, + "type": entry_type, + "services": services, + "tags": tags, + "summary": summary, + "terraform": terraform, + } + except Exception as e: + print(f" ERROR fetching {url}: {e}", file=sys.stderr) + return None + + +def extract_whats_new_links(url): + """Extract all reference architecture links from a What's New page.""" + soup = fetch_page(url, delay=0.5) + links = [] + for a in soup.find_all("a", href=True): + href = a["href"] + if "/en/solutions/" in href and href not in ( + "https://docs.oracle.com/en/solutions/oracle-architecture-center/", + ): + # Normalize URL + if href.startswith("/"): + href = urljoin("https://docs.oracle.com", href) + if not href.endswith("/index.html") and not href.endswith("/"): + href = href.rstrip("/") + "/index.html" + title = a.get_text(strip=True) + if title and len(title) > 10: + links.append({"url": href, "title": title}) + return links + + +def load_catalog(): + """Load existing catalog if present.""" + if os.path.exists(CATALOG_PATH): + with open(CATALOG_PATH, "r") as f: + data = yaml.safe_load(f) + return data if data else {"entries": []} + return {"entries": []} + + +def save_catalog(data): + """Save catalog to YAML.""" + os.makedirs(os.path.dirname(CATALOG_PATH), exist_ok=True) + with open(CATALOG_PATH, "w") as f: + f.write("# =============================================================================\n") + f.write("# ORACLE ARCHITECTURE CENTER — REFERENCE CATALOG\n") + f.write("# =============================================================================\n") + f.write("#\n") + f.write("# Index of Oracle Architecture Center content for the Deal Accelerator.\n") + f.write("# Used to match customer architectures with official Oracle reference designs.\n") + f.write("#\n") + f.write(f"# Last refreshed: {datetime.now().strftime('%Y-%m-%d')}\n") + f.write(f"# Entry count: {len(data.get('entries', []))}\n") + f.write("# To refresh: python tools/refresh_arch_catalog.py --whats-new\n") + f.write("# =============================================================================\n\n") + yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False, width=120) + print(f"Saved {len(data.get('entries', []))} entries to {CATALOG_PATH}") + + +def validate_catalog(): + """Validate catalog entries have required fields.""" + data = load_catalog() + entries = data.get("entries", []) + errors = 0 + required = ["title", "url", "type", "tags", "summary"] + + for i, entry in enumerate(entries): + for field in required: + if not entry.get(field): + print(f" Entry {i}: missing '{field}' — {entry.get('title', 'UNKNOWN')}") + errors += 1 + if entry.get("type") not in ("reference-architecture", "solution-playbook", "built-deployed"): + print(f" Entry {i}: invalid type '{entry.get('type')}' — {entry.get('title')}") + errors += 1 + + print(f"\nValidated {len(entries)} entries, {errors} errors found.") + return errors == 0 + + +def main(): + parser = argparse.ArgumentParser(description="Refresh Architecture Center catalog") + parser.add_argument("--url", help="Fetch a single URL and print YAML entry") + parser.add_argument("--whats-new", action="store_true", help="Crawl What's New pages for new entries") + parser.add_argument("--validate", action="store_true", help="Validate existing catalog") + parser.add_argument("--output", default=CATALOG_PATH, help="Output file path") + args = parser.parse_args() + + if args.validate: + ok = validate_catalog() + sys.exit(0 if ok else 1) + + if args.url: + entry = extract_entry(args.url) + if entry: + print(yaml.dump([entry], default_flow_style=False, allow_unicode=True)) + else: + print("Failed to extract entry.", file=sys.stderr) + sys.exit(1) + + elif args.whats_new: + catalog = load_catalog() + existing_urls = {e["url"] for e in catalog.get("entries", [])} + new_count = 0 + + for wn_url in WHATS_NEW_URLS: + print(f"Fetching: {wn_url}") + links = extract_whats_new_links(wn_url) + print(f" Found {len(links)} links") + + for link in links: + url = link["url"] + if url in existing_urls: + continue + print(f" Processing: {link['title'][:60]}...") + entry = extract_entry(url) + if entry: + catalog.setdefault("entries", []).append(entry) + existing_urls.add(url) + new_count += 1 + + catalog["last_refreshed"] = datetime.now().strftime("%Y-%m-%d") + catalog["source"] = "https://docs.oracle.com/en/solutions/oracle-architecture-center/" + catalog["entry_count"] = len(catalog.get("entries", [])) + save_catalog(catalog) + print(f"Added {new_count} new entries.") + + else: + parser.print_help() + + +if __name__ == "__main__": + main()