303 lines
12 KiB
Go
303 lines
12 KiB
Go
//go:build !windows
|
|
|
|
// Tagged to match anchor_test.go, which is where setupBox, driveOp and the
|
|
// grant helpers live. Without this the WINDOWS test build fails on `undefined:
|
|
// setupBox` — verified with GOOS=windows go vet. The capability itself
|
|
// (contextprep.go) is platform-neutral and builds everywhere; only these gates
|
|
// borrow the anchored-box fixture.
|
|
|
|
package main
|
|
|
|
// Gates for the context-preparation capability (contextprep.go).
|
|
//
|
|
// The defect each test guards is named in its comment. The first one is the
|
|
// reason this file exists: the caps gate fails OPEN on an unrecognised op
|
|
// name, so a rename can silently hand every member pairing a lane the anchor
|
|
// never granted, with nothing visibly broken.
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// resetPrepCache clears the 10-minute probe cache so one test's probe cannot
|
|
// decide another's result.
|
|
func resetPrepCache() {
|
|
prepCache.Lock()
|
|
prepCache.at = time.Time{}
|
|
prepCache.tools = nil
|
|
prepCache.scan = prepScanReport{}
|
|
prepCache.Unlock()
|
|
}
|
|
|
|
// THE IMPORTANT ONE. capsAllow maps an op to a family with opFamily, and
|
|
// opFamily returns "" for a name it does not know — at which point capsAllow
|
|
// RETURNS "" TOO, meaning allow (anchor.go:476-479). So an op named outside
|
|
// the known prefixes is ungated on an anchored box: every member pairing may
|
|
// drive it, no error, no log line, nothing to notice. The "ctx_" prefix is
|
|
// what puts this lane in the dcn family, and it is not cosmetic.
|
|
func TestPrepGateOpResolvesToARealFamily(t *testing.T) {
|
|
fam := opFamily(prepGateOp)
|
|
if fam == "" {
|
|
t.Fatalf("prepGateOp %q maps to NO caps family — capsAllow fails open on an "+
|
|
"unknown op, so this lane would be ungated for every member pairing on an "+
|
|
"anchored box. Name it with a prefix opFamily knows (anchor.go:411).", prepGateOp)
|
|
}
|
|
if fam != prepGateOpFamily {
|
|
t.Fatalf("prepGateOp %q is family %q, not the declared %q — the gate and the "+
|
|
"declaration disagree", prepGateOp, fam, prepGateOpFamily)
|
|
}
|
|
// And the family must be one the anchor can actually grant, or no pairing
|
|
// could ever be given this lane.
|
|
found := false
|
|
for _, c := range allCaps {
|
|
if c == fam {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("family %q is not in allCaps %v — ungrantable", fam, allCaps)
|
|
}
|
|
}
|
|
|
|
// A tool table entry with a missing field would probe garbage or advertise a
|
|
// job the host cannot ask for.
|
|
func TestPrepToolTableIsWellFormed(t *testing.T) {
|
|
seen := map[string]bool{}
|
|
for _, tool := range prepToolTable {
|
|
if tool.Name == "" {
|
|
t.Fatalf("prepTool entry with no name: %+v — the host asks for a job by name", tool)
|
|
}
|
|
// A BUILTIN HAS NO BINARY AND MUST HAVE A PROOF. The two halves swap:
|
|
// a job with a program is checked by running the program, and a job this
|
|
// binary does itself is checked by doing it. What neither may be is
|
|
// unchecked, so the requirement moves rather than relaxing.
|
|
if tool.Builtin {
|
|
if tool.Bin != "" || len(tool.Args) > 0 {
|
|
t.Fatalf("%q is builtin but carries a program to run: %+v — a builtin "+
|
|
"has nothing to exec, and a half-filled entry would probe the wrong thing",
|
|
tool.Name, tool)
|
|
}
|
|
if tool.Prove == nil {
|
|
t.Fatalf("%q is builtin with no Prove — there is no exit code to stand in "+
|
|
"for the capability, so a builtin that is never proved is advertised on "+
|
|
"faith", tool.Name)
|
|
}
|
|
} else if tool.Bin == "" || len(tool.Args) == 0 {
|
|
t.Fatalf("incomplete prepTool entry: %+v — every tool needs the job name "+
|
|
"the host asks for, the binary, and an argv that proves it runs", tool)
|
|
}
|
|
if seen[tool.Name] {
|
|
t.Fatalf("duplicate prep tool name %q — the host looks tools up by name", tool.Name)
|
|
}
|
|
seen[tool.Name] = true
|
|
}
|
|
}
|
|
|
|
// Availability is the EXIT CODE, never the output. `pdftotext -v` prints its
|
|
// banner to stderr and exits 0, so a probe that required stdout would report a
|
|
// working poppler as absent (measured). Conversely a binary that
|
|
// exits non-zero is not available however much it printed.
|
|
func TestPrepProbeUsesExitCodeNotOutput(t *testing.T) {
|
|
// exits 0, prints nothing: available, no version.
|
|
ok, ver := probePrepTool(prepTool{Name: "silent", Bin: "true", Args: []string{}})
|
|
if !ok {
|
|
t.Fatal("a tool that exits 0 with no output must count as AVAILABLE — " +
|
|
"absent output is not absent capability")
|
|
}
|
|
if ver != "" {
|
|
t.Fatalf("expected no version from a silent tool, got %q", ver)
|
|
}
|
|
// exits non-zero: not available.
|
|
if ok, _ := probePrepTool(prepTool{Name: "fails", Bin: "false", Args: []string{}}); ok {
|
|
t.Fatal("a tool that exits non-zero must NOT be advertised")
|
|
}
|
|
// missing binary: not available, and no panic.
|
|
if ok, _ := probePrepTool(prepTool{
|
|
Name: "gone", Bin: "privatae-no-such-binary-9f3c", Args: []string{"-v"},
|
|
}); ok {
|
|
t.Fatal("a missing binary must NOT be advertised")
|
|
}
|
|
}
|
|
|
|
// A chatty or hostile binary must not be able to inflate the heartbeat body.
|
|
func TestFirstLineTrimsAndCaps(t *testing.T) {
|
|
if got := firstLine(" tesseract 5.5.0\nmore\nlines\n"); got != "tesseract 5.5.0" {
|
|
t.Fatalf("firstLine kept more than the first line: %q", got)
|
|
}
|
|
if got := firstLine(strings.Repeat("x", 500)); len(got) != 120 {
|
|
t.Fatalf("firstLine must cap at 120 chars, got %d", len(got))
|
|
}
|
|
if got := firstLine(" \n "); got != "" {
|
|
t.Fatalf("whitespace-only output must be empty, got %q", got)
|
|
}
|
|
}
|
|
|
|
// On an anchored box a member pairing with no dcn grant must get NO capability
|
|
// AND no probe: advertising a lane the op will refuse is a control that fails
|
|
// after the user has already chosen it (mirror.go:63-67's stated reason), and
|
|
// probing for a pairing that cannot use the result is work done for nothing.
|
|
func TestPrepWithheldFromMemberWithoutDCN(t *testing.T) {
|
|
setupBox(t)
|
|
resetPrepCache()
|
|
|
|
tools, scan, ok := prepCapable(tMember)
|
|
if ok {
|
|
t.Fatal("a member pairing holding no grant must not be offered context-prep")
|
|
}
|
|
if len(tools) != 0 || scan.Supported {
|
|
t.Fatalf("a withheld capability must report nothing, got tools=%v scan=%+v", tools, scan)
|
|
}
|
|
prepCache.Lock()
|
|
probed := !prepCache.at.IsZero()
|
|
prepCache.Unlock()
|
|
if probed {
|
|
t.Fatal("the probe RAN for a pairing that is refused at the op — withheld " +
|
|
"must skip the probe entirely, as the inference lanes do (agents.go:351)")
|
|
}
|
|
}
|
|
|
|
// THE CASE THE FAIL-OPEN ACTUALLY BITES. A member holding NO grant is
|
|
// default-denied before opFamily is ever consulted (anchor.go:473-475), so it
|
|
// stays refused even with the op misnamed — which makes the test above a poor
|
|
// guard on its own. The real exposure is a pairing that HAS a verified grant
|
|
// for some OTHER family: there capsAllow reaches opFamily, and an unrecognised
|
|
// op name returns allow. This is the assertion that fails when the "ctx_"
|
|
// prefix is dropped.
|
|
func TestPrepWithheldFromMemberGrantedAnotherFamily(t *testing.T) {
|
|
priv := setupBox(t)
|
|
resetPrepCache()
|
|
|
|
// A real, signed, verified grant — for observe only. Nothing about this
|
|
// pairing is malformed; it simply was not given the dcn family.
|
|
writePairingState(t, tMember, pairingPending)
|
|
future := time.Now().Add(time.Hour).UTC().Format(time.RFC3339)
|
|
terms, sig := signTerms(t, priv, grantTerms(tMember, []string{"observe"}, future))
|
|
if _, errOut, code := driveOp(t, tAnchor, "pairing_grant_sync", []string{terms, sig}); code != 0 {
|
|
t.Fatalf("could not install the observe-only grant: %d %s", code, errOut)
|
|
}
|
|
|
|
if _, _, ok := prepCapable(tMember); ok {
|
|
t.Fatal("a pairing granted 'observe' was offered context-prep — the op name " +
|
|
"no longer resolves to the dcn family, so capsAllow fell through to its " +
|
|
"allow branch (anchor.go:476-479)")
|
|
}
|
|
}
|
|
|
|
// The anchor pairing is unconstrained (its user IS the machine's admin), so it
|
|
// sees whatever this box actually has. This asserts the gate lets the anchor
|
|
// through; whether any tool is installed is the box's business, not the gate's.
|
|
func TestPrepReachesTheAnchor(t *testing.T) {
|
|
setupBox(t)
|
|
resetPrepCache()
|
|
|
|
tools, scan, ok := prepCapable(tAnchor)
|
|
if !scan.Supported {
|
|
t.Fatal("the anchor pairing must reach the probe — it holds every family")
|
|
}
|
|
if scan.Probed != len(prepToolTable) {
|
|
t.Fatalf("scan must report every tool it tried: probed=%d table=%d",
|
|
scan.Probed, len(prepToolTable))
|
|
}
|
|
if scan.Found != len(tools) {
|
|
t.Fatalf("scan.Found %d disagrees with the tool list length %d", scan.Found, len(tools))
|
|
}
|
|
// The capability travels only when something was actually found — a box
|
|
// with the lane granted but no tools installed must read as "nothing here",
|
|
// not as a working feature.
|
|
if ok != (len(tools) > 0) {
|
|
t.Fatalf("capability %v disagrees with %d tools found", ok, len(tools))
|
|
}
|
|
for _, tool := range tools {
|
|
if tool.Name == "" {
|
|
t.Fatalf("advertised tool missing its identity: %+v", tool)
|
|
}
|
|
// A builtin names no program because there is none. It still has to say
|
|
// SOMETHING about itself, or a machine card shows a job with a blank
|
|
// where every other row says what does the work.
|
|
if tool.Builtin {
|
|
if tool.Bin != "" {
|
|
t.Fatalf("a builtin advertised a program to install: %+v", tool)
|
|
}
|
|
if tool.Version == "" {
|
|
t.Fatalf("a builtin advertised with nothing said about it: %+v", tool)
|
|
}
|
|
continue
|
|
}
|
|
if tool.Bin == "" {
|
|
t.Fatalf("advertised tool missing its identity: %+v", tool)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ADVERTISING IS A PROMISE. Every tool in prepToolTable travels to the host in
|
|
// the heartbeat, is offered by the host's machine picker, and is what the
|
|
// host filters machines by tool on — so a name here that ctx_extract cannot
|
|
// actually run tells a user their machine can do something it will then refuse.
|
|
//
|
|
// That happened: ocr and video-frames were advertised for a day with no entry in
|
|
// extractArgv, and the refusal even read "this box runs only the jobs it
|
|
// advertises", which had quietly stopped being true. This is the same
|
|
// quiet-vs-blind rule the rest of this file enforces, pointed the other way —
|
|
// claiming MORE than you can do is as dishonest as claiming less.
|
|
func TestAdvertisedToolsCanActuallyRun(t *testing.T) {
|
|
for _, tool := range prepToolTable {
|
|
_, single := extractArgv[tool.Name]
|
|
_, multi := extractMulti[tool.Name]
|
|
frames := frameTools[tool.Name]
|
|
_, stt := transcribeTools[tool.Name]
|
|
if !single && !multi && !frames && !stt {
|
|
t.Fatalf("prepToolTable advertises %q but no runner can run it — it is in "+
|
|
"none of extractArgv, extractMulti, frameTools or transcribeTools. The "+
|
|
"heartbeat would offer a job ctx_extract then refuses. Add the runner in "+
|
|
"the same change that adds the tool, or do not advertise it.", tool.Name)
|
|
}
|
|
// EXACTLY ONE RUNNER. Two means the answer shape is undecided:
|
|
// handleCtxExtract picks one and the host reads the reply expecting the
|
|
// other's, which for the text/frames pair is a clip filed as zero
|
|
// chunks of nothing and called a success.
|
|
n := 0
|
|
for _, in := range []bool{single, multi, frames, stt} {
|
|
if in {
|
|
n++
|
|
}
|
|
}
|
|
if n > 1 {
|
|
t.Fatalf("%q is claimed by more than one runner (argv=%v multi=%v "+
|
|
"frames=%v transcribe=%v) — one tool, one answer shape",
|
|
tool.Name, single, multi, frames, stt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And the reverse: a runner with nothing advertising it is unreachable, because
|
|
// handleCtxExtract requires the name to be in prepToolTable too.
|
|
func TestEveryArgvIsAdvertised(t *testing.T) {
|
|
names := []string{}
|
|
for name := range extractArgv {
|
|
names = append(names, name)
|
|
}
|
|
for name := range extractMulti {
|
|
names = append(names, name)
|
|
}
|
|
for name := range frameTools {
|
|
names = append(names, name)
|
|
}
|
|
for name := range transcribeTools {
|
|
names = append(names, name)
|
|
}
|
|
for _, name := range names {
|
|
found := false
|
|
for _, tool := range prepToolTable {
|
|
if tool.Name == name {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("ctx_extract can run %q but prepToolTable never advertises it — "+
|
|
"no host will ever ask for it", name)
|
|
}
|
|
}
|
|
}
|