asset preload alignment

This commit is contained in:
bQUARKz 2026-03-11 08:27:37 +00:00
parent d7c970a3e2
commit d576f7ddbd
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
11 changed files with 367 additions and 22 deletions

View File

@ -2,7 +2,7 @@
use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller};
use prometeu_hal::AssetBridge; use prometeu_hal::AssetBridge;
use prometeu_hal::asset::{ use prometeu_hal::asset::{
AssetEntry, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus,
PreloadEntry, SlotRef, SlotStats, PreloadEntry, SlotRef, SlotStats,
}; };
use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::cartridge::AssetsPayloadSource;
@ -48,7 +48,7 @@ impl<T> ResidentEntry<T> {
/// This is internal to the AssetManager and not visible to peripherals. /// This is internal to the AssetManager and not visible to peripherals.
pub struct BankPolicy<T> { pub struct BankPolicy<T> {
/// Dedup table: asset_id -> resident entry (value + telemetry). /// Dedup table: asset_id -> resident entry (value + telemetry).
resident: Arc<RwLock<HashMap<u32, ResidentEntry<T>>>>, resident: Arc<RwLock<HashMap<AssetId, ResidentEntry<T>>>>,
/// Staging area: handle -> value ready to commit. /// Staging area: handle -> value ready to commit.
staging: Arc<RwLock<HashMap<HandleId, Arc<T>>>>, staging: Arc<RwLock<HashMap<HandleId, Arc<T>>>>,
@ -63,7 +63,7 @@ impl<T> BankPolicy<T> {
} }
/// Try get a resident value by asset_id (dedupe path). /// Try get a resident value by asset_id (dedupe path).
pub fn get_resident(&self, asset_id: u32) -> Option<Arc<T>> { pub fn get_resident(&self, asset_id: AssetId) -> Option<Arc<T>> {
let mut map = self.resident.write().unwrap(); let mut map = self.resident.write().unwrap();
let entry = map.get_mut(&asset_id)?; let entry = map.get_mut(&asset_id)?;
entry.last_used = Instant::now(); entry.last_used = Instant::now();
@ -71,7 +71,7 @@ impl<T> BankPolicy<T> {
} }
/// Insert or reuse a resident entry. Returns the resident Arc<T>. /// Insert or reuse a resident entry. Returns the resident Arc<T>.
pub fn put_resident(&self, asset_id: u32, value: Arc<T>, bytes: usize) -> Arc<T> { pub fn put_resident(&self, asset_id: AssetId, value: Arc<T>, bytes: usize) -> Arc<T> {
let mut map = self.resident.write().unwrap(); let mut map = self.resident.write().unwrap();
match map.get_mut(&asset_id) { match map.get_mut(&asset_id) {
Some(existing) => { Some(existing) => {
@ -104,8 +104,8 @@ impl<T> BankPolicy<T> {
} }
pub struct AssetManager { pub struct AssetManager {
assets: Arc<RwLock<HashMap<u32, AssetEntry>>>, assets: Arc<RwLock<HashMap<AssetId, AssetEntry>>>,
name_to_id: Arc<RwLock<HashMap<String, u32>>>, name_to_id: Arc<RwLock<HashMap<String, AssetId>>>,
handles: Arc<RwLock<HashMap<HandleId, LoadHandleInfo>>>, handles: Arc<RwLock<HashMap<HandleId, LoadHandleInfo>>>,
next_handle_id: Mutex<HandleId>, next_handle_id: Mutex<HandleId>,
assets_data: Arc<RwLock<AssetsPayloadSource>>, assets_data: Arc<RwLock<AssetsPayloadSource>>,
@ -115,8 +115,8 @@ pub struct AssetManager {
sound_installer: Arc<dyn SoundBankPoolInstaller>, sound_installer: Arc<dyn SoundBankPoolInstaller>,
/// Track what is installed in each hardware slot (for stats/info). /// Track what is installed in each hardware slot (for stats/info).
gfx_slots: Arc<RwLock<[Option<u32>; 16]>>, gfx_slots: Arc<RwLock<[Option<AssetId>; 16]>>,
sound_slots: Arc<RwLock<[Option<u32>; 16]>>, sound_slots: Arc<RwLock<[Option<AssetId>; 16]>>,
/// Residency policy for GFX tile banks. /// Residency policy for GFX tile banks.
gfx_policy: BankPolicy<TileBank>, gfx_policy: BankPolicy<TileBank>,
@ -128,7 +128,7 @@ pub struct AssetManager {
} }
struct LoadHandleInfo { struct LoadHandleInfo {
_asset_id: u32, _asset_id: AssetId,
slot: SlotRef, slot: SlotRef,
status: LoadStatus, status: LoadStatus,
} }
@ -238,8 +238,7 @@ impl AssetManager {
for item in preload { for item in preload {
let entry_opt = { let entry_opt = {
let assets = self.assets.read().unwrap(); let assets = self.assets.read().unwrap();
let name_to_id = self.name_to_id.read().unwrap(); assets.get(&item.asset_id).cloned()
name_to_id.get(&item.asset_name).and_then(|id| assets.get(id)).cloned()
}; };
if let Some(entry) = entry_opt { if let Some(entry) = entry_opt {
@ -302,10 +301,7 @@ impl AssetManager {
} }
} }
// else { // else {
// eprintln!( // eprintln!("[AssetManager] Preload failed: asset id '{}' not found in table", item.asset_id);
// "[AssetManager] Preload failed: asset '{}' not found in table",
// item.asset_name
// );
// } // }
} }
} }
@ -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( let am = AssetManager::new(
vec![], vec![],
@ -1076,7 +1072,7 @@ mod tests {
let mut asset_entry = test_tile_asset_entry("my_tiles", data.len()); let mut asset_entry = test_tile_asset_entry("my_tiles", data.len());
asset_entry.asset_id = 10; 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( let am = AssetManager::new(
vec![], vec![],

View File

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub type HandleId = u32; pub type HandleId = u32;
pub type AssetId = i32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@ -13,7 +14,7 @@ pub enum BankType {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AssetEntry { pub struct AssetEntry {
pub asset_id: u32, pub asset_id: AssetId,
pub asset_name: String, pub asset_name: String,
pub bank_type: BankType, pub bank_type: BankType,
pub offset: u64, pub offset: u64,
@ -25,7 +26,7 @@ pub struct AssetEntry {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PreloadEntry { pub struct PreloadEntry {
pub asset_name: String, pub asset_id: AssetId,
pub slot: usize, pub slot: usize,
} }
@ -70,7 +71,7 @@ pub struct BankStats {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotStats { pub struct SlotStats {
pub asset_id: Option<u32>, pub asset_id: Option<AssetId>,
pub asset_name: Option<String>, pub asset_name: Option<String>,
pub generation: u32, pub generation: u32,
pub resident_bytes: usize, pub resident_bytes: usize,

View File

@ -1,3 +1,4 @@
use crate::asset::{AssetEntry, BankType};
use crate::cartridge::{ use crate::cartridge::{
ASSETS_PA_MAGIC, ASSETS_PA_PRELUDE_SIZE, ASSETS_PA_SCHEMA_VERSION, AssetsPackHeader, ASSETS_PA_MAGIC, ASSETS_PA_PRELUDE_SIZE, ASSETS_PA_SCHEMA_VERSION, AssetsPackHeader,
AssetsPackPrelude, AssetsPayloadSource, Capability, Cartridge, CartridgeDTO, CartridgeError, AssetsPackPrelude, AssetsPayloadSource, Capability, Cartridge, CartridgeDTO, CartridgeError,
@ -168,11 +169,29 @@ fn parse_assets_pack(path: &Path) -> Result<ParsedAssetsPack, CartridgeError> {
file.read_exact(&mut header_bytes).map_err(|_| CartridgeError::InvalidFormat)?; file.read_exact(&mut header_bytes).map_err(|_| CartridgeError::InvalidFormat)?;
let header: AssetsPackHeader = let header: AssetsPackHeader =
serde_json::from_slice(&header_bytes).map_err(|_| CartridgeError::InvalidFormat)?; 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)?; let payload_len = u64::try_from(file_len - payload_offset).map_err(|_| CartridgeError::InvalidFormat)?;
Ok(ParsedAssetsPack { header, payload_offset: prelude.payload_offset, payload_len }) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -380,7 +399,7 @@ mod tests {
let payload = vec![1_u8, 2, 3, 4]; let payload = vec![1_u8, 2, 3, 4];
dir.write_assets_pa( dir.write_assets_pa(
vec![test_asset_entry(0, payload.len() as u64)], 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, &payload,
); );
@ -396,9 +415,52 @@ mod tests {
assert_eq!(cartridge.asset_table.len(), 1); assert_eq!(cartridge.asset_table.len(), 1);
assert_eq!(cartridge.asset_table[0].asset_name, "tiles"); assert_eq!(cartridge.asset_table[0].asset_name, "tiles");
assert_eq!(cartridge.preload.len(), 1); assert_eq!(cartridge.preload.len(), 1);
assert_eq!(cartridge.preload[0].asset_id, 7);
assert_eq!(cartridge.preload[0].slot, 2); 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] #[test]
fn load_rejects_invalid_assets_pa_prelude() { fn load_rejects_invalid_assets_pa_prelude() {
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"]))); let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["asset"])));

View File

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

View File

@ -24,7 +24,6 @@ As agendas atuais são:
- `020-perf-host-debug-overlay-isolation.md` - `020-perf-host-debug-overlay-isolation.md`
- `021-perf-vm-allocation-and-copy-pressure.md` - `021-perf-vm-allocation-and-copy-pressure.md`
- `022-perf-cartridge-boot-and-program-ownership.md` - `022-perf-cartridge-boot-and-program-ownership.md`
## Sequenciamento Recomendado [PERF] ## Sequenciamento Recomendado [PERF]
Ordem sugerida para discussao das agendas de performance: Ordem sugerida para discussao das agendas de performance:

View File

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

View File

@ -20,6 +20,7 @@ Decisoes ativas:
- `006-vm-owned-stateful-core-contract.md` - `006-vm-owned-stateful-core-contract.md`
- `007-filesystem-fault-core-policy.md` - `007-filesystem-fault-core-policy.md`
- `011-assets-pa-autocontained-runtime-contract.md` - `011-assets-pa-autocontained-runtime-contract.md`
- `012-assets-preload-asset-id-contract.md`
Decisoes implementadas e aposentadas (migradas para `learn/`): Decisoes implementadas e aposentadas (migradas para `learn/`):

View File

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

View File

@ -38,3 +38,4 @@ Uma PR deste diretório deve:
PRs propostas para execucao da rodada atual: PRs propostas para execucao da rodada atual:
- `012-assets-preload-asset-id-propagation.md`

View File

@ -109,6 +109,13 @@ The JSON header carries:
`asset_table` and `preload` are therefore no longer the primary runtime contract of `manifest.json`. `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: Runtime lifecycle:
- `asset_table` is loaded from `assets.pa` and remains live while the cartridge is running; - `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 without `assets.pa` remain valid when `Asset` capability is not declared;
- cartridges that declare `Asset` capability must provide valid `assets.pa`; - 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. - failure must occur as early as cartridge bootstrap, before preload execution.
## 7 Runtime Forms ## 7 Runtime Forms

View File

@ -96,6 +96,8 @@ AssetEntry {
This table describes content identity and storage layout, not live residency. 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`. `offset` is relative to the start of the payload region inside `assets.pa`.
## 5 Banks and Slots ## 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`. `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. 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: Lifecycle rule:
- `preload` is boot-time input only; - `preload` is boot-time input only;
- it does not need to remain live after initialization completes. - 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 ## 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. - [`13-cartridge.md`](13-cartridge.md) defines the cartridge package and the requirement that `assets.pa` carries its own asset header.