Work breakdown 1.2. Two sessions run on the control-plane node — the node's own and the mesh's (novox/hq ADR 0026) — so a machine stopped being a usable answer to "whose licence is this". 14-model-access.md called per-module-per-machine "a step toward it and not it", and that is true of a worker: many run on one machine from one module, so the pair cannot name them apart. It is not true of a session. The two sessions are two modules — the same mechanism started in different context roots, and a context root is what a module delivers — so (node, module) tells them apart and nothing needed adding. Checked rather than argued: different licences on one machine, each with its own key, and a session on no licence is not handed the other's. The third test exists because a fault injection stayed silent. The first two put the sessions on different licences, so the licence alone disambiguates and the module argument is never load-bearing — removing it from the query changed nothing and everything still passed. Two sessions on the SAME licence is the case that needs the pair to be the identity: releasing one must leave the other, and a machine-shaped answer takes both.
311 lines
10 KiB
Go
311 lines
10 KiB
Go
package licences
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// These run against a real PostgreSQL, like every other context's. `make check` raises one.
|
|
func fresh(t *testing.T) (*Licences, context.Context) {
|
|
t.Helper()
|
|
return ForTest(t), t.Context()
|
|
}
|
|
|
|
func aKey(t *testing.T) string { return ASealingKey(t) }
|
|
|
|
// The mesh does not keep a key it cannot seal to somebody, because keeping it for later means
|
|
// keeping it readably — which is the whole thing this refuses to do.
|
|
func TestAKeyWithNobodyToSealItToIsRefused(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
if err := held.Add(ctx, "personal", "anthropic", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := held.Accept(ctx, "personal", "sk-test", func(string) (string, error) {
|
|
return "", nil
|
|
})
|
|
if err == nil {
|
|
t.Fatal("a key was taken with nobody to seal it to, so it was kept in the open")
|
|
}
|
|
if !strings.Contains(err.Error(), "consumer on it first") {
|
|
t.Fatalf("the refusal does not say what to do: %v", err)
|
|
}
|
|
}
|
|
|
|
// Sealed to each holder, and the plaintext discarded.
|
|
func TestAKeyIsSealedToEachHolderAndNotKept(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
if err := held.Add(ctx, "personal", "anthropic", map[string]any{"model": "a-model"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, node := range []string{"workstation", "laptop"} {
|
|
if err := held.Use(ctx, "personal", node, "assistant"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
keys := map[string]string{"workstation": aKey(t), "laptop": aKey(t)}
|
|
|
|
const value = "sk-the-operators-own-key"
|
|
sealed, err := held.Accept(ctx, "personal", value, func(node string) (string, error) {
|
|
return keys[node], nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sealed != 2 {
|
|
t.Fatalf("%d holder(s) were sealed to, and there are two", sealed)
|
|
}
|
|
|
|
first, err := held.KeyFor(ctx, "personal", "workstation", "assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := held.KeyFor(ctx, "personal", "laptop", "assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first == "" || second == "" {
|
|
t.Fatal("a holder was left with no key")
|
|
}
|
|
// Sealed to different machines, so the blobs differ even though the key is one key. Two
|
|
// identical blobs would mean one of them was sealed to a machine that cannot open it.
|
|
if first == second {
|
|
t.Fatal("both holders were given the same blob, so one of them cannot open it")
|
|
}
|
|
// And nowhere in the open. This is the argument, not a detail.
|
|
for _, blob := range []string{first, second} {
|
|
if strings.Contains(blob, value) {
|
|
t.Fatal("the key is in the stored value in the open")
|
|
}
|
|
}
|
|
}
|
|
|
|
// A holder recorded after the key was supplied has none, and the mesh cannot make one.
|
|
//
|
|
// Reported rather than hidden: a machine that resolves cleanly and receives nothing fails later,
|
|
// somewhere that names neither the licence nor the mesh.
|
|
func TestAHolderAddedAfterTheKeyHasNone(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
if err := held.Add(ctx, "personal", "anthropic", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := held.Use(ctx, "personal", "workstation", "assistant"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key := aKey(t)
|
|
if _, err := held.Accept(ctx, "personal", "sk-test", func(string) (string, error) {
|
|
return key, nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := held.Use(ctx, "personal", "laptop", "assistant"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
later, err := held.KeyFor(ctx, "personal", "laptop", "assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if later != "" {
|
|
t.Fatal("a holder added after the key was discarded was somehow given one")
|
|
}
|
|
// And the first holder still has theirs — a new holder must not disturb an existing one.
|
|
first, err := held.KeyFor(ctx, "personal", "workstation", "assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first == "" {
|
|
t.Fatal("adding a holder took the key away from one that had it")
|
|
}
|
|
}
|
|
|
|
// Taking a consumer off a licence takes its copy of the key with it.
|
|
func TestReleasingAConsumerTakesItsKey(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
if err := held.Add(ctx, "personal", "anthropic", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := held.Use(ctx, "personal", "workstation", "assistant"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key := aKey(t)
|
|
if _, err := held.Accept(ctx, "personal", "sk-test", func(string) (string, error) {
|
|
return key, nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := held.StopUsing(ctx, "personal", "workstation", "assistant"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
holders, err := held.HoldersOf(ctx, "personal")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(holders) != 0 {
|
|
t.Fatalf("a released consumer is still a holder: %+v", holders)
|
|
}
|
|
}
|
|
|
|
// A licence nobody recorded is not a licence somebody can be put on.
|
|
func TestUsingALicenceThatDoesNotExistIsRefused(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
err := held.Use(ctx, "invented", "workstation", "assistant")
|
|
if err == nil {
|
|
t.Fatal("a consumer was put on a licence this mesh has never heard of")
|
|
}
|
|
// Named, in words a person can act on. The database's own foreign-key message is true and
|
|
// mentions a constraint rather than a licence, which sends somebody reading a schema instead
|
|
// of typing the name they meant.
|
|
if !strings.Contains(err.Error(), `"invented"`) {
|
|
t.Fatalf("the refusal does not name the licence: %v", err)
|
|
}
|
|
}
|
|
|
|
// Two sessions on one machine, each on its own licence (novox/hq work breakdown 1.2).
|
|
//
|
|
// **The case ADR 0026 creates.** The control-plane node runs its own node session and the mesh's,
|
|
// so a machine is no longer a usable answer to *whose licence is this*. `14-model-access.md`
|
|
// records that gap as "a consumer that is not a machine", and the question this answers is whether
|
|
// it needs a new consumer identity or whether the existing one already distinguishes them.
|
|
//
|
|
// It does. The two sessions are two modules — the same mechanism started in different context
|
|
// roots (ADR 0026), and a context root is what a module delivers — so `(node, module)` names them
|
|
// apart without a schema knowing anything about sessions.
|
|
func TestTwoSessionsOnOneMachineHoldDifferentLicences(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
for _, l := range []struct{ name, provider string }{
|
|
{"personal", "anthropic"},
|
|
{"company", "anthropic"},
|
|
} {
|
|
if err := held.Add(ctx, l.name, l.provider, map[string]any{"model": "a-model"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Both on the machine that holds the control plane.
|
|
const machine = "anchor"
|
|
if err := held.Use(ctx, "personal", machine, "node-session"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := held.Use(ctx, "company", machine, "mesh-session"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Each is asked for separately, and neither answer is the other's.
|
|
node, err := held.Chosen(ctx, machine, "node-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mesh, err := held.Chosen(ctx, machine, "mesh-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if node != "personal" || mesh != "company" {
|
|
t.Fatalf("the node session is on %q and the mesh session on %q; expected personal and company",
|
|
node, mesh)
|
|
}
|
|
|
|
// And the keys are separate too, which is the half that matters: a machine-wide answer would
|
|
// hand both sessions whichever key was sealed last.
|
|
sealing := aKey(t)
|
|
for _, l := range []struct{ name, value string }{
|
|
{"personal", "sk-the-operators-own-key"},
|
|
{"company", "sk-the-companys-key"},
|
|
} {
|
|
if _, err := held.Accept(ctx, l.name, l.value, func(string) (string, error) {
|
|
return sealing, nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
first, err := held.KeyFor(ctx, "personal", machine, "node-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := held.KeyFor(ctx, "company", machine, "mesh-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first == "" || second == "" {
|
|
t.Fatal("a session on a licence was given no key")
|
|
}
|
|
if first == second {
|
|
t.Fatal("both sessions were given the same sealed key, so the machine answered rather " +
|
|
"than the session")
|
|
}
|
|
}
|
|
|
|
// A session put on no licence is not silently given the machine's other one.
|
|
func TestASessionOnNoLicenceIsAnsweredWithNothing(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
if err := held.Add(ctx, "personal", "anthropic", map[string]any{"model": "a-model"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := held.Use(ctx, "personal", "anchor", "node-session"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chosen, err := held.Chosen(ctx, "anchor", "mesh-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if chosen != "" {
|
|
t.Fatalf("a session nobody put on a licence was answered with %q, which belongs to "+
|
|
"something else on the same machine", chosen)
|
|
}
|
|
}
|
|
|
|
// Two sessions on one machine and the SAME licence are still two holders.
|
|
//
|
|
// **Written because a fault injection stayed silent.** The test above puts them on different
|
|
// licences, so the licence alone tells them apart and the module argument is never load-bearing —
|
|
// removing it from the query changed nothing and every assertion still passed. This is the case
|
|
// that needs `(node, module)` to be the identity: releasing one session must leave the other,
|
|
// and a machine-shaped answer would take both.
|
|
func TestReleasingOneSessionLeavesTheOtherOnTheSameMachine(t *testing.T) {
|
|
held, ctx := fresh(t)
|
|
if err := held.Add(ctx, "company", "anthropic", map[string]any{"model": "a-model"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const machine = "anchor"
|
|
for _, session := range []string{"node-session", "mesh-session"} {
|
|
if err := held.Use(ctx, "company", machine, session); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := held.Accept(ctx, "company", "sk-the-companys-key", func(string) (string, error) {
|
|
return aKey(t), nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := held.StopUsing(ctx, "company", machine, "node-session"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gone, err := held.Chosen(ctx, machine, "node-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if gone != "" {
|
|
t.Errorf("the released session is still on %q", gone)
|
|
}
|
|
|
|
kept, err := held.Chosen(ctx, machine, "mesh-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if kept != "company" {
|
|
t.Fatal("releasing one session took the other's licence with it, so the machine was " +
|
|
"released rather than the session")
|
|
}
|
|
key, err := held.KeyFor(ctx, "company", machine, "mesh-session")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if key == "" {
|
|
t.Fatal("the session that was kept lost its key")
|
|
}
|
|
}
|