From 7215a058c06bfa3b0573fbe3140a904546611c05 Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Tue, 29 Sep 2026 01:08:22 -0500 Subject: [PATCH] feat(cloudstore): record explicit prompt source attestations --- internal/cloud/cloudstore/cloudstore.go | 10 +++ .../cloudstore/prompt_source_attestation.go | 59 +++++++++++++++ .../prompt_source_attestation_test.go | 74 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 internal/cloud/cloudstore/prompt_source_attestation.go create mode 100644 internal/cloud/cloudstore/prompt_source_attestation_test.go diff --git a/internal/cloud/cloudstore/cloudstore.go b/internal/cloud/cloudstore/cloudstore.go index 99acc554a..26e3a6e6e 100644 --- a/internal/cloud/cloudstore/cloudstore.go +++ b/internal/cloud/cloudstore/cloudstore.go @@ -781,6 +781,16 @@ func (cs *CloudStore) migrate(ctx context.Context) error { claimed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (session_id, source_inbox_id) )`, + `CREATE TABLE IF NOT EXISTS cloud_prompt_source_attestations ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + session_id TEXT NOT NULL, + source_inbox_id TEXT NOT NULL, + sync_id TEXT NOT NULL, + owner_project TEXT NOT NULL, + prompt_project TEXT NOT NULL, + actor_id TEXT NOT NULL, + attested_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + )`, `CREATE TABLE IF NOT EXISTS cloud_project_sessions ( project_name TEXT NOT NULL, session_id TEXT NOT NULL, diff --git a/internal/cloud/cloudstore/prompt_source_attestation.go b/internal/cloud/cloudstore/prompt_source_attestation.go new file mode 100644 index 000000000..9d942260b --- /dev/null +++ b/internal/cloud/cloudstore/prompt_source_attestation.go @@ -0,0 +1,59 @@ +package cloudstore + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "time" +) + +var ErrPromptSourceAttestationUnbound = errors.New("cloudstore: prompt source attestation requires exact session authority and prompt pair claim") + +// PromptSourceAttestation is an independent, append-only record of an explicit +// human source assertion; it does not alter registration or claim audit history. +type PromptSourceAttestation struct { + ID int64 + SessionID string + SourceInboxID string + SyncID string + OwnerProject string + PromptProject string + ActorID string + AttestedAt time.Time +} + +// AttestPromptSource records an already-authorized human assertion. Storage +// does not check grants: the future authenticated server route must verify the +// actor's current grants to both owner and prompt projects before calling it. +func (cs *CloudStore) AttestPromptSource(ctx context.Context, sessionID, sourceInboxID, syncID, ownerProject, promptProject, actorID string) (*PromptSourceAttestation, error) { + if cs == nil || cs.db == nil { + return nil, fmt.Errorf("cloudstore: not initialized") + } + values := []*string{&sessionID, &sourceInboxID, &syncID, &ownerProject, &promptProject, &actorID} + for _, value := range values { + *value = strings.TrimSpace(*value) + if *value == "" { + return nil, fmt.Errorf("cloudstore: all attestation fields are required") + } + } + const query = `INSERT INTO cloud_prompt_source_attestations + (session_id, source_inbox_id, sync_id, owner_project, prompt_project, actor_id) + SELECT a.session_id, c.source_inbox_id, c.sync_id, a.owner_project, c.prompt_project, $6 + FROM cloud_session_authority a + JOIN cloud_prompt_pair_claims c ON c.session_id = a.session_id + WHERE a.session_id = $1 AND c.source_inbox_id = $2 AND c.sync_id = $3 + AND a.owner_project = $4 AND c.prompt_project = $5 + RETURNING id, session_id, source_inbox_id, sync_id, owner_project, prompt_project, actor_id, attested_at` + var record PromptSourceAttestation + err := cs.db.QueryRowContext(ctx, query, sessionID, sourceInboxID, syncID, ownerProject, promptProject, actorID). + Scan(&record.ID, &record.SessionID, &record.SourceInboxID, &record.SyncID, &record.OwnerProject, &record.PromptProject, &record.ActorID, &record.AttestedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrPromptSourceAttestationUnbound + } + if err != nil { + return nil, fmt.Errorf("cloudstore: attest prompt source: %w", err) + } + return &record, nil +} diff --git a/internal/cloud/cloudstore/prompt_source_attestation_test.go b/internal/cloud/cloudstore/prompt_source_attestation_test.go new file mode 100644 index 000000000..f610f9bb7 --- /dev/null +++ b/internal/cloud/cloudstore/prompt_source_attestation_test.go @@ -0,0 +1,74 @@ +package cloudstore + +import ( + "context" + "errors" + "testing" +) + +func TestPromptSourceAttestationExactAuthorityAndAppendOnlyAudit(t *testing.T) { + cs := openIsolatedCloudStore(t) + ctx := context.Background() + if _, err := cs.db.ExecContext(ctx, `INSERT INTO cloud_project_sessions (project_name, session_id) VALUES ('owner', 'unregistered')`); err != nil { + t.Fatal(err) + } + if _, err := cs.AttestPromptSource(ctx, "unregistered", "inbox", "sync", "owner", "prompt", "human"); !errors.Is(err, ErrPromptSourceAttestationUnbound) { + t.Fatalf("indexed session conferred authority: %v", err) + } + if err := cs.RegisterSessionAuthority(ctx, "session", "owner", "registrar"); err != nil { + t.Fatal(err) + } + if _, err := cs.AttestPromptSource(ctx, "session", "inbox", "sync", "owner", "prompt", "human"); !errors.Is(err, ErrPromptSourceAttestationUnbound) { + t.Fatalf("unclaimed pair conferred authority: %v", err) + } + if err := cs.ClaimPromptPair(ctx, "session", "inbox", "sync", "prompt", "claimer"); err != nil { + t.Fatal(err) + } + before, err := cs.GetPromptPairClaim(ctx, "session", "inbox") + if err != nil { + t.Fatal(err) + } + for _, tc := range []struct { + name string + args [6]string + }{ + {"wrong session", [6]string{"other", "inbox", "sync", "owner", "prompt", "human"}}, + {"wrong inbox", [6]string{"session", "other", "sync", "owner", "prompt", "human"}}, + {"wrong sync", [6]string{"session", "inbox", "other", "owner", "prompt", "human"}}, + {"wrong owner", [6]string{"session", "inbox", "sync", "prompt", "prompt", "human"}}, + {"wrong prompt", [6]string{"session", "inbox", "sync", "owner", "owner", "human"}}, + } { + t.Run(tc.name, func(t *testing.T) { + a := tc.args + if _, err := cs.AttestPromptSource(ctx, a[0], a[1], a[2], a[3], a[4], a[5]); !errors.Is(err, ErrPromptSourceAttestationUnbound) { + t.Fatalf("accepted mismatch: %v", err) + } + }) + } + for i := 0; i < 6; i++ { + a := [6]string{"session", "inbox", "sync", "owner", "prompt", "human"} + a[i] = " " + if _, err := cs.AttestPromptSource(ctx, a[0], a[1], a[2], a[3], a[4], a[5]); err == nil { + t.Fatalf("accepted blank field %d", i) + } + } + first, err := cs.AttestPromptSource(ctx, "session", "inbox", "sync", "owner", "prompt", "human-one") + if err != nil { + t.Fatal(err) + } + second, err := cs.AttestPromptSource(ctx, "session", "inbox", "sync", "owner", "prompt", "human-two") + if err != nil { + t.Fatal(err) + } + if first.ID == second.ID || first.AttestedAt.IsZero() || second.AttestedAt.IsZero() || first.ActorID != "human-one" || second.ActorID != "human-two" { + t.Fatalf("audit records not independent: %+v %+v", first, second) + } + var count int + if err := cs.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM cloud_prompt_source_attestations`).Scan(&count); err != nil || count != 2 { + t.Fatalf("count=%d err=%v", count, err) + } + after, err := cs.GetPromptPairClaim(ctx, "session", "inbox") + if err != nil || *before != *after { + t.Fatalf("claim audit changed: %+v %+v %v", before, after, err) + } +}