feat: add deployment foundation and cross-device handoff

This commit is contained in:
2026-09-25 08:49:19 +08:00
parent 8ccb8b7c15
commit e965b0943d
77 changed files with 8018 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
package cli
import (
"encoding/json"
"strings"
"testing"
"server-deploy/internal/installplan"
"server-deploy/internal/preflight"
)
func TestEnvironmentPlanCLI(t *testing.T) {
r := preflight.Collect()
suite, arch := "resolute", "amd64"
if r.Runtime.OS == "linux" {
suite = r.Distribution.Codename
arch = r.Runtime.Architecture
}
if suite != "resolute" && suite != "noble" && suite != "jammy" {
t.Skip("local Linux distribution outside initial lock policy")
}
l := installplan.Lock{ProtocolVersion: 1, Repository: "https://download.docker.com/linux/ubuntu", Suite: suite, Architecture: arch, ReleaseDigest: "sha256:" + strings.Repeat("a", 64), Packages: []installplan.Package{}}
for _, name := range []string{"docker-ce", "docker-ce-cli", "containerd.io", "docker-buildx-plugin", "docker-compose-plugin"} {
l.Packages = append(l.Packages, installplan.Package{Name: name, Version: "1.2.3-1", Filename: "dists/" + suite + "/pool/stable/" + arch + "/" + name + "_1.2.3-1_" + arch + ".deb", Digest: "sha256:" + strings.Repeat("b", 64), Size: 123})
}
raw, _ := json.Marshal(struct {
Lock installplan.Lock `json:"lock"`
}{l})
code, out, diagnostics := run([]string{"plan-environment"}, string(raw))
var result struct {
Mode string `json:"mode"`
Draft installplan.Draft `json:"draft"`
}
if code != 0 || json.Unmarshal([]byte(out), &result) != nil || result.Mode != "local-environment-draft" || result.Draft.Executable || result.Draft.RepositoryAuthenticated || len(result.Draft.RequestedPackages) != 5 || len(result.Draft.Blockers) == 0 {
t.Fatalf("bad draft %s %s", out, diagnostics)
}
for _, bad := range []string{`{}`, strings.Replace(string(raw), `"1.2.3-1"`, `"secret;reboot"`, 1)} {
code, out, diagnostics := run([]string{"plan-environment"}, bad)
if code == 0 || out != "" || strings.Contains(diagnostics, "secret") {
t.Fatal("invalid request accepted or leaked")
}
}
}
+8
View File
@@ -0,0 +1,8 @@
package cli
import (
"io"
"server-deploy/internal/wire"
)
func decodeStrict(in io.Reader, target any) error { return wire.Decode(in, target, 1<<20) }
+117
View File
@@ -0,0 +1,117 @@
package cli
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
func packageFixture(t *testing.T) (string, string) {
return packageFixturePayload(t, []byte("services: {}\n"))
}
func packageFixturePayload(t *testing.T, payload []byte) (string, string) {
t.Helper()
dir := t.TempDir()
digest := func(data []byte) string { sum := sha256.Sum256(data); return "sha256:" + hex.EncodeToString(sum[:]) }
manifest := map[string]any{
"protocolVersion": 1, "appId": "example", "version": "1.0.0", "runtime": "compose", "entrypoint": "compose.yaml",
"platforms": []string{"linux/amd64"},
"components": []map[string]string{{"name": "server", "image": "example/server@sha256:" + strings.Repeat("a", 64)}},
"files": []map[string]string{{"path": "compose.yaml", "digest": digest(payload)}},
}
raw, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "manifest.json"), raw, 0600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "compose.yaml"), payload, 0600); err != nil {
t.Fatal(err)
}
return dir, digest(raw)
}
func TestCheckPackagePolicyCLI(t *testing.T) {
payload := `{"services":{"server":{"image":"example/server@sha256:` + strings.Repeat("a", 64) + `","user":"1000:1000","read_only":true,"cap_drop":["ALL"],"security_opt":["no-new-privileges:true"],"restart":"no","networks":["backend"],"volumes":[]}},"networks":{"backend":{"internal":true}},"volumes":{}}`
for _, tc := range []struct {
payload string
accepted bool
}{
{payload, true},
{strings.Replace(payload, `"read_only":true`, `"read_only":false`, 1), false},
{`services: {}`, false},
} {
dir, digest := packageFixturePayload(t, []byte(tc.payload))
input, _ := json.Marshal(map[string]string{"directory": dir, "expectedDigest": digest})
code, out, diagnostics := run([]string{"check-package"}, string(input))
if tc.accepted {
var result struct {
PolicyPassed bool `json:"policyPassed"`
Executable bool `json:"executable"`
Profile string `json:"profile"`
Digest string `json:"digest"`
}
if code != 0 || json.Unmarshal([]byte(out), &result) != nil || !result.PolicyPassed || result.Executable || result.Profile == "" || result.Digest != digest {
t.Fatalf("bad check result: %s %s", out, diagnostics)
}
if strings.Contains(out, "1000:1000") {
t.Fatal("payload leaked")
}
if err := os.WriteFile(filepath.Join(dir, "compose.yaml"), []byte(tc.payload+" "), 0600); err != nil {
t.Fatal(err)
}
if code, _, _ := run([]string{"check-package"}, string(input)); code == 0 {
t.Fatal("policy bypassed integrity check")
}
} else if code == 0 || out != "" {
t.Fatal("unsafe package accepted")
}
}
}
func TestVerifyPackageCLI(t *testing.T) {
dir, digest := packageFixture(t)
input, _ := json.Marshal(map[string]string{"directory": dir, "expectedDigest": digest})
code, out, diagnostics := run([]string{"verify-package"}, string(input))
if code != 0 || diagnostics != "" {
t.Fatalf("package verification failed: %s", diagnostics)
}
var response struct {
Verified bool `json:"verified"`
Executable bool `json:"executable"`
PublisherAuthenticated bool `json:"publisherAuthenticated"`
FileCount int `json:"fileCount"`
Digest string `json:"digest"`
}
if err := json.Unmarshal([]byte(out), &response); err != nil {
t.Fatal(err)
}
if !response.Verified || response.Executable || response.PublisherAuthenticated || response.FileCount != 1 || response.Digest != digest {
t.Fatalf("misleading verification: %s", out)
}
if strings.Contains(out, "services: {}") {
t.Fatal("file contents leaked")
}
if err := os.WriteFile(filepath.Join(dir, "compose.yaml"), []byte("secret tampering"), 0600); err != nil {
t.Fatal(err)
}
code, out, diagnostics = run([]string{"verify-package"}, string(input))
if code == 0 || out != "" || strings.Contains(diagnostics, "secret tampering") {
t.Fatal("tampering accepted or leaked")
}
}
func TestVerifyPackageInvalidRequests(t *testing.T) {
for _, input := range []string{`{}`, `{"directory":".","expectedDigest":"latest"}`, `{"directory":"secret","expectedDigest":"bad","password":"do-not-echo"}`} {
code, out, diagnostics := run([]string{"verify-package"}, input)
if code == 0 || out != "" || strings.Contains(diagnostics, "do-not-echo") {
t.Fatal("invalid package request accepted or leaked")
}
}
}
+21
View File
@@ -0,0 +1,21 @@
package cli
import (
"encoding/json"
"testing"
)
func TestPreflightDoesNotAuthorizeInstallation(t *testing.T) {
code, out, diagnostics := run([]string{"preflight"}, "")
var response struct {
ProtocolVersion int `json:"protocolVersion"`
Mode string `json:"mode"`
Proposal struct {
Executable bool `json:"executable"`
Blockers []string `json:"blockers"`
} `json:"proposal"`
}
if code != 0 || json.Unmarshal([]byte(out), &response) != nil || response.ProtocolVersion != 1 || response.Mode != "local-environment-proposal" || response.Proposal.Executable || len(response.Proposal.Blockers) == 0 {
t.Fatalf("unsafe report: %s %s", out, diagnostics)
}
}
+37
View File
@@ -0,0 +1,37 @@
package cli
import (
"bytes"
"strings"
"testing"
"time"
)
func TestRepositoryRejectsUntrustedRequests(t *testing.T) {
for _, input := range []string{`{}`, `{"directory":"secret-relative","suite":"resolute","architecture":"amd64","versions":{}}`,
`{"directory":"secret-relative","suite":"resolute","architecture":"amd64","versions":{},"repositoryAuthenticated":true}`} {
var out, diagnostics bytes.Buffer
if Run([]string{"verify-repository"}, strings.NewReader(input), &out, &diagnostics, time.Now) == 0 {
t.Fatal("accepted invalid request")
}
if out.Len() != 0 || strings.Contains(diagnostics.String(), "secret-relative") {
t.Fatal("invalid response leaked input")
}
}
}
func TestArtifactCommandRejectsCallerTrust(t *testing.T) {
for _, field := range []string{`"packageBytesVerified":true`, `"repositoryAuthenticated":true`, `"lock":{}`} {
var out, diagnostics bytes.Buffer
input := `{"directory":"secret-relative","artifactDirectory":"secret-artifacts","suite":"resolute","architecture":"amd64","versions":{},` + field + `}`
if Run([]string{"verify-artifacts"}, strings.NewReader(input), &out, &diagnostics, time.Now) == 0 {
t.Fatal("caller trust accepted")
}
if out.Len() != 0 || strings.Contains(diagnostics.String(), "secret") {
t.Fatal("input leaked")
}
if diagnostics.String() != "invalid artifact verification request\n" {
t.Fatal("request was not rejected at the protocol boundary", diagnostics.String())
}
}
}
+167
View File
@@ -0,0 +1,167 @@
// Package cli exposes read-only protocol endpoints, not a shell wrapper.
package cli
import (
"encoding/json"
"fmt"
"io"
"time"
"server-deploy/internal/appbundle"
"server-deploy/internal/aptrepo"
"server-deploy/internal/composepolicy"
"server-deploy/internal/inspect"
"server-deploy/internal/installplan"
"server-deploy/internal/planner"
"server-deploy/internal/preflight"
)
func Run(args []string, in io.Reader, out, diagnostics io.Writer, now func() time.Time) int {
fail := func(message string) int { fmt.Fprintln(diagnostics, message); return 1 }
if len(args) != 1 {
return fail("usage: deployctl version | inspect | preflight | plan-environment | verify-repository | verify-artifacts | plan | verify-plan | verify-package | check-package")
}
var response any
switch args[0] {
case "verify-artifacts":
var request struct {
Directory string `json:"directory"`
ArtifactDirectory string `json:"artifactDirectory"`
Suite string `json:"suite"`
Architecture string `json:"architecture"`
Versions map[string]string `json:"versions"`
}
if decodeStrict(in, &request) != nil {
return fail("invalid artifact verification request")
}
verified, err := aptrepo.VerifyArtifacts(request.Directory, request.ArtifactDirectory, request.Suite, request.Architecture, request.Versions, now())
if err != nil {
return fail("artifact verification failed")
}
response = verified
case "verify-repository":
var request struct {
Directory string `json:"directory"`
Suite string `json:"suite"`
Architecture string `json:"architecture"`
Versions map[string]string `json:"versions"`
}
if decodeStrict(in, &request) != nil {
return fail("invalid repository verification request")
}
verified, err := aptrepo.Verify(request.Directory, request.Suite, request.Architecture, request.Versions, now())
if err != nil {
return fail("repository verification failed")
}
response = verified
case "plan-environment":
var request struct {
Lock installplan.Lock `json:"lock"`
}
if decodeStrict(in, &request) != nil {
return fail("invalid environment lock request")
}
report := preflight.Collect()
draft, err := installplan.Build(report, request.Lock)
if err != nil {
return fail("environment lock rejected")
}
response = struct {
ProtocolVersion int `json:"protocolVersion"`
Mode string `json:"mode"`
ObservedAt time.Time `json:"observedAt"`
Report preflight.Report `json:"report"`
Draft installplan.Draft `json:"draft"`
}{1, "local-environment-draft", now().UTC().Truncate(time.Second), report, draft}
case "preflight":
report := preflight.Collect()
response = struct {
ProtocolVersion int `json:"protocolVersion"`
Mode string `json:"mode"`
ObservedAt time.Time `json:"observedAt"`
Report preflight.Report `json:"report"`
Proposal preflight.Proposal `json:"proposal"`
}{1, "local-environment-proposal", now().UTC().Truncate(time.Second), report, preflight.Plan(report)}
case "verify-package", "check-package":
var request struct {
Directory string `json:"directory"`
ExpectedDigest string `json:"expectedDigest"`
}
if err := decodeStrict(in, &request); err != nil {
return fail("invalid package verification request")
}
verified, err := appbundle.Verify(request.Directory, request.ExpectedDigest)
if err != nil {
return fail("package verification failed: invalid manifest, inventory or digest")
}
if args[0] == "check-package" {
if composepolicy.Check(verified.Manifest, verified.Files[verified.Manifest.Entrypoint]) != nil {
return fail("package rejected by restricted Compose policy")
}
response = struct {
ProtocolVersion int `json:"protocolVersion"`
PolicyPassed bool `json:"policyPassed"`
Profile string `json:"profile"`
Digest string `json:"digest"`
Executable bool `json:"executable"`
PublisherAuthenticated bool `json:"publisherAuthenticated"`
}{1, true, composepolicy.Profile, verified.Digest, false, false}
break
}
response = struct {
ProtocolVersion int `json:"protocolVersion"`
Verified bool `json:"verified"`
Executable bool `json:"executable"`
PublisherAuthenticated bool `json:"publisherAuthenticated"`
Digest string `json:"digest"`
FileCount int `json:"fileCount"`
Manifest appbundle.Manifest `json:"manifest"`
}{1, true, false, false, verified.Digest, len(verified.Files), verified.Manifest}
case "inspect":
response = inspect.Collect()
case "version":
response = struct {
Version string `json:"version"`
ProtocolVersion int `json:"protocolVersion"`
WritesEnabled bool `json:"writesEnabled"`
}{"0.1.0-dev", planner.ProtocolVersion, false}
case "plan":
var intent planner.Intent
if err := decodeStrict(in, &intent); err != nil {
return fail("invalid request: expected strict protocol JSON (maximum 1 MiB)")
}
plan, err := planner.Build(intent, now())
if err != nil {
return fail("invalid deployment intent")
}
response = struct {
ProtocolVersion int `json:"protocolVersion"`
Mode string `json:"mode"`
Executable bool `json:"executable"`
Plan planner.Plan `json:"plan"`
}{planner.ProtocolVersion, "offline-preview", false, plan}
case "verify-plan":
var request struct {
Plan planner.Plan `json:"plan"`
Current planner.Intent `json:"current"`
}
if err := decodeStrict(in, &request); err != nil {
return fail("invalid verification request")
}
if err := request.Plan.Verify(request.Current, now()); err != nil {
return fail("plan rejected: expired, changed or invalid")
}
response = struct {
ProtocolVersion int `json:"protocolVersion"`
Mode string `json:"mode"`
Valid bool `json:"valid"`
Executable bool `json:"executable"`
}{planner.ProtocolVersion, "offline-preview", true, false}
default:
return fail("unsupported command; deployment writes are not enabled")
}
if err := json.NewEncoder(out).Encode(response); err != nil {
return fail("cannot write response")
}
return 0
}
+187
View File
@@ -0,0 +1,187 @@
package cli
import (
"bytes"
"encoding/json"
"errors"
"strings"
"testing"
"time"
"server-deploy/internal/planner"
)
func fixture() string {
return `{"protocolVersion":1,"hostId":"host-one","instanceId":"git-one","appId":"gitea","packageDigest":"sha256:` + strings.Repeat("a", 64) + `","imageDigest":"sha256:` + strings.Repeat("b", 64) + `","domain":"git.example.com","observedStateDigest":"sha256:` + strings.Repeat("c", 64) + `"}`
}
func run(args []string, input string) (int, string, string) {
var out, diagnostic bytes.Buffer
code := Run(args, strings.NewReader(input), &out, &diagnostic, func() time.Time { return time.Date(2026, 9, 25, 12, 0, 0, 0, time.UTC) })
return code, out.String(), diagnostic.String()
}
func TestPlanAndVerify(t *testing.T) {
code, output, diagnostic := run([]string{"plan"}, fixture())
if code != 0 || diagnostic != "" {
t.Fatalf("plan failed: %d %s", code, diagnostic)
}
var response struct {
ProtocolVersion int `json:"protocolVersion"`
Mode string `json:"mode"`
Executable bool `json:"executable"`
Plan planner.Plan `json:"plan"`
}
if err := json.Unmarshal([]byte(output), &response); err != nil {
t.Fatal(err)
}
if response.Mode != "offline-preview" || response.Executable || response.ProtocolVersion != 1 || response.Plan.ProjectName != "sd-git-one" {
t.Fatalf("misleading plan: %s", output)
}
request, _ := json.Marshal(struct {
Plan planner.Plan `json:"plan"`
Current planner.Intent `json:"current"`
}{response.Plan, response.Plan.Intent})
code, output, diagnostic = run([]string{"verify-plan"}, string(request))
if code != 0 || diagnostic != "" || !strings.Contains(output, `"valid":true`) {
t.Fatalf("verify failed: %d %s %s", code, output, diagnostic)
}
response.Plan.Hash = "tampered"
request, _ = json.Marshal(struct {
Plan planner.Plan `json:"plan"`
Current planner.Intent `json:"current"`
}{response.Plan, response.Plan.Intent})
code, _, _ = run([]string{"verify-plan"}, string(request))
if code == 0 {
t.Fatal("accepted tampered plan")
}
}
func TestRejectsAmbiguousAndOversizedInput(t *testing.T) {
cases := []string{
``, `{}`, `null`, `[]`,
strings.TrimSuffix(fixture(), "}") + `,"password":"do-not-echo"}`,
strings.TrimSuffix(fixture(), "}") + `,"hostId":"other"}`,
strings.TrimSuffix(fixture(), "}") + `,"HostId":"other"}`,
strings.Replace(fixture(), `"hostId"`, `"HostId"`, 1),
strings.Replace(fixture(), `"protocolVersion":1`, `"protocolVersion":null`, 1),
fixture() + ` {}`, fixture() + ` trailing`,
strings.Repeat(" ", 1024*1024) + fixture(),
}
for _, input := range cases {
code, out, diagnostic := run([]string{"plan"}, input)
if code == 0 || out != "" || diagnostic == "" {
t.Fatalf("invalid input accepted: code=%d", code)
}
if strings.Contains(diagnostic, "do-not-echo") {
t.Fatal("secret leaked")
}
}
}
func TestRejectsWriteCommandsAndUnexpectedArguments(t *testing.T) {
for _, args := range [][]string{nil, {"apply"}, {"upgrade"}, {"restore"}, {"plan", "extra"}, {"version", "extra"}} {
code, out, _ := run(args, fixture())
if code == 0 || out != "" {
t.Fatalf("accepted command %v", args)
}
}
}
func TestVersion(t *testing.T) {
code, out, diagnostic := run([]string{"version"}, "")
if code != 0 || diagnostic != "" || !json.Valid([]byte(out)) {
t.Fatal("version failed")
}
}
func TestInspectIsReadOnlyAndExplicitlyIncomplete(t *testing.T) {
code, out, diagnostics := run([]string{"inspect"}, "")
if code != 0 || diagnostics != "" {
t.Fatalf("inspect failed: %s", diagnostics)
}
var report struct {
ProtocolVersion int `json:"protocolVersion"`
OS string `json:"os"`
DeploymentReady bool `json:"deploymentReady"`
DockerDaemon string `json:"dockerDaemon"`
}
if err := json.Unmarshal([]byte(out), &report); err != nil {
t.Fatal(err)
}
if report.ProtocolVersion != 1 || report.OS == "" || report.DeploymentReady || report.DockerDaemon != "not_checked" {
t.Fatalf("misleading report: %s", out)
}
}
func TestNestedVerificationRejectsDuplicateAndNullFields(t *testing.T) {
_, output, _ := run([]string{"plan"}, fixture())
var response struct {
Plan json.RawMessage `json:"plan"`
}
if err := json.Unmarshal([]byte(output), &response); err != nil {
t.Fatal(err)
}
valid := `{"plan":` + string(response.Plan) + `,"current":` + fixture() + `}`
for _, input := range []string{
strings.Replace(valid, `"projectName":"sd-git-one"`, `"projectName":"sd-git-one","projectName":"sd-git-one"`, 1),
strings.Replace(valid, `"createdAt":"2026-09-25T12:00:00Z"`, `"createdAt":null`, 1),
strings.Replace(valid, `"intent":`, `"Intent":`, 1),
} {
code, out, _ := run([]string{"verify-plan"}, input)
if code == 0 || out != "" {
t.Fatal("accepted ambiguous nested request")
}
}
}
func TestVerificationRequiresCanonicalUTCTimestamps(t *testing.T) {
_, output, _ := run([]string{"plan"}, fixture())
var response struct {
Plan json.RawMessage `json:"plan"`
}
if err := json.Unmarshal([]byte(output), &response); err != nil {
t.Fatal(err)
}
valid := `{"plan":` + string(response.Plan) + `,"current":` + fixture() + `}`
for _, timestamp := range []string{
"2026-09-25T12:00:00,000Z",
"2026-09-25T12:00:00.000Z",
"2026-09-25T12:00:00+00:00",
"2026-09-26T12:00:00+24:00",
"2026-09-25T13:00:00+00:60",
} {
t.Run(timestamp, func(t *testing.T) {
input := strings.Replace(valid, "2026-09-25T12:00:00Z", timestamp, 1)
code, out, _ := run([]string{"verify-plan"}, input)
if code == 0 || out != "" {
t.Fatal("accepted noncanonical timestamp")
}
})
}
}
type brokenReader struct{}
func (brokenReader) Read([]byte) (int, error) { return 0, errors.New("secret reader error") }
type brokenWriter struct{}
func (brokenWriter) Write([]byte) (int, error) { return 0, errors.New("secret writer error") }
func TestIOErrorsFailWithoutLeakingDetails(t *testing.T) {
var out, diagnostics bytes.Buffer
if Run([]string{"plan"}, brokenReader{}, &out, &diagnostics, time.Now) == 0 {
t.Fatal("ignored read error")
}
if out.Len() != 0 || strings.Contains(diagnostics.String(), "secret") {
t.Fatal("leaked input error")
}
diagnostics.Reset()
if Run([]string{"version"}, strings.NewReader(""), brokenWriter{}, &diagnostics, time.Now) == 0 {
t.Fatal("ignored write error")
}
if strings.Contains(diagnostics.String(), "secret") {
t.Fatal("leaked output error")
}
}