Files
sencho/docs/features/stack-dossier.mdx
T
Anso f5178889eb feat(recovery): complete authored-project atomic rollback generations (#1819)
* feat(recovery): capture complete authored Compose project for atomic rollback

Replace the root-compose-only backup slot with staged recovery generations that
record the managed inventory, exact Compose invocation, and prior image identity,
and wire the same engine through deploy, update, manual rollback, and Git apply.

* fix(recovery): satisfy CodeQL path barriers and update-guard mock

Inline resolve+startsWith checks at generation/inventory fs sinks and stub getCurrentStackUpdateRecovery in UpdateGuardService tests.

* fix(recovery): drop unused FileSystemService import in generation store test

* fix(recovery): harden authored-project rollback for upgrade and restore safety

Preserve legacy UUID backup rows, restore Git deploy state with files, make multi-file restore recoverable, evaluate policy on the restored target, and fail closed when Git capture cannot cover an apply.

* fix(recovery): unblock Git apply unit tests and CodeQL pre-restore TOCTOU

Mock recovery capture in git-source-service tests after fail-closed apply capture, and re-resolve live paths immediately before pre-restore snapshot reads.

* fix(recovery): fall back to authored inventory when Git manifesto is missing

First Git apply captures before promote, so a missing managed-project manifesto must not block rollback capture when the live stack already has authored files.

* fix(recovery): make authored-project rollback atomic across Git state

Restore the managed-project manifesto with files, keep nullable Git identity on first-apply captures, persist Git side-state in restore intents for startup reconcile, compensate legacy materialize failures, and refuse directory collisions before mutation.

* fix(recovery): satisfy CodeQL path and TOCTOU barriers on manifesto restore

Add inline resolve barriers for manifesto read/clear sinks and remove the access-then-read race when restoring a generation manifesto snapshot.

* fix(recovery): close third-audit rollback generation blockers

Fail closed on incomplete Git inventory fallbacks, execute captured Compose
invocation during recovery, refuse startup and mutations while restore intents
remain unresolved, propagate legacy stale-delete failures, and add Docker-level
exact prior-image coverage plus regression tests.

* fix(recovery): mark acquired before handoff in prior-image Docker test

Match the production updateStack CAS sequence so the exact prior-image
integration test does not fail handoff from the captured phase.

* fix(recovery): close fourth-audit rollback safety blockers

Evaluate policy against held images, use index-based pre-restore snapshots, hold the shared stack lock across Git apply, replay Mesh and empty captured invocations exactly, restore POSIX modes with fail-closed sensitive permissions, keep case-sensitive paths, and link Git auto-deploy health gates. Add regression coverage for these cases.

* test(recovery): fix mocks for health-gate link and authored compose args

Add linkGateOrRetain to the Git apply recovery mock, and mock authoredComposeArgs so the case-collision inventory test is not masked by a missing getComposeDir stub.

* fix(recovery): close fifth-audit rollback safety blockers

Share git_apply locking for webhook auto-apply, fail closed on malformed recovery service records, refuse mixed-image capture, and require exact probe counts with hold-tag eligibility checks.

* fix(recovery): close sixth-audit rollback safety blockers

Preserve the legacy backup slot during generation capture, encrypt sensitive pre-restore snapshots, revert files on a failed health probe without committing Git, fail closed when an absent-file revert would delete a directory, skip Compose one-offs, route manual and scheduled backup through the current generation, and persist runtime image platform identity.

* fix(recovery): close seventh-audit rollback safety blockers

Fleet snapshot restore and restore-all now capture a recovery generation under the stack lock before any authored file write, including on remote nodes.

* fix(recovery): keep pre-deploy generations during health-gate observe

Link deploy recovery generations to the observing gate so backup cannot replace them mid-observe. Distinguish missing hold tags from probe failures, refuse generation release when services metadata is corrupt, classify mixed-replica and coverage refusals, and toast the backend rollback message.

* fix(recovery): wrap webhook deploy case for eslint

const bindings in an unbraced switch case trip no-case-declarations. Match the pull case block.
2026-08-13 03:48:09 -04:00

166 lines
17 KiB
Plaintext

---
title: Stack Dossier
description: Pair the operational facts Sencho derives from each stack with your own notes, then export everything as a portable Markdown document.
---
The **Dossier** tab in the Anatomy panel is where you turn a running stack into something a teammate, a future self, or a runbook can actually use. It pairs facts Sencho already has (services, ports, volumes, env files, restart policy) with the context only you know: what the stack does, who owns it, where it sits on the network, and how to recover it if the host is lost. The result is a single document that stays current on the generated side and grows richer as you fill in the operator side.
This stops operational knowledge from living in spreadsheets, comments, and people's heads while Sencho is already reading the Compose file.
## How it works
The tab has two halves. The top half is read-only: Sencho derives facts from the Compose file on every load. The bottom half is a form you fill in. Neither half depends on the other; a stack with no operator notes still has accurate generated facts, and a stack you have not deployed yet still accepts operator notes.
The export (copy or download) combines both halves into one Markdown document. The document format is designed to be readable outside Sencho: in a Git repo, a wiki, an Obsidian vault, or alongside a backup. Sencho never embeds secrets into the export; only variable names and counts appear from the generated side, and your operator notes are exported as you write them.
## Generated facts
The top of the tab shows a read-only summary derived live from the stack's Compose file. Because it is never copied into a field you maintain, it cannot drift independently of the file.
<Frame>
<img src="/images/stack-dossier/dossier-overview.png" alt="Dossier tab showing the Generated Facts panel above and the Operator Notes form below" />
</Frame>
| Fact | What is shown | Source |
|------|--------------|--------|
| **Services** | Names of declared services | Compose file service keys |
| **Ports** | A count of every `ports:` entry declared across services, followed by each port number | `ports:` entries. The count is not filtered to host-published mappings: a container-only port (declared without a host side) is counted and listed the same way as a published one. |
| **Volumes** | Count of mounted volumes | `volumes:` references across services |
| **Network** | Stack's primary network name, shown with a fixed `bridge` label (for example, `arr-net · bridge`) | Compose network definitions. The `bridge` label is fixed text, not read from the network's actual driver. |
| **Restart** | Restart policy of the first service with one set | `restart:` key |
| **Env file** | The env file path, its variable count, and a count of any `${VAR}` with no supplied value. The Markdown export additionally lists the missing variable names; the live tab shows the count only. | `.env` / `env_file:` at the stack root |
| **Source** | `local`, or the Git remote URL, branch, and path when the stack is linked | Stack's git source configuration |
**Multi-file Compose stacks:** when a stack has a Git source with multiple Compose files, Sencho merges them (equivalent to `docker compose config`) before reading facts. Services, ports, and volumes shown in the Generated Facts reflect the merged result, not any individual file.
Environment and label values are never read or shown: only variable names and counts appear, exactly as in the Anatomy tab. The one exception is a value interpolated into a structural field such as `${DB_PORT}` in a port declaration, which Compose resolves before the facts are read. Keep secrets in `environment:` or `env_file:` rather than interpolating them into structural fields. See [Environment and Secrets Guardrails](/features/environment-guardrails).
## Operator notes
Below the generated facts is a form for the details Sencho cannot derive. Every field is optional. Save applies all fields at once; clearing a field and saving removes it. The Save button activates only when there are unsaved changes.
| Field | Max length | What to record |
|-------|-----------|---------------|
| **Purpose** | 1,000 chars | One or two sentences: what the stack does and why it runs |
| **Owner** | 1,000 chars | Team, person, or alias responsible for this stack |
| **Static IP** | 255 chars | The host IP or LAN address this stack is pinned to, if any |
| **VLAN** | 255 chars | VLAN tag or name if the stack is on a segmented network |
| **Access URLs** | 2,000 chars | Full URLs where the stack is reachable, one per line. Include the port explicitly (`http://homelab:8096`) rather than relying on scheme defaults, so the documentation drift check can validate the port against what the stack publishes. |
| **Firewall** | 8,000 chars | Ports opened in firewall rules, zone assignments, any ingress or egress rules |
| **Reverse proxy** | 8,000 chars | Upstream name and port, virtual host, TLS termination, cert source |
| **Backup** | 8,000 chars | Which volumes or bind-mounts matter, backup tool, schedule, offsite policy |
| **Upgrade** | 8,000 chars | Steps required to update this stack, any ordering dependencies, known breaking changes between versions |
| **Recovery** | 8,000 chars | Steps to rebuild this stack from scratch if the host is lost. Write this as if no prior state exists. |
| **Notes** | 8,000 chars | Anything else: quirks, known bugs, related stacks, useful references |
## Documentation drift
Documentation should describe reality, so Sencho watches the one place it most often slips: a port you wrote into **Access URLs** that the stack no longer publishes. When Compose moves a service from `:32400` to `:32401`, or an access URL points at a port nothing serves, a warning appears in the Dossier tab naming the port to review. This is why the Access URLs field asks for full URLs with explicit ports.
<Frame>
<img src="/images/stack-dossier/dossier-doc-drift.png" alt="Dossier tab showing a documentation drift warning for an access URL whose port does not match any published port" />
</Frame>
The check is deterministic and advisory. It reads the ports in your Access URLs and compares them against the published ports in the generated facts above, the same ports the Anatomy tab shows. It never edits your notes or your Compose file, and it never guesses:
- A URL with no explicit port, or on the scheme default (`:80` for `http`, `:443` for `https`), is left alone. Those usually reach the stack through a reverse proxy rather than a published port.
- When a stack publishes a port through a variable such as `${PLEX_PORT}:32400`, Sencho does not know the resolved value from the Compose source, so the check stays quiet rather than risk a false warning.
- A bare single-label host with a port (such as `plex:32400`) is also left alone, since it cannot be distinguished from a plain note. Write it as a full URL (`http://plex:32400`) to have its port checked.
Fix either side (the access URL or the stack's ports) and the warning clears on its own.
## Markdown export
Two actions in the tab header produce a single Markdown document combining the generated facts and your operator notes:
<Frame>
<img src="/images/stack-dossier/dossier-export-bar.png" alt="Dossier tab header showing the COPY MD and DOWNLOAD export buttons" />
</Frame>
- **copy md** copies the document to the clipboard.
- **download** saves it as `<stack-name>-dossier.md`.
The exported document always includes the Generated Facts and all Operator Notes. When the active node advertises the [Compose Networking](/features/compose-networking) capability, the export also includes a network exposure summary; when it advertises the [Storage Portability](/features/compose-storage) capability, the export also includes a storage summary. Both additions are automatic when the capability is present, but each summary is fetched fresh at export time rather than cached, so a capable, up-to-date node can still omit a section if that fetch fails. See Troubleshooting below.
The export is clean Markdown that reads well in Git, Obsidian, BookStack, a README, or stored alongside a backup. The generated facts never include `.env` values, only variable names and counts. Your operator notes are exported exactly as you write them, so treat them like any document you might share and avoid pasting secrets you do not want in the export.
The export buttons are disabled when the Compose file cannot be parsed. Your operator notes are unaffected and still save normally even when parsing fails.
To export every stack across every node at once, use the [Fleet Dossier](/features/fleet-dossier) action in the Fleet view.
## Connected features
### Drift tab (compose vs runtime)
Adjacent to the Dossier tab in the same Anatomy panel is the **Drift** tab. It answers a different question: does what is actually running still match the Compose file? Drift detection compares services, images, and published ports against the live Docker runtime. It is independent of the dossier: you do not need operator notes for drift detection, and documentation drift warnings in the Dossier tab are separate from runtime drift in the Drift tab. See [Drift Detection](/features/stack-drift).
### Fleet Dossier
The [Fleet Dossier](/features/fleet-dossier) export in the Fleet view produces a ZIP archive containing Markdown documents for every stack on every node, organized by node. It uses the same per-stack dossier format and pulls the operator notes you have saved in each stack's Dossier tab.
### Fleet Snapshots and dossier notes
Fleet Snapshots can optionally capture dossier notes alongside Compose files. When the **snapshot documentation** setting is enabled, each snapshot includes the operator notes for every stack that has them. When restoring a snapshot, you can choose whether to restore files only, or files and dossier notes together. Dossier restore is opt-in at restore time and does not overwrite notes unless you confirm it. See [Fleet Backups](/features/fleet-backups).
### Activity timeline
Events from the Drift tab (drift detected, drift resolved) appear in the stack's **Activity** timeline alongside deploys and restarts, giving a chronological view of when the stack drifted and when it recovered.
### Rollback readiness
At the bottom of the Dossier tab, on nodes that advertise the `update-guard` capability, a **Rollback readiness** section reports an overall **Ready**, **Partial**, or **Not ready** state for the stack. It covers managed-input coverage, whether a current recovery generation exists, rollback policy eligibility, plus the existing disclosures for compose/env coverage, previous image identity, last successful deploy, and healthchecks. Application data in named volumes and bind mounts remains explicitly not covered. It shares the same capability-plus-fetch-failure behavior described above for the Networking and Storage export summaries. See [Health-Gated Updates](/features/health-gated-updates#rollback-readiness) for what each signal means and how to act on it.
## Permissions and per-node scoping
Any authenticated user with `stack:read` permission can view a stack's dossier. Read-only users see all fields and the export buttons, but no Save button appears.
Saving changes requires stack edit permission (`stack:edit`). In multi-user setups with role-based access control, check your role if the Save button is absent.
Dossiers are stored per stack and per node. A stack named `plex` on your home node and `plex` on a remote VPS have independent dossiers. Edit a stack's dossier while that node is the active node to update the right one.
## Limitations
- **Operator notes are per-instance.** Notes entered on one Sencho instance are not synchronized to another instance that manages the same stack. Fleet Snapshots with documentation capture is the only way to transfer notes between instances.
- **Documentation drift checks Access URLs only.** The port-validity check applies only to the Access URLs field. It does not validate other fields or check hostnames.
- **No history on operator notes.** The dossier stores only the current version of each field. Sencho does not record change history for operator notes. Use a version-controlled wiki or Git if you need note history.
- **Export requires a parseable Compose file.** When the Compose file cannot be parsed, the export buttons are disabled. Operator notes are unaffected and still save normally.
## Accessing the Dossier tab
1. Click any stack in the left sidebar to open it.
2. Switch to the **Dossier** tab in the Anatomy panel header.
3. Fill in any fields you want and click **Save**. An **unsaved changes** marker appears while edits are pending.
The Anatomy panel is visible in the right column when a stack is open and the editor is not active (that is, when not in the compose-file edit mode). If the panel is hidden, click away from the edit view or close the editor using the `X` button at the top of the editor card.
## Troubleshooting
<AccordionGroup>
<Accordion title="The Save button is not visible or stays greyed out">
The Save button appears only for users with stack edit permission. If you do not see it, you are signed in as a read-only user or your role does not include `stack:edit` for this stack. Users with read-only access can view all fields and use the export buttons.
If the button is visible but greyed out, it activates only when there are unsaved changes. Edit any field and it enables.
</Accordion>
<Accordion title="Generated facts show 'Unable to parse compose.yaml'">
The facts panel is built from the stack's Compose file. If the file is empty, missing, or not valid YAML, the facts cannot be generated. Fix the Compose file in the editor and the panel updates. Your operator notes are unaffected and save normally even when the compose file is unparseable. The export buttons are also disabled when the file cannot be parsed.
</Accordion>
<Accordion title="My notes for the same stack name differ between two nodes">
That is expected. A dossier is stored per stack and per node, so a stack with the same name on two nodes keeps an independent dossier on each. Make sure the correct node is active before saving, or use Fleet Snapshots with documentation capture to copy notes between instances.
</Accordion>
<Accordion title="An access URL spilled onto one line in the export">
The Access URLs field treats each line as one URL. If a URL wrapped visually in the form, it was entered on a single line. Paste each URL on its own line to have them appear as separate entries in the export.
</Accordion>
<Accordion title="A documentation drift warning flags a port my reverse proxy fronts">
The warning appears when an access URL names a port the stack does not publish. That is expected when a reverse proxy answers on a port the stack never published. The warning is advisory and changes nothing: either write the reverse-proxied URL without an explicit port (so it is not checked), or publish the port in the Compose file if the URL should reach the stack directly.
</Accordion>
<Accordion title="A documentation drift warning appears for a port defined as a variable">
When a stack's compose file publishes a port through a variable such as `${PLEX_PORT}:32400`, Sencho does not know the resolved value from the Compose source, so it treats that port as unverifiable and stays quiet. If your access URL refers to the same port and a warning still appears, the URL's port does not match any statically declared published port. Either update the URL or pin the port in the Compose file.
</Accordion>
<Accordion title="The networking or storage summary is missing from the export">
A missing section has one of two causes. Either the active node does not currently advertise the [Compose Networking](/features/compose-networking) or [Storage Portability](/features/compose-storage) capability, because it is running an older Sencho build or was briefly unreachable when Sencho last checked, or the node does have the capability but the on-demand fetch for that summary failed at export time (a transient API, proxy, or Docker issue). Retry the export before assuming the node needs an upgrade. The core generated facts and all operator notes are always included regardless of either cause.
</Accordion>
<Accordion title="Dossier notes were not restored after a fleet snapshot restore">
Fleet snapshot restore has two steps: restoring Compose files and optionally restoring dossier notes. Notes are restored only when you explicitly choose to restore them at restore time. If you did not select that option, or if the original snapshot was captured without documentation enabled, the notes will not be present. To restore notes, choose a snapshot that includes documentation and confirm the notes restore option during the restore flow.
</Accordion>
</AccordionGroup>