Newer
Older
openstack-caracal-dc-dc / docs / archive / changelogs / changelog-20260727-skill-repackage.md

Changelog -- 2026-07-27 -- repackage the openstack-cloud-ops .skill artifact

Session scope: regenerate the committed .skill upload artifact from the authoritative skill dir, ahead of standing up a troubleshooting clone for the devteam on a SEPARATE cloud (openstack-caracal-ipv4 test env). Packaging + changelog only -- NO cloud mutation, NO script change, NO runbook change, no skill CONTENT edit. CURRENT-STATE deliberately untouched (no status-bearing surface changed: no docs/audit/ file, no **Status: line, no | G<n> | gate row -- L10 not triggered).

Branch base: post-merge main (6495cfb), which was verified to carry the 2026-07-27 skill sweep -- git merge-base --is-ancestor 39e8988 origin/main returned 0, and .claude/ + skills/ are byte-identical between origin/main and dc-dc-stage5-preconditions. This matters because the derived artifact must not be built from a base older than its source.

Item 1 -- skills/openstack-cloud-ops/openstack-cloud-ops.skill REGENERATED

The committed upload artifact was last written 2026-07-05 (35cfe41) and had gone 22 days stale against .claude/skills/openstack-cloud-ops/, which is the single source of truth (changelog-20260703-process-hardening, items at :304 and :349-350: "edit under .claude/skills/, repackage the .skill from it").

Measured staleness of the OLD artifact:

  • SKILL.md 129 lines packaged vs 325 authoritative -- it predates the GA-R1..R7 record-authority section, the VR1 DC-DC routing rows, the D-128 operating model, the D-135 per-DC artifact-strategy invariant, the DOCFIX-200 "Stage 4 hands off READY nodes" ruling, and the four audit-derived invariants (finding-vs-conclusion, citation-as-existence-claim, ruled-is-not-built, a-checker-that-cannot-fail).
  • Two reference files were absent entirely: platform-traps.md (517 lines -- the per-tool trap + verbatim-error index) and opentofu-provider-docs.md (154 lines).
  • environment.md, operating-discipline.md, script-authoring.md, troubleshooting.md all differed.
  • Consequential content defect: the packaged frontmatter description names openstack-caracal-ipv4 as "the repository this skill operates against". The 2026-07-05 build predates BOTH the D-110 seed of this repo (2026-07-09) and the repo-agnostic sweep of the same week (changelog-20260709-repo-agnostic-sweep), so the literal was correct WHEN BUILT. It is wrong NOW, and not because of a rename: per D-110, openstack-caracal-ipv4 is the separate, still-live SOURCE project (VR0 v1, IPv4-only) this repo was seeded FROM at 8813efc -- with its own D-series ceiling at D-075 and the D-076..D-099 band reserved to it. The authoritative copy is repo-agnostic ("the repository this skill operates against") per the L9 self-ref guard. So an upload of the stale package points the operating skill at a DIFFERENT PROJECT's repo, which is a live confusion risk right now: that is the same repo backing the devteam's test env.

Build (deterministic by construction, so a rebuild from unchanged sources is byte-identical and drift is detectable by hash):

STAGE=<tmp>; cp -r .claude/skills/openstack-cloud-ops "$STAGE/openstack-cloud-ops"
find "$STAGE" -exec touch -h -t 202607270000.00 {} +
cd "$STAGE" && zip -r -X -q out.zip openstack-cloud-ops

Verification captured this session:

  • Internal layout preserved against the old package: a top-level openstack-cloud-ops/ prefix dir, NOT a bare ./SKILL.md (a bare layout mis-mounts on claude.ai upload). unzip -l = 9 entries / 7 files (was 5 files), 104171 bytes uncompressed.
  • Sources verified ASCII + LF before packing: 0 non-ASCII lines and 0 CR lines across all 7 files.
  • Round-trip: unzip of the committed artifact diff -r against .claude/skills/openstack-cloud-ops/ -> IDENTICAL, byte-for-byte.
  • Determinism: independent rebuild reproduced sha256 287424fd8ad014db18d9f3609ef4edd2fe434f34fb26c899f67506b68154555d.
  • EOL-normalization checked, since .gitattributes pins *.zip binary but the extension here is .skill (see Item 2): working file sha256 == staged blob sha256, and git diff --cached reports Bin 22230 -> 50267 bytes. Git's text=auto content detection classified it binary, so no normalization occurred. The artifact is correct as committed.
  • bash scripts/repo-lint.sh -> PASS (0 fail, 0 warn) on this base.

  • Revert: git revert this commit, or restore the prior blob directly: git checkout 35cfe41 -- skills/openstack-cloud-ops/openstack-cloud-ops.skill (prior sha256 is recoverable from that blob; prior size 22230 bytes). Note that reverting restores an artifact that mis-names the repo, per Item 1.

Item 2 -- findings LOGGED, not executed (hard rule 1)

Both surfaced while packaging. Neither is actioned here; both are put to the operator rather than fixed mid-step.

  1. .gitattributes covers *.zip binary but not *.skill. The stanza's own comment states the intent -- "Binary artifacts must never be EOL-normalized (DOCFIX-071 ships a committed zip)" -- and this file IS a committed zip that the pattern misses. It is currently benign (measured above: git's content detection catches it), but the protection is incidental rather than declared. The 2026-07-05 artifact used compression method store, which packs markdown RAW -- exactly the case where a text-looking blob is most at risk if content detection ever went the other way. Minimal fix would be one line: *.skill binary.

  2. Nothing gates this artifact's freshness -- which is why it went 22 days stale, spanning three stage closes. The stage-close ritual in SKILL.md names two derived outputs -- keep .claude/skills/ current, then regenerate the dated consolidated .md snapshot -- but does NOT name the .skill. The snapshot was regenerated 2026-07-27 (39e8988); the .skill was not. This is the repo's own "a checker that cannot fail is not a gate" pattern in its weaker form: no checker at all. A scripts/skill-package.sh with its tests/skill-package/run-tests.sh harness, plus a repo-lint rule asserting the artifact's CONTENT matches the source dir (assert on content, never on existence), would make the staleness impossible rather than noticed. Scoped as OPS under GA-R3 -- no Roosevelt delta, fails the A1 grep test.

