v0.317.0: a second copy counts only on a proven different disk; the no-drive install speaks in words (felhom.eu architecture/16, build A)
gates / gates (push) Successful in 46s
gates / gates (push) Successful in 46s
Tier-2 and the one-copy notice ask the host agent (POST /disks/same-disk, agent 0.157.0): different + proven -> made and counted; same disk -> not made (D1); unknown (VM, hidden USB serial) -> made, not counted, the row says why (D2); a NAS never counts and no longer hides the notice (R-1075). An older agent keeps the st_dev rule, logged once. D4: a data-folder or media app on a box with no drive is refused in words with a link to the drives page (R-1068's raw-error half). MinAgent 0.157.0. Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// D4 (felhom.eu architecture/16-storage-physical-identity.md §5, R-1068): a data-folder or media app on a
|
||||
// box with NO drive is refused in words with the way forward — never the field name, never sys_drive.
|
||||
// COMPANION RED-PROOF: delete the D4 branch in deploy.go → the plain required-field refusal comes back
|
||||
// („a(z) "Médiatár útvonal" (HDD_PATH) mező kitöltése kötelező", the OMV test's sentence) and this fails.
|
||||
func TestD4_NoDriveRefusalSpeaksInWords(t *testing.T) {
|
||||
for name, yml := range map[string]string{
|
||||
"media app": "display_name: Jellyfin\nmedia_library:\n folder: films\ndeploy_fields:\n - env_var: HDD_PATH\n label: Médiatár útvonal\n type: path\n required: true\n",
|
||||
"data-folder app": "display_name: Paperless\ndeploy_fields:\n - env_var: HDD_PATH\n label: Adattárolási útvonal\n type: path\n required: true\n",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
m := r553Manager(t, yml)
|
||||
_, err := m.DeployStack(DeployRequest{StackName: "r553app", Values: map[string]string{}})
|
||||
if err == nil {
|
||||
t.Fatal("deploy was admitted on a box with no drive")
|
||||
}
|
||||
if !errors.Is(err, ErrNeedsDrive) {
|
||||
t.Fatalf("want ErrNeedsDrive, got %v", err)
|
||||
}
|
||||
msg := err.Error()
|
||||
if msg != "Ehhez az alkalmazáshoz egy adattároló kell. A Meghajtók oldalon adhatsz hozzá egyet." {
|
||||
t.Errorf("the D4 sentence moved: %q", msg)
|
||||
}
|
||||
for _, bad := range []string{"HDD_PATH", "útvonal", "sys_drive", "kötelező"} {
|
||||
if strings.Contains(msg, bad) {
|
||||
t.Errorf("the refusal names %q — it must speak in words, not fields: %q", bad, msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A path field the household FILLED is not a D4 case: a missing path keeps its own refusal.
|
||||
func TestD4_FilledPathKeepsItsOwnRefusal(t *testing.T) {
|
||||
m := r553Manager(t, "display_name: X\ndeploy_fields:\n - env_var: HDD_PATH\n label: Adat\n type: path\n required: true\n")
|
||||
_, err := m.DeployStack(DeployRequest{StackName: "r553app", Values: map[string]string{"HDD_PATH": "/definitely/not/here/d4"}})
|
||||
if !errors.Is(err, ErrPathMissing) {
|
||||
t.Fatalf("want ErrPathMissing for a filled, missing path, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -390,6 +390,14 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// D4 (`16` §5, R-1068): a path field left empty on a box with NO drive at all is not a form mistake —
|
||||
// the household has nowhere to put the data. Say so in words with the way forward; never the field
|
||||
// name. (sys_drive is never suggested: it rides in every whole-guest copy, `16` §11.)
|
||||
if field.Required && value == "" && !field.AfterStart && field.Type == "path" && m.defaultMediaDrive() == "" {
|
||||
clearDeploying()
|
||||
m.logger.Printf("[INFO] [stacks] Deploy %s: refused — the box has no drive for %s (D4)", req.StackName, field.EnvVar)
|
||||
return "", util.MsgErrorf(ErrNeedsDrive, "err.stacks.needs_drive")
|
||||
}
|
||||
// Validate required fields (an after_start field is asked after the start — R-1028)
|
||||
if field.Required && value == "" && !field.AfterStart {
|
||||
clearDeploying()
|
||||
|
||||
@@ -28,6 +28,10 @@ var (
|
||||
// deploy's own `up` makes BOTH fail, and the deploy then records „not deployed" over whatever
|
||||
// containers the race left behind. Pinned by TestR634_StopAndStartRefuseADeployingStack.
|
||||
ErrStackDeploying = errors.New("stack is being deployed")
|
||||
// ErrNeedsDrive — a data-folder or media app on a box with NO drive (felhom.eu architecture/
|
||||
// 16-storage-physical-identity.md §5, D4; R-1068). API: 400 with code "needs_drive" and a link to the
|
||||
// drives page; the sentence never names the field. Pinned by TestD4_NoDriveRefusalSpeaksInWords.
|
||||
ErrNeedsDrive = errors.New("a storage drive is needed")
|
||||
)
|
||||
|
||||
// KeyMemoryOvercommitWarning is the soft overcommit warning's bundle key. It travels as a KEY from
|
||||
|
||||
Reference in New Issue
Block a user