chore: bootstrap FuzeSDLC capability installer (wave 6) - #155
Conversation
Adopt the FuzeSDLC canonical capability set: schema-validate, core-agents, workflows, community, roles, npm-registry, node-toolchain (engines/.nvmrc only), entity-identity scaffolding, design-system conformance gate, skills, portal-registration, and hardening pins across .github/workflows. mf-remote is explicitly skipped: FuzeAgent shares React via a bare array (no singleton semantics) and currently bundles its own React copy. Converting that to a real MF singleton is exactly the change that produces a green build with a runtime-only "Invalid hook call", so it is deferred to its own PR with a real browser check. React stays at ^18 in services/ui-react and services/ui; only engines.node/npm and .nvmrc were raised. The frozen agent-templates/contracts/a2a/v1/ A2A contract is untouched, and auth (services/orchestrator/auth.py, agent-templates/a2a/authz.py) was not touched, per the tracked hand-rolled-HS256 finding.
|
Re-runs the SDLC bootstrap installer against FuzeSDLC main (post #79, #105-#110) to pick up canonical fixes merged since this PR was cut: harden-gate now triggers on ready_for_review, gate-sast/gate-authz get setup-python, runs-on labels are preserved per file, nightly crons are derived per repo, and check-registration regains nav.section validation. Also intended to unstick CI on this PR's head, which was showing zero check runs due to a webhook-delivery gap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| # Scope -> registry mapping for the private @fuzefront/* packages. | ||
| # | ||
| # READ THIS BEFORE CHANGING A SCOPE LINE. Publishing RENAMES the packages: | ||
| # scripts/publish-packages.mjs rewrites @fuzefront/<x> to @izzywdev/fuzefront-<x>, | ||
| # because GitHub Packages requires the scope to match the owning ACCOUNT and there | ||
| # is no `fuzefront` org. So the name you install is NOT the name you import: | ||
| # | ||
| # published @izzywdev/fuzefront-design-system <- what npm fetches | ||
| # imported @fuzefront/design-system <- what the code says | ||
| # | ||
| # A plain `"@fuzefront/design-system": "^1.0.0"` therefore 404s. That is not | ||
| # hypothetical — it is exactly what fuzeplan and fuzefinance have. Depend on it | ||
| # through an npm: alias instead, which keeps every import statement unchanged: | ||
| # | ||
| # "dependencies": { | ||
| # "@fuzefront/design-system": "npm:@izzywdev/fuzefront-design-system@^1.0.0" | ||
| # } | ||
| # | ||
| # @izzywdev is the scope npm actually resolves against the registry; the others | ||
| # are mapped so that any package still carrying an old scope name resolves too | ||
| # rather than silently falling through to the public registry and 404ing. | ||
| @izzywdev:registry=https://npm.pkg.github.com | ||
| @fuzefront:registry=https://npm.pkg.github.com | ||
| @fuzeone:registry=https://npm.pkg.github.com | ||
|
|
||
| # Auth comes from the environment — NEVER commit a token. In CI, GITHUB_TOKEN is | ||
| # already present; locally, export a PAT with read:packages. | ||
| //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} | ||
| always-auth=true | ||
|
|
||
| # WHY THIS FILE ALSO LIVES AT THE REPO ROOT, not only next to the consuming | ||
| # package.json: npm resolves .npmrc by walking UP from the current directory. A | ||
| # repo whose only .npmrc sits in packages/<x>/ gives no mapping at all to anything | ||
| # run from the root — CI steps, tooling, a fresh clone — and every private scope | ||
| # 404s there. FuzeBI had precisely that shape. |
| # Scope -> registry mapping for the private @fuzefront/* packages. | ||
| # | ||
| # READ THIS BEFORE CHANGING A SCOPE LINE. Publishing RENAMES the packages: | ||
| # scripts/publish-packages.mjs rewrites @fuzefront/<x> to @izzywdev/fuzefront-<x>, | ||
| # because GitHub Packages requires the scope to match the owning ACCOUNT and there | ||
| # is no `fuzefront` org. So the name you install is NOT the name you import: | ||
| # | ||
| # published @izzywdev/fuzefront-design-system <- what npm fetches | ||
| # imported @fuzefront/design-system <- what the code says | ||
| # | ||
| # A plain `"@fuzefront/design-system": "^1.0.0"` therefore 404s. That is not | ||
| # hypothetical — it is exactly what fuzeplan and fuzefinance have. Depend on it | ||
| # through an npm: alias instead, which keeps every import statement unchanged: | ||
| # | ||
| # "dependencies": { | ||
| # "@fuzefront/design-system": "npm:@izzywdev/fuzefront-design-system@^1.0.0" | ||
| # } | ||
| # | ||
| # @izzywdev is the scope npm actually resolves against the registry; the others | ||
| # are mapped so that any package still carrying an old scope name resolves too | ||
| # rather than silently falling through to the public registry and 404ing. | ||
| @izzywdev:registry=https://npm.pkg.github.com | ||
| @fuzefront:registry=https://npm.pkg.github.com | ||
| @fuzeone:registry=https://npm.pkg.github.com | ||
|
|
||
| # Auth comes from the environment — NEVER commit a token. In CI, GITHUB_TOKEN is | ||
| # already present; locally, export a PAT with read:packages. | ||
| //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} | ||
| always-auth=true | ||
|
|
||
| # WHY THIS FILE ALSO LIVES AT THE REPO ROOT, not only next to the consuming | ||
| # package.json: npm resolves .npmrc by walking UP from the current directory. A | ||
| # repo whose only .npmrc sits in packages/<x>/ gives no mapping at all to anything | ||
| # run from the root — CI steps, tooling, a fresh clone — and every private scope | ||
| # 404s there. FuzeBI had precisely that shape. |
| # Scope -> registry mapping for the private @fuzefront/* packages. | ||
| # | ||
| # READ THIS BEFORE CHANGING A SCOPE LINE. Publishing RENAMES the packages: | ||
| # scripts/publish-packages.mjs rewrites @fuzefront/<x> to @izzywdev/fuzefront-<x>, | ||
| # because GitHub Packages requires the scope to match the owning ACCOUNT and there | ||
| # is no `fuzefront` org. So the name you install is NOT the name you import: | ||
| # | ||
| # published @izzywdev/fuzefront-design-system <- what npm fetches | ||
| # imported @fuzefront/design-system <- what the code says | ||
| # | ||
| # A plain `"@fuzefront/design-system": "^1.0.0"` therefore 404s. That is not | ||
| # hypothetical — it is exactly what fuzeplan and fuzefinance have. Depend on it | ||
| # through an npm: alias instead, which keeps every import statement unchanged: | ||
| # | ||
| # "dependencies": { | ||
| # "@fuzefront/design-system": "npm:@izzywdev/fuzefront-design-system@^1.0.0" | ||
| # } | ||
| # | ||
| # @izzywdev is the scope npm actually resolves against the registry; the others | ||
| # are mapped so that any package still carrying an old scope name resolves too | ||
| # rather than silently falling through to the public registry and 404ing. | ||
| @izzywdev:registry=https://npm.pkg.github.com | ||
| @fuzefront:registry=https://npm.pkg.github.com | ||
| @fuzeone:registry=https://npm.pkg.github.com | ||
|
|
||
| # Auth comes from the environment — NEVER commit a token. In CI, GITHUB_TOKEN is | ||
| # already present; locally, export a PAT with read:packages. | ||
| //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} | ||
| always-auth=true | ||
|
|
||
| # WHY THIS FILE ALSO LIVES AT THE REPO ROOT, not only next to the consuming | ||
| # package.json: npm resolves .npmrc by walking UP from the current directory. A | ||
| # repo whose only .npmrc sits in packages/<x>/ gives no mapping at all to anything | ||
| # run from the root — CI steps, tooling, a fresh clone — and every private scope | ||
| # 404s there. FuzeBI had precisely that shape. |
| function shippedContent(cmPath, cmText, key) { | ||
| const lines = cmText.split('\n') | ||
| const start = lines.findIndex(l => | ||
| new RegExp(`^ ${key.replace('.', '\\.')}:\\s*\\|`).test(l) |
| uniq = sorted(set(locs)) | ||
| # recurring AND across >1 file = an extraction signal | ||
| if len(uniq) >= threshold and len({l.split(":")[0] for l in uniq}) >= 2: | ||
| fp = hashlib.sha1(norm.encode()).hexdigest()[:12] |
| # Scope -> registry mapping for the private @fuzefront/* packages. | ||
| # | ||
| # READ THIS BEFORE CHANGING A SCOPE LINE. Publishing RENAMES the packages: | ||
| # scripts/publish-packages.mjs rewrites @fuzefront/<x> to @izzywdev/fuzefront-<x>, | ||
| # because GitHub Packages requires the scope to match the owning ACCOUNT and there | ||
| # is no `fuzefront` org. So the name you install is NOT the name you import: | ||
| # | ||
| # published @izzywdev/fuzefront-design-system <- what npm fetches | ||
| # imported @fuzefront/design-system <- what the code says | ||
| # | ||
| # A plain `"@fuzefront/design-system": "^1.0.0"` therefore 404s. That is not | ||
| # hypothetical — it is exactly what fuzeplan and fuzefinance have. Depend on it | ||
| # through an npm: alias instead, which keeps every import statement unchanged: | ||
| # | ||
| # "dependencies": { | ||
| # "@fuzefront/design-system": "npm:@izzywdev/fuzefront-design-system@^1.0.0" | ||
| # } | ||
| # | ||
| # @izzywdev is the scope npm actually resolves against the registry; the others | ||
| # are mapped so that any package still carrying an old scope name resolves too | ||
| # rather than silently falling through to the public registry and 404ing. | ||
| @izzywdev:registry=https://npm.pkg.github.com | ||
| @fuzefront:registry=https://npm.pkg.github.com | ||
| @fuzeone:registry=https://npm.pkg.github.com | ||
|
|
||
| # Auth comes from the environment — NEVER commit a token. In CI, GITHUB_TOKEN is | ||
| # already present; locally, export a PAT with read:packages. | ||
| //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} | ||
| always-auth=true | ||
|
|
||
| # WHY THIS FILE ALSO LIVES AT THE REPO ROOT, not only next to the consuming | ||
| # package.json: npm resolves .npmrc by walking UP from the current directory. A | ||
| # repo whose only .npmrc sits in packages/<x>/ gives no mapping at all to anything | ||
| # run from the root — CI steps, tooling, a fresh clone — and every private scope | ||
| # 404s there. FuzeBI had precisely that shape. |
| # Scope -> registry mapping for the private @fuzefront/* packages. | ||
| # | ||
| # READ THIS BEFORE CHANGING A SCOPE LINE. Publishing RENAMES the packages: | ||
| # scripts/publish-packages.mjs rewrites @fuzefront/<x> to @izzywdev/fuzefront-<x>, | ||
| # because GitHub Packages requires the scope to match the owning ACCOUNT and there | ||
| # is no `fuzefront` org. So the name you install is NOT the name you import: | ||
| # | ||
| # published @izzywdev/fuzefront-design-system <- what npm fetches | ||
| # imported @fuzefront/design-system <- what the code says | ||
| # | ||
| # A plain `"@fuzefront/design-system": "^1.0.0"` therefore 404s. That is not | ||
| # hypothetical — it is exactly what fuzeplan and fuzefinance have. Depend on it | ||
| # through an npm: alias instead, which keeps every import statement unchanged: | ||
| # | ||
| # "dependencies": { | ||
| # "@fuzefront/design-system": "npm:@izzywdev/fuzefront-design-system@^1.0.0" | ||
| # } | ||
| # | ||
| # @izzywdev is the scope npm actually resolves against the registry; the others | ||
| # are mapped so that any package still carrying an old scope name resolves too | ||
| # rather than silently falling through to the public registry and 404ing. | ||
| @izzywdev:registry=https://npm.pkg.github.com | ||
| @fuzefront:registry=https://npm.pkg.github.com | ||
| @fuzeone:registry=https://npm.pkg.github.com | ||
|
|
||
| # Auth comes from the environment — NEVER commit a token. In CI, GITHUB_TOKEN is | ||
| # already present; locally, export a PAT with read:packages. | ||
| //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} | ||
| always-auth=true | ||
|
|
||
| # WHY THIS FILE ALSO LIVES AT THE REPO ROOT, not only next to the consuming | ||
| # package.json: npm resolves .npmrc by walking UP from the current directory. A | ||
| # repo whose only .npmrc sits in packages/<x>/ gives no mapping at all to anything | ||
| # run from the root — CI steps, tooling, a fresh clone — and every private scope | ||
| # 404s there. FuzeBI had precisely that shape. |
Summary
Adopts the FuzeSDLC canonical capability set (
schema-validate,core-agents,workflows,community,roles,npm-registry,node-toolchain,entity-identityscaffolding,design-systemconformance gate,skills,chart,argo-app,portal-registration) viascripts/bootstrap/main.py --adopt-canonical. This is Wave 6 (highest blast radius, sequenced last).mf-remotewas explicitly SKIPPEDFuzeAgent is one of four repos whose Module-Federation config shares React as a bare array (
shared: ['react','react-dom']), which carries no singleton semantics, and currently works only because it bundles its own React copy. Converting that to a real MF singleton is exactly the change that produces a green build with a runtime-only"Invalid hook call"— nothing in CI catches it, and the required browser check has not been run. Every invocation used--skip mf-remote.Verified React was left untouched:
services/ui-react/package.json:"react": "^18.3.1"(unchanged)services/ui/package.json:"react": "^18.2.0"(unchanged)services/ui-react/vite.config.ts: no diff at all — MF shared config untouchedOnly
engines.node/engines.npmand.nvmrcwere raised per the toolchain floor (Node 24) — these are independent of the React/MF singleton question.node-toolchainitself reports thate2e/package.jsonandservices/ui-react/package.jsonsit beside a committed lockfile, so no dependency floor was applied there either — engines/.nvmrconly.Frozen A2A contract — untouched
agent-templates/contracts/a2a/v1/(the frozen A2A contract other repos mirror) has zero diff againstorigin/main. Confirmed viagit diff origin/main -- agent-templates/contracts/a2a/v1/producing no output.Auth — not touched
Per the tracked hand-rolled-HS256-with-shared-secret finding, auth code was left alone. Confirmed no diff/status changes to
services/orchestrator/auth.py,agent-templates/a2a/authz.py, or any other*auth*file.--adopt-canonicalwas destructive here — what it overwrote21 UNTRACKED files (no provenance record, i.e. this repo had hand-edited or hand-authored copies that predated the installer) were overwritten with canonical content. Full list: 6
.claude/agents/*.md(contract-designer, backend-engineer, test-engineer, devops-engineer, agile-manager, security), 13.github/workflows/*.yml,agent-templates/sync/role_loader.py,scripts/sync-chart-files.sh.Inspected every workflow diff (
git diff origin/main -- .github/workflows/) for self-hosted runners, custom triggers, extra steps, or a2a/mcp maintainer wiring that might be silently dropped:runs-on: self-hostedwas removed anywhere (grep for removedruns-on/self-hostedlines returned nothing).claude-ci-autofix.ymltrigger list changed to names that don't fully exist in this repo. The old (untracked) version watchedworkflow_run: workflows: ["CI/CD Pipeline", "FuzeAgent Test Suite"], which match this repo's actual workflow names (ci.yml→ "CI/CD Pipeline",test.yml→ "FuzeAgent Test Suite"). The canonical replaced this with["Infrastructure Tests", "Helm Chart Validation", "Harden Gate"]. "Helm Chart Validation" and "Harden Gate" exist here; "Infrastructure Tests" does not — there is no workflow by that name in this repo. Net effect: the auto-fix workflow no longer triggers off this repo's actual CI (ci.yml) or test suite (test.yml) failures. Flagging rather than silently accepting — may need a follow-up to add this repo's real workflow names back into the trigger list, or to renameci.yml/test.ymlto match the canonical naming convention if that's the intended convergence.auto-merge.yml: canonical drops acontinue-on-error: truearound thegh pr merge --autostep — this is a behavior change (a real merge-enable failure now surfaces instead of being swallowed), called out as intentional in the canonical's own comment, not a loss of local logic.sync-chart-files.shandrole_loader.py: canonical versions are strict supersets of the local logic (chart-dir auto-discovery, additional registration-file sync, structured MCP-server-drop logging) — nothing repo-specific appears lost.a2a-maintain.yml/mcp-maintain.yml: only action-pin hardening (actions/checkout@v4→ pinned SHA), no trigger/step changes, no contract touches.Installer findings (verbatim)
No
canonical/baselineRef mismatchwas hit, so--allow-canonical-driftwas never needed.Verification
--dry-runthen--adopt-canonicalthen--adopt-canonicalagain → second adopt run reportedDONE — 0 file(s) would be written; 0 not matching canonical(idempotent, confirmed).Rules followed
No gate/test/check was weakened. No
continue-on-error,|| true, deleted assertion, or allowlisted scanner finding was added by this PR (the onecontinue-on-error: trueremoval inauto-merge.ymlcame from the canonical itself, tightening rather than loosening).Generated by Claude Code