diff --git a/.agents/skills/oci-deal-accelerator/SKILL.md b/.agents/skills/oci-deal-accelerator/SKILL.md index 36acb4a..9d4451d 100644 --- a/.agents/skills/oci-deal-accelerator/SKILL.md +++ b/.agents/skills/oci-deal-accelerator/SKILL.md @@ -137,6 +137,7 @@ Pick a number, or just describe what you need. - **The cached `_template.yaml` is your YAML SCAFFOLD** — auto-extracted from the canonical `.drawio` by `tools/archcenter_drawio_to_template.py`. It carries Oracle's container geometry (region/vcn/ad/subnet bboxes), service positions, and edge waypoints in the `absolute_layout:` shape your spec uses. Copy it as the starting point, rename ids, fill in `type:` for services (the extractor leaves `type: TODO_identify` because the canonical icon TYPE is encoded in stencil bytes — pick the right alias from the renderer's TYPE_TO_ICON), and adapt to the customer's components. Lookup result surfaces it as `cached: yaml=`. - **The cached `.drawio` is the visual source of truth** if the template extractor missed something — open it (drawio.exe, the SVG companion, or the XML) for the canonical look. - **Do NOT use `examples/` as a geometry source.** `examples/` is previous user output, not authoritative. The `_template.yaml` covers everything `examples/` was being abused for. + - **Lookup budget: max 2 queries — never loop.** If query 1 returns nothing convincing, run ONE refinement (broader phrasing or different keyword angle), then STOP. Re-querying the catalog 3+ times to find a "better" match is the dominant time-sink in this phase — the catalog has 123 entries indexed once; if two passes can't surface a topology match, none exists. Output of the lookup tool also surfaces a **`⚠ KNOWN GAP`** banner at the top whenever the query touches a documented gap (e.g. OCI-native services accessed FROM GCP via Cross-Cloud Interconnect, or OCI Cache/Redis/PostgreSQL as primary subject). When that banner appears, follow its **Recommended composition** verbatim instead of running more queries — it lists the primitives to combine. When NO gap banner appears AND no top-3 entry matches the topology, present the closest 3 to the user with: *"No exact ref-arch matches. Closest hits are X / Y / Z. I can either base the diagram on the closest of these (geometry transfers, service names don't), or compose from ``. Which?"* — let the user pick the chassis instead of guessing. 2. **Pre-generation review.** Confirm the component list with the user (REQUESTED + TECHNICAL DEPENDENCIES per the whitelist). 3. **Author the spec in `absolute_layout` shape** — this is REQUIRED. Do NOT use the legacy workload-driven shape (`tenancy → regions → compartments → services`); it does not resolve OCI icon stencils, does not run through the spec validator, and produces wireframe-looking output (rectangles with text instead of real OCI icons). Every container, service, label, and connection needs explicit `(x, y, w, h)`. diff --git a/Makefile b/Makefile index 0ec8544..34c2e5f 100644 --- a/Makefile +++ b/Makefile @@ -19,11 +19,20 @@ help: ## Show this help VENV_PYTHON := $(shell command -v python3.12 2>/dev/null || command -v python3.11 2>/dev/null || command -v python3.10 2>/dev/null || echo python3) -venv: ## Create virtual environment and install dependencies - @echo "Using $(VENV_PYTHON) to create venv..." - $(VENV_PYTHON) -m venv .venv +venv: .venv/.deps-installed ## Create or update venv (idempotent — no-op if requirements.txt unchanged) + +# Marker file pattern: the venv only rebuilds when requirements.txt is +# newer than the marker. A fresh `make venv` after a successful install +# is an instant no-op (was 1m53s on Codex sandboxes — every turn). +# To force a rebuild: `rm .venv/.deps-installed` (or `rm -rf .venv`). +.venv/.deps-installed: requirements.txt + @if [ ! -x .venv/bin/python ]; then \ + echo "Using $(VENV_PYTHON) to create venv..."; \ + $(VENV_PYTHON) -m venv .venv; \ + fi .venv/bin/pip install --upgrade pip .venv/bin/pip install -r requirements.txt + @touch .venv/.deps-installed @echo "Virtual environment ready. Run: source .venv/bin/activate" @echo "Tip: run 'make install-hooks' once to enable the pre-commit" @echo " hook that keeps SKILL.md in sync with the Codex copy." diff --git a/SKILL.md b/SKILL.md index ff48726..4b70540 100644 --- a/SKILL.md +++ b/SKILL.md @@ -135,6 +135,7 @@ Pick a number, or just describe what you need. - **The cached `_template.yaml` is your YAML SCAFFOLD** — auto-extracted from the canonical `.drawio` by `tools/archcenter_drawio_to_template.py`. It carries Oracle's container geometry (region/vcn/ad/subnet bboxes), service positions, and edge waypoints in the `absolute_layout:` shape your spec uses. Copy it as the starting point, rename ids, fill in `type:` for services (the extractor leaves `type: TODO_identify` because the canonical icon TYPE is encoded in stencil bytes — pick the right alias from the renderer's TYPE_TO_ICON), and adapt to the customer's components. Lookup result surfaces it as `cached: yaml=`. - **The cached `.drawio` is the visual source of truth** if the template extractor missed something — open it (drawio.exe, the SVG companion, or the XML) for the canonical look. - **Do NOT use `examples/` as a geometry source.** `examples/` is previous user output, not authoritative. The `_template.yaml` covers everything `examples/` was being abused for. + - **Lookup budget: max 2 queries — never loop.** If query 1 returns nothing convincing, run ONE refinement (broader phrasing or different keyword angle), then STOP. Re-querying the catalog 3+ times to find a "better" match is the dominant time-sink in this phase — the catalog has 123 entries indexed once; if two passes can't surface a topology match, none exists. Output of the lookup tool also surfaces a **`⚠ KNOWN GAP`** banner at the top whenever the query touches a documented gap (e.g. OCI-native services accessed FROM GCP via Cross-Cloud Interconnect, or OCI Cache/Redis/PostgreSQL as primary subject). When that banner appears, follow its **Recommended composition** verbatim instead of running more queries — it lists the primitives to combine. When NO gap banner appears AND no top-3 entry matches the topology, present the closest 3 to the user with: *"No exact ref-arch matches. Closest hits are X / Y / Z. I can either base the diagram on the closest of these (geometry transfers, service names don't), or compose from ``. Which?"* — let the user pick the chassis instead of guessing. 2. **Pre-generation review.** Confirm the component list with the user (REQUESTED + TECHNICAL DEPENDENCIES per the whitelist). 3. **Author the spec in `absolute_layout` shape** — this is REQUIRED. Do NOT use the legacy workload-driven shape (`tenancy → regions → compartments → services`); it does not resolve OCI icon stencils, does not run through the spec validator, and produces wireframe-looking output (rectangles with text instead of real OCI icons). Every container, service, label, and connection needs explicit `(x, y, w, h)`. diff --git a/kb/architecture-center/catalog.yaml b/kb/architecture-center/catalog.yaml index 70a9e5a..dfea437 100644 --- a/kb/architecture-center/catalog.yaml +++ b/kb/architecture-center/catalog.yaml @@ -18,6 +18,78 @@ last_verified: "2026-03-16" source: "https://docs.oracle.com/en/solutions/oracle-architecture-center/" entry_count: 123 +# ============================================================================= +# KNOWN GAPS — topologies the catalog does NOT cover well +# ============================================================================= +# +# Surfaced by tools/archcenter_pattern_lookup.py at the top of its output +# whenever the user's query tokens match any one of the `triggers` token +# sets below. Purpose: stop the lookup loop early when no canonical Oracle +# ref-arch exists for what the user asked, and tell the agent how to +# COMPOSE the topology from primitives instead of refining query phrasing. +# +# Schema per gap: +# id: unique slug +# triggers: list of token sets — fires when the expanded query contains +# EVERY token in at least ONE set (OR across sets, AND inside +# each set). Tokens are lowercased and synonym-expanded the +# same way as scoring tokens. +# notice: multi-line string surfaced verbatim above the score-based +# top-K. Should explain WHY this is a gap and HOW to compose +# from existing primitives. Be concrete (slugs, alias names). +# +# Add a gap entry only after a real lookup loop wasted user time on a +# topology with no canonical match. Speculation belongs elsewhere. + +known_gaps: + + - id: gcp-to-oci-native-services + triggers: + - [gcp, postgresql] + - [gcp, redis] + - [gcp, mysql] + - [gcp, oke] + - [gcp, cache] + - [gcp, compute] + - [google, postgresql] + - [google, redis] + - [google, mysql] + - [google, oke] + - [google, cache] + - [google, compute] + notice: | + No canonical Oracle ref-arch covers OCI-native services (PostgreSQL, + OKE, Cache, etc.) accessed FROM Google Cloud via Cross-Cloud + Interconnect. The `Database@Google Cloud` entries below are the + OPPOSITE direction (Oracle DB running ON GCP, not GCP apps reaching + OCI-native services). + + Recommended composition — STOP refining the lookup query, build from: + 1. A simple VCN baseline (e.g. `start-with-an-oci-virtual-cloud-network`). + 2. The OCI service in a private subnet behind DRG + FastConnect VC. + 3. A `GCP ` container on the left with `cloud_icon: gcp_compute` + (or the relevant GCP service — the renderer maps to mxgraph.gcp2.* + the same way it does for AWS). + + - id: newer-oci-services-without-icon + triggers: + - [redis] + - [valkey] + - [oci_cache] + notice: | + OCI Cache with Redis/Valkey is GA after the v24.2 toolkit shipped, so + no dedicated ref-arch exists in the catalog and no toolkit icon ships + either. Oracle's own multi-service ref archs that DO mention Redis + embed inline SVG over the generic database/cache stencil. + + Recommended path — don't keep searching: + 1. Pick the closest topology BY SHAPE (HA pair, primary+replica, + multi-AZ) from the catalog — geometry transfers, names don't. + 2. Use `type: cache` (or `type: database`) with explicit + `label: "OCI Cache (Redis)"`. The renderer's alias table already + routes `type: redis` → generic stencil. + 3. Reference: SKILL.md option 2 step 3 (newer-OCI-services convention). + entries: # ===== DATABASE — MULTICLOUD (Database@Azure) ===== diff --git a/tools/archcenter_pattern_lookup.py b/tools/archcenter_pattern_lookup.py index 8c34d7c..52007d7 100644 --- a/tools/archcenter_pattern_lookup.py +++ b/tools/archcenter_pattern_lookup.py @@ -327,12 +327,42 @@ def _patterns_for(entry: dict) -> list[str]: return _build_patterns_index().get(entry.get("url", ""), []) -def lookup(query: str, top: int = 5, expand_synonyms: bool = True) -> list[dict]: +def _matched_gaps(query_tokens: set[str], gaps: list[dict]) -> list[dict]: + """Return every known gap whose triggers match the (expanded) query. + + A gap fires when ANY one of its `triggers` token sets is a subset of + `query_tokens` — i.e. AND inside each set, OR across sets. This + matches the "stop searching, compose instead" intent: if even one + narrow trigger pair (e.g. `[gcp, postgresql]`) is in the query, the + catalog has nothing canonical to offer. + """ + matched: list[dict] = [] + for gap in gaps or []: + triggers = gap.get("triggers") or [] + for trigger_set in triggers: + tokens_required = {t.lower() for t in trigger_set} + if tokens_required and tokens_required.issubset(query_tokens): + matched.append(gap) + break + return matched + + +def lookup(query: str, top: int = 5, expand_synonyms: bool = True) -> dict: + """Score the catalog against `query` and return the top-K plus any + known-gap notices the query triggers. + + Returns a dict shaped as: + {"matches": [], "gaps": []} + + Callers that only care about matches can read `result["matches"]`. + """ catalog = yaml.safe_load(CATALOG.read_text(encoding="utf-8")) entries = catalog.get("entries", []) + gaps_def = catalog.get("known_gaps", []) or [] qt = _tokens(query) if expand_synonyms: qt = _expand_query_tokens(qt) + matched_gaps = _matched_gaps(qt, gaps_def) scored = [] # Two-pass scoring: cheap fields first (title/tags/services/summary), # then enrich the top-K with expensive fields (description text from @@ -372,10 +402,19 @@ def lookup(query: str, top: int = 5, expand_synonyms: bool = True) -> list[dict] "visual_patterns": _patterns_for(e), "has_description": bool(c.get("_description")), }) - return scored + return {"matches": scored, "gaps": matched_gaps} -def _print_results(query: str, results: list[dict]) -> None: +def _print_results(query: str, payload: dict) -> None: + gaps = payload.get("gaps") or [] + results = payload.get("matches") or [] + for gap in gaps: + notice = (gap.get("notice") or "").rstrip() + gap_id = gap.get("id", "unknown") + print(f"⚠ KNOWN GAP — {gap_id}") + for line in notice.splitlines(): + print(f" {line}" if line else "") + print() if not results: print(f"No matches for: {query}") return