feat(framework): P1+P2 — public sanitization + blocking CI gate
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

Adds tools/quality/scripts/verify-sanitized.sh (two-class, self-tested) wired
blocking in .woodpecker/ci.yml; sanitizes operator identity from the public
framework package so the gate is green.

- purge jarvis/jason/woltje/PDA across 26 files -> generic
- delete jarvis-loop.json overlay; add neutral examples/{personas,overlays}
- relocate maintainer AUDIT to docs/audits/; delete 2 jarvis-brain rule blocks
- neutralize SOUL persona; strip "(Policy: Jason ...)" keeping universal rule
- test fixtures jason.woltje -> ci-bot (both git tests pass)

Deferred (tracked): private third-party host (uscllc) genericization.

Refs #542, closes #571

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 20:57:08 -05:00
parent 92316ab41e
commit 6ad96c37cd
31 changed files with 207 additions and 101 deletions

View File

@@ -309,7 +309,7 @@ if [[ -f "$pi_settings" ]]; then
fi
# Mosaic-specific skills presence check.
mosaic_skills=(mosaic-board mosaic-forge mosaic-prdy mosaic-macp mosaic-standards mosaic-prd mosaic-jarvis mosaic-setup-cicd)
mosaic_skills=(mosaic-board mosaic-forge mosaic-prdy mosaic-macp mosaic-standards mosaic-prd mosaic-setup-cicd)
for skill_name in "${mosaic_skills[@]}"; do
if [[ -d "$MOSAIC_HOME/skills/$skill_name" ]] || [[ -L "$MOSAIC_HOME/skills/$skill_name" ]]; then
pass "Mosaic skill present: $skill_name"

View File

@@ -5,8 +5,8 @@ set -euo pipefail
#
# Usage:
# mosaic-init # Interactive mode
# mosaic-init --name "Jarvis" --style direct # Flag overrides
# mosaic-init --name "Jarvis" --role "memory steward" --style direct \
# mosaic-init --name "Mosaic Agent" --style direct # Flag overrides
# mosaic-init --name "Mosaic Agent" --role "memory steward" --style direct \
# --accessibility "ADHD-friendly chunking" --guardrails "Never auto-commit"
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
@@ -50,7 +50,7 @@ Generate Mosaic identity and configuration files:
Interactive by default. Use flags to skip prompts.
Options:
--name <name> Agent name (e.g., "Jarvis", "Assistant")
--name <name> Agent name (e.g., "Mosaic Agent", "Assistant")
--role <description> Role description (e.g., "memory steward, execution partner")
--style <style> Communication style: direct, friendly, or formal
--accessibility <prefs> Accessibility preferences (e.g., "ADHD-friendly chunking")

View File

