# Chargement et résolution des dépendances

Entrée publique `alloframe/loading`, également réexportée depuis `alloframe`.

Le chargeur sélectionne explicitement une composition d’un manifeste, lit ses
documents et les bibliothèques déclarées transitivement, normalise les déclarations puis construit les registres et liens exacts. Il ne crée aucune instance,
n’active aucun acteur et n’exécute aucun scénario.

## Utilisation

```js
import {
  createLoadContext, createMemorySourceReader, loadComposition,
  resolveLoadedReference,
} from 'alloframe/loading';

const root = 'https://example.test/demo/';
const context = createLoadContext({
  frameworkVersion: '1.0.0',
  readSource: createMemorySourceReader({
    [root + 'alloframe.manifest.json']: {
      format_version: '1.0.0',
      compositions: {main: {composition: 'composition.json'}},
    },
    [root + 'composition.json']: {
      format_version: '1.0.0', namespace: 'demo',
      composition: {
        id: 'composition_main', type: 'composition',
        data: {scenes: [{ref: 'scene_main'}]},
      },
      entities: [{id: 'scene_main', type: 'scene', data: {}}],
    },
  }),
});
const result = await loadComposition({
  context, manifest: root + 'alloframe.manifest.json', composition: 'main',
});
if (result.ok) {
  const scene = resolveLoadedReference(result, 'scene_main', {
    namespace: 'demo', expectedType: 'scene',
  });
  console.log(result.status, scene.value.identity);
}
```

`ready` signifie que les contrôles de chargement ont réussi. La validation du
contenu métier de `scene.data` reste différée à son contrat ; ce résultat ne
garantit donc pas encore qu’une représentation peut être exécutée.

## API

| Interface | Rôle |
|---|---|
| `createLoadContext({readSource, frameworkVersion?, allowMissingReference?})` | Nouveau cache et contexte isolé |
| `context.loadComposition({manifest, composition})` | Promesse d’un résultat immuable |
| `loadComposition({context, manifest, composition})` | Façade équivalente |
| `context.getSourceState(url)` | `unrequested`, `loading`, `loaded`, `failed` : lecture uniquement |
| `context.getLoadState({manifest, composition})` | `unrequested`, `loading`, `ready`, `degraded`, `blocked` |
| `createMemorySourceReader(objectOrMap)` | Lecteur mémoire avec copie initiale des sources |
| `resolveLoadedReference(result, ref, {namespace?, expectedType?})` | Résolution exacte dans un résultat disponible |

`readSource(url)` est fourni par l’application et retourne un objet JSON ou un
texte JSON, directement ou par promesse. Un échec de lecture se signale par une
exception ou un rejet. Le noyau ne dépend d’aucune API disque/réseau/DOM. Le lecteur
peut utiliser `fetch`, `readFile` ou une table mémoire ; la déclaration ne choisit
ni n’exécute de lecteur. Les redirections éventuelles du lecteur ne modifient pas
la base déclarative : celle-ci demeure l’URL demandée.

`manifest` doit être une chaîne URL absolue ; la composition nommée est obligatoire.
Les URL sont canonisées par `new URL`. Les URL mémoire suivent la même règle :
une URL comme `https://example.test/...` n’entraîne aucun accès réseau avec le
lecteur mémoire. Chaque localisation relative est résolue depuis le document qui
la déclare. Les déclarations normalisées conservent leurs chaînes d’origine ;
les URL résolues et provenances sont portées par leurs métadonnées.

`frameworkVersion`, si fourni, suit le format strict `MAJEURE.MINEURE.CORRECTIVE`
des déclarations. Il est obligatoire dès qu’un manifeste chargé déclare
`requires.framework`. Fournir explicitement la version du framework utilisée par l’application, sans la déduire du format JSON. Les suffixes de préversion ne sont pas acceptés dans les contraintes déclaratives.

Les erreurs de configuration de l’API lèvent `TypeError`. Les problèmes de
lecture, JSON, déclarations, dépendances et résolution produisent des diagnostics.

## Résultat et disponibilité

Toutes les sorties de chargement contiennent `ok`, `status`, `diagnostics`,
`graph`, `unavailable` et `deferred`. Seuls les succès possèdent `value` :

- `manifest`, `composition` : sélection explicite ;
- `documents` : documents normalisés avec déclaration, références, localisations et provenance ;
- `definitions` : `{identity, type, source, path, declaration}` ;
- `links` : `{reference, target}`, ou cible `null` et descripteur `unavailable` pour un repli autorisé.

Une définition conserve sa déclaration ; un lien pointe vers la définition
correspondante sans réécrire les références déclaratives. Aucune instance d’usage
n’est créée. Les collections sont des tableaux immuables, les index internes des
`Map` privés. Une définition n’est jamais publiée pendant un chargement incomplet.
Le résolveur public refuse un résultat indisponible.

| État | Sens |
|---|---|
| `ready` / `ok:true` | Résolution terminée ; des avertissements et contrôles métier différés peuvent subsister |
| `degraded` / `ok:true` | Au moins une référence absente possède un repli explicitement autorisé |
| `blocked` / `ok:false` | Au moins un problème bloquant ; aucun `value` ni registre partiel publié |

Le graphe décrit les dépendances documentaires, avec des nœuds `source`, `kind`,
`state` (`resolved` ou `blocked`) et des arêtes `from`, `to`, `path`, `kind`,
éventuellement `constraint`. `resolved` est un état documentaire de ce graphe,
pas une disponibilité publique autonome ni une certification métier. Un résultat
global bloqué peut donc montrer des branches documentaires résolues sans les publier.

