feat: add deployment foundation and cross-device handoff
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
package planner
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const planLifetime = 15 * time.Minute
|
||||
|
||||
// Plan is an offline intent preview, not an executable authorization. Hash binds
|
||||
// its contents but is not a signature. No secret may be added to this structure.
|
||||
type Plan struct {
|
||||
Intent Intent `json:"intent"`
|
||||
ProjectName string `json:"projectName"`
|
||||
DataPath string `json:"dataPath"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
func Build(i Intent, now time.Time) (Plan, error) {
|
||||
if err := i.Validate(); err != nil {
|
||||
return Plan{}, err
|
||||
}
|
||||
now = now.UTC().Truncate(time.Second)
|
||||
p := Plan{Intent: i, ProjectName: "sd-" + i.InstanceID, DataPath: "/var/lib/server-deploy/instances/" + i.InstanceID, CreatedAt: now, ExpiresAt: now.Add(planLifetime)}
|
||||
encoded, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return Plan{}, errors.New("cannot encode plan")
|
||||
}
|
||||
sum := sha256.Sum256(encoded)
|
||||
p.Hash = "sha256:" + hex.EncodeToString(sum[:])
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Verify checks offline consistency only. Current must be independently inspected
|
||||
// under a host lock before any future write operation uses this comparison.
|
||||
func (p Plan) Verify(current Intent, now time.Time) error {
|
||||
if now.Before(p.CreatedAt) || !now.Before(p.ExpiresAt) {
|
||||
return errors.New("plan is not within its validity window")
|
||||
}
|
||||
if p.Intent != current {
|
||||
return errors.New("plan does not match current intent or observed state")
|
||||
}
|
||||
expected, err := Build(current, p.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.ProjectName != expected.ProjectName || p.DataPath != expected.DataPath || !p.CreatedAt.Equal(expected.CreatedAt) || !p.ExpiresAt.Equal(expected.ExpiresAt) || p.Hash != expected.Hash {
|
||||
return errors.New("plan integrity check failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user