Newer
Older
openstack-caracal-dc-dc / docs / changelog-20260724-caveman-disable.md

Changelog -- 2026-07-24 -- caveman plugin: disable on live host + governance + guardrails

Session scope: acting on a 4-seat council review of the third-party caveman Claude Code plugin (installed + off-by-default-guarded 2026-07-23, commit 338c919). Operator accepted the review recommendations (exact utterance: "I'll accept your recommendations"). Harness-config + repo-governance only -- NO cloud mutation, NO runbook change. CURRENT-STATE deliberately untouched (adding a SEC row is not an L10 trigger; the G14 open-SEC count reconciles at Stage 4 close -- see item 2). Council finding that drove the disable: enabled:true runs third-party node on every session start + every prompt REGARDLESS of CAVEMAN_DEFAULT_MODE=off (the env guard gates arming/terse-output, not hook execution), and the plugin's own honest docs put net token savings at break-even-to-negative on this host's gated-approval workload -- so a standing per-turn execution surface on a credential-bearing jumphost was not worth carrying.

Item 1 -- caveman DISABLED on the live host (enabled:false, both scopes)

  • ~/.claude/settings.json (user scope, authoritative for vcloud): enabledPlugins."caveman@caveman" flipped true -> false. Stops the SessionStart + UserPromptSubmit hooks from wiring at the next session start.
  • /home/jessea123/openstack-caracal-dc-dc/.claude/settings.json (committed repo scope): added enabledPlugins {"caveman@caveman": false}. Project scope outranks user scope (Local > Project > User), so this is a fail-closed, travels-with-the-clone off-switch across the operator's workstations -- mirrors the dual placement used for the env guard.
  • CAVEMAN_DEFAULT_MODE=off retained in both files as a residual layer; the extraKnownMarketplaces.caveman entry RETAINED (plugin stays installed) so a deliberate re-enable for hardened-workflow deployment testing remains cheap.
  • Verified: both settings.json parse (jq); user + repo enabledPlugins."caveman@caveman" = false; no ~/.claude/.caveman-active flag (not armed). repo-lint 0 fail / 1 legacy warn (unchanged).
  • Revert: set both enabledPlugins."caveman@caveman" back to true (repo copy via git revert/edit; user copy by hand). Note: re-enabling re-incurs the SEC-017 re-verify duty.

