# Instances et cycle de vie

Entrée `alloframe/instances`, réexportée depuis `alloframe`.

## Utilisation

Après `loadComposition`, l'application choisit les fabriques de code et crée
explicitement chaque instance. Les déclarations ne chargent ni n'exécutent du code.

```js
import {createInstanceContext, createInstance} from 'alloframe/instances';

// loaded : résultat disponible du chargeur ; demo:scene_main : définition chargée.
const context = createInstanceContext({
  loaded,
  factories: {
    scene: ({id, definition}) => ({
      init(ctx) {
        ctx.data.title = definition.declaration.data.title ?? id;
        // ctx.own(() => releasePreparedResource(), 'prepared-resource');
      },
      activate(ctx) {
        const timer = setInterval(ctx.guard(() => console.log(id)), 1000);
        ctx.own(() => clearInterval(timer), 'timer');
      },
    }),
  },
});
const created = createInstance({context, id: 'scene_left',
  reference: 'demo:scene_main', initialState: {title: ''}});
if (created.ok) {
  const instance = created.value;
  if ((await instance.init()).ok && (await instance.activate()).ok) {
    console.log(instance.inspect());
    await instance.stop();
  }
  await instance.destroy();
}
```

Pour les acteurs intégrés, utiliser [createActorContext](../actors/builtins.md).
Pour assembler et coordonner les instances, voir [composition.md](composition.md).

## Interfaces et identité

| Interface | Contrat |
|---|---|
| `createInstanceContext({loaded, factories?, timeouts?})` | Registre indépendant, résultat de chargement ready/degraded requis |
| `context.createInstance({id, reference, namespace?, expectedType?, initialState?, connections?})` | Résultat synchrone `{ok, value?, diagnostics}` |
| `createInstance({context, ...options})` | Façade équivalente |
| `context.getInstance(id)` | Instance enregistrée ou undefined |
| `context.listInstances()` | Tableau figé des instances encore enregistrées |
| `instance.id`, `instance.definition`, `instance.status` | Identité d'usage, définition immuable et état observables |
| `instance.init/activate/stop/destroy()` | Promesse `{ok, status, value: snapshot, diagnostics}` |
| `instance.inspect()` | Instantané figé : état, opération, données, connexions, ressources, diagnostics cumulés |

L'identité d'exécution est le couple **contexte + id**. Elle est distincte de
`namespace:id`, identité de la définition. Plusieurs instances peuvent partager
exactement la même définition. Les résultats immuables du chargeur restent partagés par
référence, même entre contextes. Un résultat fourni mutable est copié et figé.
Les données locales sont copiées ; l'arrêt/destruction d'une instance ne modifie
ni sa source ni les autres instances.

Aucun remplacement implicite. Un ID déjà attribué reste réservé dans son contexte,
même après destruction ; créer une nouvelle instance exige un nouvel ID ou un
nouveau contexte. Le registre vivant retire l'instance après destruction ; une
ancienne référence conserve son état terminal et ses diagnostics. Ce choix évite
qu'une référence tardive désigne un nouvel objet sous le même ID.

Les fabriques sont indexées par catégorie de définition (`scene`, `actor`, etc.).
Une fabrique peut examiner la déclaration pour choisir une implémentation précise.
Elle reçoit `{id, definition}` et retourne synchroniquement les fonctions
optionnelles `init`, `activate`, `stop`, `destroy`. Elle crée ses propres closures,
sans acquisition de ressources ni démarrage avant les hooks. Les hooks sont
appelés avec leur contexte explicite ; ne pas utiliser `this` comme état d'instance.
Une catégorie sans fabrique est diagnostiquée ; aucune implémentation fictive n'est
installée. Une erreur de fabrique ne publie pas d'instance partielle.

Les paramètres API invalides lèvent `TypeError`. Référence introuvable, collision,
transition, connexion et échec de hook produisent des résultats diagnostiqués.

## États et transitions

| Opération | États de départ | État obtenu |
|---|---|---|
| init | created | initializing puis ready |
| activate | ready, stopped | active après achèvement |
| stop | ready, active | stopping puis stopped |
| destroy | created, ready, active, stopped, failed | destroyed après nettoyage réussi |

`init` en ready/active/stopped, `activate` en active, `stop` en stopped et `destroy`
en destroyed sont des succès sans répétition des hooks. Réactiver une instance
arrêtée n'appelle pas `init`. Un arrêt depuis ready n'appelle pas le hook stop.
Une destruction active appelle stop, puis destroy, puis les nettoyeurs.
La destruction depuis created appelle destroy : ce hook doit accepter une
préparation absente ou partielle.

Une seule opération par instance. Deux appels identiques en cours retournent
**la même promesse** ; tout appel incompatible est refusé, y compris destroy.
Il faut attendre la fin ou le délai maximal avant de détruire. Pas de file
implicite, d'activation automatique, ni de relance automatique.
Pendant activate et destroy, l'état stable précédent reste visible ; le champ
`operation` signale l'opération en cours. Les résultats ne sont publiés qu'une
fois cette opération terminée.

