dev/ajustments-asset-entry #12

Merged
bquarkz merged 10 commits from dev/ajustments-asset-entry into master 2026-04-10 05:31:58 +00:00
12 changed files with 547 additions and 50 deletions
Showing only changes of commit 5faf9ac6db - Show all commits

View File

@ -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<AssetOpMode, String> {
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
}),
};

View File

@ -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<dyn TileBankPoolAccess>) -> 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,

View File

@ -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,
}

View File

@ -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));
}
}

View File

@ -2,4 +2,4 @@
pub struct Glyph {
pub glyph_id: u16,
pub palette_id: u8,
}
}

View File

@ -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};

View File

@ -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,

View File

@ -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
}),
}
}

View File

@ -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":[]}

View File

@ -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.

View File

@ -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.

View File

@ -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.