Item 2 -- SEC-017 opened (supply-chain re-verify obligation)

  • Added row SEC-017 to docs/security-ledger.md (next-free by grep; highest prior = SEC-016). Records the standing third-party per-turn code-execution surface, its point-in-time benign verdict at SHA 0d95a81, the not-declaratively-pinnable caveat, the disable disposition, and the STANDING DUTY to re-verify the hooks on any re-enable or plugin update.
  • Register call = OPS (no D-number): fails GA-R3's Roosevelt-delta / A1 grep test (no bare-metal analog of a jumphost output-style plugin); precedent D-129 (OPS plugin-install stays OPS). The SEC row is the durable home the security-ledger mandate requires ("a row at discovery, never only a note") -- the prior changelog (338c919) is GA-R2 non-citable/archive-bound, so the posture had no durable home before this.
  • Count: open SEC rows 12 -> 13. This is NOT an L10 status-couple trigger (L10 fires on docs/audit/*, design-decisions Status lines, and session-ledger gate rows -- not security-ledger.md), so CURRENT-STATE is untouched; the G14 count reconciles at Stage 4 close per its existing deferral pattern.
  • Self-measured drift baseline (sha256, 2026-07-24; executing cache copy == git clone at 0d95a81, only a .in_use runtime marker differing):
    • src/hooks/caveman-activate.js bc0af3ba327657630e5f6c60be619e6b5394bf03edc29b1384a15f09a1b348bf
    • src/hooks/caveman-mode-tracker.js 3e34f004d4a9e65609f8c95b47dfe5f0e3a6f2d16b88c7f8af730187ee2fb246
    • src/hooks/caveman-config.js f47fe2e6440578eeb20408bc3131b63fb8e1ae4f6ea26dbdcca33e7901e5ee8c
    • src/hooks/caveman-stats.js 433a345a279c2d54be51864ef77c0a7b19d0f8604b9a441ce0a1eded31f113e8
    • src/hooks/cavecrew-model-overrides.js 9f2601e8551653609f0b9d700c9bd75bffa43747d2db18ffe83fb73de5dc3607
    • .claude-plugin/plugin.json b76999bd244fa59c2b8805b1c2b573458e2d065b58e5904fc8e9e545e3dac7a6
  • Revert: delete the SEC-017 row from docs/security-ledger.md (git revert <sha> for the commit).

Item 3 -- guardrails: repo-lint L11 + fail-closed compress prohibition

Council-reviewed guardrail set. (a) a repo-lint hash-baseline of the plugin files was REJECTED (layering violation -- the plugin lives outside the repo under ~/.claude, non-portable across workstations, and duplicates the plugin's own checksums.sha256; drift-on-update belongs in the SEC-017 re-verify duty, not repo-lint). Built (b) and (c):

  • (b) repo-lint L11 -- caveman-compress residue detector. scripts/repo_lint.py gains L11: FAIL on any *.original.md in the tree (the backup signature of a lossy in-place /caveman-compress rewrite). Excludes .git, .claude worktrees, .terraform, __pycache__ (reuses the all_text() skip set); opt-out marker repo-lint: allow-original-md per the L9 convention. Docstring catalog + repo-lint.sh header updated L1-L10 -> L1-L11. Harness tests/repo-lint/run-tests.sh gains T42 (residue FAILS), T43 (opt-out suppresses), T44 (worktree excluded): 45/45 PASS (was 42). It is a POST-HOC detector (the original is already overwritten when the backup exists) -- it complements, not replaces, (c).
  • (c) fail-closed compress prohibition. Mechanism measured read-only: /caveman-compress <file> is a SKILL that mutates by running python3 -m scripts <file> from the skill dir (in-place overwrite + <name>.original.md backup). Two layers added: (1) .claude/settings.json deny rules Bash(*python3 -m scripts*) + Bash(*caveman-compress*) -- verified no legit repo usage collides (the strings appear only inside repo_lint.py's L11 text, never in an executed argv); (2) a CLAUDE.md "Tooling guardrails" section prohibiting the compressor against ANY repo-tracked prose file, scoped to the whole record corpus (not a 4-file hand-list, which would rot). Note the pre-existing Skill(*) ask rule already gates the skill invocation itself; the deny is the fail-closed backstop that does not depend on that routing.
  • Net enforcement stack: plugin DISABLED (item 1) is primary; Skill(*) ask + the compress deny gate invocation; L11 catches residue; CLAUDE.md is the human-readable rationale.
  • Verification: jq valid; all touched files ASCII/LF; repo-lint 0 fail / 1 legacy warn; bash scripts/run-tests-all.sh -> GAUNTLET: ALL GREEN (77 harnesses).
  • Revert: git revert <sha> -- removes L11 (+ its 3 harness cases), the two deny rules, and the CLAUDE.md section together.

(Session continued into the VR1 DC-mirror work -- distinct topic, same session, so per GA-R2 it lands in this one changelog.)

Item 4 -- DC1 debmirror PAUSED; bandwidth measured NOT the bottleneck

  • Operator asked to pause one sync so a single mirror completes before tomorrow, on the premise the two syncs share the vcloud uplink. PAUSED dc1 (live): systemctl stop dc1-mirror-sync.service dc1-mirror-sync.timer on the dc1 rack; debmirror killed (0 procs), held at 329.9 GiB (resumable; status left stale "RUNNING"). Timer stopped so it cannot auto-restart and re-contend.
  • MEASURED result: pausing dc1 did NOT speed dc0 up -- solo dc0 averaged ~37 GiB/h vs ~35 GiB/h while contended. So the vcloud uplink is NOT the binding constraint (it is per-connection/per-file, dominated by universe's small-package tail). The pause neither helped nor hurt dc0; dc0 is on track (~70% at 2026-07-24T15:35Z, ETA ~this evening UTC). This finding fed the DC1 decision (item 5): rather than idle dc1, switch it to a caching proxy.
  • Revert: resume dc1's full sync -- on the dc1 rack: systemctl daemon-reload && systemctl start dc1-mirror-sync.service dc1-mirror-sync.timer (debmirror resumes from 329.9 GiB, skipping downloaded files).

Item 5 -- DC1 interim apt caching proxy: D-135 amendment + dc-cache-proxy.sh

  • D-135 AMENDED 2026-07-24 (GA-R5, operator utterance quoted in design-decisions.md): DC1 gets an INTERIM apt caching proxy to unblock its build now; DC0 stays the full-mirror deployment under test; DC1 proxy REPLACED by a full mirror afterward. Both artifact-delivery models exercised this rehearsal (Roosevelt input, D-132/D-133). The paused dc1 debmirror (~330 GiB) is KEPT dormant as the fallback, not deleted.
  • Shipped scripts/dc-cache-proxy.sh (site-keyed check/install; apt-cacher-ng in proxy mode at the D-134 utility .4:3142, consumed via juju model-config apt-http-proxy=http://<LISTEN>:3142; caches archive + security + UCA with no per-repo remap). REUSES the <site>-mirror-net.service net layer (does not re-create it; fails loud if absent, pointing at dc-mirror.sh). Feasibility recon confirmed read-only first: apt-cacher-ng installable (3.7.4-1ubuntu5.24.04.1), all three upstreams 200 via the edge, port 3142 free.
  • Harness tests/dc-cache-proxy/run-tests.sh (15 static cases: syntax, both sites' utility .4 + ruled edge gw, stable-identity keying, port pin, proxy-mode consumption, archive+UCA smoke coverage, net-layer reuse, MEASURED-tag discipline, arg/site validation) -- 15/15 PASS.
  • CURRENT-STATE Stage-4 narrative updated (DC0/DC1 artifact-path split). repo-lint 0 fail / 1 legacy warn; bash scripts/run-tests-all.sh -> GAUNTLET: ALL GREEN (78 harnesses) (was 77; +tests/dc-cache-proxy).
  • Live install on the DC1 rack (apt-get install apt-cacher-ng + dc-cache-proxy.sh install dc1) DONE + verified 2026-07-24 (gated): apt-cacher-ng 3.7.4-1ubuntu5.24.04.1 active on .4:3142, drop-in matches, net layer present, check PASS 8/8 -- proxy serves archive (200) AND UCA caracal (200) (UCA cached with no remap, as expected in proxy mode). NOTE: the DC1 rack reports a pending kernel reboot (running 6.8.0-134 vs installed -136) -- pre-existing, deferred to a maintenance window (a reboot would disrupt the paused mirror).
  • Revert: git revert <sha> (removes the script, harness, D-135 amendment paragraph, and CURRENT-STATE split); if installed live, on the dc1 rack: systemctl disable --now apt-cacher-ng; rm -f /etc/apt-cacher-ng/acng.conf.d/dc1-cache-proxy.conf.