Three-layer defense to keep root SKILL.md (Claude Code) and
.agents/skills/oci-deal-accelerator/SKILL.md (Codex) byte-aligned.
Without this, Claude Code and Codex can drift and give the user
contradictory instructions — exactly the failure mode that produced
the workload-driven-mode incident on 2026-04-25.
Layers (least → most enforcement):
1. Local pre-commit hook (.githooks/pre-commit, opt-in via
``make install-hooks`` which sets core.hooksPath). When SKILL.md
is staged, auto-runs scripts/sync-skill.py and stages the
regenerated .agents/ copy in the same commit. Idempotent.
2. ``make install-hooks`` target + a tip line in ``make venv``'s
output so any new clone discovers the hook setup.
3. CI gate (.gitea/workflows/skill-sync.yaml) on every push/PR
touching SKILL.md or the .agents/ copy. Runs
``scripts/sync-skill.py --check`` and fails the build on drift.
Catches anyone who skipped the hook.
README documents the rationale and the three layers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements points 3–8 of the post-procedure follow-up.
- Make targets: diagram-lookup, diagram-validate-spec, diagram-spec-audit,
archcenter-descriptions-refresh, archcenter-smoke. Promotes the new
tools out of "remember the CLI" into discoverable build-automation.
- .gitea/workflows/diagram-validators.yaml — runs spec validator across
every absolute_layout spec on push/PR, plus a drawio re-render smoke
test on the canonical example. Hard fail blocks merges.
- archcenter_pattern_lookup --llm-rewrite — opt-in (ANTHROPIC_API_KEY
env var, mirroring DRAWIO_EXE pattern). Rewrites natural-language
queries into canonical OCI terminology before scoring; falls back to
the original query on any error.
- diagram_spec_validator — documents the policy on legacy archcenter
reproductions: LABEL_NEAR_PARENT_EDGE stays a warning so verbatim
reconstructions don't lose pixel-fidelity from cosmetic edits.
- README — documents the asset refresh procedure (downloader +
description fetcher are idempotent, refresh quarterly).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous curl calls ignored exit codes — PR/issue creation was silently
failing (branch got pushed but no PR/issue landed) while the step reported
green. Now each curl captures the response body and HTTP status, prints
both for debugging, and exits non-zero if status != 201.
Also dropped the labels field from the issue payload — Gitea rejects
issue creation when referenced labels don't exist in the repo, which
may have contributed to the silent failure. Labels can be added
manually post-creation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prior run pushed automation/sku-refresh-2026-04-24 successfully but
failed before opening the PR (issue-creation bug, now fixed). On the
next run, main has moved, so a new commit on the same branch name has
a different parent → non-fast-forward rejection.
Append ${{ github.run_number }} to make the branch unique per workflow
invocation. Orphan branches left behind can be cleaned up after their
PRs are merged/closed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The workflow's grep-on-free-text approach was fragile: local run correctly
extracted 162, but in the CI run the 'Open issue' step was skipped, meaning
new_count ended up as 0 after parsing. Likely causes: stdout buffering,
color codes, or shell differences between local and the Gitea runner's
shell.
Fix: tools/refresh_sku_catalog.py --discover now emits a deterministic
last-line marker `DISCOVER_MISSING_COUNT=<n>`. The workflow parses that
with sed (anchored, unambiguous) instead of the human-facing summary
line. Also added a diagnostic echo so the parsed value shows up in CI
logs for future debugging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gitea Actions runner does not support actions/upload-artifact v2.0.0+
(error: "@actions/artifact v2.0.0+, upload-artifact@v4+ and
download-artifact@v4+ are not currently supported on GHES").
The v4 release switched to a new artifact API that only GitHub-hosted
runners implement. v3 still uses the v1 API and works on Gitea /
self-hosted / GHES runners.
Downgraded in both sku-catalog-refresh.yaml and kb-health.yaml.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The oci-mcp-runner uses node:20-bookworm which runs as root and does
NOT ship with sudo installed. Our workflows used 'sudo apt-get' → step
failed with 'sudo: command not found' (exit 127) on first invocation
of sku-catalog-refresh.yaml.
Fix both affected workflows (sku-catalog-refresh + kb-health):
- Drop the `sudo` prefix (we're already root).
- Install Python deps via apt packages (python3-requests, python3-yaml,
python3-bs4) instead of pip. Avoids PEP 668 / externally-managed
environment error on Debian 12 and is faster/cacheable by the runner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch from manually-configured secrets.GITEA_TOKEN to the auto-provisioned
secrets.GITHUB_TOKEN that Gitea Actions creates per run. Scoped to the repo,
rotates per workflow run, no manual config needed.
Added explicit permissions block (contents:write, pull-requests:write,
issues:write) so the ephemeral token has the scopes required to push a
branch, open the PR, and open the issue for new SKUs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- refresh_sku_catalog.py: add --discover to report API SKUs missing from
catalog, filtered to already-curated serviceCategory values. Auto-runs
at end of --validate and --refresh so gaps surface on every maintenance
pass. First run found 162 missing SKUs (Blackwell GPUs, X12 Ax compute,
VMware reserved tiers, WebLogic-on-OKE, Analytics/OIC BYOL).
- oci_bom_gen.py: fix Cost % column being blank. Back-fill was gated on
formula detection but data rows write raw numbers, so the predicate
never matched. Now tracks data_item_rows explicitly.
- .gitea/workflows/sku-catalog-refresh.yaml: monthly automation (1st at
09:00 UTC). Auto-refresh prices → push branch + open PR; detect new
SKUs → open issue with labels. Gated on secrets.GITEA_TOKEN.
- kb/pricing/oci-sku-catalog.yaml: add B95714 / B95715 (Autonomous ATP
Dedicated ECPU, LI + BYOL) — canonical SKUs for ADB-D pricing.
- kb/services/compute.yaml: add X12 family (VM.Standard4.Ax.Flex,
BM.Standard4.Ax.120), E6.Ax and A4.Ax variants. Verified against
docs.oracle.com computeshapes.htm. Bump last_verified + changelog.
- templates/bom-spec.yaml: document 730 hrs/month convention as default
(= real annual billing / 12; 744 overstates by 1.92% on 12-month TCO).
- Makefile: new 'make sku-discover' target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
actions/setup-python@v5 fails on the arm64 Gitea runner with "version '3.12'
with architecture 'arm64' was not found" — the prebuilt Python manifest
doesn't cover this runner/arch combo. Use the system python3 from apt; the
link-check scripts don't require 3.12 specifically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Syncs skill files via rsync over SSH, restarts MCP containers, and waits
for healthy status. Requires OCI_SSH_KEY and OCI_HOST secrets in Gitea.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>