diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index b5df47e4..73397f18 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -2,8 +2,8 @@ use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; use prometeu_hal::AssetBridge; use prometeu_hal::asset::{ - AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, - PreloadEntry, SlotRef, SlotStats, + AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, + LoadStatus, PreloadEntry, SlotRef, SlotStats, }; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::color::Color; @@ -179,10 +179,6 @@ impl AssetBridge for AssetManager { } impl AssetManager { - fn codec_is_none_or_legacy_raw(codec: &str) -> bool { - matches!(codec, "NONE" | "RAW") - } - fn decode_tile_bank_layout( entry: &AssetEntry, ) -> Result<(TileSize, usize, usize, usize), String> { @@ -242,14 +238,9 @@ impl AssetManager { } fn op_mode_for(entry: &AssetEntry) -> Result { - match entry.bank_type { - BankType::TILES if Self::codec_is_none_or_legacy_raw(entry.codec.as_str()) => { - Ok(AssetOpMode::StageInMemory) - } - BankType::SOUNDS if Self::codec_is_none_or_legacy_raw(entry.codec.as_str()) => { - Ok(AssetOpMode::DirectFromSlice) - } - _ => Err(format!("Unsupported codec: {}", entry.codec)), + match (entry.bank_type, entry.codec) { + (BankType::TILES, AssetCodec::None) => Ok(AssetOpMode::StageInMemory), + (BankType::SOUNDS, AssetCodec::None) => Ok(AssetOpMode::DirectFromSlice), } } @@ -882,6 +873,7 @@ impl AssetManager { mod tests { use super::*; use crate::memory_banks::{MemoryBanks, SoundBankPoolAccess, TileBankPoolAccess}; + use prometeu_hal::asset::AssetCodec; fn expected_tile_payload_size(width: usize, height: usize) -> usize { (width * height).div_ceil(2) + TILE_BANK_PALETTE_BYTES_V1 @@ -905,12 +897,13 @@ mod tests { offset: 0, size: expected_tile_payload_size(width, height) as u64, decoded_size: expected_tile_decoded_size(width, height) as u64, - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: serde_json::json!({ "tile_size": 16, "width": width, "height": height, - "palette_count": TILE_BANK_PALETTE_COUNT_V1 + "palette_count": TILE_BANK_PALETTE_COUNT_V1, + "palette_authored": TILE_BANK_PALETTE_COUNT_V1 }), } } @@ -964,10 +957,8 @@ mod tests { } #[test] - fn test_op_mode_for_tiles_raw_is_legacy_alias() { - let mut entry = test_tile_asset_entry("tiles", 16, 16); - entry.codec = "RAW".to_string(); - + fn test_op_mode_for_tiles_none_uses_typed_codec() { + let entry = test_tile_asset_entry("tiles", 16, 16); assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory)); } @@ -980,9 +971,10 @@ mod tests { offset: 0, size: 8, decoded_size: 8, - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: serde_json::json!({ - "sample_rate": 44100 + "sample_rate": 44100, + "channels": 1 }), }; @@ -1074,9 +1066,10 @@ mod tests { offset: 0, size: data.len() as u64, decoded_size: data.len() as u64, - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: serde_json::json!({ - "sample_rate": 44100 + "sample_rate": 44100, + "channels": 1 }), }; @@ -1117,9 +1110,10 @@ mod tests { offset: 0, size: data.len() as u64, decoded_size: data.len() as u64, - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: serde_json::json!({ - "sample_rate": 44100 + "sample_rate": 44100, + "channels": 1 }), }; diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index b9b9d4dc..13acba61 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -1,12 +1,12 @@ use crate::memory_banks::TileBankPoolAccess; use prometeu_hal::GfxBridge; use prometeu_hal::color::Color; +use prometeu_hal::glyph::Glyph; use prometeu_hal::sprite::Sprite; use prometeu_hal::tile::Tile; use prometeu_hal::tile_bank::{TileBank, TileSize}; use prometeu_hal::tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; use std::sync::Arc; -use prometeu_hal::glyph::Glyph; /// Blending modes inspired by classic 16-bit hardware. /// Defines how source pixels are combined with existing pixels in the framebuffer. @@ -275,11 +275,7 @@ impl GfxBridge for Gfx { impl Gfx { /// Initializes the graphics system with a specific resolution and shared memory banks. pub fn new(w: usize, h: usize, tile_banks: Arc) -> Self { - - const EMPTY_GLYPH: Glyph = Glyph { - glyph_id: 0, - palette_id: 0, - }; + const EMPTY_GLYPH: Glyph = Glyph { glyph_id: 0, palette_id: 0 }; const EMPTY_SPRITE: Sprite = Sprite { glyph: EMPTY_GLYPH, diff --git a/crates/console/prometeu-hal/src/asset.rs b/crates/console/prometeu-hal/src/asset.rs index 40f49462..8e9346eb 100644 --- a/crates/console/prometeu-hal/src/asset.rs +++ b/crates/console/prometeu-hal/src/asset.rs @@ -12,6 +12,12 @@ pub enum BankType { // BLOBS, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum AssetCodec { + #[serde(rename = "NONE")] + None, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct AssetEntry { pub asset_id: AssetId, @@ -20,7 +26,7 @@ pub struct AssetEntry { pub offset: u64, pub size: u64, pub decoded_size: u64, - pub codec: String, // e.g., "NONE" (legacy alias: "RAW") + pub codec: AssetCodec, pub metadata: serde_json::Value, } diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index 9ed03612..4b410015 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -198,7 +198,7 @@ fn validate_preload( #[cfg(test)] mod tests { use super::*; - use crate::asset::{AssetEntry, BankType, PreloadEntry}; + use crate::asset::{AssetCodec, AssetEntry, BankType, PreloadEntry}; use crate::cartridge::{ASSETS_PA_MAGIC, ASSETS_PA_SCHEMA_VERSION, AssetsPackPrelude}; use crate::tile_bank::TILE_BANK_PALETTE_COUNT_V1; use serde_json::json; @@ -367,12 +367,13 @@ mod tests { offset, size, decoded_size: 16 * 16 + (TILE_BANK_PALETTE_COUNT_V1 as u64 * 16 * 2), - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: json!({ "tile_size": 16, "width": 16, "height": 16, - "palette_count": TILE_BANK_PALETTE_COUNT_V1 + "palette_count": TILE_BANK_PALETTE_COUNT_V1, + "palette_authored": TILE_BANK_PALETTE_COUNT_V1 }), } } @@ -449,12 +450,13 @@ mod tests { offset: 4, size: 4, decoded_size: 16 * 16 + (TILE_BANK_PALETTE_COUNT_V1 as u64 * 16 * 2), - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: json!({ "tile_size": 16, "width": 16, "height": 16, - "palette_count": TILE_BANK_PALETTE_COUNT_V1 + "palette_count": TILE_BANK_PALETTE_COUNT_V1, + "palette_authored": TILE_BANK_PALETTE_COUNT_V1 }), }, ]; @@ -497,4 +499,88 @@ mod tests { assert!(matches!(error, CartridgeError::InvalidFormat)); } + + #[test] + fn load_rejects_unknown_codec_string_in_assets_pa_header() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"]))); + let header = serde_json::json!({ + "asset_table": [{ + "asset_id": 7, + "asset_name": "tiles", + "bank_type": "TILES", + "offset": 0, + "size": 4, + "decoded_size": 768, + "codec": "LZ4", + "metadata": { + "tile_size": 16, + "width": 16, + "height": 16, + "palette_count": TILE_BANK_PALETTE_COUNT_V1, + "palette_authored": TILE_BANK_PALETTE_COUNT_V1 + } + }], + "preload": [] + }); + let header_bytes = serde_json::to_vec(&header).expect("header must serialize"); + let prelude = AssetsPackPrelude { + magic: ASSETS_PA_MAGIC, + schema_version: ASSETS_PA_SCHEMA_VERSION, + header_len: header_bytes.len() as u32, + payload_offset: (ASSETS_PA_PRELUDE_SIZE + header_bytes.len()) as u64, + flags: 0, + reserved: 0, + header_checksum: 0, + }; + let mut bytes = prelude.to_bytes().to_vec(); + bytes.extend_from_slice(&header_bytes); + bytes.extend_from_slice(&[1_u8, 2, 3, 4]); + fs::write(dir.path().join("assets.pa"), bytes).expect("must write assets.pa"); + + let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err(); + + assert!(matches!(error, CartridgeError::InvalidFormat)); + } + + #[test] + fn load_rejects_legacy_raw_codec_string_in_assets_pa_header() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"]))); + let header = serde_json::json!({ + "asset_table": [{ + "asset_id": 7, + "asset_name": "tiles", + "bank_type": "TILES", + "offset": 0, + "size": 4, + "decoded_size": 768, + "codec": "RAW", + "metadata": { + "tile_size": 16, + "width": 16, + "height": 16, + "palette_count": TILE_BANK_PALETTE_COUNT_V1, + "palette_authored": TILE_BANK_PALETTE_COUNT_V1 + } + }], + "preload": [] + }); + let header_bytes = serde_json::to_vec(&header).expect("header must serialize"); + let prelude = AssetsPackPrelude { + magic: ASSETS_PA_MAGIC, + schema_version: ASSETS_PA_SCHEMA_VERSION, + header_len: header_bytes.len() as u32, + payload_offset: (ASSETS_PA_PRELUDE_SIZE + header_bytes.len()) as u64, + flags: 0, + reserved: 0, + header_checksum: 0, + }; + let mut bytes = prelude.to_bytes().to_vec(); + bytes.extend_from_slice(&header_bytes); + bytes.extend_from_slice(&[1_u8, 2, 3, 4]); + fs::write(dir.path().join("assets.pa"), bytes).expect("must write assets.pa"); + + let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err(); + + assert!(matches!(error, CartridgeError::InvalidFormat)); + } } diff --git a/crates/console/prometeu-hal/src/glyph.rs b/crates/console/prometeu-hal/src/glyph.rs index e6d41e3a..793e8ece 100644 --- a/crates/console/prometeu-hal/src/glyph.rs +++ b/crates/console/prometeu-hal/src/glyph.rs @@ -2,4 +2,4 @@ pub struct Glyph { pub glyph_id: u16, pub palette_id: u8, -} \ No newline at end of file +} diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index 0495586e..1f7bd7af 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -7,6 +7,7 @@ pub mod cartridge_loader; pub mod color; pub mod debugger_protocol; pub mod gfx_bridge; +pub mod glyph; pub mod hardware_bridge; pub mod host_context; pub mod host_return; @@ -26,7 +27,6 @@ pub mod tile_layer; pub mod touch_bridge; pub mod vm_fault; pub mod window; -pub mod glyph; pub use asset_bridge::AssetBridge; pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode}; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index f4ec871a..d8fd483e 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -4,6 +4,7 @@ use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value}; use prometeu_hal::asset::{AssetId, AssetOpStatus, BankType, SlotRef}; use prometeu_hal::cartridge::AppMode; use prometeu_hal::color::Color; +use prometeu_hal::glyph::Glyph; use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; @@ -12,7 +13,6 @@ use prometeu_hal::{ AudioOpStatus, GfxOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, }; -use prometeu_hal::glyph::Glyph; impl VirtualMachineRuntime { fn syscall_log_write(&mut self, level_val: i64, tag: u16, msg: String) -> Result<(), VmFault> { @@ -162,10 +162,7 @@ impl NativeInterface for VirtualMachineRuntime { } *hw.gfx_mut().sprite_mut(index) = Sprite { - glyph: Glyph { - glyph_id, - palette_id, - }, + glyph: Glyph { glyph_id, palette_id }, x, y, bank_id, diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs index 810c73c1..2dd34c41 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs @@ -8,7 +8,9 @@ use prometeu_drivers::hardware::Hardware; use prometeu_hal::AudioOpStatus; use prometeu_hal::GfxOpStatus; use prometeu_hal::InputSignals; -use prometeu_hal::asset::{AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus}; +use prometeu_hal::asset::{ + AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus, +}; use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge}; use prometeu_hal::syscalls::caps; use prometeu_hal::tile_bank::TILE_BANK_PALETTE_COUNT_V1; @@ -108,12 +110,13 @@ fn test_tile_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry { offset: 0, size: data_len as u64, decoded_size: test_tile_decoded_size(16, 16) as u64, - codec: "NONE".to_string(), + codec: AssetCodec::None, metadata: serde_json::json!({ "tile_size": 16, "width": 16, "height": 16, - "palette_count": TILE_BANK_PALETTE_COUNT_V1 + "palette_count": TILE_BANK_PALETTE_COUNT_V1, + "palette_authored": TILE_BANK_PALETTE_COUNT_V1 }), } } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 2de2de00..391a384b 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,6 +1,6 @@ -{"type":"meta","next_id":{"DSC":21,"AGD":19,"DEC":5,"PLN":3,"LSN":24,"CLSN":1}} -... (mantendo as linhas anteriores) ... +{"type":"meta","next_id":{"DSC":22,"AGD":20,"DEC":6,"PLN":5,"LSN":24,"CLSN":1}} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} +{"type":"discussion","id":"DSC-0021","status":"open","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[{"id":"AGD-0019","file":"AGD-0019-asset-entry-codec-enum-with-metadata.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-asset-entry-codec-enum-contract.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09","ref_agenda":"AGD-0019"}],"plans":[{"id":"PLN-0004","file":"PLN-0004-asset-entry-codec-enum-execution.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09","ref_decisions":["DEC-0005"]}],"lessons":[]} {"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} {"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} diff --git a/discussion/workflow/agendas/AGD-0019-asset-entry-codec-enum-with-metadata.md b/discussion/workflow/agendas/AGD-0019-asset-entry-codec-enum-with-metadata.md new file mode 100644 index 00000000..00f57657 --- /dev/null +++ b/discussion/workflow/agendas/AGD-0019-asset-entry-codec-enum-with-metadata.md @@ -0,0 +1,183 @@ +--- +id: AGD-0019 +ticket: asset-entry-codec-enum-with-metadata +title: Agenda - Asset Entry Codec as Enum with Metadata +status: accepted +created: 2026-04-09 +resolved: 2026-04-09 +decision: DEC-0005 +tags: [asset, runtime, codec, metadata] +--- + +# Agenda - Asset Entry Codec as Enum with Metadata + +## Contexto + +A `DSC-0017` consolidou a normalizacao de `AssetEntry.metadata`, separando campos criticos na raiz e empurrando detalhes tecnicos para subarvores como `codec` e `pipeline`. + +Isso resolveu a organizacao do contrato, mas nao fechou a representacao tipada de `codec` dentro do runtime. Hoje o dado ainda e percebido mais como JSON dinamico do que como parte explicita do modelo de dominio. + +O tema desta agenda e decidir se `codec` em `AssetEntry` deve deixar de ser apenas um blob/subarvore e passar a ser um enum tipado, capaz de carregar os metadados pertinentes a cada codec. + +## Problema + +Quando `codec` e tratado como JSON generico ou como informacao frouxa em metadados, o runtime perde: + +- clareza sobre quais codecs existem de fato; +- validacao estrutural forte por variante; +- ergonomia para loaders e drivers; +- separacao nitida entre metadata do banco e metadata do codec. + +Ao mesmo tempo, tipar `codec` cedo demais ou de forma larga demais pode misturar responsabilidades e transformar o enum em um deposito generico de qualquer detalhe tecnico do asset. + +## Pontos Criticos + +1. Limite de responsabilidade. + `codec` deve carregar apenas dados necessarios para decodificacao, nao toda a metadata do asset. + +2. Compatibilidade com a normalizacao anterior. + A agenda nao deve reabrir a decisao de segmentar metadata; ela deve apenas fechar como o segmento `codec` vira modelo tipado no runtime. + +3. Evolucao de formato. + O modelo precisa permitir codecs sem payload adicional e codecs com configuracao especifica, sem cair em campos opcionais demais. + +4. Unknown/forward compatibility. + Precisamos decidir se codecs desconhecidos falham no parse, se ficam preservados como raw JSON, ou se existe modo hibrido. + +5. Impacto nos callers. + A mudanca afeta serializacao/deserializacao, helpers, validacao de cart e ergonomia dos loaders. + +## Opcoes + +### Opcao A - Manter `codec` como JSON dinamico + +- **Abordagem:** manter `codec` como subarvore em `metadata` ou como `serde_json::Value`, usando helpers tipados apenas nos pontos de consumo. +- **Pro:** menor migracao imediata e maior flexibilidade para formatos experimentais. +- **Con:** o contrato real continua implicito, com checagens espalhadas e mais risco de drift entre parser e consumidores. +- **Tradeoff:** adia a decisao de dominio e preserva a ambiguidade exatamente no ponto onde o runtime precisa de semantica forte. + +### Opcao B - Tornar `codec` um enum tipado com payload por variante + +- **Abordagem:** introduzir algo como `AssetCodec`, com variantes explicitas (`None`, `Png { ... }`, `Jpeg { ... }`, etc.) carregando apenas os metadados do proprio codec. +- **Pro:** o runtime ganha exaustividade, parse centralizado e um modelo de dominio mais honesto. +- **Con:** exige decidir fronteiras claras entre metadata do codec e metadata do banco/asset; tambem aumenta custo de evolucao quando novos codecs surgem. +- **Tradeoff:** troca flexibilidade irrestrita por contrato forte e melhor ergonomia operacional. + +### Opcao C - Separar `codec_kind` de um payload opcional tipado + +- **Abordagem:** usar um enum leve para o tipo de codec e um campo separado para configuracao adicional. +- **Pro:** reduz acoplamento do discriminante com o payload e pode simplificar alguns formatos. +- **Con:** reintroduz estados invalidos (`kind` e payload divergentes), exigindo validacao cruzada manual. +- **Tradeoff:** parece mais flexivel, mas perde a principal vantagem de um tagged enum: coerencia estrutural por construcao. + +## Sugestao / Recomendacao + +Seguir com a **Opcao B**. + +`codec` em `AssetEntry` deveria ser um enum tipado e capaz de carregar os metadados estritamente relativos a ele. Isso fecha melhor o modelo de dominio: o runtime deixa de tratar codec como detalhe textual solto e passa a reconhece-lo como parte semantica do contrato do asset. + +A recomendacao, no entanto, vem com uma fronteira explicita: + +- metadata especifica de decodificacao fica dentro do enum do codec; +- metadata do banco/formato de consumo continua fora dele; +- metadata editorial ou operacional do asset tambem continua fora dele. + +Em outras palavras, a discussao nao e "colocar toda metadata dentro de `codec`", e sim "dar ao segmento `codec` uma representacao tipada, exaustiva e com payload por variante quando necessario". + +Com as respostas atuais, a recomendacao fica mais concreta: + +- o JSON serializado pelo Studio dentro de `asset.pa` pode manter `codec` como string opaca; +- o runtime Rust deve desserializar essa string diretamente para um enum em `AssetEntry`; +- o nome canonico de cada codec no JSON deve seguir o contrato definido pelo runtime, e o Studio/paker deve se conformar exatamente a ele; +- a variante inicial canonica e apenas `None`; +- `Raw` deve ser considerada removida do modelo; +- codec desconhecido no runtime e erro fatal de carregamento e deve impedir a execucao do cartucho; +- `pipeline` nao deve sobreviver no runtime e pode ser descartado completamente desse lado. + +## Perguntas em Aberto + +- Como o modelo evolui quando surgir o primeiro codec real com payload adicional sem perder a separacao entre o discriminante string no JSON e a interpretacao tipada no runtime? + +## Discussao + +### Direcao fechada ate aqui + +1. **Serializacao no Studio** + O JSON serializado pelo Studio dentro de `asset.pa` deve expor `codec` como string opaca. + +2. **Desserializacao no runtime** + O runtime Rust deve carregar essa string diretamente em um enum no proprio `AssetEntry`. O wire format continua string, mas o modelo carregado no runtime passa a ser tipado. + +3. **Canon do nome** + O nome textual do codec no JSON nao e arbitrario por produtor. Ele deve seguir exatamente o canon definido pelo contrato. O canon fechado para este tema e `SCREAMING_SNAKE_CASE`, alinhado a `BankType`, e o packer deve seguir esse formato sem aliases livres. + +4. **Variante inicial** + O unico codec canonico agora e `None`. + +5. **Fim de `Raw`** + `Raw` nao deve mais existir como conceito no modelo. + +6. **Codecs desconhecidos** + Nao devem ser empacotados. Se mesmo assim chegarem ao runtime, isso e erro de validacao do `AssetEntry` e o cartucho deve ser fechado antes de seguir. + +7. **Escopo atual de codecs** + Como ainda nao existe codec real implementado, a primeira versao do enum pode ser minima e conter apenas `None`. + +8. **Destino de `pipeline`** + `pipeline` nao tem valor operacional no runtime atual e pode ser descartado completamente desse lado. + +### Leitura arquitetural dessas respostas + +As respostas empurram a agenda para um contrato bem mais estrito do que o texto inicial sugeria: + +- `codec` existe como conceito de dominio mesmo antes de haver codecs concretos alem de `None`; +- o JSON de `asset.pa` pode manter um discriminante simples e opaco, enquanto o runtime desserializa isso diretamente para enum e usa esse valor para escolher a interpretacao tipada de `metadata.codec`; +- a autoridade sobre o spelling do codec pertence ao contrato compartilhado, nao ao Studio isoladamente; +- o runtime nao deve carregar extensibilidade aberta para codecs desconhecidos; +- a compatibilidade futura deve ser dirigida pelo Studio e pelo empacotamento, nao por tolerancia dinamica no runtime; +- `pipeline` deixa de disputar espaco conceitual com `codec` no runtime. + +Isso favorece um modelo de enum fechado, validado cedo e com semantica fail-fast. + +## Criterio para Encerrar + +Esta agenda pode ser encerrada quando houver consenso escrito sobre: + +- o papel exato de `codec` no modelo de dominio do `AssetEntry`; +- a fronteira entre payload do codec e restante da metadata; +- a estrategia para codecs sem metadata adicional e para codecs desconhecidos; +- o shape string de `codec` no JSON e sua relacao com o enum carregado no runtime e com `metadata.codec`; +- o proximo passo normativo para transformar isso em decisao sem reabrir `DSC-0017`. + +## Resolucao Provisoria + +Ha consenso provisoriamente estabelecido sobre os seguintes pontos: + +- `codec` deve existir como enum tipado no modelo do runtime; +- a variante inicial unica e `None`; +- `Raw` sai do contrato; +- codecs desconhecidos sao invalidos e devem falhar de forma fatal no carregamento do cartucho; +- `pipeline` pode ser descartado do runtime; +- o JSON de `asset.pa` deve serializar `codec` como string opaca; +- o runtime Rust deve desserializar essa string diretamente para enum no `AssetEntry`; +- o nome textual do codec no JSON deve seguir `SCREAMING_SNAKE_CASE`, e o Studio deve se conformar exatamente a ele; +- codec desconhecido deve falhar na validacao do `AssetEntry`, antes de qualquer carga efetiva. + +O unico ponto em aberto passa a ser a politica de evolucao para o primeiro codec real com payload, preservando a distincao entre: + +- discriminante string no JSON do asset; e +- shape tipado de `metadata.codec` no runtime. + +## Resolucao + +A agenda fica encerrada com a seguinte orientacao: + +- `codec` permanece `string` no JSON de `asset.pa`; +- o runtime Rust deve desserializar essa string diretamente para enum no `AssetEntry`; +- o nome textual do codec segue `SCREAMING_SNAKE_CASE`, alinhado a `BankType`; +- `None` e a unica variante inicial; +- `Raw` sai do contrato; +- codec desconhecido falha na validacao do `AssetEntry` e fecha o cartucho; +- `pipeline` e descartado do runtime. + +A evolucao para codecs com payload adicional fica explicitamente adiada para uma decisao futura motivada por um codec real. diff --git a/discussion/workflow/decisions/DEC-0005-asset-entry-codec-enum-contract.md b/discussion/workflow/decisions/DEC-0005-asset-entry-codec-enum-contract.md new file mode 100644 index 00000000..bb2a0911 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0005-asset-entry-codec-enum-contract.md @@ -0,0 +1,91 @@ +--- +id: DEC-0005 +ticket: asset-entry-codec-enum-with-metadata +title: Asset Entry Codec Enum Contract +status: accepted +created: 2026-04-09 +accepted: 2026-04-09 +agenda: AGD-0019 +plans: [PLN-0004] +tags: [asset, runtime, codec, metadata] +--- + +## Status + +Accepted on 2026-04-09. + +## Contexto + +A discussao `AGD-0019` fechou que o campo `codec` de `AssetEntry` faz parte do contrato de dominio do runtime e nao deve permanecer como texto frouxo consumido ad hoc por loaders. + +Hoje o empacotamento e produzido no Studio em Java/Jackson e o runtime consome o header JSON de `asset.pa` em Rust. Portanto, o contrato precisa distinguir claramente: + +- o wire format serializado pelo Studio; +- o modelo tipado carregado pelo runtime; +- a relacao entre o discriminante do codec e qualquer metadata especifica de codec. + +A decisao tambem precisa consolidar o fim do alias legado `RAW` e a politica de falha para codecs desconhecidos. + +## Decisao + +1. `AssetEntry.codec` no runtime MUST ser representado por um enum tipado, e MUST NOT permanecer como `String` no modelo carregado. +2. O JSON serializado em `asset.pa` MUST continuar representando `codec` como uma `string`. +3. O runtime Rust MUST desserializar essa `string` diretamente para o enum de codec no proprio `AssetEntry`. +4. O spelling textual do codec no JSON MUST seguir exatamente o canon definido por este contrato. O canon inicial MUST ser `SCREAMING_SNAKE_CASE`, alinhado ao padrao ja usado por `BankType`. O Studio/paker MUST emitir exatamente esse spelling e MUST NOT introduzir aliases livres. +5. A variante inicial unica do enum MUST ser `None`. +6. O alias/conceito `Raw` MUST be removed from the contract and MUST NOT be emitted pelo Studio nem aceito como shape valido futuro. +7. Se o runtime encontrar um codec desconhecido para aquele binario, a validacao de `AssetEntry` MUST falhar antes de qualquer carga efetiva do asset, e o cartucho MUST ser rejeitado. +8. `pipeline` MUST NOT fazer parte do modelo operacional do runtime para esse contrato e MAY ser descartado completamente no lado do runtime. +9. Metadados especificos de codec, quando existirem, MUST ser interpretados a partir do codec resolvido e MUST permanecer separados da metadata editorial ou da metadata propria do banco consumidor. +10. A estrategia para o primeiro codec com payload adicional is deferred. Esta decisao fecha apenas o contrato atual e o ponto de extensao, sem antecipar um shape normativo para codecs futuros com payload. + +## Rationale + +- O wire format em `string` preserva simplicidade e compatibilidade com o Studio em Java/Jackson. +- O enum no runtime torna o contrato exaustivo, evita parsing textual espalhado e reduz drift entre loader, validacao e consumidores. +- Rejeitar codec desconhecido cedo mantem o sistema fail-fast e evita estados parcialmente carregados. +- Remover `Raw` evita manter semantica historica ambigua em paralelo ao contrato novo. +- Expulsar `pipeline` do runtime reduz ruido conceitual e impede sobreposicao de responsabilidade com `codec`. +- Adiar o desenho de codecs com payload evita overdesign antes de existir um caso concreto. + +## Invariantes / Contrato + +- O valor de `codec` no JSON de `asset.pa` e um discriminante textual canonico. +- O discriminante textual canonico de `codec` no JSON usa `SCREAMING_SNAKE_CASE`. +- O valor de `codec` carregado em Rust e um enum fechado para o conjunto de codecs suportados por aquele runtime. +- O Studio e o runtime compartilham o mesmo spelling canonico para cada variante. +- O runtime nao oferece modo tolerante para codecs desconhecidos. +- O contrato inicial possui uma unica variante valida: `None`. +- `Raw` nao pertence mais ao contrato canonico. +- `pipeline` nao participa do contrato operacional do runtime. +- Quando `metadata.codec` existir para algum codec futuro, sua interpretacao dependera do discriminante ja validado. + +## Impactos + +- `prometeu-hal` deve trocar `AssetEntry.codec: String` por um enum serializavel/desserializavel com canon textual explicito. +- `prometeu-drivers` deve remover a aceitacao de `RAW` como alias legado e passar a operar sobre enum, nao sobre comparacao textual solta. +- O loader/validacao do cart deve falhar cedo se a desserializacao ou validacao do codec falhar. +- O Studio/paker deve emitir exatamente o nome canonico definido para o codec. +- O nome canonico inicial de `None` no JSON deve ser `NONE`. +- Nao ha necessidade de definir agora um DTO separado apenas para codec; o proprio `AssetEntry` continua sendo o contrato serializado e desserializado. + +## Referencias + +- AGD-0019: Asset Entry Codec as Enum with Metadata +- DSC-0017: Asset Entry Metadata Normalization Contract +- LSN-0023: Typed Helpers for Asset Metadata +- `crates/console/prometeu-hal/src/asset.rs` +- `crates/console/prometeu-hal/src/cartridge_loader.rs` +- `crates/console/prometeu-drivers/src/asset.rs` + +## Propagacao Necessaria + +- Atualizar o modelo Rust de `AssetEntry` para usar enum de codec. +- Atualizar a validacao/desserializacao do header de `asset.pa`. +- Atualizar drivers para abandonar `RAW` e comparacoes por `String`. +- Ajustar o Studio/paker para emitir o spelling canonico escolhido pelo contrato. +- Escrever um plano de execucao antes de alterar spec/codigo. + +## Revision Log + +- 2026-04-09: Initial accepted decision from AGD-0019. diff --git a/discussion/workflow/plans/PLN-0004-asset-entry-codec-enum-execution.md b/discussion/workflow/plans/PLN-0004-asset-entry-codec-enum-execution.md new file mode 100644 index 00000000..71222e63 --- /dev/null +++ b/discussion/workflow/plans/PLN-0004-asset-entry-codec-enum-execution.md @@ -0,0 +1,141 @@ +--- +id: PLN-0004 +ticket: asset-entry-codec-enum-with-metadata +title: Asset Entry Codec Enum Execution +status: accepted +created: 2026-04-09 +completed: +tags: [asset, runtime, codec, metadata] +--- + +## Briefing + +Implement DEC-0005 by replacing `AssetEntry.codec: String` with a typed Rust enum while keeping the `assets.pa` JSON wire format as a canonical `SCREAMING_SNAKE_CASE` string. The initial supported codec set contains only `None`, serialized as `NONE`. Unknown codecs must fail during `AssetEntry` validation, `RAW` must be removed from the accepted contract, and runtime code must stop relying on free-form string comparisons. + +## Decisions de Origem + +- DEC-0005: Asset Entry Codec Enum Contract + +## Alvo + +Land a runtime-side implementation that: + +- introduces a serializable/deserializable `AssetCodec` enum in `prometeu-hal`; +- migrates `AssetEntry` to use the enum directly; +- rejects unknown codec strings during `assets.pa` parsing/validation; +- removes legacy `RAW` handling from runtime consumers and tests; +- preserves the current JSON transport contract for the Studio packer: `codec` remains a `SCREAMING_SNAKE_CASE` string in `assets.pa`. + +## Escopo + +- Runtime contract changes in `prometeu-hal` for `AssetEntry` and codec serialization. +- Cartridge loading and `assets.pa` validation behavior. +- Asset driver logic and tests that currently assume `codec` is a `String`. +- Runtime test fixtures that construct `AssetEntry` instances directly. + +## Fora de Escopo + +- Studio/Java packer implementation work. +- Introduction of any real codec beyond `None`. +- Design or implementation of `metadata.codec` payload shapes for future codecs. +- Discussion or implementation of a separate DTO just for `AssetEntry`. + +## Plano de Execucao + +### Step 1 - Introduce `AssetCodec` in `prometeu-hal` + +**What:** +Define the runtime enum for asset codecs and migrate `AssetEntry.codec` from `String` to that enum. + +**How:** +Add `AssetCodec` to `crates/console/prometeu-hal/src/asset.rs` with idiomatic Rust variant naming and explicit serde mapping to `SCREAMING_SNAKE_CASE`. The initial enum must contain only `None`, serialized as `NONE`. Update `AssetEntry` to use `AssetCodec` and remove the legacy comment about `RAW`. + +**File(s):** +- `crates/console/prometeu-hal/src/asset.rs` + +### Step 2 - Make `assets.pa` parsing fail on unknown codecs + +**What:** +Ensure unknown codec strings are rejected before any asset load path proceeds. + +**How:** +Rely on enum deserialization failure or an explicit validation hook during `assets.pa` header parsing so that invalid codec values produce `CartridgeError::InvalidFormat`. Keep the failure at `AssetEntry` validation time inside cartridge loading, not delayed to driver execution. + +**File(s):** +- `crates/console/prometeu-hal/src/cartridge_loader.rs` +- Any supporting type definitions in `crates/console/prometeu-hal/src/cartridge.rs` if required by parsing flow + +### Step 3 - Remove runtime string-based codec branching + +**What:** +Update asset runtime behavior to consume the enum directly and remove legacy `RAW` acceptance. + +**How:** +Replace `codec_is_none_or_legacy_raw` and string matches in `crates/console/prometeu-drivers/src/asset.rs` with enum-based matching on `AssetCodec`. Ensure unsupported codecs remain an error path, but `RAW` is no longer recognized anywhere in runtime logic. + +**File(s):** +- `crates/console/prometeu-drivers/src/asset.rs` + +### Step 4 - Update test fixtures and regression coverage + +**What:** +Bring existing tests and fixtures in line with the new typed contract and add regression tests for rejection behavior. + +**How:** +Replace direct string fixture values such as `"NONE"` and `"RAW"` with enum construction where tests instantiate `AssetEntry` directly. Add or update loader tests to cover: + +- successful parse of `codec: "NONE"`; +- failure on unknown codec strings; +- failure on legacy `RAW` if it still appears in serialized input. + +Update driver tests to assert enum-based behavior for the supported codec set. + +**File(s):** +- `crates/console/prometeu-hal/src/cartridge_loader.rs` +- `crates/console/prometeu-drivers/src/asset.rs` +- `crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs` + +### Step 5 - Align downstream packer work item + +**What:** +Capture the non-runtime propagation requirement for the Studio packer. + +**How:** +Record in implementation notes, issue tracking, or follow-up execution context that the Studio/Java packer must emit canonical `SCREAMING_SNAKE_CASE` codec strings and must stop producing `RAW`. No runtime code should add compatibility shims for packer drift. + +**File(s):** +- No runtime file changes required in this repository for this step + +## Criterios de Aceite + +- `AssetEntry.codec` is an enum in Rust, not a `String`. +- `assets.pa` still serializes/deserializes codec as a JSON string. +- The canonical wire spelling for the initial codec is `NONE`. +- Unknown codec strings cause cartridge loading to fail before asset loading proceeds. +- `RAW` is no longer accepted by runtime code or runtime tests. +- Asset driver code branches on `AssetCodec`, not string literals. +- Existing runtime tests pass after fixture migration, and regression tests cover the new failure behavior. + +## Tests / Validacao + +### Unit Tests + +- Serialization/deserialization tests for `AssetCodec` proving `AssetCodec::None <-> "NONE"`. +- Asset driver tests that match on enum variants instead of strings. + +### Integration Tests + +- Cartridge loader test that accepts `assets.pa` headers containing `codec: "NONE"`. +- Cartridge loader test that rejects an unknown codec string. +- Cartridge loader test that rejects legacy `RAW`. + +### Manual Verification + +- Inspect generated `assets.pa` header JSON from a known-good sample and verify `codec` remains a string field with `SCREAMING_SNAKE_CASE`. +- Run the relevant Rust test suites for `prometeu-hal`, `prometeu-drivers`, and any affected runtime tests. + +## Riscos + +- Existing tests and fixtures may be numerous because `AssetEntry` is widely constructed directly. +- If deserialization failure maps too generically, debugging bad packer output may become opaque unless tests assert the intended failure path clearly. +- Studio work is out of scope for this repository, so rollout coordination is required to avoid runtime/packer contract skew.