0fbca06d3d
Cartographie complète (liste_taches/coherence_taches), briefs tacheN + gates validation_tacheN, design tâche 2 (docs/design/tache2/), specs/plans jalon 1-2 et tâche 1.9/2 (Phase 1, Phase 2, SJ-0→3). Validations consignées (1.9 ✅, 2-8 🟡). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
312 lines
12 KiB
Markdown
312 lines
12 KiB
Markdown
# 40 — Contrats JSON canoniques étendus + types TypeScript
|
|
|
|
> Axe B + livrable §4.3. Tranche la question §3.5 (extensions de `shared/types.ts`). **Tous les ajouts sont rétro-compatibles** : champs optionnels, unions élargies. Un `UpdateSnapshot`/`ExecutionResult` du jalon 1 reste strictement valide.
|
|
|
|
---
|
|
|
|
## 1. Principe de rétro-compatibilité
|
|
|
|
État actuel (`shared/types.ts`) :
|
|
|
|
```ts
|
|
export type OsFamily = "debian" | "ubuntu" | "unknown";
|
|
export type AptProxyMode = "direct" | "runtime";
|
|
export type ActionType = "apt_full_upgrade" | "reboot";
|
|
// UpdateSnapshot.apt: { enabled, count, rebootRequired, packages: AptPackage[] }
|
|
// ExecutionResult: { ... action: ActionType, status, rebootRequiredAfterRun, importantLogLines, rawLogRef, reportRef }
|
|
```
|
|
|
|
Règles d'extension :
|
|
1. **Élargir les unions** (`OsFamily`, `AptProxyMode`, `ActionType`) — additif, aucun retrait.
|
|
2. **Ajouter des blocs optionnels** (`docker?`, `errors?`, `apt` détaillé optionnel, `reboot?`, `postInstall?`) — un payload sans ces blocs reste valide.
|
|
3. **Ne jamais retirer ni renommer** un champ existant. Le jalon 1 émet `apt: { enabled, count, rebootRequired, packages }` ; on **ajoute** des champs optionnels à côté.
|
|
4. Versionner via `schemaVersion?: number` (aligné `snapshots.schema_version` / `executions.schema_version` de `tache1.9.md`). Absence ⇒ version 1.
|
|
|
|
---
|
|
|
|
## 2. Extensions des unions
|
|
|
|
```ts
|
|
// Élargissement additif. Le jalon 1 ("debian"|"ubuntu"|"unknown") reste valide.
|
|
export type OsFamily = "debian" | "ubuntu" | "proxmox" | "raspbian" | "unknown";
|
|
|
|
export type MachineKind =
|
|
| "physical" | "vm" | "proxmox_host" | "lxc"
|
|
| "raspberry_pi" | "workstation" | "unknown";
|
|
|
|
// "persistent" ajouté (écriture dans /etc/apt/apt.conf.d/).
|
|
export type AptProxyMode = "direct" | "runtime" | "persistent";
|
|
|
|
export type ActionType =
|
|
// jalon 1 (conservés tels quels)
|
|
| "apt_full_upgrade" | "reboot"
|
|
// APT
|
|
| "apt_update_analyze" | "apt_upgrade" | "apt_dist_upgrade"
|
|
| "apt_autoremove" | "apt_clean" | "reboot_verified"
|
|
// Docker
|
|
| "docker_scan" | "docker_inspect_current" | "docker_pull_check"
|
|
| "docker_compose_apply" | "docker_prune_images" | "docker_compose_down"
|
|
// probe + custom
|
|
| "machine_probe" | "post_install";
|
|
|
|
export type SnapshotStatus = "ok" | "updates_available" | "warning" | "error";
|
|
export type ExecutionStatus = "ok" | "warning" | "error"; // inchangé
|
|
```
|
|
|
|
> `reboot` (jalon 1) et `reboot_verified` coexistent : `reboot_verified` ajoute la vérification boot_id ; le code jalon 1 continue d'émettre `reboot`.
|
|
|
|
---
|
|
|
|
## 3. Snapshot canonique étendu (`UpdateSnapshot`)
|
|
|
|
```ts
|
|
export interface AptPackage {
|
|
name: string;
|
|
currentVersion: string | null;
|
|
targetVersion: string;
|
|
origin: string | null;
|
|
// Ajouts optionnels (rétro-compatibles) :
|
|
arch?: string;
|
|
operation?: "upgrade" | "install" | "remove" | "hold";
|
|
severityHint?: "normal" | "security";
|
|
}
|
|
|
|
export interface AptSnapshotDetail {
|
|
enabled: boolean;
|
|
count: number;
|
|
rebootRequired: boolean;
|
|
packages: AptPackage[];
|
|
// Ajouts optionnels :
|
|
status?: SnapshotStatus; // ok | updates_available | warning | error
|
|
upgradeCount?: number; // simulation `upgrade`
|
|
distUpgradeCount?: number; // simulation `dist-upgrade`
|
|
installed?: AptPackage[]; // nouveaux paquets (dist-upgrade)
|
|
removed?: AptPackage[]; // suppressions prévues (=> status warning)
|
|
held?: string[]; // paquets retenus (=> status warning)
|
|
rebootPkgs?: string[]; // depuis reboot-required.pkgs
|
|
}
|
|
|
|
export interface DockerSnapshotService {
|
|
serviceName: string;
|
|
image: string; // image ref (ex. jellyfin/jellyfin:latest)
|
|
currentImageId?: string | null;
|
|
currentDigest?: string | null;
|
|
candidateImageId?: string | null; // après pull-check
|
|
candidateDigest?: string | null;
|
|
currentVersion?: string | null; // label OCI org.opencontainers.image.version
|
|
candidateVersion?: string | null;
|
|
sourceUrl?: string | null; // label OCI source
|
|
status?: "up_to_date" | "updates_available" | "warning" | "error";
|
|
}
|
|
|
|
export interface DockerSnapshotStack {
|
|
name: string;
|
|
workingDir: string;
|
|
composeFiles: string[];
|
|
projectName?: string | null;
|
|
status: "candidate" | "enabled" | "ignored" | "error";
|
|
detectedBy?: "root_scan" | "label" | "manual";
|
|
services: DockerSnapshotService[];
|
|
}
|
|
|
|
export interface DockerSnapshot {
|
|
enabled: boolean;
|
|
installed: boolean;
|
|
count: number; // services avec update dispo
|
|
declaredRoots?: string[];
|
|
stacks: DockerSnapshotStack[];
|
|
status?: SnapshotStatus;
|
|
}
|
|
|
|
export interface SnapshotError {
|
|
source: "apt" | "docker" | "post_install" | "ssh" | "system";
|
|
kind: string; // voir 50-erreurs.md (taxonomie)
|
|
severity: "info" | "warning" | "error";
|
|
message: string; // nettoyé, jamais de secret
|
|
remediation?: string;
|
|
importantLines?: string[];
|
|
}
|
|
|
|
export interface UpdateSnapshot {
|
|
machineId: string;
|
|
hostname: string;
|
|
os: { family: OsFamily; version: string };
|
|
checkedAt: string; // ISO 8601
|
|
status: MachineStatus;
|
|
apt: AptSnapshotDetail; // bloc jalon 1 conservé, champs additifs optionnels
|
|
// Ajouts optionnels (rétro-compatibles) :
|
|
schemaVersion?: number;
|
|
kind?: "apt_update_analyze" | "docker_scan" | "reboot_check" | "combined";
|
|
machineKind?: MachineKind;
|
|
docker?: DockerSnapshot;
|
|
errors?: SnapshotError[];
|
|
rawHints?: { logImportantLines: string[] };
|
|
}
|
|
```
|
|
|
|
> Le bloc `apt` reste **requis** (présent au jalon 1) ; seuls ses champs *additifs* sont optionnels. `docker`, `errors`, `machineKind`, `kind`, `schemaVersion` sont **optionnels** → un snapshot jalon 1 (sans eux) reste valide.
|
|
|
|
### Bloc Docker minimal exigé par la validation (couverture §6)
|
|
|
|
Le bloc snapshot Docker contient au minimum : stacks déclarés (`declaredRoots`), stacks candidats (`stacks[].status="candidate"`), services (`stacks[].services[]`), image ref actuelle (`image`), image ID actuelle (`currentImageId`), digest actuel si dispo (`currentDigest`), labels de version si dispo (`currentVersion`), image ID/digest candidat après pull (`candidateImageId`/`candidateDigest`), statut `up_to_date|updates_available|warning|error`. ✔
|
|
|
|
---
|
|
|
|
## 4. Résultat d'exécution étendu (`ExecutionResult`)
|
|
|
|
```ts
|
|
export interface AptChange {
|
|
name: string;
|
|
arch?: string;
|
|
fromVersion: string | null;
|
|
toVersion: string | null;
|
|
operation: "upgraded" | "installed" | "removed" | "unchanged";
|
|
origin?: string | null;
|
|
}
|
|
|
|
export interface AptExecutionResult {
|
|
planned: AptPackage[]; // ce qui était prévu (simulation pré-action)
|
|
applied: AptChange[]; // diff dpkg réel before/after
|
|
installed: AptChange[];
|
|
removed: AptChange[];
|
|
held: string[];
|
|
errors?: SnapshotError[];
|
|
rebootRequiredAfterRun: boolean;
|
|
}
|
|
|
|
export interface DockerImageChange {
|
|
stack: string;
|
|
serviceName?: string;
|
|
imageRef?: string;
|
|
fromImageId?: string | null;
|
|
toImageId?: string | null;
|
|
fromDigest?: string | null;
|
|
toDigest?: string | null;
|
|
operation: "pulled" | "recreated" | "pruned";
|
|
}
|
|
|
|
export interface DockerExecutionResult {
|
|
pull?: { changes: DockerImageChange[]; errors?: SnapshotError[] };
|
|
up?: { recreated: string[]; running: string[]; exited: string[]; errors?: SnapshotError[] };
|
|
prune?: { imagesDeleted: string[]; bytesReclaimed: number; errors?: SnapshotError[] };
|
|
errors?: SnapshotError[];
|
|
}
|
|
|
|
export interface RebootResult {
|
|
beforeBootId: string | null;
|
|
afterBootId: string | null;
|
|
requestedAt: string;
|
|
sshWentDownAt: string | null;
|
|
sshCameBackAt: string | null;
|
|
waitedSeconds: number;
|
|
status: "ok" | "reboot_command_failed" | "ssh_never_went_down"
|
|
| "machine_did_not_return" | "boot_id_unchanged" | "timeout";
|
|
lastRebootDurationSeconds?: number;
|
|
nextRecommendedWaitSeconds?: number;
|
|
errors?: SnapshotError[];
|
|
}
|
|
|
|
export interface PostInstallResult {
|
|
profilesRun: string[];
|
|
variablesUsed: Record<string, string | number | boolean>; // NON sensible uniquement
|
|
filesModified: string[];
|
|
packagesInstalled: string[];
|
|
servicesEnabled: string[];
|
|
rebootsRequested: boolean;
|
|
networkChange?: {
|
|
oldEndpoint: string | null;
|
|
newEndpoint: string | null;
|
|
reconnectHost: string | null;
|
|
};
|
|
errors?: SnapshotError[];
|
|
}
|
|
|
|
export interface ExecutionResult {
|
|
executionId: string;
|
|
machineId: string;
|
|
startedAt: string;
|
|
finishedAt: string;
|
|
mode: "manual" | "scheduled" | "hermes_requested"; // élargi (jalon 1 = "manual")
|
|
action: ActionType;
|
|
status: ExecutionStatus;
|
|
rebootRequiredAfterRun: boolean;
|
|
importantLogLines: string[];
|
|
rawLogRef: string;
|
|
reportRef: string;
|
|
// Ajouts optionnels (rétro-compatibles) :
|
|
schemaVersion?: number;
|
|
apt?: AptExecutionResult;
|
|
docker?: DockerExecutionResult;
|
|
reboot?: RebootResult;
|
|
postInstall?: PostInstallResult;
|
|
errors?: SnapshotError[];
|
|
}
|
|
```
|
|
|
|
> `mode` était `"manual"` (littéral) au jalon 1. L'élargir en union `"manual" | "scheduled" | "hermes_requested"` reste compatible (le jalon 1 émet toujours `"manual"`). Tous les nouveaux blocs (`apt`, `docker`, `reboot`, `postInstall`, `errors`) sont optionnels → une exécution jalon 1 reste valide.
|
|
|
|
---
|
|
|
|
## 5. Extension de `TemplateVars` (rendu Mustache)
|
|
|
|
```ts
|
|
export interface TemplateVars {
|
|
aptProxy?: string | null; // existant
|
|
// Ajouts (tous optionnels) :
|
|
osProfile?: OsFamily;
|
|
machineKind?: MachineKind;
|
|
confValues?: boolean;
|
|
inactivityTimeout?: number;
|
|
// Docker :
|
|
composeRoots?: string; // liste rendue shell-safe par le backend
|
|
composeScanDepth?: number;
|
|
stackDir?: string;
|
|
aggressive?: boolean; // prune agressif
|
|
// Custom :
|
|
operatorUser?: string;
|
|
packages?: string; // liste shell-safe
|
|
newHostname?: string;
|
|
interfaceName?: string;
|
|
staticAddress?: string;
|
|
reconnectHost?: string;
|
|
dockerUser?: string;
|
|
composeRoot?: string;
|
|
rebootAfterInstall?: boolean;
|
|
// ... champs de profil custom (typés au cas par cas en tâche 4)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Déduplication (empreinte fonctionnelle)
|
|
|
|
- **APT** : `dedupKey = os_family + "|" + package + "|" + from + "|" + to + "|" + origin`. Permet à Hermes de mutualiser une même mise à jour vue sur plusieurs machines (une seule recherche web, un seul résumé). Stocké dans `apt_planned_packages.dedup_key` / `apt_applied_packages.dedup_key`.
|
|
- **Docker** : `dedupKey = image + "|" + fromDigest + "|" + toDigest` ; fallback `image + "|" + fromImageId + "|" + toImageId` quand le digest manque.
|
|
|
|
Le calcul de `dedupKey` se fait **côté backend TS** (déterministe), pas dans le shell.
|
|
|
|
---
|
|
|
|
## 7. Réduction déterministe avant Hermes/MCP
|
|
|
|
Le réducteur actuel (`server/templates/aptReduce.ts`) garde les lignes : `Inst `, `Conf `, `Remv `, `Err `, `E:`, `W:`, `dpkg:`, `reboot-required`/`REBOOT_REQUIRED`. **Extension proposée** (renommage suggéré `reduceLines.ts`, additif, sans casser `reduceAptLines`) ajoutant les préfixes Docker : `Pulling`, `Digest`, `Status`, `Downloaded newer image`, `Recreating`, `Started`, `Error`, `deleted`, `Total reclaimed space`.
|
|
|
|
Ce que Hermes reçoit : **JSON canonique réduit** (`important_json`) + lignes importantes (`importantLogLines`). **Jamais** le log brut complet (archivé dans `raw_artifacts`/`rawLogPath`), jamais de secret.
|
|
|
|
---
|
|
|
|
## 8. Mapping vers `tache1.9.md` (tables dérivées)
|
|
|
|
| Bloc JSON | Table dérivée | Colonnes clés |
|
|
|---|---|---|
|
|
| `apt.packages` / `installed` / `removed` / `held` (simulation) | `apt_planned_packages` | `mode`, `operation`, `current_version`, `target_version`, `origin`, `dedup_key` |
|
|
| `apt.applied` (diff dpkg) | `apt_applied_packages` | `from_version`, `to_version`, `operation`, `dedup_key` |
|
|
| `errors[]` source apt | `apt_errors` | `kind`, `severity`, `message`, `important_lines_json`, `remediation` |
|
|
| `docker.stacks[]` | `docker_compose_stacks` + `docker_stack_services` | `status`, `detected_by`, `current_image_id`, `candidate_digest`, … |
|
|
| `docker.pull/up/prune changes` | `docker_image_events` | `from_image_id`, `to_image_id`, `operation`, `bytes_reclaimed` |
|
|
| lignes importantes/notices | `important_messages` | `source`, `category`, `package_name`, `message` |
|
|
| payload complet snapshot | `snapshots.payload_json` + `important_json` | `kind`, `schema_version`, `status` |
|
|
| payload complet exécution | `executions.result_json` + `important_json` | `error_kind`, `error_message`, `exit_code` |
|
|
|
|
Le JSON complet reste la vérité canonique (archivé) ; les tables dérivées servent recherche/filtres/dédup/badges (conforme à la règle structurante `tache1.9.md §2`).
|