Item 3 -- record correction (commit message of 9ffc904)

The commit body of 9ffc904 (this session's packaging commit, already pushed) describes the stale artifact's frontmatter literal as "correct when built (pre-D-110 seed)". That phrasing reads as though openstack-caracal-ipv4 were a FORMER NAME of this repo. It is not, and D-110 says so explicitly: openstack-caracal-ipv4 is the separate, still-live SOURCE project this repo was seeded FROM at 8813efc; its D-series ceils at D-075 and the D-076..D-099 band is reserved to it and NEVER assigned here.

Corrected in Item 1 above, which is the authoritative statement. The commit message is left as pushed rather than amended: git push --force* is a deny rule in .claude/settings.json, and working around a permission rule is barred by hard rule 3. Append-only correction also matches this repo's verbatim-record discipline -- the error and its correction both stay visible, which is the outcome an amend would have destroyed.

Worth keeping, because the misreading is the failure mode the skill already names: a finding is an observation, not a conclusion. "The packaged description names a repo that is not this one" was the reliable OBSERVATION; "therefore it is a stale name for this repo" was an unmeasured CONCLUSION, and one grep of docs/design-decisions.md inverted it.

Item 4 -- ADVISORY (no repo change): devteam TestCluster resize, openstack-caracal-ipv4

Recorded here as SESSION NARRATIVE, not as an adopted finding. The cloud in question is openstack-caracal-ipv4 -- per D-110 a SEPARATE, still-live source project with its own D-0NN series -- so nothing here is imported into this repo's decision record. What is captured is the research, because it was expensive to derive and was otherwise transcript-only.

Symptom, operator-reported: signed into Horizon as devteam-cluster, Project > Container Infra > Clusters > TestCluster > Resize Cluster returns Error: Unable to get cluster's working nodes. TestCluster is single-node; the goal is three. That cloud runs Magnum with the magnum-capi-helm CAPI driver.

MEASURED (upstream source, not inferred):

  • The string is raised in magnum-ui's magnum.service.js, in the .catch() of getClusterNodes(id), which wraps GET /api/container_infra/clusters/<id>/resize.
  • That URL is Horizon's OWN ClusterResize.get() view in magnum_ui/api/rest/magnum.py, which only POPULATES the dialog. It has exactly three failure exits, ALL of which surface as this one toast: cluster_show() raising AttributeError -> 404; nodegroup_list() raising AttributeError -> 404; any other exception -> 500 (a 403 from nodegroup_list is NOT caught by that except AttributeError, so authz failures land in the 500 branch).
  • Therefore the resize call is never made. This is a READ failure populating the dialog, BEFORE any resize is attempted -- so it is not the resize action being denied. Authz remains a candidate CAUSE, but of reading nodegroups.
  • Launchpad #2054444 ("Cluster Resize is failing in UI") is Invalid (2024-03-01, Ubuntu-focal-specific) and carries a DIFFERENT string (Unable to resize given cluster id). Do not cite it as the cause. Checked per the skill's own rule: a citation is an existence claim, only its content is evidence.
  • magnum-capi-helm DOES support nodegroups, so a driver gap is not the explanation.

In-repo prior art that bears on it: D-063's C3 verification resized beta-cluster 1->2 AS THE TENANT to UPDATE_COMPLETE / HEALTHY, which is why the CLI (openstack coe cluster resize) was recommended as the unblock -- with the qualifier that C3 was on THIS project's cloud, not on ipv4. clientdocs/jenkins- kubernetes-guide.md documents both the CLI and the dashboard path; the dashboard line is a DOCFIX candidate IN THE IPV4 REPO if the dialog is broken there.

A self-contained diagnostic prompt for the dev-cloud Code agent was delivered to the operator as a FILE (environment-scoped against VR1 value bleed, four phases, report-before-acting, resize gated as an approved mutation). It is deliberately NOT committed here: it is an ipv4-directed operational document, and importing one is the boundary D-110 draws.

Escalation path if it matters to VR1: should the root cause prove ARCHITECTURAL (a genuine magnum-ui/CAPI-driver incompatibility rather than something local to that cloud), it would also affect the DC-DC deployment, and the correct move is a PROPOSED D-1NN in THIS repo that CITES the ipv4 finding by its original id -- never a merge of ipv4-derived commits.

Item 5 -- authoring trap found this session (logged, not executed)

.claude/hooks/guard-destructive.py inspects the Bash COMMAND STRING, so it blocks a git commit whose MESSAGE TEXT merely contains a force-push string -- the message was being passed via a heredoc, and the hook matched the literal inside it. The block is correct behaviour (fail-closed on a substring match) and should NOT be loosened; the authoring workaround is to write the message to a file and use git commit -F <file>.

Worth folding into references/script-authoring.md at the next stage close, as it will recur for anyone documenting the force-push ban in a commit or changelog. Not done here: editing the authoritative skill is a stage-close activity and this session closed no stage.