Commit Graph

3 Commits

Author SHA1 Message Date
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
Anso 2e2b095b00 fix(proxy): forward scoped stack evidence for alerts, auto-heal, and node-wide image refresh (#1749)
* fix(proxy): forward scoped stack evidence for alerts, auto-heal, and node-wide image refresh

Extend the remote proxy scoped-evidence mechanism beyond /stacks/*
routes. Three new gates in runGatedProxy:

- Alerts POST: reuse the already-buffered body from the existing
  isAlertCreateRoute block, extract stack_name, check hub-side
  scoped permission, and forward SCOPED_STACK_AUTH_EVIDENCE headers.
- Auto-heal POST: same pattern with new body buffering and encoding
  rejection (no pre-existing buffering exists for this route).
- Node-wide image refresh: elevate PROXY_ROLE_HEADER to node-admin
  when the user has a scoped node:manage grant on the target node,
  matching the Settings pre-auth gate pattern.

Also extend classifyStackApiPath to recognize
/image-updates/refresh/:stackName as a named-stack route (stack:deploy),
ready for when PR #1743 adds the per-stack refresh endpoint.

Explicitly excluded: ID-based routes (DELETE /alerts/:id,
PATCH/DELETE /auto-heal/policies/:id) where the hub cannot resolve
remote-owned IDs to stack names; GET routes where stack:read is
globally granted to every role; and POST /auto-update/execute where
multi-stack/wildcard targets need a different evidence format.

* chore(proxy): add RBAC diagnostic logging to scoped permission path

Add developer_mode-gated diagnostic logs to checkPermission to expose
which check is failing when a scoped user is denied: effective tier,
DB query parameters, and node-scoped assignment lookups.

* chore(rbac): log effective tier and license status when scoped checks are blocked

Add an always-visible console.warn in checkPermission when the
effective tier prevents scoped role-assignment lookups, logging
both the resolved tier and the raw license_status DB value. This
surfaces the failure reason in container logs without requiring
developer_mode, so QA can diagnose why scoped users are denied.

* chore(rbac): sanitize scoped-tier log values to satisfy CodeQL log-injection check
2026-08-01 23:37:35 -04:00
Anso 9922d8e765 feat(rbac): make stack-scoped grants node-specific (#1727)
* feat(rbac): make stack-scoped grants node-specific

Qualify stack role assignments as (nodeId, stackName), migrate legacy rows to the default node, and forward bound multi-action evidence on Proxy/Pilot hops so scoped users keep least-privilege remote access without shipping the full grant table.

* fix: mirror scoped-stack-auth-evidence capability to frontend, sanitize node id in role assignment log

Backend added the scoped-stack-auth-evidence capability without the
matching frontend entry, failing the capability parity test. The role
assignment log also interpolated the node id without sanitizeForLog,
unlike the rest of the line.

* fix(rbac): honor node-wide scopes and fix proxied DELETE cleanup

Node-scoped grants now authorize that role's stack actions on the same node in the backend resolver, frontend can(), and remote evidence. Proxied DELETE cleanup uses the gate-stashed route because pathRewrite mutates req.path before proxyRes. Add proxy integration coverage and drop the stale scoped-permissions screenshot.

* fix(rbac): preserve node-qualified grants during repair
2026-07-29 09:42:14 -04:00