Blaze Source Patch CLI
Blaze source patches are versioned JSON requests that target #168 inspect handles and exact source revisions. Source is authoritative: patch, apply, and media bind never edit generated world JSON, fetch remote media, generate assets, upload, or publish. Run blaze build explicitly after source edits when a bundle refresh is wanted.
Commands
npx tsx scripts/blaze.ts patch examples/blaze/adirondack-mystery/main.blaze --file patch.json
npx tsx scripts/blaze.ts apply examples/blaze/adirondack-mystery/main.blaze --file patch.json
npx tsx scripts/blaze.ts media bind examples/blaze/adirondack-mystery/main.blaze --file binding.json
npx tsx scripts/blaze.ts media unbind examples/blaze/adirondack-mystery/main.blaze --file binding.jsonpatch previews only. apply rechecks current source fingerprints and dependency fingerprint immediately before writing, validates a staged candidate project, then commits through .blaze-transactions/ with lock, journal, backups, rollback, and recovery before later Blaze reads.
Exit statuses: 0 successful preview/apply, 1 valid input rejected with no writes, 2 usage, schema, IO, or transaction lock errors.
Patch Schema
{
"schemaVersion": "adventure-kit.blaze.patch.v0.1",
"expectedRevisions": {
"source": [{ "file": "/abs/main.blaze", "sha256": "...", "byteLength": 1234 }],
"dependencyFingerprint": "..."
},
"operations": [
{
"id": "dialogue-copy",
"kind": "update_text",
"handle": "blaze://line/example.line",
"field": "canonicalText",
"value": "New authored line.",
"allowSharedConsumers": true
}
]
}Supported operations are bounded to update_text, replace_typed_field, insert_local, remove_local, bind_media, and unbind_media. Typed replacements are limited to existing gate/effect/destination fields. Local insertion/removal is limited to line and choice lists. Untouched bytes, comments, field order, and declaration order are preserved; non-ASCII touched files fail closed until byte-index patching is implemented.
Common diagnostics include PATCH_SCHEMA, PATCH_STALE_REVISION, PATCH_ZERO_MATCH, PATCH_AMBIGUOUS, PATCH_UNSUPPORTED_KIND, PATCH_OUT_OF_SCOPE, PATCH_SHARED_CONSUMERS, PATCH_DANGLING_REFERENCE, PATCH_DUPLICATE_ID, PATCH_INVALID_MEDIA, PATCH_CANDIDATE_INVALID, PATCH_TRANSACTION_LOCKED, and PATCH_TRANSACTION_RECOVERED.
Media Binding
media bind accepts immutable external results only when evidence matches the current typed slot. The CLI preserves supplied rights, provenance, and evidence; it does not invent missing metadata.
Line audio requires current text/voice/synthesis evidence such as canonicalTextHash, voiceRevision, synthesisInputsHash, and generatedTtsId. Room visuals require visualBriefHash, variant, sourceIdentityHash, asset identity/version/dimensions, and sourceDimensionsHash when geometry is supplied. Character portraits use the same media_companion mechanism with character.portrait, immutable asset identity, dimensions, content hash, rights, provenance, alt text, intended use, visibility, and status.
Examples
Mechanics gate edit:
{
"schemaVersion": "adventure-kit.blaze.patch.v0.1",
"expectedRevisions": { "source": [], "dependencyFingerprint": "inspect-fingerprint" },
"operations": [{
"id": "gate-ridge",
"kind": "replace_typed_field",
"handle": "blaze://exit/to_ridge",
"field": "conditions",
"value": [{ "has_item": { "itemId": "trail_map" } }],
"allowSharedConsumers": true
}]
}Dialogue-only edit:
{
"schemaVersion": "adventure-kit.blaze.patch.v0.1",
"expectedRevisions": { "source": [], "dependencyFingerprint": "inspect-fingerprint" },
"operations": [{
"id": "line-copy",
"kind": "update_text",
"handle": "blaze://line/story.line.check_in",
"field": "canonicalText",
"value": "The lobby clock chimes once.",
"allowSharedConsumers": true
}]
}TTS association:
{
"schemaVersion": "adventure-kit.blaze.media-binding.v0.1",
"expectedRevisions": { "source": [], "dependencyFingerprint": "inspect-fingerprint" },
"handle": "blaze://line/story.line.check_in",
"slot": "line.audio",
"binding": { "generatedTtsId": "tts.check_in.v1", "media": { "contentHash": "sha256:...", "byteCount": 123, "mimeType": "audio/mpeg" } },
"evidence": { "canonicalTextHash": "sha256:...", "voiceRevision": "voice:v1", "synthesisInputsHash": "sha256:...", "generatedTtsId": "tts.check_in.v1" },
"rights": { "consent": "licensed", "allowedUses": ["gameplay"] },
"provenance": { "source": "moltazine", "jobId": "immutable-job" }
}Room-art association:
{
"schemaVersion": "adventure-kit.blaze.media-binding.v0.1",
"expectedRevisions": { "source": [], "dependencyFingerprint": "inspect-fingerprint" },
"handle": "blaze://room/story.room.lobby",
"slot": "room.visual",
"binding": { "assetIdentity": "sha256:asset", "assetVersion": "asset:v1", "dimensions": { "width": 1536, "height": 1024 } },
"evidence": { "visualBriefHash": "sha256:brief", "variant": "fallback", "sourceIdentityHash": "sha256:source", "sourceDimensionsHash": "sha256:dims" }
}