From 85a02e5dcd0c0f750e54fbd807dbaf326e47b48b Mon Sep 17 00:00:00 2001 From: Israel Weinberg <99821070+izzywdev@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:10:54 +0300 Subject: [PATCH 1/2] ci: add gate-line-endings (block new CRLF) [FuzeSDLC#73] --- .github/workflows/gate-line-endings.yml | 131 ++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 .github/workflows/gate-line-endings.yml diff --git a/.github/workflows/gate-line-endings.yml b/.github/workflows/gate-line-endings.yml new file mode 100644 index 0000000..080c208 --- /dev/null +++ b/.github/workflows/gate-line-endings.yml @@ -0,0 +1,131 @@ +name: gate-line-endings + +# Blocks NEW CRLF from entering a repo. Part of the standard workflow stack. +# +# WHY THIS EXISTS (a repeated production failure — do not weaken it): +# FuzeFront's release workflow bumps image tags in values-prod.yaml with an awk that +# anchors on line content. A CRLF check-in made every $-anchored match miss, the count +# guard hit 0, and EVERY release died until a human restored the endings by hand. The +# same class of break has hit a vendored OpenAPI contract (byte diff vs upstream) and +# CSS (PostCSS "Unexpected }" on balanced braces). CRLF in a committed .sh breaks at +# runtime, with a \r that does not show up in a diff. +# +# WHY .gitattributes IS NOT ENOUGH — the reason this is a gate: +# 1. It only covers paths someone thought to list. FuzeFront added +# `deploy/helm/** text eol=lf` after the first incident but NOT +# `.github/workflows/**` — so the workflow the CRLF had broken was itself still +# unprotected, and git re-offered CRLF on the very next edit to it. +# 2. Commits through the GitHub Contents API (bots, agent runs, web edits) BYPASS +# .gitattributes entirely — no clean filter runs. Only a check on the committed +# blob sees those. +# +# SCOPE — deliberately PR-only for the hard failure: +# Repos carry a large pre-existing backlog (FuzeFront: 207 CRLF blobs at the time this +# gate was written). Failing on the whole tree would red every PR in the org on day one +# and would get switched off within a week — a gate nobody can keep enabled protects +# nothing. So: +# • pull_request -> HARD FAIL, but only on files the PR actually adds/changes. +# Prevents every new regression, needs no big-bang cleanup. +# • push to default -> report the backlog as warnings. Visible, never blocking. +# Clean a repo up with `git add --renormalize .`; the PR gate keeps it clean after. +# +# WHAT IT INSPECTS: the INDEX side of `git ls-files --eol` (i/...) — the bytes actually +# stored in git, not the working copy, which the runner may have normalised on checkout. +# Binary blobs (i/-text) are skipped by construction (they report neither crlf nor mixed). + +on: + push: + branches: [main, master] + pull_request: + workflow_call: + +permissions: + contents: read + +concurrency: + group: gate-line-endings-${{ github.ref }} + cancel-in-progress: true + +jobs: + gate-line-endings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check committed blobs for CRLF + shell: bash + env: + EVENT: ${{ github.event_name }} + BASE_REF: ${{ github.base_ref }} + run: | + set -uo pipefail + + # Formats that legitimately require CRLF on Windows. Keep this list SHORT — + # every entry is a hole in the gate. + is_allowed() { + case "${1,,}" in + *.bat|*.cmd|*.ps1|*.psm1|*.sln|*.vcxproj|*.csproj|*.rc) return 0 ;; + *) return 1 ;; + esac + } + + # Collect every blob stored with CRLF or mixed endings. + # Format: "i/\tw/\tattr/\t" (path is the last tab field) + declare -A bad_eol=() + while IFS= read -r line; do + case "$line" in + i/crlf*|i/mixed*) ;; + *) continue ;; + esac + path="${line##*$'\t'}" + is_allowed "$path" && continue + bad_eol["$path"]="${line%% *}" + done < <(git ls-files --eol) + + if [ "$EVENT" != "pull_request" ]; then + # Report-only: surface the backlog without blocking the default branch. + if [ "${#bad_eol[@]}" -eq 0 ]; then + echo "✅ no CRLF in committed text blobs" + else + echo "::warning title=gate-line-endings::${#bad_eol[@]} pre-existing file(s) stored with CRLF — run 'git add --renormalize .' to clean up" + for p in "${!bad_eol[@]}"; do echo " ${bad_eol[$p]} $p"; done | sort -k2 + fi + exit 0 + fi + + # PR: hard-fail, but only for files this PR adds or modifies. + git fetch --no-tags --quiet origin "$BASE_REF" || true + changed="$(git diff --name-only --diff-filter=ACMR "origin/${BASE_REF}...HEAD" || true)" + if [ -z "$changed" ]; then + echo "✅ no added/changed files to check"; exit 0 + fi + + offenders="" + count=0 + while IFS= read -r path; do + [ -n "$path" ] || continue + if [ -n "${bad_eol[$path]+set}" ]; then + offenders+=" ${bad_eol[$path]} $path"$'\n' + count=$((count + 1)) + fi + done <<< "$changed" + + if [ "$count" -eq 0 ]; then + echo "✅ no CRLF introduced by this PR ($(wc -l <<< "$changed") file(s) checked)" + exit 0 + fi + + echo "::error title=gate-line-endings::${count} file(s) in this PR are stored with CRLF but must be LF" + echo "" + echo "$offenders" + echo "Line-anchored tooling breaks on CRLF in ways that look like unrelated bugs —" + echo "a CRLF check-in has taken down every release in this org before." + echo "" + echo "Fix, from a clean tree:" + echo " 1. Cover the path in .gitattributes, e.g.: text eol=lf" + echo " 2. git add --renormalize . && git commit -m 'chore: normalise line endings to LF'" + echo "If a file genuinely needs CRLF, add its extension to is_allowed() above," + echo "with a comment saying why." + exit 1 From d522f009478721ae713c00d977c26b9239710aab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 08:12:34 +0000 Subject: [PATCH 2/2] chore(governance): reconcile managed files to FuzeSDLC v1 [skip ci] --- .claude/agents/backend-engineer.md | 2 +- .claude/agents/frontend-engineer.md | 2 +- .../schema/role-manifest.schema.json | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.claude/agents/backend-engineer.md b/.claude/agents/backend-engineer.md index dbcc18d..a4bcb03 100644 --- a/.claude/agents/backend-engineer.md +++ b/.claude/agents/backend-engineer.md @@ -4,7 +4,7 @@ model: sonnet description: Implements ONLY the backend slice of a feature — HTTP API/services, business logic, DB schema/migrations, events, and the backend's own unit tests — against a frozen API contract. Does NOT build UI, the independent test suite, deploy wiring, or docs. Use for backend implementation in a contract-first fan-out. # Figma is reserved for frontend-engineer; pure-code agent gets core tools only (no MCP). tools: Task, Bash, Glob, Grep, LS, Read, Edit, MultiEdit, Write, NotebookEdit, WebFetch, WebSearch, TodoWrite -skills: [api-contract-first, feature-flags, verification-protocol, model-cascade] +skills: [api-contract-first, feature-flags, logging, verification-protocol, model-cascade] --- You are a **backend engineer** for FuzeFront. You implement the **backend slice only**. diff --git a/.claude/agents/frontend-engineer.md b/.claude/agents/frontend-engineer.md index 2ccbc44..3e8d6a0 100644 --- a/.claude/agents/frontend-engineer.md +++ b/.claude/agents/frontend-engineer.md @@ -5,7 +5,7 @@ description: Implements ONLY the UI slice of a feature — a design-system-first # SOLE owner of the Figma MCP plugin (design-to-code). All other domain agents have # Figma removed from their tool grant — it is reserved here for the UI/design-system slice. tools: "*" -skills: [fuzefront-ui-package, design-system-inheritance, design-system-conformance, ui-frame-contract, frontend-design, feature-flags, ui-runtime-validation, verification-protocol, model-cascade] +skills: [fuzefront-ui-package, design-system-inheritance, design-system-conformance, ui-frame-contract, frontend-design, feature-flags, logging, ui-runtime-validation, verification-protocol, model-cascade] --- You are a **frontend engineer**. You implement the **UI slice only**. diff --git a/agent-templates/schema/role-manifest.schema.json b/agent-templates/schema/role-manifest.schema.json index 8780983..f10670f 100644 --- a/agent-templates/schema/role-manifest.schema.json +++ b/agent-templates/schema/role-manifest.schema.json @@ -80,6 +80,40 @@ "metadata": { "type": "object", "description": "Passed through as agent `metadata` (free-form tracking)." + }, + "a2a": { + "type": "object", + "additionalProperties": false, + "description": "OPTIONAL A2A discoverability/publication block. Mirrors the frozen contract FuzeAgent/agent-templates/contracts/a2a/v1/schema/role-a2a-extension.schema.json. Every field has a derived default, so no existing role.json needs it. The card projection reads role/name/description/services/metadata/coordinator regardless; this block only lets a role improve discoverability (examples/tags) or opt out of publication.", + "properties": { + "publish": { + "type": "boolean", + "default": true, + "description": "false hides this role from the public card. Still reachable on the EXTENDED card if the caller is allowlisted (authz.md §5)." + }, + "extendedOnly": { + "type": "boolean", + "default": false, + "description": "true publishes this skill ONLY on the authenticated extended card, never on the anonymous /.well-known/agent-card.json. Use for skills whose mere existence is sensitive." + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Extra tags merged with the derived tags. Derived tags are never removed." + }, + "examples": { + "type": "array", + "items": { "type": "string" }, + "description": "Example prompts a caller can send to this skill — the primary signal a calling agent uses to decide fit. Absent examples make a skill effectively undiscoverable." + }, + "inputModes": { "type": "array", "items": { "type": "string" } }, + "outputModes": { "type": "array", "items": { "type": "string" } }, + "scopes": { + "type": "array", + "items": { "type": "string" }, + "description": "OAuth scopes required for THIS skill, projected into the skill's securityRequirements." + } + } } } }