## Sources partagées, déterminisme et reprise

Une seule lecture est effectuée par URL absolue canonique et par contexte,
y compris entre appels simultanés de compositions différentes. Le couple
manifeste/composition mutualise aussi la promesse complète et son résultat.
Les lectures d’une vague sont parallèles ; l’intégration suit l’ordre déclaratif
et l’ordre de découverte, indépendamment de l’ordre de fin des lectures.

Chaque composition dispose d’un registre de son ensemble chargé. Deux sélections
ne s’alimentent pas implicitement et ne se contaminent pas : une définition
présente uniquement dans une autre sélection ne résout pas une référence locale.
Deux définitions homonymes dans des sélections distinctes ne collisionnent pas ;
deux sources dans le même ensemble chargé, oui. Cette séparation prépare des
usages indépendants tout en partageant les lectures.

Les échecs sont mémorisés. Refaire une demande, même après correction de la source,
ne relance pas sa lecture dans ce contexte. **La reprise exige un nouveau contexte.**
Il n’existe ni `retry`, ni invalidation de cache, ni rechargement à chaud dans cette API.
Un contexte devenu inutile est libéré en cessant de le référencer ; il ne possède
aucune instance ou ressource d’exécution à détruire.

## Règles de résolution

- Seuls les fichiers de la composition choisie sont chargés, avec les bibliothèques
  explicitement déclarées par le manifeste et les documents de cet ensemble.
- Le manifeste complet est normalisé ; ses entrées non sélectionnées doivent donc
  rester structurellement valides, mais leurs fichiers ne sont pas lus.
- Contributions et bibliothèques transitives sont cumulées, répétitions d’URL
  mutualisées. Pas de fusion d’entités ni de priorité entre sources.
- Identité exacte `namespace:id`, toutes catégories confondues ; aucune recherche
  dans les namespaces parents, aucun namespace déduit du chemin.
- Toutes les contraintes d’une bibliothèque partagée doivent être satisfaites.
  Une version absente n’est acceptable qu’en l’absence de contrainte.
- Une URL demandée dans deux catégories de documents incompatibles bloque.
- Les cycles de chargement sont fatals, y compris via les contributions ; les
  diagnostics contiennent le cycle complet et la provenance de chaque arête.
- Une dépendance échouée bloque ses demandeurs, avec chaque lien causal direct
  dans les diagnostics et le graphe. Pas d’installation ou de recherche de version.
- Les cibles ne sont déclarées absentes qu’après la découverte complète réussie.
  Une source non lue ou défaillante ne devient pas artificiellement une liste
  de références introuvables.
- Les ressources déclarées conservent leur URL résolue, sans lecture du média.
  Les champs métier opaques ne sont pas inspectés à la recherche de fausses dépendances.

Les références actuellement reconnues sont celles décrites par les métadonnées
du normaliseur déclaratif : scènes d’une composition, acteurs, scénarios, directeur et
chorégraphies d’une mise en scène. Les dépendances documentaires et les références
d’entités sont deux structures distinctes ; une référence n’est pas un chemin.
Les payloads métier opaques nécessitent leurs contrats selon leur type.

## Repli contractuel

Par défaut, une référence absente bloque. Le code d’intégration d’un contrat peut
fournir `allowMissingReference(reference)` : seul un retour synchrone exactement
égal à `true` autorise un descripteur d’indisponibilité pour cette référence.
Ce crochet est une décision du contrat appelant, jamais une permission tirée du
JSON. L’application doit le limiter aux usages où son contrat autorise un acte
manqué ; aucun type n’en bénéficie par défaut.

Le descripteur conserve référence, namespace, type attendu, source, chemin et
code de cause. Il n’invente ni définition ni rendu de remplacement. Il ne permet
pas de récupérer une collision, un mauvais type, un cycle, un manifeste invalide,
une lecture échouée ou une incompatibilité de version. Une exception du contrat
est diagnostiquée et bloque. Un avertissement seul ne provoque jamais `degraded`.

## Diagnostics et vérification

Codes ajoutés : `E_SOURCE_READ`, `E_SOURCE_LOCATION`, `E_COMPOSITION_NOT_FOUND`,
`E_DEPENDENCY_CYCLE`, `E_DEPENDENCY_FAILED`, `E_REFERENCE_POLICY`,
`E_FRAMEWORK_VERSION_REQUIRED`. Les codes de collision, référence, catégorie,
version et cible existants sont réutilisés. Les diagnostics conservent leur phase
de normalisation ou de chargement et leurs champs communs.

`npm run test:loading` vérifie lecteurs injectés, graphes, références, versions,
cycles, déterminisme, cache et repli. Les résultats de chargement ne remplacent
pas les contrôles du cycle de vie, des acteurs ou du vérificateur.

## Chargement d’un document isolé

`context.loadDocument({source, kind})` charge un document non-project (`library`,
`entities`, `composition`, `decor`, `staging`, `scenario`, `actor`, `director`,
`choreography`) et ses dépendances déclarées avec le même cache, graphe et règles.
`source` est une URL absolue ; catégorie invalide : `TypeError`. Le résultat
conserve le format du chargeur ; `value.composition` vaut `null`. Cette extension
sert à inspecter les bibliothèques seules. Un fichier exigeant les autres éléments
d’un projet doit être vérifié avec son manifeste de contexte.
