Implement controlled app launch (T-401)

This commit is contained in:
ila
2026-07-19 21:10:59 +08:00
parent d0cf333394
commit 87083c387f
32 changed files with 1802 additions and 31 deletions
+31 -4
View File
@@ -14,6 +14,11 @@ var (
type HealthCheck func(currentPath string) error
// PreSwitchCheck runs immediately before replacing an existing current
// directory. It is intentionally core-only so callers can inject process
// state without bringing platform APIs into the transaction.
type PreSwitchCheck func() error
type switchStep string
const (
@@ -42,13 +47,27 @@ func (err *RollbackError) Unwrap() []error {
// Switcher activates a verified staging directory and runs an injected check.
type Switcher struct {
health HealthCheck
afterStep func(switchStep) error
durability durabilityFence
health HealthCheck
preSwitchCheck PreSwitchCheck
afterStep func(switchStep) error
durability durabilityFence
}
func NewSwitcher(health HealthCheck) *Switcher {
return &Switcher{health: health, durability: defaultDurability()}
return NewSwitcherWithPreSwitchCheck(health, nil)
}
// NewSwitcherWithPreSwitchCheck creates a switcher that performs the optional
// check only for updates with an existing current directory.
func NewSwitcherWithPreSwitchCheck(
health HealthCheck,
preSwitchCheck PreSwitchCheck,
) *Switcher {
return &Switcher{
health: health,
preSwitchCheck: preSwitchCheck,
durability: defaultDurability(),
}
}
func (switcher *Switcher) Switch(root string) error {
@@ -75,6 +94,14 @@ func (switcher *Switcher) Switch(root string) error {
if state.backup {
return ErrBackupExists
}
if state.current && switcher.preSwitchCheck != nil {
if err := switcher.preSwitchCheck(); err != nil {
if cleanupErr := removeManagedDirectoryWithFence(layout, layout.staging, fence); cleanupErr != nil {
return errors.Join(err, cleanupErr)
}
return err
}
}
record := newTransaction(phasePrepared, state.current)
if err := writeTransactionWithFence(layout, record, fence); err != nil {