From d576f7ddbdcc71876855ddce3e0ca087e6fa3e34 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 11 Mar 2026 08:27:37 +0000 Subject: [PATCH] asset preload alignment --- crates/console/prometeu-drivers/src/asset.rs | 30 ++-- crates/console/prometeu-hal/src/asset.rs | 7 +- .../prometeu-hal/src/cartridge_loader.rs | 64 ++++++- ...eload-and-asset-table-id-based-contract.md | 29 ++++ docs/runtime/agendas/README.md | 1 - .../012-assets-preload-asset-id-contract.md | 157 ++++++++++++++++++ docs/runtime/decisions/README.md | 1 + ...012-assets-preload-asset-id-propagation.md | 69 ++++++++ docs/runtime/pull-requests/README.md | 1 + docs/runtime/specs/13-cartridge.md | 8 + docs/runtime/specs/15-asset-management.md | 22 +++ 11 files changed, 367 insertions(+), 22 deletions(-) create mode 100644 docs/runtime/agendas/024-assets-pa-preload-and-asset-table-id-based-contract.md create mode 100644 docs/runtime/decisions/012-assets-preload-asset-id-contract.md create mode 100644 docs/runtime/pull-requests/012-assets-preload-asset-id-propagation.md diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index d374e1fb..236c8a93 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -2,7 +2,7 @@ use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; use prometeu_hal::AssetBridge; use prometeu_hal::asset::{ - AssetEntry, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, + AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, }; use prometeu_hal::cartridge::AssetsPayloadSource; @@ -48,7 +48,7 @@ impl ResidentEntry { /// This is internal to the AssetManager and not visible to peripherals. pub struct BankPolicy { /// Dedup table: asset_id -> resident entry (value + telemetry). - resident: Arc>>>, + resident: Arc>>>, /// Staging area: handle -> value ready to commit. staging: Arc>>>, @@ -63,7 +63,7 @@ impl BankPolicy { } /// Try get a resident value by asset_id (dedupe path). - pub fn get_resident(&self, asset_id: u32) -> Option> { + pub fn get_resident(&self, asset_id: AssetId) -> Option> { let mut map = self.resident.write().unwrap(); let entry = map.get_mut(&asset_id)?; entry.last_used = Instant::now(); @@ -71,7 +71,7 @@ impl BankPolicy { } /// Insert or reuse a resident entry. Returns the resident Arc. - pub fn put_resident(&self, asset_id: u32, value: Arc, bytes: usize) -> Arc { + pub fn put_resident(&self, asset_id: AssetId, value: Arc, bytes: usize) -> Arc { let mut map = self.resident.write().unwrap(); match map.get_mut(&asset_id) { Some(existing) => { @@ -104,8 +104,8 @@ impl BankPolicy { } pub struct AssetManager { - assets: Arc>>, - name_to_id: Arc>>, + assets: Arc>>, + name_to_id: Arc>>, handles: Arc>>, next_handle_id: Mutex, assets_data: Arc>, @@ -115,8 +115,8 @@ pub struct AssetManager { sound_installer: Arc, /// Track what is installed in each hardware slot (for stats/info). - gfx_slots: Arc; 16]>>, - sound_slots: Arc; 16]>>, + gfx_slots: Arc; 16]>>, + sound_slots: Arc; 16]>>, /// Residency policy for GFX tile banks. gfx_policy: BankPolicy, @@ -128,7 +128,7 @@ pub struct AssetManager { } struct LoadHandleInfo { - _asset_id: u32, + _asset_id: AssetId, slot: SlotRef, status: LoadStatus, } @@ -238,8 +238,7 @@ impl AssetManager { for item in preload { let entry_opt = { let assets = self.assets.read().unwrap(); - let name_to_id = self.name_to_id.read().unwrap(); - name_to_id.get(&item.asset_name).and_then(|id| assets.get(id)).cloned() + assets.get(&item.asset_id).cloned() }; if let Some(entry) = entry_opt { @@ -302,10 +301,7 @@ impl AssetManager { } } // else { - // eprintln!( - // "[AssetManager] Preload failed: asset '{}' not found in table", - // item.asset_name - // ); + // eprintln!("[AssetManager] Preload failed: asset id '{}' not found in table", item.asset_id); // } } } @@ -1047,7 +1043,7 @@ mod tests { }), }; - let preload = vec![PreloadEntry { asset_name: "preload_sound".to_string(), slot: 5 }]; + let preload = vec![PreloadEntry { asset_id: 2, slot: 5 }]; let am = AssetManager::new( vec![], @@ -1076,7 +1072,7 @@ mod tests { let mut asset_entry = test_tile_asset_entry("my_tiles", data.len()); asset_entry.asset_id = 10; - let preload = vec![PreloadEntry { asset_name: "my_tiles".to_string(), slot: 3 }]; + let preload = vec![PreloadEntry { asset_id: 10, slot: 3 }]; let am = AssetManager::new( vec![], diff --git a/crates/console/prometeu-hal/src/asset.rs b/crates/console/prometeu-hal/src/asset.rs index e78b7d6b..0a3d3ab9 100644 --- a/crates/console/prometeu-hal/src/asset.rs +++ b/crates/console/prometeu-hal/src/asset.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; pub type HandleId = u32; +pub type AssetId = i32; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] #[allow(non_camel_case_types)] @@ -13,7 +14,7 @@ pub enum BankType { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct AssetEntry { - pub asset_id: u32, + pub asset_id: AssetId, pub asset_name: String, pub bank_type: BankType, pub offset: u64, @@ -25,7 +26,7 @@ pub struct AssetEntry { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PreloadEntry { - pub asset_name: String, + pub asset_id: AssetId, pub slot: usize, } @@ -70,7 +71,7 @@ pub struct BankStats { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SlotStats { - pub asset_id: Option, + pub asset_id: Option, pub asset_name: Option, pub generation: u32, pub resident_bytes: usize, diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index b99c10bf..749e41d2 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -1,3 +1,4 @@ +use crate::asset::{AssetEntry, BankType}; use crate::cartridge::{ ASSETS_PA_MAGIC, ASSETS_PA_PRELUDE_SIZE, ASSETS_PA_SCHEMA_VERSION, AssetsPackHeader, AssetsPackPrelude, AssetsPayloadSource, Capability, Cartridge, CartridgeDTO, CartridgeError, @@ -168,11 +169,29 @@ fn parse_assets_pack(path: &Path) -> Result { file.read_exact(&mut header_bytes).map_err(|_| CartridgeError::InvalidFormat)?; let header: AssetsPackHeader = serde_json::from_slice(&header_bytes).map_err(|_| CartridgeError::InvalidFormat)?; + validate_preload(&header.asset_table, &header.preload)?; let payload_len = u64::try_from(file_len - payload_offset).map_err(|_| CartridgeError::InvalidFormat)?; Ok(ParsedAssetsPack { header, payload_offset: prelude.payload_offset, payload_len }) } +fn validate_preload(asset_table: &[AssetEntry], preload: &[crate::asset::PreloadEntry]) -> Result<(), CartridgeError> { + let mut claimed_slots = HashSet::<(BankType, usize)>::new(); + + for item in preload { + let entry = asset_table + .iter() + .find(|entry| entry.asset_id == item.asset_id) + .ok_or(CartridgeError::InvalidFormat)?; + + if !claimed_slots.insert((entry.bank_type, item.slot)) { + return Err(CartridgeError::InvalidFormat); + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -380,7 +399,7 @@ mod tests { let payload = vec![1_u8, 2, 3, 4]; dir.write_assets_pa( vec![test_asset_entry(0, payload.len() as u64)], - vec![PreloadEntry { asset_name: "tiles".to_string(), slot: 2 }], + vec![PreloadEntry { asset_id: 7, slot: 2 }], &payload, ); @@ -396,9 +415,52 @@ mod tests { assert_eq!(cartridge.asset_table.len(), 1); assert_eq!(cartridge.asset_table[0].asset_name, "tiles"); assert_eq!(cartridge.preload.len(), 1); + assert_eq!(cartridge.preload[0].asset_id, 7); assert_eq!(cartridge.preload[0].slot, 2); } + #[test] + fn load_rejects_preload_with_missing_asset_id() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"]))); + dir.write_assets_pa( + vec![test_asset_entry(0, 4)], + vec![PreloadEntry { asset_id: 999, slot: 2 }], + &[1_u8, 2, 3, 4], + ); + + let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err(); + + assert!(matches!(error, CartridgeError::InvalidFormat)); + } + + #[test] + fn load_rejects_preload_slot_clash_per_bank_type() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"]))); + let asset_table = vec![ + test_asset_entry(0, 4), + AssetEntry { + asset_id: 8, + asset_name: "other_tiles".to_string(), + bank_type: BankType::TILES, + offset: 4, + size: 4, + decoded_size: 4, + codec: "RAW".to_string(), + metadata: json!({ + "tile_size": 16, + "width": 16, + "height": 16 + }), + }, + ]; + let preload = vec![PreloadEntry { asset_id: 7, slot: 2 }, PreloadEntry { asset_id: 8, slot: 2 }]; + dir.write_assets_pa(asset_table, preload, &[1_u8, 2, 3, 4, 5, 6, 7, 8]); + + let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err(); + + assert!(matches!(error, CartridgeError::InvalidFormat)); + } + #[test] fn load_rejects_invalid_assets_pa_prelude() { let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"]))); diff --git a/docs/runtime/agendas/024-assets-pa-preload-and-asset-table-id-based-contract.md b/docs/runtime/agendas/024-assets-pa-preload-and-asset-table-id-based-contract.md new file mode 100644 index 00000000..6c9109b9 --- /dev/null +++ b/docs/runtime/agendas/024-assets-pa-preload-and-asset-table-id-based-contract.md @@ -0,0 +1,29 @@ +# Agenda 024 - `assets.pa` Preload and Asset Table ID-based Contract (Fechada) + +## Status + +Fechada pela decisao: + +- `../decisions/012-assets-preload-asset-id-contract.md` + +## O Que Foi Fechado + +1. `PreloadEntry` deixa de usar `asset_name` e passa a ser normativamente `{ asset_id, slot }`. +2. `asset_id` passa a ser o identificador operacional de preload com semantica de `java int`. +3. `asset_name` permanece apenas como metadata descritiva/debug e nao participa do preload. +4. `preload` deve ser validado contra a `asset_table` do proprio `assets.pa` durante o bootstrap. +5. `asset_id` ausente da `asset_table` e clash de slot por tipo caracterizam erro estrutural de formacao do cart. +6. Nao existe compatibilidade normativa para preload legado por `asset_name`. + +## Efeito Pratico + +- `spec 15` passa a precisar explicitar o shape normativo de `PreloadEntry` e a validacao estrutural de preload. +- loader, parser de `assets.pa` e `AssetManager` passam a consumir preload por `asset_id`. +- a modelagem atual em codigo baseada em `u32` para `asset_id` precisa ser revisitada onde o contrato exposto ainda assume unsigned. + +## Follow-up + +- propagar a decisao em `specs 13/15`; +- criar suporte de parse/validacao estrutural para preload por `asset_id`; +- remover o preload por `asset_name` do runtime e dos testes; +- manter `asset.load(name, kind, slot)` inalterado ate decisao futura sobre a superficie VM-facing. diff --git a/docs/runtime/agendas/README.md b/docs/runtime/agendas/README.md index dd51c29d..50c745d0 100644 --- a/docs/runtime/agendas/README.md +++ b/docs/runtime/agendas/README.md @@ -24,7 +24,6 @@ As agendas atuais são: - `020-perf-host-debug-overlay-isolation.md` - `021-perf-vm-allocation-and-copy-pressure.md` - `022-perf-cartridge-boot-and-program-ownership.md` - ## Sequenciamento Recomendado [PERF] Ordem sugerida para discussao das agendas de performance: diff --git a/docs/runtime/decisions/012-assets-preload-asset-id-contract.md b/docs/runtime/decisions/012-assets-preload-asset-id-contract.md new file mode 100644 index 00000000..21a245ed --- /dev/null +++ b/docs/runtime/decisions/012-assets-preload-asset-id-contract.md @@ -0,0 +1,157 @@ +# Decision Record - `assets.pa` Preload Uses `asset_id` + +## Status + +Accepted + +## Contexto + +A `decision 011` fechou o envelope runtime-facing de `assets.pa`: + +- `assets.pa` e o contrato primario de assets do runtime; +- `asset_table` e `preload` vivem no header interno do artefato; +- `preload` e consumido apenas no bootstrap. + +Essa decisao, no entanto, nao definiu a semantica de identidade de `PreloadEntry`. + +O estado atual do runtime ainda mistura dois modelos: + +- `AssetEntry` ja trata `asset_id` como identidade operacional do asset; +- `PreloadEntry` ainda usa `asset_name` como chave pratica de resolucao no boot. + +Isso preserva uma dependencia estrutural de campo mutavel justamente na fase em que o cart deveria ser mais deterministicamente validavel. + +## Decisao + +### 1. `PreloadEntry` passa a ser `{ asset_id, slot }` + +O shape normativo de preload em `assets.pa` passa a ser: + +```text +PreloadEntry { + asset_id, + slot +} +``` + +`asset_name` deixa de fazer parte do contrato operacional de preload. + +### 2. `asset_id` tem semantica de `java int` + +`asset_id` no contrato runtime-facing de assets passa a ser um inteiro de 32 bits com semantica de `java int`. + +Qualquer representacao interna do runtime deve respeitar essa semantica e nao introduzir contrato unsigned por acidente. + +### 3. `asset_name` permanece apenas como metadata descritiva + +`asset_name` continua permitido em `AssetEntry` para: + +- debug; +- observabilidade; +- tooling editorial; +- UX de inspecao. + +Mas `asset_name` nao participa: + +- da identidade normativa do asset; +- da validacao de preload; +- da resolucao operacional de preload. + +### 4. `preload` deve ser validado contra a `asset_table` + +Quando `assets.pa` existir, o runtime deve validar `preload` durante o bootstrap usando a `asset_table` carregada do mesmo artefato. + +Validacoes minimas obrigatorias: + +- todo `preload.asset_id` deve existir na `asset_table`; +- nao pode haver clash de slot por tipo. + +### 5. Clash de slot por tipo e proibido + +O namespace de slot de preload e derivado de `bank_type` da `asset_table`. + +Portanto, depois de resolver cada `preload.asset_id` para sua respectiva `AssetEntry`, o cart e invalido se houver mais de uma entry de preload para o mesmo par: + +```text +(bank_type, slot) +``` + +Isso vale mesmo que as entries apontem para o mesmo asset. + +### 6. Nao ha compatibilidade normativa com preload legado por `asset_name` + +O contrato do runtime nao oferece modo de compatibilidade para preload legado baseado em `asset_name`. + +Artefatos que nao seguirem o shape novo devem ser considerados invalidos para o contrato vigente. + +### 7. Falhas de preload invalido sao estruturais de cart + +Os seguintes casos sao erros estruturais de formacao do cart: + +- `preload.asset_id` ausente da `asset_table`; +- clash de slot por tipo dentro de `preload`. + +Esses casos devem falhar no bootstrap do cart, antes da execucao normal, e nao pertencem ao espaco de `status` operacional de `asset.load`. + +## Rationale + +Esta decisao foi adotada porque: + +- alinha preload com a identidade estavel do dominio de assets; +- remove dependencia de metadata mutavel no bootstrap; +- preserva `asset_table` como fonte de verdade unica para identidade e tipo; +- torna a validacao do cart mais deterministica; +- impede que compatibilidade legado vire contrato permanente por inercia. + +O uso de `java int` foi escolhido para manter alinhamento de semantica de ID com a fronteira de linguagem/plataforma desejada, em vez de cristalizar um detalhe unsigned especifico da implementacao atual em Rust. + +## Invariantes / Contrato + +- `PreloadEntry` e normativamente `{ asset_id, slot }`. +- `asset_id` segue semantica de `java int`. +- `asset_name` nao tem autoridade operacional sobre preload. +- `preload` so pode referenciar assets existentes na `asset_table` do mesmo `assets.pa`. +- `preload` nao pode conter mais de uma ocupacao para o mesmo par `(bank_type, slot)`. +- falhas de validacao de preload sao erros estruturais de cart e devem abortar o bootstrap. +- `asset.load(name, kind, slot)` permanece fora do escopo desta decisao. + +## Impactos + +### Specs + +- `15-asset-management.md` deve explicitar o shape normativo de `PreloadEntry`, a semantica de `asset_id` e as validacoes estruturais de preload. +- `13-cartridge.md` deve registrar que `assets.pa` invalido inclui preload com IDs ausentes ou clash de slot por tipo. + +### Runtime + +- `prometeu-hal/src/asset.rs` deve trocar `PreloadEntry.asset_name` por `asset_id`. +- tipos de `asset_id` expostos no contrato runtime-facing devem ser revisados para semantica de `java int`. +- o loader de `assets.pa` deve rejeitar preload invalido antes de iniciar preload. +- o `AssetManager` deve resolver preload diretamente por `asset_id`, sem ponte nome -> id. + +### Tooling / Packer + +- o produtor de `assets.pa` deve emitir preload apenas com `asset_id`. +- carts com preload legado por `asset_name` deixam de ser validos no contrato atual. + +## Referencias + +- `../agendas/024-assets-pa-preload-and-asset-table-id-based-contract.md` +- `../decisions/011-assets-pa-autocontained-runtime-contract.md` +- `../specs/13-cartridge.md` +- `../specs/15-asset-management.md` +- `../../crates/console/prometeu-hal/src/asset.rs` +- `../../crates/console/prometeu-hal/src/cartridge_loader.rs` +- `../../crates/console/prometeu-drivers/src/asset.rs` + +## Propagacao Necessaria + +- criar PR/plan para atualizar `spec 13` e `spec 15`; +- criar PR/plan de codigo para: + - trocar o shape de `PreloadEntry` para `asset_id`; + - validar preload durante parse/bootstrap de `assets.pa`; + - rejeitar `asset_id` ausente da `asset_table`; + - rejeitar clash de slot por tipo; + - revisar a tipagem de `asset_id` para semantica de `java int`; + - atualizar testes de loader e `AssetManager`; +- remover a `agenda 024` da lista de agendas ativas. diff --git a/docs/runtime/decisions/README.md b/docs/runtime/decisions/README.md index 8537069d..3c923598 100644 --- a/docs/runtime/decisions/README.md +++ b/docs/runtime/decisions/README.md @@ -20,6 +20,7 @@ Decisoes ativas: - `006-vm-owned-stateful-core-contract.md` - `007-filesystem-fault-core-policy.md` - `011-assets-pa-autocontained-runtime-contract.md` +- `012-assets-preload-asset-id-contract.md` Decisoes implementadas e aposentadas (migradas para `learn/`): diff --git a/docs/runtime/pull-requests/012-assets-preload-asset-id-propagation.md b/docs/runtime/pull-requests/012-assets-preload-asset-id-propagation.md new file mode 100644 index 00000000..6631d5ca --- /dev/null +++ b/docs/runtime/pull-requests/012-assets-preload-asset-id-propagation.md @@ -0,0 +1,69 @@ +# PR 012 - Propagacao da Decision 012 para Preload por `asset_id` + +## Briefing + +Esta PR executa a `decision 012`, que fecha a identidade normativa de preload em `assets.pa`. + +O objetivo e remover preload por `asset_name`, adotar `asset_id` com semantica de `java int`, e validar preload como parte estrutural da formacao do cart. + +## Decisions de Origem + +- `../decisions/011-assets-pa-autocontained-runtime-contract.md` +- `../decisions/012-assets-preload-asset-id-contract.md` + +## Alvo + +Propagar a decisao em specs e codigo do runtime para que: + +- `PreloadEntry` seja `{ asset_id, slot }`; +- preload invalido falhe no bootstrap; +- o runtime nao dependa de nome para resolver preload. + +## Escopo + +- atualizar `spec 13` e `spec 15`; +- atualizar structs e parse do header de `assets.pa`; +- validar preload contra `asset_table`; +- detectar clash de slot por tipo; +- ajustar `AssetManager` e testes para preload por `asset_id`; +- revisar tipos expostos de `asset_id` para semantica de `java int`. + +## Fora de Escopo + +- mudar a superficie VM-facing de `asset.load(name, kind, slot)`; +- redesenhar `asset_table` alem do necessario para suportar a decisao; +- introduzir compatibilidade legado por `asset_name`; +- alterar politica de bancos/slots fora da validacao de preload. + +## Plano de Execucao + +1. Atualizar specs `13` e `15` com o shape normativo novo e as regras de validacao estrutural. +2. Trocar `PreloadEntry` em `prometeu-hal` para `asset_id`. +3. Revisar os pontos runtime-facing onde `asset_id` ainda esta fixado como unsigned. +4. Validar, no parse/bootstrap de `assets.pa`, que: + - todo preload referencia `asset_id` existente; + - nao existe duplicidade de `(bank_type, slot)`. +5. Remover a resolucao nome -> id do caminho de preload no `AssetManager`. +6. Atualizar testes de loader, parse e preload em memoria. + +## Criterios de Aceite + +- `assets.pa` com preload valido por `asset_id` carrega normalmente. +- `assets.pa` com `asset_id` ausente da `asset_table` falha no bootstrap. +- `assets.pa` com clash de slot por tipo falha no bootstrap. +- preload deixa de depender de `asset_name`. +- nenhuma spec vigente continua descrevendo preload por nome. + +## Tests / Validacao + +- teste de parse do header com `PreloadEntry { asset_id, slot }`; +- teste de rejeicao para `preload.asset_id` inexistente; +- teste de rejeicao para duplicidade de `(bank_type, slot)`; +- teste de inicializacao do `AssetManager` preloading por `asset_id`; +- revisao textual das specs `13` e `15` contra a `decision 012`. + +## Riscos + +- a troca de semantica de `asset_id` para `java int` pode expor assumptos unsigned espalhados no runtime; +- validacao estrutural no loader pode exigir ajuste em fixtures de teste existentes; +- se houver produtor externo ainda emitindo preload por `asset_name`, ele quebrara imediatamente, como mandado pela decisao. diff --git a/docs/runtime/pull-requests/README.md b/docs/runtime/pull-requests/README.md index 17ade7f7..1c59dbf3 100644 --- a/docs/runtime/pull-requests/README.md +++ b/docs/runtime/pull-requests/README.md @@ -38,3 +38,4 @@ Uma PR deste diretório deve: PRs propostas para execucao da rodada atual: +- `012-assets-preload-asset-id-propagation.md` diff --git a/docs/runtime/specs/13-cartridge.md b/docs/runtime/specs/13-cartridge.md index d2a42de0..41faa6fc 100644 --- a/docs/runtime/specs/13-cartridge.md +++ b/docs/runtime/specs/13-cartridge.md @@ -109,6 +109,13 @@ The JSON header carries: `asset_table` and `preload` are therefore no longer the primary runtime contract of `manifest.json`. +`preload` is structurally validated as part of `assets.pa` bootstrap: + +- each preload entry is `{ asset_id, slot }`; +- `asset_id` uses 32-bit signed integer semantics; +- each `preload.asset_id` must resolve to an entry in the same `asset_table`; +- no two preload entries may claim the same `(bank_type, slot)` pair once `bank_type` is resolved from `asset_table`. + Runtime lifecycle: - `asset_table` is loaded from `assets.pa` and remains live while the cartridge is running; @@ -119,6 +126,7 @@ Runtime loading policy: - cartridges without `assets.pa` remain valid when `Asset` capability is not declared; - cartridges that declare `Asset` capability must provide valid `assets.pa`; +- `assets.pa` with invalid preload references or preload slot clashes is structurally invalid; - failure must occur as early as cartridge bootstrap, before preload execution. ## 7 Runtime Forms diff --git a/docs/runtime/specs/15-asset-management.md b/docs/runtime/specs/15-asset-management.md index 3281d1af..85c69b5e 100644 --- a/docs/runtime/specs/15-asset-management.md +++ b/docs/runtime/specs/15-asset-management.md @@ -96,6 +96,8 @@ AssetEntry { This table describes content identity and storage layout, not live residency. +`asset_id` is the stable runtime-facing asset identity and uses 32-bit signed integer semantics compatible with Java `int`. + `offset` is relative to the start of the payload region inside `assets.pa`. ## 5 Banks and Slots @@ -180,13 +182,33 @@ These metrics support debugging, telemetry, and certification-oriented inspectio `preload` is stored in the JSON header of `assets.pa`. +The normative preload shape is: + +```text +PreloadEntry { + asset_id + slot +} +``` + These preload entries are consumed during cartridge initialization so the asset manager can establish initial residency before normal execution flow. +Validation rules: + +- `preload` is resolved by `asset_id`, not by `asset_name`; +- every `preload.asset_id` must exist in the same `asset_table`; +- no two preload entries may resolve to the same `(bank_type, slot)` pair; +- legacy preload keyed by `asset_name` is invalid for the current contract. + Lifecycle rule: - `preload` is boot-time input only; - it does not need to remain live after initialization completes. +Bootstrap rule: + +- invalid preload is a structural cartridge error and must fail cartridge bootstrap before normal execution begins. + ## 10 Relationship to Other Specs - [`13-cartridge.md`](13-cartridge.md) defines the cartridge package and the requirement that `assets.pa` carries its own asset header.