diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 35c77bb..3fbdcc7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,14 +7,14 @@ }, "metadata": { "description": "Verified Solution Cards retrieved from an agent-authored conceptual problem statement.", - "version": "0.5.1" + "version": "0.5.2" }, "plugins": [ { "name": "blaze", "source": "./plugins/claude-code", "description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.", - "version": "0.5.1", + "version": "0.5.2", "keywords": [ "memory", "retrieval", diff --git a/install.md b/install.md index 198dad0..59dafb0 100644 --- a/install.md +++ b/install.md @@ -1,4 +1,4 @@ -# Install Blaze 0.5.1 +# Install Blaze 0.5.2 Blaze shares verified coding lessons through an explicitly invoked client. Installing it does not authorize uploading prompts, source or transcripts. @@ -31,17 +31,30 @@ source commit, and the sizes and SHA-256 hashes of exactly two artifacts: `SKILL.md` and `blaze-client.mjs`. Review the corresponding public source release when deciding whether to trust it. A hash verifies bytes, not the publisher. -Download `{BLAZE_URL}/blaze-client.mjs` to a temporary private file over HTTPS, -with redirects disabled. Inspect it before running it. Do not pipe a remote -response into a shell. An example download is: +Copy the version and the helper's SHA-256 from that reviewed manifest into the +variables below. Download its immutable artifact to a temporary private file over +HTTPS with redirects disabled, then verify its hash before inspecting or running +it. Do not pipe a remote response into a shell. ```bash umask 077 blaze_bootstrap_dir=$(mktemp -d) -curl --fail --silent --show-error --proto '=https' --max-redirs 0 --max-time 15 --max-filesize 524288 '{BLAZE_URL}/blaze-client.mjs' --output "$blaze_bootstrap_dir/blaze-client.mjs" +blaze_release_version='' +blaze_client_sha256='' +curl --fail --silent --show-error --proto '=https' --max-redirs 0 --max-time 15 --max-filesize 524288 "{BLAZE_URL}/releases/$blaze_release_version/$blaze_client_sha256/blaze-client.mjs" --output "$blaze_bootstrap_dir/blaze-client.mjs" +node --input-type=module - "$blaze_bootstrap_dir/blaze-client.mjs" "$blaze_client_sha256" <<'NODE' +import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +const [path, expected] = process.argv.slice(2); +if (!/^[0-9a-f]{64}$/.test(expected ?? '') || + createHash('sha256').update(readFileSync(path)).digest('hex') !== expected) { + throw new Error('Bootstrap hash mismatch; do not execute this download.'); +} +NODE ``` -After reviewing the download, invoke it, replacing `codex` with the current host: +Continue only after the hash check succeeds and you have reviewed the download. +Invoke it, replacing `codex` with the current host: ```bash node "$blaze_bootstrap_dir/blaze-client.mjs" install --tool codex --origin '{BLAZE_URL}' @@ -98,6 +111,15 @@ API calls also receive small version hints. No prompt hook fetches metadata or updates files. Updating requires authorization and respects pins. The direct updater refuses to modify manager or marketplace installations. +The direct 0.5.0 and 0.5.1 updaters can receive HTTP 426 while checking the saved +installation, before activating a newer release. +For that recorded direct installation, follow the reviewed download and hash checks +under [Direct installation](#direct-installation), then run the freshly downloaded +helper with `install --tool --origin '{BLAZE_URL}'`. It preserves the existing +credential, receipts, ownership record and pin; an active pin still blocks replacement. +Use the installed helper to unpin only when that change is separately authorized. +Manager and marketplace installations must still update through their manager. + Rollback restores the immediately preceding checked direct release and pins it. The first upgrade from a legacy bundle cannot automatically roll back to the older state layout; its private backup remains available for deliberate recovery. diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json index a7ec969..1b6c953 100644 --- a/plugins/claude-code/.claude-plugin/plugin.json +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "blaze", "displayName": "Blaze", - "version": "0.5.1", + "version": "0.5.2", "description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.", "author": { "name": "Blaze" diff --git a/plugins/claude-code/blaze-client.mjs b/plugins/claude-code/blaze-client.mjs index 100b63f..06afbe1 100644 --- a/plugins/claude-code/blaze-client.mjs +++ b/plugins/claude-code/blaze-client.mjs @@ -43,7 +43,7 @@ const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = idPattern(ID_PREFIXES.card); const AUTHORED_SLUG = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.5.1"; +export const CLIENT_VERSION = "0.5.2"; export const CLIENT_CONTRACT = 2; export const API_VERSION = "2026-09-07"; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; @@ -786,6 +786,8 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi return Buffer.concat(chunks,size); } const parseJSON = (value) => { try {return JSON.parse(value.toString("utf8"));} catch {throw new Error("Blaze returned invalid JSON");} }; + const serviceHeaders = headers => ({...headers, + "Blaze-Version":API_VERSION,"Blaze-Client-Version":CLIENT_VERSION,"Blaze-Client-Contract":String(CLIENT_CONTRACT)}); async function release() { return validateRelease(parseJSON(await bytes("/api/skill-release",16*1024)),base); } function ownedInvocation(meta) { return meta && resolve(dirname(helperPath)) === resolve(paths.root); } function status() { @@ -824,7 +826,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi const existing = readToolCredential(tool,home); if (existing.token) { if (existing.origin!==base) throw new Error("Keep the existing credential with its original service"); - const result = parseJSON(await bytes("/api/stats",32*1024,{headers:{authorization:`Bearer ${existing.token}`}})); + const result = parseJSON(await bytes("/api/stats",32*1024,{headers:serviceHeaders({authorization:`Bearer ${existing.token}`})})); if (result?.cards!==null && (!Number.isSafeInteger(result?.cards)||result.cards<0)) throw new Error("Invalid service status"); return {credential:"reused"}; } @@ -833,7 +835,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi exactKeys(pending,new Set(["version","origin","token"]),"Pending registration"); if (pending.version!==1 || pending.origin!==base || !TOKEN.test(pending.token ?? "")) throw new Error("Pending registration belongs to another service or is invalid"); save(pendingPath,pending); - const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)},body:JSON.stringify({tool})})); + const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:serviceHeaders({"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)}),body:JSON.stringify({tool})})); const installId = responseId(data, "install", "installation"); if (!TOKEN.test(data?.token ?? "") || !installId || data.bootstrap_contract!==2 || data.token!==pending.token) { throw new Error("This service does not support retryable registration; keep the saved pending credential"); diff --git a/plugins/claude-code/skills/blaze/SKILL.md b/plugins/claude-code/skills/blaze/SKILL.md index e432dc1..9d0a45c 100644 --- a/plugins/claude-code/skills/blaze/SKILL.md +++ b/plugins/claude-code/skills/blaze/SKILL.md @@ -3,7 +3,7 @@ name: blaze description: Quietly reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to inspect, install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition without adding routine user-visible narration. compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local model-context hooks need no network access. No model provider credentials are needed. metadata: - version: "0.5.1" + version: "0.5.2" --- # Blaze diff --git a/plugins/claude-code/skills/blaze/blaze-client.mjs b/plugins/claude-code/skills/blaze/blaze-client.mjs index 100b63f..06afbe1 100644 --- a/plugins/claude-code/skills/blaze/blaze-client.mjs +++ b/plugins/claude-code/skills/blaze/blaze-client.mjs @@ -43,7 +43,7 @@ const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = idPattern(ID_PREFIXES.card); const AUTHORED_SLUG = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.5.1"; +export const CLIENT_VERSION = "0.5.2"; export const CLIENT_CONTRACT = 2; export const API_VERSION = "2026-09-07"; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; @@ -786,6 +786,8 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi return Buffer.concat(chunks,size); } const parseJSON = (value) => { try {return JSON.parse(value.toString("utf8"));} catch {throw new Error("Blaze returned invalid JSON");} }; + const serviceHeaders = headers => ({...headers, + "Blaze-Version":API_VERSION,"Blaze-Client-Version":CLIENT_VERSION,"Blaze-Client-Contract":String(CLIENT_CONTRACT)}); async function release() { return validateRelease(parseJSON(await bytes("/api/skill-release",16*1024)),base); } function ownedInvocation(meta) { return meta && resolve(dirname(helperPath)) === resolve(paths.root); } function status() { @@ -824,7 +826,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi const existing = readToolCredential(tool,home); if (existing.token) { if (existing.origin!==base) throw new Error("Keep the existing credential with its original service"); - const result = parseJSON(await bytes("/api/stats",32*1024,{headers:{authorization:`Bearer ${existing.token}`}})); + const result = parseJSON(await bytes("/api/stats",32*1024,{headers:serviceHeaders({authorization:`Bearer ${existing.token}`})})); if (result?.cards!==null && (!Number.isSafeInteger(result?.cards)||result.cards<0)) throw new Error("Invalid service status"); return {credential:"reused"}; } @@ -833,7 +835,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi exactKeys(pending,new Set(["version","origin","token"]),"Pending registration"); if (pending.version!==1 || pending.origin!==base || !TOKEN.test(pending.token ?? "")) throw new Error("Pending registration belongs to another service or is invalid"); save(pendingPath,pending); - const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)},body:JSON.stringify({tool})})); + const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:serviceHeaders({"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)}),body:JSON.stringify({tool})})); const installId = responseId(data, "install", "installation"); if (!TOKEN.test(data?.token ?? "") || !installId || data.bootstrap_contract!==2 || data.token!==pending.token) { throw new Error("This service does not support retryable registration; keep the saved pending credential"); diff --git a/plugins/client/blaze-client.mjs b/plugins/client/blaze-client.mjs index 100b63f..06afbe1 100644 --- a/plugins/client/blaze-client.mjs +++ b/plugins/client/blaze-client.mjs @@ -43,7 +43,7 @@ const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = idPattern(ID_PREFIXES.card); const AUTHORED_SLUG = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.5.1"; +export const CLIENT_VERSION = "0.5.2"; export const CLIENT_CONTRACT = 2; export const API_VERSION = "2026-09-07"; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; @@ -786,6 +786,8 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi return Buffer.concat(chunks,size); } const parseJSON = (value) => { try {return JSON.parse(value.toString("utf8"));} catch {throw new Error("Blaze returned invalid JSON");} }; + const serviceHeaders = headers => ({...headers, + "Blaze-Version":API_VERSION,"Blaze-Client-Version":CLIENT_VERSION,"Blaze-Client-Contract":String(CLIENT_CONTRACT)}); async function release() { return validateRelease(parseJSON(await bytes("/api/skill-release",16*1024)),base); } function ownedInvocation(meta) { return meta && resolve(dirname(helperPath)) === resolve(paths.root); } function status() { @@ -824,7 +826,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi const existing = readToolCredential(tool,home); if (existing.token) { if (existing.origin!==base) throw new Error("Keep the existing credential with its original service"); - const result = parseJSON(await bytes("/api/stats",32*1024,{headers:{authorization:`Bearer ${existing.token}`}})); + const result = parseJSON(await bytes("/api/stats",32*1024,{headers:serviceHeaders({authorization:`Bearer ${existing.token}`})})); if (result?.cards!==null && (!Number.isSafeInteger(result?.cards)||result.cards<0)) throw new Error("Invalid service status"); return {credential:"reused"}; } @@ -833,7 +835,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi exactKeys(pending,new Set(["version","origin","token"]),"Pending registration"); if (pending.version!==1 || pending.origin!==base || !TOKEN.test(pending.token ?? "")) throw new Error("Pending registration belongs to another service or is invalid"); save(pendingPath,pending); - const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)},body:JSON.stringify({tool})})); + const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:serviceHeaders({"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)}),body:JSON.stringify({tool})})); const installId = responseId(data, "install", "installation"); if (!TOKEN.test(data?.token ?? "") || !installId || data.bootstrap_contract!==2 || data.token!==pending.token) { throw new Error("This service does not support retryable registration; keep the saved pending credential"); diff --git a/plugins/client/lifecycle.test.mjs b/plugins/client/lifecycle.test.mjs index 205df5e..a706bdd 100644 --- a/plugins/client/lifecycle.test.mjs +++ b/plugins/client/lifecycle.test.mjs @@ -5,7 +5,7 @@ import { createHash, randomUUID } from "node:crypto"; import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { CLIENT_CONTRACT, CLIENT_VERSION, createId, createClient, createLifecycle, compareVersions, toolPaths, validateRelease } from "./blaze-client.mjs"; +import { API_VERSION, CLIENT_CONTRACT, CLIENT_VERSION, createId, createClient, createLifecycle, compareVersions, toolPaths, validateRelease } from "./blaze-client.mjs"; const hash = value => createHash("sha256").update(value).digest("hex"); const source = readFileSync(new URL("./blaze-client.mjs",import.meta.url)); @@ -22,15 +22,21 @@ async function fixture(t) { const control={release:bundle("0.4.0"),offline:false,corrupt:false,lost:false,reject:0},requests=[],identities=new Map(); const server=createServer(async(req,res)=>{ let raw="";for await(const chunk of req) raw+=chunk; - requests.push({path:req.url,authorization:req.headers.authorization,idempotencyKey:req.headers["idempotency-key"],body:raw?JSON.parse(raw):null}); + requests.push({path:req.url,authorization:req.headers.authorization,idempotencyKey:req.headers["idempotency-key"], + apiVersion:req.headers["blaze-version"],clientVersion:req.headers["blaze-client-version"],clientContract:req.headers["blaze-client-contract"],body:raw?JSON.parse(raw):null}); if(control.offline){res.writeHead(503);res.end("PRIVATE_FAILURE_DETAIL");return;} - if(req.url==="/api/skill-release"){res.setHeader("content-type","application/json");res.end(JSON.stringify(control.release.manifest));return;} + if(req.url==="/api/skill-release"){ + if(req.headers["blaze-version"]||req.headers["blaze-client-version"]||req.headers["blaze-client-contract"]||req.headers.authorization){res.writeHead(400);res.end();return;} + res.setHeader("content-type","application/json");res.end(JSON.stringify(control.release.manifest));return; + } if(req.url.startsWith("/releases/")) { + if(req.headers["blaze-version"]||req.headers["blaze-client-version"]||req.headers["blaze-client-contract"]||req.headers.authorization){res.writeHead(400);res.end();return;} const name=req.url.split("/").at(-1),artifact=control.release.manifest.artifacts.find(a=>a.name===name); if(req.url!==`/releases/${control.release.manifest.version}/${artifact?.sha256}/${name}`){res.writeHead(404);res.end();return;} res.end(control.corrupt ? "invalid bytes" : control.release.files[name]);return; } if(req.url==="/api/installations") { + if(req.headers["blaze-version"]!==API_VERSION||req.headers["blaze-client-version"]!==CLIENT_VERSION||req.headers["blaze-client-contract"]!==String(CLIENT_CONTRACT)){res.writeHead(426);res.end();return;} if(control.reject){res.writeHead(control.reject,{"retry-after":"600"});res.end("PRIVATE_FAILURE_DETAIL");return;} const token=req.headers.authorization?.slice(7); if(!identities.has(token))identities.set(token,createId("install")); @@ -38,6 +44,7 @@ async function fixture(t) { res.end(JSON.stringify({id:identities.get(token),object:"installation",bootstrap_contract:2,token}));return; } if(req.url==="/api/stats") { + if(req.headers["blaze-version"]!==API_VERSION||req.headers["blaze-client-version"]!==CLIENT_VERSION||req.headers["blaze-client-contract"]!==String(CLIENT_CONTRACT)){res.writeHead(426);res.end();return;} if(!identities.has(req.headers.authorization?.slice(7))){res.writeHead(401);res.end("PRIVATE_FAILURE_DETAIL");return;} res.end('{"cards":0}');return; } @@ -71,11 +78,42 @@ test("direct installation keeps credentials outside its portable folder and reus const registration=requests.find(r=>r.path==="/api/installations"); assert.equal(registration.idempotencyKey,hash(registration.authorization.slice(7))); assert.notEqual(registration.idempotencyKey,registration.authorization.slice(7)); - assert.ok(requests.filter(r=>r.path.startsWith("/releases/")||r.path==="/api/skill-release").every(r=>r.authorization===undefined&&r.body===null)); + assert.deepEqual([registration.apiVersion,registration.clientVersion,registration.clientContract],[API_VERSION,CLIENT_VERSION,String(CLIENT_CONTRACT)]); + const stats=requests.find(r=>r.path==="/api/stats"); + assert.deepEqual([stats.apiVersion,stats.clientVersion,stats.clientContract],[API_VERSION,CLIENT_VERSION,String(CLIENT_CONTRACT)]); + assert.ok(requests.filter(r=>r.path.startsWith("/releases/")||r.path==="/api/skill-release").every(r=> + r.authorization===undefined&&r.apiVersion===undefined&&r.clientVersion===undefined&&r.clientContract===undefined&&r.body===null)); assert.equal(get(join(state,"installation.json")).mode,"direct"); assert.equal(lifecycle.status().update,"current"); }); +test("external 0.5.2 bootstrap upgrades a recorded unpinned 0.5.0 direct install",async t=>{ + const {home,paths,state,control,requests,identities,options,lifecycle}=await fixture(t); + control.release=bundle("0.5.0");await lifecycle.install(); + const prior=get(join(state,"installation.json"));assert.equal(prior.release.version,"0.5.0");assert.equal(prior.pin,null); + const credential=readFileSync(paths.token,"utf8"),receiptId=createId("lookup"); + const receipt={version:2,origin:options.origin,tool:"codex",decision_id:receiptId,client_event_id:createId("event"),started_wall_ms:1,retrieval_ms:2,offered:false,offers:[],context_fingerprint:null}; + put(join(paths.state,"receipts",`${receiptId}.json`),receipt); + + const bootstrap=join(home,"reviewed-bootstrap","blaze-client.mjs"); + mkdirSync(resolve(bootstrap,".."),{recursive:true,mode:0o700});writeFileSync(bootstrap,source,{mode:0o600}); + control.release=bundle(CLIENT_VERSION);const requestStart=requests.length; + const external=createLifecycle({...options,helperPath:bootstrap}); + const result=await external.install();assert.equal(result.version,CLIENT_VERSION);assert.equal(result.activation,"installed");assert.equal(result.credential,"reused"); + + const current=get(join(state,"installation.json")); + assert.equal(current.mode,"direct");assert.equal(current.origin,options.origin);assert.equal(current.root,resolve(paths.root)); + assert.equal(current.release.version,CLIENT_VERSION);assert.equal(current.pin,null);assert.ok(current.activated_at>=prior.activated_at); + assert.equal(current.previous.release.version,"0.5.0"); + assert.equal(readFileSync(paths.token,"utf8"),credential);assert.deepEqual(get(join(paths.state,"receipts",`${receiptId}.json`)),receipt);assert.equal(identities.size,1); + assert.equal(createLifecycle(options).status().installation,"direct"); + + const upgradeRequests=requests.slice(requestStart),stats=upgradeRequests.find(request=>request.path==="/api/stats"); + assert.deepEqual([stats.apiVersion,stats.clientVersion,stats.clientContract],[API_VERSION,CLIENT_VERSION,String(CLIENT_CONTRACT)]); + assert.ok(upgradeRequests.filter(request=>request.path==="/api/skill-release"||request.path.startsWith("/releases/")).every(request=> + request.authorization===undefined&&request.apiVersion===undefined&&request.clientVersion===undefined&&request.clientContract===undefined)); +}); + test("lost registration response reuses the saved pending secret and never mints a second identity",async t=>{ const {lifecycle,control,paths,identities}=await fixture(t);control.lost=true; await assert.rejects(lifecycle.setup()); diff --git a/release.json b/release.json index 914c055..aa4d2d7 100644 --- a/release.json +++ b/release.json @@ -1,7 +1,7 @@ { - "version": "0.5.1", - "created_at": "2026-09-08T02:08:28.000Z", - "updated_at": "2026-09-08T02:08:28.000Z", + "version": "0.5.2", + "created_at": "2026-09-08T02:36:49.000Z", + "updated_at": "2026-09-08T02:36:49.000Z", "client_contract": 2, "minimum_client_contract": 2 } diff --git a/skill.md b/skill.md index e432dc1..9d0a45c 100644 --- a/skill.md +++ b/skill.md @@ -3,7 +3,7 @@ name: blaze description: Quietly reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to inspect, install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition without adding routine user-visible narration. compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local model-context hooks need no network access. No model provider credentials are needed. metadata: - version: "0.5.1" + version: "0.5.2" --- # Blaze