L'échec d'init/activate/stop conduit à failed ; seule destroy est alors permise.
Le nettoyage partiel est tenté, sans dissimuler les ressources non libérées.
Une erreur du hook stop pendant destroy est conservée ; si destroy et tous les
nettoyeurs réussissent, l'état est destroyed mais le résultat reste `ok:false`
pour rendre visible cette erreur. Un échec de destroy ou une ressource restante
laisse failed : destroy peut être réessayé explicitement. Les nettoyeurs déjà
réussis et le hook destroy déjà réussi ne sont pas répétés.

## Connexions explicites

```js
const created = context.createInstance({
  id: 'consumer', reference: 'demo:scene_main',
  connections: {view: {id: 'scene_left', type: 'scene'}},
});
```

Les connexions désignent des **IDs d'instances dans le même contexte**, jamais des
identités de définitions ou un contexte voisin. À init, chaque cible doit exister,
être ready/active/stopped, sans opération en cours ; le type est vérifié s'il est
fourni. Une connexion vers soi-même est refusée. Initialiser les cibles avant les
consommateurs ; un cycle de connexions ne bénéficie d'aucune initialisation implicite.

Les hooks consultent `ctx.connections.view`, façade publique de la cible.
La connexion est figée à init et revérifiée à chaque activate. Une cible détruite
ne peut pas être remplacée silencieusement. Détruire un consommateur ne détruit
pas ses cibles. Aucun suivi continu ni propagation automatique d'arrêt n'est
ajouté : l'orchestrateur doit organiser les dépendances des instances actives.

## État local, asynchronisme et ressources

Les hooks reçoivent `{id, definition, signal, data, connections, own, guard}`.
`initialState` (objet vide par défaut) et `data` contiennent des valeurs JSON
acycliques. Les handles, abonnements, éléments DOM, fonctions ou collections
Map/Set restent dans les closures de la fabrique, pas dans les instantanés JSON.

Chaque opération reçoit une copie de l'état local. Le noyau publie une nouvelle
copie uniquement après succès. Une écriture tardive dans le brouillon d'un hook
ne modifie donc pas l'état publié. Les callbacks d'activité ne constituent pas
une API de commandes : les commandes publiques sont exposées par la façade de l’acteur.

- `ctx.own(dispose, label?)` inscrit un nettoyeur, même asynchrone. Les hooks init
  possèdent des ressources de préparation ; activate possède celles d'activité.
- `ctx.guard(callback)` rend un callback inerte après révocation du signal de son
  cycle. Enregistrer aussi le retrait de l'abonnement ou du timer avec own.
- stop révoque l'activité et libère ses ressources, en ordre inverse d'acquisition.
- destroy révoque aussi la préparation et tente tous les nettoyeurs, même après
  une erreur. Une ressource qui échoue reste visible et réessayable.
- Une acquisition tardive via un contexte révoqué est immédiatement proposée à
  son nettoyeur ; elle n'est pas transférée à une nouvelle activation.
- Un nettoyeur encore en cours n'est jamais lancé deux fois. Son succès tardif
  retire la ressource ; un rejet est observé, sans rejet de promesse non géré.

Délais par défaut : init **30 s**, activate/stop/destroy **10 s**, configurables
par `timeouts` (millisecondes strictement positives). Le délai destroy s'applique
également à chaque nettoyeur. Le temps total de nettoyage dépend donc du nombre
de ressources ; ce n'est pas un plafond global pour toute la destruction.

Un timeout révoque le signal et neutralise la publication du résultat tardif.
JavaScript ne peut pas interrompre de force du code tiers synchrone bloquant ni
annuler une ressource non déclarée. Les fabriques doivent respecter AbortSignal,
utiliser guard, enregistrer les ressources dès acquisition et assurer des
nettoyeurs fiables. Un nettoyage tardif défaillant reste dans les diagnostics et
la liste des ressources ; il ne ressuscite jamais une instance destroyed. Le
noyau ne prétend pas garantir la libération d'une ressource externe récalcitrante.

## Diagnostics et vérification

`E_INSTANCE_COLLISION` désigne une collision. Autres codes :
`E_INSTANCE_TRANSITION`, `E_INSTANCE_BUSY`, `E_INSTANCE_FACTORY`,
`E_INSTANCE_CONNECTION`, `E_INSTANCE_OPERATION`, `E_INSTANCE_TIMEOUT`,
`E_INSTANCE_CLEANUP`. Les diagnostics suivent le contrat des diagnostics : code distinct
de la sévérité/impact, phase, origine, message et contexte. Ils conservent
l'instance, la définition, l'opération ou ressource concernée et la cause textuelle.
Les résultats contiennent les diagnostics de l'appel ; inspect conserve l'historique.

Les tests couvrent partage, isolation, transitions, concurrence, délais, nettoyeurs
et libération des ressources : `npm run test:instances`.
