carlos / platform Public

Merge branch 'feat-maintenance-worker' into main

claude agent, operated by paul@keymail.dev pushed by james@keymail.dev bf4c83aba65799d4ef20cb773643a8d268bd2a4b parent 006e782parent 717bdd8
15 files changed, +1385 −0
  • implementation/maintenance-worker-progress.json +25 −0
  • internal/cgroup/strict_linux.go +13 −0
  • internal/cgroup/strict_linux_test.go +25 −0
  • internal/cgroup/strict_other.go +1 −0
  • internal/maintenance/launch.go +221 −0
  • internal/maintenance/launch_test.go +274 −0
  • internal/maintenance/launch_utf8.go +48 −0
  • internal/maintenance/testdata/generate-launch-vector.py +44 −0
  • internal/maintenance/testdata/launch-v2-binding.json +1 −0
  • internal/maintenance/testdata/launch-v2.json +29 −0
  • internal/maintenanceexec/errors.go +6 −0
  • internal/maintenanceexec/pipes_linux.go +33 −0
  • internal/maintenanceexec/run_linux.go +287 −0
  • internal/maintenanceexec/run_linux_test.go +362 −0
  • internal/maintenanceexec/run_other.go +16 −0

CI passed — run details

diff --git a/implementation/maintenance-worker-progress.json b/implementation/maintenance-worker-progress.json
new file mode 100644
index 000000000..23c22af22
--- /dev/null
+++ b/implementation/maintenance-worker-progress.json
@@ -0,0 +1,25 @@
+{
+ "scope": "Complete supported maintenance dispatch needed by Oficina provisioning; no live invocation until all approved launch gates are implemented and reviewed",
+ "base": "006e7825",
+ "current_slice": "Strict private launch codec and inert Linux subprocess supervisor; no worker mounting",
+ "authority": "Decoded context is metadata only. It cannot construct a retained lease, executor, lifecycle claim, approval or trusted invocation.",
+ "remaining": [
+ "Native cgroup/lease integration evidence for reviewed subprocess supervisor",
+ "Retained worker lease/executor and closed-checkpoint preparation proof",
+ "Home startup adapter binding signed input, image, descriptors and opened database",
+ "Owner-reviewed prepare/approval/status/result/retry/resume CLI and console workflow",
+ "Private encrypted request/result publication with authenticated worker provenance",
+ "Integrated native tests and Fable review before worker mounting",
+ "Preview owner-approved provisioning, restore drill, and public gateway cutover"
+ ],
+ "verification": [
+ "Required closed fields and exact numeric ranges",
+ "Canonical timestamps, identifiers and binding digests",
+ "UTF-8 and surrogate validity",
+ "8KiB hard limit and overflow detection",
+ "Nested v2 checkpoint evidence bindings",
+ "No command mounting or execution authority from decoded bytes",
+ "Focused race tests for descendants, expired-lease termination, exact private input and descriptor/environment isolation",
+ "Fable approved codec at 01b82bde; supervisor refusal preserved and fixes awaiting re-review"
+ ]
+}
diff --git a/internal/cgroup/strict_linux.go b/internal/cgroup/strict_linux.go
index 7d365038e..4b2557507 100644
--- a/internal/cgroup/strict_linux.go
+++ b/internal/cgroup/strict_linux.go
@@ -266,6 +266,19 @@ func (p *Prepared) Start(cmd *exec.Cmd) error {
return nil
}
+// Populated checks the retained group after the direct child has been reaped.
+func (p *Prepared) Populated() (bool, error) {
+ if p == nil || p.strictPreparedState == nil {
+ return false, ErrStrictState
+ }
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if e := p.identityOK(); e != nil {
+ return false, e
+ }
+ return strictPopulation(p.events)
+}
+
// KillAndEmpty proves kernel population is zero before the caller's deadline.
// Failure retains the pinned cleanup handle and permits another bounded attempt.
func (p *Prepared) KillAndEmpty(ctx context.Context) error {
diff --git a/internal/cgroup/strict_linux_test.go b/internal/cgroup/strict_linux_test.go
index 0d6124a94..03f687a55 100644
--- a/internal/cgroup/strict_linux_test.go
+++ b/internal/cgroup/strict_linux_test.go
@@ -65,6 +65,31 @@ func strictContext(t *testing.T, d time.Duration) context.Context {
t.Cleanup(cancel)
return ctx
}
+
+func TestStrictPopulationUsesRetainedIdentity(t *testing.T) {
+ p := strictFixture(t)
+ for _, populated := range []bool{false, true} {
+ value := "populated 0\nfrozen 0\n"
+ if populated {
+ value = "populated 1\nfrozen 0\n"
+ }
+ if e := strictWrite(p.events, value); e != nil {
+ t.Fatal(e)
+ }
+ if got, e := p.Populated(); e != nil || got != populated {
+ t.Fatal("incorrect population", got, e)
+ }
+ }
+ if e := os.Rename(p.dir.Name(), p.dir.Name()+".old"); e != nil {
+ t.Fatal(e)
+ }
+ if _, e := p.Populated(); e == nil {
+ t.Fatal("replaced cgroup accepted")
+ }
+ if _, e := (&Prepared{}).Populated(); e == nil {
+ t.Fatal("empty handle accepted")
+ }
+}
func TestStrictRejectsUnavailableManagerHostAndTier(t *testing.T) {
for _, m := range []*Manager{nil, {root: t.TempDir()}} {
for _, host := range []string{"ok.example", "../outside", "Upper.example", "", "a/b"} {
diff --git a/internal/cgroup/strict_other.go b/internal/cgroup/strict_other.go
index 0a1b0ed76..b5c10bd57 100644
--- a/internal/cgroup/strict_other.go
+++ b/internal/cgroup/strict_other.go
@@ -13,5 +13,6 @@ func (m *Manager) PrepareStrict(host string, limit Limit) (*Prepared, error) {
return nil, ErrStrictUnsupported
}
func (p *Prepared) Start(cmd *exec.Cmd) error { return ErrStrictUnsupported }
+func (p *Prepared) Populated() (bool, error) { return false, ErrStrictUnsupported }
func (p *Prepared) KillAndEmpty(ctx context.Context) error { return ErrStrictUnsupported }
func (p *Prepared) Close() error { return ErrStrictUnsupported }
diff --git a/internal/maintenance/launch.go b/internal/maintenance/launch.go
new file mode 100644
index 000000000..edc48b0d9
--- /dev/null
+++ b/internal/maintenance/launch.go
@@ -0,0 +1,221 @@
+package maintenance
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "io"
+ "strings"
+ "time"
+ "unicode/utf8"
+
+ "github.com/carlosframework/platform/internal/controlname"
+ "github.com/carlosframework/platform/internal/maintenanceguard"
+)
+
+const MaxLaunchBytes = 8192
+
+var ErrLaunch = errors.New("maintenance: invalid private launch context")
+
+// LaunchContext is private transport metadata, never proof of approval,
+// exclusion, lease ownership or a trusted worker invocation.
+type LaunchContext struct {
+ Version int `json:"version"`
+ Account string `json:"account"`
+ App string `json:"app"`
+ Host string `json:"host"`
+ RequestID string `json:"request_id"`
+ AttemptID string `json:"attempt_id"`
+ RequestDigest string `json:"request_digest"`
+ RecordIdentity string `json:"record_identity"`
+ ArtifactDigest string `json:"artifact_digest"`
+ EnvironmentEnvelopeDigest string `json:"environment_envelope_digest"`
+ GuardGeneration int64 `json:"guard_generation"`
+ InputSHA256 string `json:"input_sha256"`
+ DatabasePath string `json:"database_path"`
+ DatabaseDevice uint64 `json:"database_device"`
+ DatabaseInode uint64 `json:"database_inode"`
+ ExecutionDeadline string `json:"execution_deadline"`
+ ExecutionKind string `json:"execution_kind,omitempty"`
+ OperationID string `json:"operation_id,omitempty"`
+ ExpectedStateSHA256 string `json:"expected_state_sha256,omitempty"`
+ ClosedCheckpoint *ClosedCheckpoint `json:"closed_checkpoint,omitempty"`
+}
+
+type ClosedCheckpoint struct {
+ Version int `json:"version"`
+ PreparationID string `json:"preparation_id"`
+ CompletedAt string `json:"completed_at"`
+ MainLength int64 `json:"main_length"`
+ MainSHA256 string `json:"main_sha256"`
+ EvidenceSHA256 string `json:"evidence_sha256"`
+}
+
+// ReadLaunch requires EOF and consumes at most the bound plus one byte.
+// The worker/adapter must separately bound a reader that can block.
+func ReadLaunch(r io.Reader) (LaunchContext, error) {
+ if r == nil {
+ return LaunchContext{}, ErrLaunch
+ }
+ raw, err := io.ReadAll(io.LimitReader(r, MaxLaunchBytes+1))
+ if err != nil {
+ return LaunchContext{}, ErrLaunch
+ }
+ return DecodeLaunch(raw)
+}
+
+func DecodeLaunch(raw []byte) (LaunchContext, error) {
+ fail := func() (LaunchContext, error) { return LaunchContext{}, ErrLaunch }
+ if len(raw) == 0 || len(raw) > MaxLaunchBytes || !utf8.Valid(raw) || !validSurrogates(raw) {
+ return fail()
+ }
+ fields, err := launchObject(raw)
+ if err != nil {
+ return fail()
+ }
+ var version int
+ if json.Unmarshal(fields["version"], &version) != nil {
+ return fail()
+ }
+ names := strings.Fields("version account app host request_id attempt_id request_digest record_identity artifact_digest environment_envelope_digest guard_generation input_sha256 database_path database_device database_inode execution_deadline")
+ switch version {
+ case 1:
+ case 2:
+ names = append(names, strings.Fields("execution_kind operation_id expected_state_sha256 closed_checkpoint")...)
+ cp, err := launchObject(fields["closed_checkpoint"])
+ if err != nil || !exactLaunchFields(cp, strings.Fields("version preparation_id completed_at main_length main_sha256 evidence_sha256")) {
+ return fail()
+ }
+ default:
+ return fail()
+ }
+ if !exactLaunchFields(fields, names) {
+ return fail()
+ }
+ var c LaunchContext
+ if json.Unmarshal(raw, &c) != nil || !c.valid() {
+ return fail()
+ }
+ return c, nil
+}
+
+func EncodeLaunch(c LaunchContext) ([]byte, error) {
+ if !c.valid() {
+ return nil, ErrLaunch
+ }
+ raw, err := json.Marshal(c)
+ if err != nil || len(raw) > MaxLaunchBytes {
+ return nil, ErrLaunch
+ }
+ return raw, nil
+}
+
+func launchObject(raw []byte) (map[string]json.RawMessage, error) {
+ dec := json.NewDecoder(bytes.NewReader(raw))
+ tok, err := dec.Token()
+ if err != nil || tok != json.Delim('{') {
+ return nil, ErrLaunch
+ }
+ out := map[string]json.RawMessage{}
+ for dec.More() {
+ tok, err = dec.Token()
+ if err != nil {
+ return nil, ErrLaunch
+ }
+ key, ok := tok.(string)
+ if !ok {
+ return nil, ErrLaunch
+ }
+ if _, exists := out[key]; exists {
+ return nil, ErrLaunch
+ }
+ var value json.RawMessage
+ if dec.Decode(&value) != nil || bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
+ return nil, ErrLaunch
+ }
+ out[key] = value
+ }
+ tok, err = dec.Token()
+ if err != nil || tok != json.Delim('}') {
+ return nil, ErrLaunch
+ }
+ if _, err = dec.Token(); err != io.EOF {
+ return nil, ErrLaunch
+ }
+ return out, nil
+}
+func exactLaunchFields(fields map[string]json.RawMessage, names []string) bool {
+ if len(fields) != len(names) {
+ return false
+ }
+ for _, name := range names {
+ if _, ok := fields[name]; !ok {
+ return false
+ }
+ }
+ return true
+}
+func canonicalStamp(s string) (time.Time, bool) {
+ t, err := time.Parse(time.RFC3339Nano, s)
+ return t, err == nil && len(s) <= 30 && t.UTC().Format(time.RFC3339Nano) == s
+}
+func (c LaunchContext) valid() bool {
+ if !controlname.ValidAccount(c.Account) || !controlname.ValidApp(c.App) || !controlname.ValidHostShape(c.Host) {
+ return false
+ }
+ for _, id := range []string{c.RequestID, c.AttemptID} {
+ if !maintenanceguard.ValidID(id) {
+ return false
+ }
+ }
+ for _, digest := range []string{c.RequestDigest, c.RecordIdentity, c.ArtifactDigest, c.EnvironmentEnvelopeDigest, c.InputSHA256} {
+ if !maintenanceguard.ValidDigest(digest) {
+ return false
+ }
+ }
+ if c.GuardGeneration <= 0 || c.DatabaseDevice == 0 || c.DatabaseInode == 0 {
+ return false
+ }
+ if len(c.DatabasePath) == 0 || len(c.DatabasePath) > 1024 || c.DatabasePath[0] != '/' || strings.ContainsRune(c.DatabasePath, 0) || !utf8.ValidString(c.DatabasePath) {
+ return false
+ }
+ deadline, ok := canonicalStamp(c.ExecutionDeadline)
+ if !ok {
+ return false
+ }
+ switch c.Version {
+ case 1:
+ return c.ExecutionKind == "" && c.OperationID == "" && c.ExpectedStateSHA256 == "" && c.ClosedCheckpoint == nil
+ case 2:
+ if c.ExecutionKind != "compatibility-check" || !maintenanceguard.ValidID(c.OperationID) || !maintenanceguard.ValidDigest(c.ExpectedStateSHA256) || c.ClosedCheckpoint == nil {
+ return false
+ }
+ p := c.ClosedCheckpoint
+ completed, ok := canonicalStamp(p.CompletedAt)
+ if !ok || completed.After(deadline) || p.Version != 1 || !maintenanceguard.ValidID(p.PreparationID) || p.MainLength <= 0 || p.MainLength > 268435456 || !maintenanceguard.ValidDigest(p.MainSHA256) || !maintenanceguard.ValidDigest(p.EvidenceSHA256) {
+ return false
+ }
+ return p.EvidenceSHA256 == c.checkpointDigest()
+ }
+ return false
+}
+
+// CheckpointDigest correlates private bindings; it does not certify that a
+// checkpoint, writer exclusion or read-only namespace actually exists.
+func (c LaunchContext) CheckpointDigest() (string, error) {
+ if c.Version != 2 || c.ClosedCheckpoint == nil {
+ return "", ErrLaunch
+ }
+ return c.checkpointDigest(), nil
+}
+func (c LaunchContext) checkpointBytes() []byte {
+ p := c.ClosedCheckpoint
+ raw, _ := json.Marshal([]any{"carlos/closed-checkpoint/v1", c.Account, c.App, c.Host, c.OperationID, c.RequestID, c.AttemptID, c.RequestDigest, c.RecordIdentity, c.ArtifactDigest, c.EnvironmentEnvelopeDigest, c.GuardGeneration, c.InputSHA256, c.ExpectedStateSHA256, c.DatabasePath, c.DatabaseDevice, c.DatabaseInode, c.ExecutionDeadline, p.PreparationID, p.CompletedAt, p.MainLength, p.MainSHA256})
+ return raw
+}
+func (c LaunchContext) checkpointDigest() string {
+ sum := sha256.Sum256(c.checkpointBytes())
+ return hex.EncodeToString(sum[:])
+}
diff --git a/internal/maintenance/launch_test.go b/internal/maintenance/launch_test.go
new file mode 100644
index 000000000..51faa7c22
--- /dev/null
+++ b/internal/maintenance/launch_test.go
@@ -0,0 +1,274 @@
+package maintenance
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "io"
+ "os"
+ "strings"
+ "testing"
+)
+
+func launchFixture(t *testing.T) ([]byte, LaunchContext) {
+ t.Helper()
+ raw, err := os.ReadFile("testdata/launch-v2.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ c, err := DecodeLaunch(raw)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return raw, c
+}
+func TestLaunchExactIntegersAndIndependentCheckpointVector(t *testing.T) {
+ _, c := launchFixture(t)
+ if c.DatabaseDevice != ^uint64(0) || c.DatabaseInode != 9007199254740993 || c.GuardGeneration != 9223372036854775807 {
+ t.Fatal("integer precision lost")
+ }
+ raw, err := EncodeLaunch(c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ again, err := ReadLaunch(bytes.NewReader(raw))
+ if err != nil || again.DatabaseDevice != c.DatabaseDevice || again.DatabaseInode != c.DatabaseInode {
+ t.Fatal("round trip lost identity", err)
+ }
+ binding, err := os.ReadFile("testdata/launch-v2-binding.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Contains(binding, []byte(`18446744073709551615`)) || !bytes.Contains(binding, []byte(`\u003chome\u003e\u0026`)) {
+ t.Fatal("independent vector lacks precision/escaping cases")
+ }
+ if !bytes.Equal(binding, c.checkpointBytes()) {
+ t.Fatal("Go binding differs from independent Python vector")
+ }
+ digest := sha256.Sum256(binding)
+ if hex.EncodeToString(digest[:]) != c.ClosedCheckpoint.EvidenceSHA256 {
+ t.Fatal("independent vector digest mismatch")
+ }
+ c.Version = 1
+ c.ExecutionKind = ""
+ c.OperationID = ""
+ c.ExpectedStateSHA256 = ""
+ c.ClosedCheckpoint = nil
+ raw, err = EncodeLaunch(c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := DecodeLaunch(raw); err != nil {
+ t.Fatal(err)
+ }
+ c.ExecutionKind = "compatibility-check"
+ if _, err := EncodeLaunch(c); err == nil {
+ t.Fatal("v1 accepted checker fields")
+ }
+}
+func TestLaunchRefusesEachMissingOrAmbiguousField(t *testing.T) {
+ raw, _ := launchFixture(t)
+ var fields map[string]json.RawMessage
+ if err := json.Unmarshal(raw, &fields); err != nil {
+ t.Fatal(err)
+ }
+ for key := range fields {
+ t.Run(key, func(t *testing.T) {
+ var f map[string]json.RawMessage
+ json.Unmarshal(raw, &f)
+ delete(f, key)
+ bad, _ := json.Marshal(f)
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("missing field accepted")
+ }
+ f[strings.ToUpper(key)] = fields[key]
+ bad, _ = json.Marshal(f)
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("case alias accepted")
+ }
+ f[key] = json.RawMessage("null")
+ delete(f, strings.ToUpper(key))
+ bad, _ = json.Marshal(f)
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("null accepted")
+ }
+ })
+ }
+ for _, bad := range [][]byte{append(append([]byte{}, raw...), []byte(` {}`)...), append([]byte(`{"version":2,`), bytes.TrimSpace(raw)[1:]...), bytes.Replace(raw, []byte(`"database_inode": 9007199254740993`), []byte(`"database_inode": 18446744073709551616`), 1), bytes.Replace(raw, []byte(`"guard_generation": 9223372036854775807`), []byte(`"guard_generation": 1e3`), 1), bytes.Replace(raw, []byte(`"database_inode": 9007199254740993`), []byte(`"database_inode": 1.0`), 1), bytes.Replace(raw, []byte(`"version": 2`), []byte(`"version": 2.0`), 1), bytes.Replace(raw, []byte(`"main_length": 268435456`), []byte(`"main_length": 268435457`), 1)} {
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatalf("malformed context accepted: %s", bad)
+ }
+ }
+}
+func TestLaunchCheckpointCannotBeTransplanted(t *testing.T) {
+ _, c := launchFixture(t)
+ raw, _ := EncodeLaunch(c)
+ var fields map[string]json.RawMessage
+ json.Unmarshal(raw, &fields)
+ for key := range fields {
+ if key == "closed_checkpoint" || key == "version" || key == "execution_kind" {
+ continue
+ }
+ t.Run(key, func(t *testing.T) {
+ var f map[string]json.RawMessage
+ json.Unmarshal(raw, &f)
+ var value any
+ switch key {
+ case "account":
+ value = "abc"
+ case "app":
+ value = "docs"
+ case "host":
+ value = "other.bds.oncarlos.com"
+ case "request_id", "attempt_id", "operation_id":
+ value = strings.Repeat("A", 43)
+ case "database_path":
+ value = "/other/home.db"
+ case "execution_deadline":
+ value = "2026-09-11T21:00:00Z"
+ case "guard_generation", "database_device", "database_inode":
+ value = 2
+ default:
+ value = strings.Repeat("a", 64)
+ }
+ f[key], _ = json.Marshal(value)
+ bad, _ := json.Marshal(f)
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("changed valid binding accepted with old evidence")
+ }
+ var changed LaunchContext
+ json.Unmarshal(bad, &changed)
+ changed.ClosedCheckpoint.EvidenceSHA256 = changed.checkpointDigest()
+ if _, err := EncodeLaunch(changed); err != nil {
+ t.Fatal("mutation was not valid independent of its evidence", err)
+ }
+ })
+ }
+ for _, mutate := range []func(*ClosedCheckpoint){func(p *ClosedCheckpoint) { p.PreparationID = strings.Repeat("A", 43) }, func(p *ClosedCheckpoint) { p.MainLength-- }, func(p *ClosedCheckpoint) { p.MainSHA256 = strings.Repeat("a", 64) }, func(p *ClosedCheckpoint) { p.CompletedAt = "2026-09-11T19:59:58Z" }} {
+ _, v := launchFixture(t)
+ mutate(v.ClosedCheckpoint)
+ b, _ := json.Marshal(v)
+ if _, err := DecodeLaunch(b); err == nil {
+ t.Fatal("changed preparation accepted")
+ }
+ }
+}
+func TestLaunchSizeUTF8AndSurrogates(t *testing.T) {
+ _, c := launchFixture(t)
+ c.Version = 1
+ c.ExecutionKind = ""
+ c.OperationID = ""
+ c.ExpectedStateSHA256 = ""
+ c.ClosedCheckpoint = nil
+ raw, err := EncodeLaunch(c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ exact := append(bytes.Repeat([]byte(" "), MaxLaunchBytes-len(raw)), raw...)
+ if _, err := DecodeLaunch(exact); err != nil {
+ t.Fatal("exact bound refused", err)
+ }
+ r := bytes.NewReader(append(exact, []byte("xx")...))
+ if _, err := ReadLaunch(r); err == nil || r.Len() != 1 {
+ t.Fatal("overflow bound", err, r.Len())
+ }
+ for _, path := range []string{`"/bad\ud800"`, `"/bad\udc00"`, `"/bad\ud800\u0001"`, `"/bad` + string([]byte{0xff}) + `"`} {
+ oldPath, _ := json.Marshal(c.DatabasePath)
+ bad := bytes.Replace(raw, oldPath, []byte(path), 1)
+ if bytes.Equal(bad, raw) {
+ t.Fatal("mutation missed path")
+ }
+
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("invalid string accepted")
+ }
+ }
+ c.DatabasePath = "/valid/😀/�/quoted\".db"
+ raw, err = EncodeLaunch(c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ raw = bytes.ReplaceAll(raw, []byte("😀"), []byte(`\ud83d\ude00`))
+ if _, err := DecodeLaunch(raw); err != nil {
+ t.Fatal("valid surrogate pair refused", err)
+ }
+ if _, err := ReadLaunch(io.MultiReader(bytes.NewReader(raw), errReader{})); err == nil {
+ t.Fatal("read error accepted")
+ }
+}
+
+type errReader struct{}
+
+func (errReader) Read([]byte) (int, error) { return 0, io.ErrUnexpectedEOF }
+
+func TestNestedLaunchFieldsAreClosed(t *testing.T) {
+ raw, _ := launchFixture(t)
+ var top, fields map[string]json.RawMessage
+ json.Unmarshal(raw, &top)
+ original := top["closed_checkpoint"]
+ json.Unmarshal(original, &fields)
+ refuse := func(b json.RawMessage) {
+ t.Helper()
+ top["closed_checkpoint"] = b
+ bad, _ := json.Marshal(top)
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("ambiguous checkpoint accepted")
+ }
+ }
+ for key, value := range fields {
+ t.Run(key, func(t *testing.T) {
+ var f map[string]json.RawMessage
+ json.Unmarshal(original, &f)
+ delete(f, key)
+ bad, _ := json.Marshal(f)
+ refuse(bad)
+ f[strings.ToUpper(key)] = value
+ bad, _ = json.Marshal(f)
+ refuse(bad)
+ delete(f, strings.ToUpper(key))
+ f[key] = json.RawMessage("null")
+ bad, _ = json.Marshal(f)
+ refuse(bad)
+ prefix, _ := json.Marshal(key)
+ dup := append(append(append([]byte{'{'}, prefix...), ':'), value...)
+ dup = append(dup, ',')
+ dup = append(dup, bytes.TrimSpace(original)[1:]...)
+ refuse(dup)
+ })
+ }
+ var f map[string]json.RawMessage
+ json.Unmarshal(original, &f)
+ f["extra"] = json.RawMessage("1")
+ bad, _ := json.Marshal(f)
+ refuse(bad)
+}
+func TestLaunchNumericAndTimestampLexicalForms(t *testing.T) {
+ raw, c := launchFixture(t)
+ for _, key := range []string{"version", "guard_generation", "database_device", "database_inode"} {
+ for _, value := range []string{"0", "-0", "-1", "1.0", "1e0", "18446744073709551616"} {
+ var f map[string]json.RawMessage
+ json.Unmarshal(raw, &f)
+ f[key] = json.RawMessage(value)
+ bad, _ := json.Marshal(f)
+ if _, err := DecodeLaunch(bad); err == nil {
+ t.Fatal("noncanonical integer accepted", key, value)
+ }
+ }
+ }
+ for _, stamp := range []string{"2026-09-11T20:00:00+00:00", "2026-09-11T20:00:00.120Z", "2026-09-11t20:00:00Z", "2026-09-11T20:00:00z"} {
+ v := c
+ v.ExecutionDeadline = stamp
+ v.ClosedCheckpoint = &ClosedCheckpoint{}
+ *v.ClosedCheckpoint = *c.ClosedCheckpoint
+ v.ClosedCheckpoint.EvidenceSHA256 = v.checkpointDigest()
+ if _, err := EncodeLaunch(v); err == nil {
+ t.Fatal("noncanonical timestamp accepted", stamp)
+ }
+ }
+ c.ClosedCheckpoint.CompletedAt = c.ExecutionDeadline
+ c.ClosedCheckpoint.EvidenceSHA256 = c.checkpointDigest()
+ if _, err := EncodeLaunch(c); err != nil {
+ t.Fatal("equal completion/deadline refused", err)
+ }
+}
diff --git a/internal/maintenance/launch_utf8.go b/internal/maintenance/launch_utf8.go
new file mode 100644
index 000000000..ba1cdb955
--- /dev/null
+++ b/internal/maintenance/launch_utf8.go
@@ -0,0 +1,48 @@
+package maintenance
+
+import "strconv"
+
+// encoding/json replaces unpaired UTF-16 escapes with U+FFFD. Refuse that
+// lossy conversion before decoding identities and paths.
+func validSurrogates(raw []byte) bool {
+ quoted := false
+ for i := 0; i < len(raw); i++ {
+ if raw[i] == '"' {
+ quoted = !quoted
+ continue
+ }
+ if !quoted || raw[i] != '\\' {
+ continue
+ }
+ i++
+ if i >= len(raw) {
+ return false
+ }
+ if raw[i] != 'u' {
+ continue
+ }
+ if i+4 >= len(raw) {
+ return false
+ }
+ n, err := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16)
+ if err != nil {
+ return false
+ }
+ i += 4
+ if n >= 0xdc00 && n <= 0xdfff {
+ return false
+ }
+ if n < 0xd800 || n > 0xdbff {
+ continue
+ }
+ if i+6 >= len(raw) || raw[i+1] != '\\' || raw[i+2] != 'u' {
+ return false
+ }
+ low, err := strconv.ParseUint(string(raw[i+3:i+7]), 16, 16)
+ if err != nil || low < 0xdc00 || low > 0xdfff {
+ return false
+ }
+ i += 6
+ }
+ return true
+}
diff --git a/internal/maintenance/testdata/generate-launch-vector.py b/internal/maintenance/testdata/generate-launch-vector.py
new file mode 100644
index 000000000..81b4481d3
--- /dev/null
+++ b/internal/maintenance/testdata/generate-launch-vector.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Generate the launch binding with Python integers, independent of Go code."""
+import base64
+import hashlib
+import json
+from pathlib import Path
+
+root = Path(__file__).parent
+identity = lambda n: base64.urlsafe_b64encode(bytes([n]) * 32).decode().rstrip('=')
+c = {
+ 'version': 2, 'account': 'bds', 'app': 'home',
+ 'host': 'overnight-home.bds.oncarlos.com',
+ 'request_id': identity(1), 'attempt_id': identity(2),
+ 'request_digest': '1' * 64, 'record_identity': '2' * 64,
+ 'artifact_digest': '3' * 64, 'environment_envelope_digest': '4' * 64,
+ 'guard_generation': 9223372036854775807, 'input_sha256': '5' * 64,
+ 'database_path': '/data/<home>&/☃.db',
+ 'database_device': 18446744073709551615, 'database_inode': 9007199254740993,
+ 'execution_deadline': '2026-09-11T20:00:00.123456789Z',
+ 'execution_kind': 'compatibility-check', 'operation_id': identity(3),
+ 'expected_state_sha256': '6' * 64,
+ 'closed_checkpoint': {
+ 'version': 1, 'preparation_id': identity(4),
+ 'completed_at': '2026-09-11T19:59:59.123456789Z',
+ 'main_length': 268435456, 'main_sha256': '7' * 64,
+ 'evidence_sha256': '',
+ },
+}
+p = c['closed_checkpoint']
+values = ['carlos/closed-checkpoint/v1'] + [c[k] for k in [
+ 'account', 'app', 'host', 'operation_id', 'request_id', 'attempt_id',
+ 'request_digest', 'record_identity', 'artifact_digest',
+ 'environment_envelope_digest', 'guard_generation', 'input_sha256',
+ 'expected_state_sha256', 'database_path', 'database_device',
+ 'database_inode', 'execution_deadline',
+]] + [p[k] for k in ['preparation_id', 'completed_at', 'main_length', 'main_sha256']]
+binding = json.dumps(values, ensure_ascii=False, separators=(',', ':'))
+for char, escaped in [('<', '\\u003c'), ('>', '\\u003e'), ('&', '\\u0026'),
+ ('\u2028', '\\u2028'), ('\u2029', '\\u2029')]:
+ binding = binding.replace(char, escaped)
+binding = binding.encode()
+p['evidence_sha256'] = hashlib.sha256(binding).hexdigest()
+(root / 'launch-v2.json').write_text(json.dumps(c, ensure_ascii=False, indent=2) + '\n')
+(root / 'launch-v2-binding.json').write_bytes(binding)
diff --git a/internal/maintenance/testdata/launch-v2-binding.json b/internal/maintenance/testdata/launch-v2-binding.json
new file mode 100644
index 000000000..5b09c62b4
--- /dev/null
+++ b/internal/maintenance/testdata/launch-v2-binding.json
@@ -0,0 +1 @@
+["carlos/closed-checkpoint/v1","bds","home","overnight-home.bds.oncarlos.com","AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM","AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE","AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI","1111111111111111111111111111111111111111111111111111111111111111","2222222222222222222222222222222222222222222222222222222222222222","3333333333333333333333333333333333333333333333333333333333333333","4444444444444444444444444444444444444444444444444444444444444444",9223372036854775807,"5555555555555555555555555555555555555555555555555555555555555555","6666666666666666666666666666666666666666666666666666666666666666","/data/\u003chome\u003e\u0026/☃.db",18446744073709551615,9007199254740993,"2026-09-11T20:00:00.123456789Z","BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ","2026-09-11T19:59:59.123456789Z",268435456,"7777777777777777777777777777777777777777777777777777777777777777"]
\ No newline at end of file
diff --git a/internal/maintenance/testdata/launch-v2.json b/internal/maintenance/testdata/launch-v2.json
new file mode 100644
index 000000000..39fd33ff7
--- /dev/null
+++ b/internal/maintenance/testdata/launch-v2.json
@@ -0,0 +1,29 @@
+{
+ "version": 2,
+ "account": "bds",
+ "app": "home",
+ "host": "overnight-home.bds.oncarlos.com",
+ "request_id": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE",
+ "attempt_id": "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI",
+ "request_digest": "1111111111111111111111111111111111111111111111111111111111111111",
+ "record_identity": "2222222222222222222222222222222222222222222222222222222222222222",
+ "artifact_digest": "3333333333333333333333333333333333333333333333333333333333333333",
+ "environment_envelope_digest": "4444444444444444444444444444444444444444444444444444444444444444",
+ "guard_generation": 9223372036854775807,
+ "input_sha256": "5555555555555555555555555555555555555555555555555555555555555555",
+ "database_path": "/data/<home>&/☃.db",
+ "database_device": 18446744073709551615,
+ "database_inode": 9007199254740993,
+ "execution_deadline": "2026-09-11T20:00:00.123456789Z",
+ "execution_kind": "compatibility-check",
+ "operation_id": "AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM",
+ "expected_state_sha256": "6666666666666666666666666666666666666666666666666666666666666666",
+ "closed_checkpoint": {
+ "version": 1,
+ "preparation_id": "BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ",
+ "completed_at": "2026-09-11T19:59:59.123456789Z",
+ "main_length": 268435456,
+ "main_sha256": "7777777777777777777777777777777777777777777777777777777777777777",
+ "evidence_sha256": "3dd1eda296f15c602461c856bd344fb4dacc07780c6bfd33f9f756c6a6c34dda"
+ }
+}
diff --git a/internal/maintenanceexec/errors.go b/internal/maintenanceexec/errors.go
new file mode 100644
index 000000000..818cdcf84
--- /dev/null
+++ b/internal/maintenanceexec/errors.go
@@ -0,0 +1,6 @@
+package maintenanceexec
+
+import "errors"
+
+var ErrRefused = errors.New("maintenance execution: refused")
+var ErrUncertain = errors.New("maintenance execution: process cleanup uncertain")
diff --git a/internal/maintenanceexec/pipes_linux.go b/internal/maintenanceexec/pipes_linux.go
new file mode 100644
index 000000000..2198881bf
--- /dev/null
+++ b/internal/maintenanceexec/pipes_linux.go
@@ -0,0 +1,33 @@
+//go:build linux
+
+package maintenanceexec
+
+import "os"
+
+type processPipes struct{ inR, inW, outR, outW, contextR, contextW *os.File }
+
+func newPipes() (p *processPipes, err error) {
+ p = &processPipes{}
+ defer func() {
+ if err != nil {
+ p.close()
+ }
+ }()
+ if p.inR, p.inW, err = os.Pipe(); err != nil {
+ return p, err
+ }
+ if p.outR, p.outW, err = os.Pipe(); err != nil {
+ return p, err
+ }
+ if p.contextR, p.contextW, err = os.Pipe(); err != nil {
+ return p, err
+ }
+ return p, nil
+}
+func (p *processPipes) close() {
+ for _, f := range []*os.File{p.inR, p.inW, p.outR, p.outW, p.contextR, p.contextW} {
+ if f != nil {
+ f.Close()
+ }
+ }
+}
diff --git a/internal/maintenanceexec/run_linux.go b/internal/maintenanceexec/run_linux.go
new file mode 100644
index 000000000..252662fe6
--- /dev/null
+++ b/internal/maintenanceexec/run_linux.go
@@ -0,0 +1,287 @@
+//go:build linux
+
+// Package maintenanceexec supplies process and pipe mechanics. Its result is
+// not an approval, database-compatibility receipt or permission to resume.
+package maintenanceexec
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "os"
+ "os/exec"
+ "time"
+
+ "github.com/carlosframework/platform/internal/cgroup"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/maintenance"
+ "golang.org/x/sys/unix"
+)
+
+const stopMargin = 2 * time.Second
+
+type containment interface {
+ Start(*exec.Cmd) error
+ Populated() (bool, error)
+ KillAndEmpty(context.Context) error
+}
+type ownership interface {
+ SafeUntil() (time.Time, error)
+ Lost() <-chan struct{}
+}
+
+// Run requires concrete retained containment and lease handles. The caller
+// must already hold the lifecycle fence, checked approval and verified release
+// and environment snapshots. This package exposes no HTTP or CLI entry point.
+// Artifact, containment and lease ownership remain with the caller on return.
+func Run(ctx context.Context, group *cgroup.Prepared, held *lease.Held, artifact *os.File, launch maintenance.LaunchContext, input maintenance.Input, environment []string) ([]byte, error) {
+ if group == nil || held == nil {
+ return nil, ErrRefused
+ }
+ return run(ctx, group, held, artifact, launch, input, environment)
+}
+
+func run(ctx context.Context, group containment, held ownership, artifact *os.File, launch maintenance.LaunchContext, input maintenance.Input, environment []string) (result []byte, err error) {
+ if ctx == nil || ctx.Err() != nil || artifact == nil || launch.Version != 1 || len(input.Arguments) > maintenance.MaxArguments || len(input.Stdin) > maintenance.MaxInputBytes {
+ return nil, ErrRefused
+ }
+ input.Arguments = append([]string(nil), input.Arguments...)
+ input.Stdin = append([]byte(nil), input.Stdin...)
+ defer clear(input.Stdin)
+ raw, e := maintenance.EncodeLaunch(launch)
+ if e != nil {
+ return nil, ErrRefused
+ }
+ defer clear(raw)
+ frame, e := maintenance.EncodeInput(input)
+ if e != nil {
+ return nil, ErrRefused
+ }
+ clear(frame)
+ sum := sha256.Sum256(input.Stdin)
+ if hex.EncodeToString(sum[:]) != launch.InputSHA256 {
+ return nil, ErrRefused
+ }
+ executionDeadline, _ := time.Parse(time.RFC3339Nano, launch.ExecutionDeadline)
+ until, e := held.SafeUntil()
+ if e != nil {
+ return nil, ErrRefused
+ }
+ if !time.Now().Add(stopMargin).Before(until) || !time.Now().Add(stopMargin).Before(executionDeadline) {
+ return nil, ErrRefused
+ }
+ hashDeadline := until.Add(-stopMargin)
+ if executionDeadline.Add(-stopMargin).Before(hashDeadline) {
+ hashDeadline = executionDeadline.Add(-stopMargin)
+ }
+ hashCtx, cancelHash := context.WithDeadline(ctx, hashDeadline)
+ e = checkArtifact(hashCtx, artifact, launch.ArtifactDigest)
+ cancelHash()
+ if e != nil {
+ return nil, ErrRefused
+ }
+ pipes, e := newPipes()
+ if e != nil {
+ return nil, ErrRefused
+ }
+ defer pipes.close()
+ null, e := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+ if e != nil {
+ return nil, ErrRefused
+ }
+ defer null.Close()
+ cmd := exec.Command("/proc/self/fd/4", input.Arguments...)
+ cmd.Env = append([]string{}, environment...)
+ cmd.Dir = "/"
+ cmd.Stdin = pipes.inR
+ cmd.Stdout = pipes.outW
+ cmd.Stderr = null
+ cmd.ExtraFiles = []*os.File{pipes.contextR, artifact}
+ if until, e = held.SafeUntil(); e != nil || ctx.Err() != nil || !time.Now().Add(stopMargin).Before(until) || !time.Now().Add(stopMargin).Before(executionDeadline) {
+ return nil, ErrRefused
+ }
+ if e = group.Start(cmd); e != nil {
+ stopErr := stop(group, until)
+ if stopErr != nil {
+ return nil, ErrUncertain
+ }
+ return nil, ErrRefused
+ }
+ pipes.inR.Close()
+ pipes.outW.Close()
+ pipes.contextR.Close()
+ cleanupDone := false
+ defer func() {
+ if cleanupDone {
+ return
+ }
+ pipes.close()
+ if stop(group, until) != nil {
+ err = ErrUncertain
+ result = nil
+ }
+ }()
+ events := make(chan pipeEvent, 4)
+ go writePipe(pipes.inW, append([]byte(nil), input.Stdin...), "input", events)
+ go writePipe(pipes.contextW, append([]byte(nil), raw...), "context", events)
+ go func() {
+ b, e := io.ReadAll(io.LimitReader(pipes.outR, maintenance.MaxResultBytes+1))
+ if len(b) > maintenance.MaxResultBytes {
+ clear(b)
+ b = nil
+ e = ErrRefused
+ }
+ events <- pipeEvent{"output", b, e}
+ }()
+ go func() { events <- pipeEvent{"wait", nil, cmd.Wait()} }()
+ tick := time.NewTicker(10 * time.Millisecond)
+ defer tick.Stop()
+ left := 4
+ failed := false
+ waited := false
+ for left > 0 {
+ select {
+ case event := <-events:
+ left--
+ if event.kind == "wait" {
+ waited = true
+ }
+ if event.err != nil {
+ failed = true
+ }
+ if event.kind == "output" {
+ result = event.data
+ }
+ case <-ctx.Done():
+ failed = true
+ case <-held.Lost():
+ failed = true
+ case <-tick.C:
+ next, e := held.SafeUntil()
+ if e != nil {
+ failed = true
+ } else {
+ until = next
+ }
+ now := time.Now()
+ if !now.Add(stopMargin).Before(until) || !now.Add(stopMargin).Before(executionDeadline) {
+ failed = true
+ }
+ }
+ if failed || waited {
+ if waited {
+ populated, e := group.Populated()
+ if e != nil || populated {
+ failed = true
+ }
+ }
+ e := stop(group, until)
+ if e != nil {
+ clear(result)
+ return nil, ErrUncertain
+ }
+ cleanupDone = true
+ // Closing parent pipe endpoints also releases blocked I/O after the
+ // kernel has proved the child tree empty.
+ if failed {
+ pipes.close()
+ }
+ for left > 0 {
+ select {
+ case event := <-events:
+ left--
+ if event.err != nil {
+ failed = true
+ }
+ if event.kind == "output" {
+ result = event.data
+ }
+ case <-time.After(time.Until(until)):
+ clear(result)
+ return nil, ErrUncertain
+ }
+ }
+ break
+ }
+ }
+ if !cleanupDone {
+ e := stop(group, until)
+ if e != nil {
+ clear(result)
+ return nil, ErrUncertain
+ }
+ cleanupDone = true
+ }
+ if _, e = held.SafeUntil(); e != nil || ctx.Err() != nil || !time.Now().Before(executionDeadline) {
+ failed = true
+ }
+ if failed || len(result) == 0 {
+ clear(result)
+ return nil, ErrRefused
+ }
+ return result, nil
+}
+
+func stop(group containment, until time.Time) error {
+ deadline := time.Now().Add(stopMargin)
+ if until.After(deadline) {
+ deadline = until
+ }
+ ctx, cancel := context.WithDeadline(context.Background(), deadline)
+ defer cancel()
+ err := group.KillAndEmpty(ctx)
+ if err != nil || !time.Now().Before(until) {
+ return ErrUncertain
+ }
+ return nil
+}
+
+type pipeEvent struct {
+ kind string
+ data []byte
+ err error
+}
+
+func writePipe(f *os.File, data []byte, kind string, events chan<- pipeEvent) {
+ defer clear(data)
+ _, err := f.Write(data)
+ closeErr := f.Close()
+ if err == nil {
+ err = closeErr
+ }
+ events <- pipeEvent{kind: kind, err: err}
+}
+func checkArtifact(ctx context.Context, f *os.File, want string) error {
+ flags, e := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0)
+ if e != nil || flags&unix.O_ACCMODE != unix.O_RDONLY {
+ return ErrRefused
+ }
+ st, e := f.Stat()
+ if e != nil || !st.Mode().IsRegular() || st.Size() <= 0 {
+ return ErrRefused
+ }
+ hash := sha256.New()
+ reader := io.NewSectionReader(f, 0, st.Size())
+ buf := make([]byte, 64<<10)
+ for {
+ if ctx.Err() != nil {
+ return ErrRefused
+ }
+ n, e := reader.Read(buf)
+ if n > 0 {
+ hash.Write(buf[:n])
+ }
+ if e == io.EOF {
+ break
+ }
+ if e != nil {
+ return ErrRefused
+ }
+ }
+ after, e := f.Stat()
+ if e != nil || after.Size() != st.Size() || !os.SameFile(st, after) || hex.EncodeToString(hash.Sum(nil)) != want {
+ return ErrRefused
+ }
+ return nil
+}
diff --git a/internal/maintenanceexec/run_linux_test.go b/internal/maintenanceexec/run_linux_test.go
new file mode 100644
index 000000000..9b65cf118
--- /dev/null
+++ b/internal/maintenanceexec/run_linux_test.go
@@ -0,0 +1,362 @@
+//go:build linux
+
+package maintenanceexec
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/maintenance"
+ "golang.org/x/sys/unix"
+)
+
+// This fixture exercises actual exec and pipe behavior. Its process-group
+// cleanup is a test substitute, not native cgroup containment evidence.
+type fixtureGroup struct {
+ cmd *exec.Cmd
+ starts, kills int
+ startErr, killErr error
+ before func()
+}
+
+func (g *fixtureGroup) Start(c *exec.Cmd) error {
+ g.starts++
+ g.cmd = c
+ if g.before != nil {
+ g.before()
+ }
+ if g.startErr != nil {
+ return g.startErr
+ }
+ c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ return c.Start()
+}
+func (g *fixtureGroup) Populated() (bool, error) {
+ if g.cmd == nil || g.cmd.Process == nil {
+ return false, nil
+ }
+ entries, err := os.ReadDir("/proc")
+ if err != nil {
+ return false, err
+ }
+ for _, entry := range entries {
+ if _, err := strconv.Atoi(entry.Name()); err != nil {
+ continue
+ }
+ raw, err := os.ReadFile("/proc/" + entry.Name() + "/stat")
+ if err != nil {
+ continue
+ }
+ fields := strings.Fields(string(raw)[strings.LastIndexByte(string(raw), ')')+1:])
+ if len(fields) >= 3 && fields[2] == strconv.Itoa(g.cmd.Process.Pid) && fields[0] != "Z" {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+func (g *fixtureGroup) KillAndEmpty(ctx context.Context) error {
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ g.kills++
+ if g.cmd != nil && g.cmd.Process != nil {
+ unix.Kill(-g.cmd.Process.Pid, unix.SIGKILL)
+ }
+ return g.killErr
+}
+
+type fixtureLease struct {
+ mu sync.Mutex
+ until time.Time
+ lost chan struct{}
+ err error
+}
+
+func (l *fixtureLease) SafeUntil() (time.Time, error) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ return l.until, l.err
+}
+func (l *fixtureLease) Lost() <-chan struct{} { return l.lost }
+func (l *fixtureLease) lose() { l.mu.Lock(); defer l.mu.Unlock(); l.err = ErrRefused; close(l.lost) }
+
+func TestChildFixture(t *testing.T) {
+ mode := os.Getenv("MAINTENANCE_EXEC_FIXTURE")
+ if mode == "" {
+ return
+ }
+ if len(os.Environ()) != 2 || os.Getenv("GODEBUG") != "containermaxprocs=0,updatemaxprocs=0" {
+ os.Exit(15)
+ }
+ if cwd, err := os.Getwd(); err != nil || cwd != "/" {
+ os.Exit(16)
+ }
+ stderr, err := os.Stderr.Stat()
+ null, nullErr := os.Stat(os.DevNull)
+ if err != nil || nullErr != nil || !os.SameFile(stderr, null) {
+ os.Exit(17)
+ }
+ entries, err := os.ReadDir("/proc/self/fd")
+ if err != nil {
+ os.Exit(18)
+ }
+ for _, entry := range entries {
+ fd, err := strconv.Atoi(entry.Name())
+ if err != nil || fd <= 4 {
+ continue
+ }
+ target, err := os.Readlink("/proc/self/fd/" + entry.Name())
+ if errors.Is(err, os.ErrNotExist) || strings.HasPrefix(target, "anon_inode:") {
+ continue
+ }
+ os.Exit(19)
+ }
+ if mode == "blocked" {
+ time.Sleep(time.Minute)
+ return
+ }
+ if mode == "overflow" {
+ os.Stdout.Write([]byte(strings.Repeat("x", maintenance.MaxResultBytes+1)))
+ time.Sleep(time.Minute)
+ return
+ }
+ if mode == "nonzero" {
+ os.Exit(7)
+ }
+ if mode == "descendant" {
+ child := exec.Command("/proc/self/exe", "-test.run=^TestChildFixture$")
+ child.Env = []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=blocked"}
+ child.Dir = "/"
+ child.Stdout = os.Stdout
+ if child.Start() != nil {
+ os.Exit(14)
+ }
+ }
+ c, err := maintenance.ReadLaunch(os.NewFile(3, "context"))
+ if err != nil {
+ os.Exit(8)
+ }
+ pin := os.NewFile(4, "artifact")
+ info, err := pin.Stat()
+ if err != nil {
+ os.Exit(9)
+ }
+ self, err := os.Stat("/proc/self/exe")
+ if err != nil || !os.SameFile(info, self) {
+ os.Exit(10)
+ }
+ flags, err := unix.FcntlInt(pin.Fd(), unix.F_GETFL, 0)
+ if err != nil || flags&unix.O_ACCMODE != unix.O_RDONLY {
+ os.Exit(11)
+ }
+ raw, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ os.Exit(12)
+ }
+ sum := sha256.Sum256(raw)
+ if hex.EncodeToString(sum[:]) != c.InputSHA256 {
+ os.Exit(13)
+ }
+ json.NewEncoder(os.Stdout).Encode(map[string]any{"input_bytes": len(raw), "bound_image": true})
+ os.Exit(0)
+}
+func executionFixture(t *testing.T) (*os.File, maintenance.LaunchContext, maintenance.Input, *fixtureGroup, *fixtureLease) {
+ t.Helper()
+ t.Setenv("MAINTENANCE_PARENT_ONLY", "must-not-reach-child")
+ src, err := os.Open("/proc/self/exe")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer src.Close()
+ path := filepath.Join(t.TempDir(), "image")
+ dst, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0700)
+ if err != nil {
+ t.Fatal(err)
+ }
+ hash := sha256.New()
+ if _, err := io.Copy(io.MultiWriter(dst, hash), src); err != nil {
+ t.Fatal(err)
+ }
+ if err := dst.Close(); err != nil {
+ t.Fatal(err)
+ }
+ pin, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { pin.Close() })
+ input := maintenance.Input{Arguments: []string{"-test.run=^TestChildFixture$"}, Stdin: []byte(`{"test":"` + strings.Repeat("a", 60000) + `"}`)}
+ sum := sha256.Sum256(input.Stdin)
+ id := base64.RawURLEncoding.EncodeToString(make([]byte, 32))
+ digest := strings.Repeat("a", 64)
+ c := maintenance.LaunchContext{Version: 1, Account: "bds", App: "home", Host: "home.bds.oncarlos.com", RequestID: id, AttemptID: id, RequestDigest: digest, RecordIdentity: digest, ArtifactDigest: hex.EncodeToString(hash.Sum(nil)), EnvironmentEnvelopeDigest: digest, GuardGeneration: 1, InputSHA256: hex.EncodeToString(sum[:]), DatabasePath: "/synthetic/home.db", DatabaseDevice: 1, DatabaseInode: 2, ExecutionDeadline: time.Now().Add(8 * time.Second).UTC().Format(time.RFC3339Nano)}
+ return pin, c, input, &fixtureGroup{}, &fixtureLease{until: time.Now().Add(10 * time.Second), lost: make(chan struct{})}
+}
+func TestPinnedImageAndExactPrivateInput(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ g.before = func() {
+ if err := os.Rename(pin.Name(), pin.Name()+".old"); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(pin.Name(), []byte("not an executable"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ }
+ out, err := run(t.Context(), g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=success"})
+ if err != nil {
+ t.Fatal(err, g.cmd.ProcessState)
+ }
+ var result struct {
+ InputBytes int `json:"input_bytes"`
+ BoundImage bool `json:"bound_image"`
+ }
+ if json.Unmarshal(out, &result) != nil || result.InputBytes != len(input.Stdin) || !result.BoundImage {
+ t.Fatal("wrong child result")
+ }
+ if g.starts != 1 || g.kills != 1 {
+ t.Fatal("wrong containment lifecycle", g.starts, g.kills)
+ }
+}
+func TestBlockedIOOverflowFailureAndLeaseLoss(t *testing.T) {
+ for _, mode := range []string{"blocked", "overflow", "nonzero", "lease-loss"} {
+ t.Run(mode, func(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond)
+ defer cancel()
+ fixtureMode := mode
+ if mode == "lease-loss" {
+ fixtureMode = "blocked"
+ g.before = func() { go func() { time.Sleep(50 * time.Millisecond); l.lose() }() }
+ }
+ started := time.Now()
+ out, err := run(ctx, g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=" + fixtureMode})
+ if err == nil || len(out) != 0 || g.starts != 1 || g.kills == 0 {
+ t.Fatal("failure became success", err, g.starts, g.kills)
+ }
+ if time.Since(started) > 3*time.Second {
+ t.Fatal("supervisor blocked")
+ }
+ })
+ }
+}
+
+func TestDescendantHoldingOutputPipe(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ ctx, cancel := context.WithTimeout(t.Context(), 4*time.Second)
+ defer cancel()
+ started := time.Now()
+ out, err := run(ctx, g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=descendant"})
+ if err != ErrRefused || len(out) != 0 || g.kills != 1 {
+ t.Fatal("surviving descendant was accepted", err, g.kills)
+ }
+ if time.Since(started) > 3*time.Second {
+ t.Fatal("waited for descendant instead of terminating it")
+ }
+}
+
+func TestExpiredLeaseStillTerminatesProcess(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ l.until = time.Now().Add(2200 * time.Millisecond)
+ g.before = func() { time.Sleep(time.Until(l.until) + 10*time.Millisecond) }
+ out, err := run(t.Context(), g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=blocked"})
+ if err != ErrUncertain || len(out) != 0 || g.kills == 0 {
+ t.Fatal("expired lease skipped termination", err, g.kills)
+ }
+}
+
+func descriptorSnapshot(t *testing.T) map[string]string {
+ t.Helper()
+ entries, err := os.ReadDir("/proc/self/fd")
+ if err != nil {
+ t.Fatal(err)
+ }
+ out := map[string]string{}
+ for _, entry := range entries {
+ target, err := os.Readlink("/proc/self/fd/" + entry.Name())
+ if errors.Is(err, os.ErrNotExist) || strings.HasPrefix(target, "anon_inode:") {
+ continue
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ out[entry.Name()] = target
+ }
+ return out
+}
+
+func TestParentDescriptorsClose(t *testing.T) {
+ for _, mode := range []string{"success", "blocked", "overflow", "nonzero", "descendant"} {
+ t.Run(mode, func(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ before := descriptorSnapshot(t)
+ ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
+ defer cancel()
+ _, err := run(ctx, g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=" + mode})
+ if (err == nil) != (mode == "success") {
+ t.Fatal("wrong result", err)
+ }
+ if !reflect.DeepEqual(before, descriptorSnapshot(t)) {
+ t.Fatal("parent descriptor set changed after execution")
+ }
+ })
+ }
+}
+
+func TestInputRemainsBoundAfterPreflight(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ g.before = func() { input.Stdin[10] = 'b' }
+ out, err := run(t.Context(), g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=success"})
+ if err != nil || len(out) == 0 {
+ t.Fatal("child received input changed after validation", err)
+ }
+}
+func TestPreflightRefusesWithoutSpawn(t *testing.T) {
+ for _, mode := range []string{"input", "artifact", "deadline", "checker", "lease"} {
+ t.Run(mode, func(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ switch mode {
+ case "input":
+ input.Stdin = []byte(`{}`)
+ case "artifact":
+ c.ArtifactDigest = strings.Repeat("b", 64)
+ case "deadline":
+ c.ExecutionDeadline = time.Now().Add(-time.Second).UTC().Format(time.RFC3339Nano)
+ case "checker":
+ c.Version = 2
+ case "lease":
+ l.lose()
+ }
+ if _, err := run(t.Context(), g, l, pin, c, input, nil); err == nil || g.starts != 0 {
+ t.Fatal("invalid launch spawned")
+ }
+ })
+ }
+}
+func TestFailedStartAndUncertainCleanup(t *testing.T) {
+ pin, c, input, g, l := executionFixture(t)
+ g.startErr = errors.New("synthetic private start detail")
+ if _, err := run(t.Context(), g, l, pin, c, input, nil); err != ErrRefused || g.starts != 1 || g.kills != 1 {
+ t.Fatal("start failure lost cleanup", err)
+ }
+ pin, c, input, g, l = executionFixture(t)
+ g.killErr = errors.New("synthetic private cleanup detail")
+ out, err := run(t.Context(), g, l, pin, c, input, []string{"GODEBUG=containermaxprocs=0,updatemaxprocs=0", "MAINTENANCE_EXEC_FIXTURE=success"})
+ if err != ErrUncertain || len(out) != 0 {
+ t.Fatal("uncertain cleanup accepted", err)
+ }
+}
diff --git a/internal/maintenanceexec/run_other.go b/internal/maintenanceexec/run_other.go
new file mode 100644
index 000000000..0aa531438
--- /dev/null
+++ b/internal/maintenanceexec/run_other.go
@@ -0,0 +1,16 @@
+//go:build !linux
+
+package maintenanceexec
+
+import (
+ "context"
+ "os"
+
+ "github.com/carlosframework/platform/internal/cgroup"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/maintenance"
+)
+
+func Run(context.Context, *cgroup.Prepared, *lease.Held, *os.File, maintenance.LaunchContext, maintenance.Input, []string) ([]byte, error) {
+ return nil, ErrRefused
+}