@@ -2,7 +2,7 @@
#
# Usage:
# mosaic-init.ps1 # Interactive mode
# mosaic-init.ps1 -Name "Jarvis" -Style direct # Flag overrides
# mosaic-init.ps1 -Name "Mosaic Agent" -Style direct # Flag overrides
$ErrorActionPreference = "Stop"
param(

View File

@@ -62,7 +62,6 @@ legacy_paths=(
"$HOME/.claude/presets/domains"
"$HOME/.claude/presets/tech-stacks"
"$HOME/.claude/presets/workflows"
"$HOME/.claude/presets/jarvis-loop.json"
)
for p in "${legacy_paths[@]}"; do

View File

@@ -70,7 +70,6 @@ $legacyPaths = @(
(Join-Path $env:USERPROFILE ".claude\presets\domains"),
(Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"),
(Join-Path $env:USERPROFILE ".claude\presets\workflows"),
(Join-Path $env:USERPROFILE ".claude\presets\jarvis-loop.json")
)
foreach ($p in $legacyPaths) {

View File

@@ -8,7 +8,7 @@ usage() {
cat <<USAGE
Usage: $(basename "$0") [--apply]
Migrate runtime-local skill directories (e.g. ~/.claude/skills/jarvis) to Mosaic-managed
Migrate runtime-local skill directories (e.g. ~/.claude/skills/<name>) to Mosaic-managed
skills by replacing local directories with symlinks to ~/.config/mosaic/skills-local.
Default mode is dry-run.

View File

@@ -16,7 +16,7 @@ if ($Help) {
Write-Host @"
Usage: mosaic-migrate-local-skills.ps1 [-Apply] [-Help]
Migrate runtime-local skill directories (e.g. ~/.claude/skills/jarvis) to
Migrate runtime-local skill directories (e.g. ~/.claude/skills/<name>) to
Mosaic-managed skills by replacing local directories with junctions to
~/.config/mosaic/skills-local.

View File

@@ -5,7 +5,7 @@ Manage Authentik identity provider (SSO, users, groups, applications, flows) via
## Prerequisites
- `jq` installed
- Authentik credentials in `~/src/jarvis-brain/credentials.json` (or `$MOSAIC_CREDENTIALS_FILE`)
- Authentik credentials in `~/.config/mosaic/credentials.json` (or `$MOSAIC_CREDENTIALS_FILE`)
- Required fields: `authentik.url`, `authentik.username`, `authentik.password`
## Authentication
@@ -47,7 +47,7 @@ All scripts support:
~/.config/mosaic/tools/authentik/user-list.sh
# Search for a user
~/.config/mosaic/tools/authentik/user-list.sh -s "jason"
~/.config/mosaic/tools/authentik/user-list.sh -s "alice"
# Create a user in the admins group
~/.config/mosaic/tools/authentik/user-create.sh -u newuser -n "New User" -e new@example.com -g admins

View File

@@ -4,7 +4,7 @@
# Usage:
# agent-lint.sh # Scan all projects in ~/src/
# agent-lint.sh --project <path> # Scan single project
# agent-lint.sh --json # Output JSON for jarvis-brain
# agent-lint.sh --json # Output JSON for machine consumption
# agent-lint.sh --verbose # Show per-check details
# agent-lint.sh --fix-hint # Show fix commands for failures
#

View File

@@ -5,7 +5,7 @@ Manage Coolify container deployment platform (projects, services, deployments, e
## Prerequisites
- `jq` and `curl` installed
- Coolify credentials in `~/src/jarvis-brain/credentials.json` (or `$MOSAIC_CREDENTIALS_FILE`)
- Coolify credentials in `~/.config/mosaic/credentials.json` (or `$MOSAIC_CREDENTIALS_FILE`)
- Required fields: `coolify.url`, `coolify.app_token`
## Scripts

View File

@@ -39,7 +39,7 @@ if [[ "$*" == "login list --output json" ]]; then
cat <<'JSON'
[
{"name":"evil-usc","url":"https://evilgit.uscllc.com","user":"bad.actor"},
{"name":"usc","url":"https://git.uscllc.com","user":"jason.woltje"}
{"name":"usc","url":"https://git.uscllc.com","user":"ci-bot"}
]
JSON
exit 0
@@ -263,8 +263,8 @@ set -euo pipefail
if [[ "$*" == "login list --output json" ]]; then
cat <<'JSON'
[
{"name":"mosaicstack","url":"https://git.mosaicstack.dev","user":"jason.woltje"},
{"name":"usc","url":"https://git.uscllc.com","user":"jason.woltje"}
{"name":"mosaicstack","url":"https://git.mosaicstack.dev","user":"ci-bot"},
{"name":"usc","url":"https://git.uscllc.com","user":"ci-bot"}
]
JSON
exit 0

View File

@@ -49,7 +49,7 @@ set -euo pipefail
if [[ "$*" == "login list --output json" ]]; then
cat <<'JSON'
[
{"name":"mosaicstack","url":"https://git.mosaicstack.dev","user":"jason.woltje"}
{"name":"mosaicstack","url":"https://git.mosaicstack.dev","user":"ci-bot"}
]
JSON
exit 0

View File

@@ -5,7 +5,7 @@ Manage GLPI IT service management (tickets, computers/assets, users).
## Prerequisites
- `jq` and `curl` installed
- GLPI credentials in `~/src/jarvis-brain/credentials.json` (or `$MOSAIC_CREDENTIALS_FILE`)
- GLPI credentials in `~/.config/mosaic/credentials.json` (or `$MOSAIC_CREDENTIALS_FILE`)
- Required fields: `glpi.url`, `glpi.app_token`, `glpi.user_token`
## Authentication

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# verify-sanitized.sh — blocking CI gate: the public framework package must
# contain no operator-specific personal data or private executable defaults.
#
# Two rule classes:
# 1. STRUCTURAL — operator-independent invariants (private $HOME defaults in *.sh).
# 2. DENYLIST — a LABELED, one-time regression guard for the CURRENT operator's
# identity tokens. This is NOT a general PII detector (a future
# operator's name can't be enumerated); the durable control is the
# L0 prose firewall + human review. This gate just stops *this*
# contamination from coming back.
#
# Scope: all of the framework package — *.md, *.sh, *.ps1, and the CLI scripts under
# tools/_scripts/ (which are extensionless). Excluded: examples/ (holds
# sanitized, placeholdered worked examples), node_modules/, and this gate file.
#
# NOTE on scope: private THIRD-PARTY host references (e.g. a maintainer's employer
# Gitea) are intentionally NOT in this denylist — they are functionally entangled in
# host-routing + test fixtures and are tracked as a separate follow-up.
#
# Self-tests run first: plant known tokens and assert the scan catches them, so a
# broken regex cannot silently no-op the gate.
#
# Usage: verify-sanitized.sh [FRAMEWORK_ROOT]
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FRAMEWORK_ROOT="${1:-$(cd "$SCRIPT_DIR/../../.." && pwd)}"
SELF_REL="tools/quality/scripts/verify-sanitized.sh"
# Labeled current-contaminant denylist. Anchored so substrings like "comparison" or
# "jsonwebtoken" do not match. (jarvis-brain is caught by 'jarvis'.)
DENYLIST='jarvis|jason|woltje|brain\.woltje\.com|/home/jwoltje|\bPDA\b'
# Structural: a private $HOME path used as a shell default (e.g. ${VAR:-$HOME/src/...}).
STRUCTURAL_SH=':[-=]\$\{?HOME\}?/src/'
# Build the in-scope file list once (NUL-delimited).
_scope_files() {
find "$FRAMEWORK_ROOT" -type f \
\( -name '*.md' -o -name '*.sh' -o -name '*.ps1' -o -path '*/tools/_scripts/*' \) \
-not -path '*/examples/*' \
-not -path '*/node_modules/*' \
-not -path "*/$SELF_REL" \
-print0
}
fail=0
cd "$FRAMEWORK_ROOT" || { echo "FRAMEWORK_ROOT not found: $FRAMEWORK_ROOT" >&2; exit 3; }
deny_hits="$(_scope_files | xargs -0 -r grep -nIEi "$DENYLIST" 2>/dev/null || true)"
if [[ -n "$deny_hits" ]]; then
echo "✗ [denylist] operator-identity tokens in shipped files:"
echo "$deny_hits" | sed "s#$FRAMEWORK_ROOT/##; s/^/ /"
fail=1
fi
struct_hits="$(_scope_files | xargs -0 -r grep -nIE "$STRUCTURAL_SH" 2>/dev/null \
| grep -E '\.sh:|/tools/_scripts/' || true)"
if [[ -n "$struct_hits" ]]; then
echo "✗ [structural] private \$HOME/src default in a shipped script:"
echo "$struct_hits" | sed "s#$FRAMEWORK_ROOT/##; s/^/ /"
fail=1
fi
# ---- self-test: the gate must catch planted tokens ----
_selftest() {
local tmp; tmp="$(mktemp -d)" || return 1
printf 'contact jason.woltje at jarvis-brain (PDA note)\n' > "$tmp/planted.md"
printf 'X="${VAR:-$HOME/src/whatever/x.json}"\n' > "$tmp/planted.sh"
local ok=0
grep -qIEi "$DENYLIST" "$tmp/planted.md" || { echo "✗ SELF-TEST: denylist regex broken" >&2; ok=1; }
grep -qIE "$STRUCTURAL_SH" "$tmp/planted.sh" || { echo "✗ SELF-TEST: structural regex broken" >&2; ok=1; }
rm -rf "$tmp"
return $ok
}
_selftest || exit 2
if [[ "$fail" -ne 0 ]]; then
echo
echo "Sanitization gate FAILED. Public framework files must not contain operator identity" >&2
echo "or private \$HOME defaults. Move personal content to init-generated files or examples/." >&2
exit 1
fi
echo "✓ sanitization gate passed (framework *.md/*.sh/*.ps1/_scripts; examples/ excluded)"

View File

@@ -5,7 +5,7 @@ Interact with Woodpecker CI pipelines (list builds, check status, trigger builds
## Prerequisites
- `jq` and `curl` installed
- Woodpecker credentials in `~/src/jarvis-brain/credentials.json`
- Woodpecker credentials in `~/.config/mosaic/credentials.json`
## Setup