From bc90fdbc38c9f54f8d7ef538c4c02d891d227b75 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:05:21 +0000 Subject: [PATCH 01/18] chore(gen): ReadJournal contract, waiting for the saas-starter release STOPGAP, labelled: ModuleCapabilitiesService.ReadJournal and its messages (ModuleReadJournalRequest, ModuleJournalEntry, ModuleReadJournalResponse) are not in module-saas-starter v0.0.94 and no release carries them yet. These two files were generated by this repo's buf.gen.yaml (buf 1.73.0, pinned plugins) from the v0.0.94 proto with the agreed wire contract added to module_capabilities.proto in a scratch checkout. The v0.0.94 proto alone reproduces gen/ byte for byte, so the diff is the contract and nothing else. The gen-drift job fails until gen/ is regenerated from the release that publishes the contract; SOURCE.txt says so. Co-Authored-By: Claude Opus 5.5 --- SOURCE.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SOURCE.txt b/SOURCE.txt index 1e2661d..8564b05 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -9,3 +9,9 @@ Released upstream tag: v0.0.103 (2cb00737ed56c79c823bc82f4055681a51919e7c). Its auth gateway requires codefly-dev/core at b81d92e6feda, the floor this SDK's go.mod records. Regeneration does not publish an SDK release. + +PENDING (not part of the recorded ref): gen/ also carries +ModuleCapabilitiesService.ReadJournal and its messages, which v0.0.94 does not +have. They were generated by this repo's buf.gen.yaml from the v0.0.94 proto +plus the contract, in a scratch checkout. Regenerate from the saas-starter +release that publishes ReadJournal and delete this paragraph. From c91ffdda48f3055132d5587172a9ef93f0490162 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:05:21 +0000 Subject: [PATCH 02/18] feat(moduleauthority): ReadJournal reads the host's journal forward Client.ReadJournal(ctx, afterSeq, limit, types) calls ModuleCapabilitiesService.ReadJournal on the authority endpoint as the module, through callAsModule like the sibling methods: host codes preserved, a rejected capability refreshed once, nothing else retried. JournalPage.NextAfterSeq is the host's resume point. A negative position, a limit outside 0..500 or no type is ErrInvalidJournalRead before any call. Depends on the generated contract in the previous commit, which is waiting for the saas-starter release (see CHANGELOG.md). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 25 +++ moduleauthority/host_fake_test.go | 56 ++++++- moduleauthority/journal.go | 88 +++++++++++ moduleauthority/journal_test.go | 255 ++++++++++++++++++++++++++++++ 4 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 moduleauthority/journal.go create mode 100644 moduleauthority/journal_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4dc98..042371c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -234,6 +234,31 @@ compiling. `gen/` and `SOURCE.txt` are unchanged (module-saas-starter v0.0.94). v0.0.94 gateway relays a pre-cutover token on all three mints, and the SDK refuses each. The guarded `sdk-go/workcontext` now stays at the host's own pin in the conformance build, since this SDK no longer requires it. +## Unreleased — read the host's journal (waiting for the host release) + +**`gen/` here is ahead of the ref `SOURCE.txt` records.** `ReadJournal` and its +three messages are not in `module-saas-starter` v0.0.94, and no release carries +them yet. The two generated files below were produced by this repo's own +`buf.gen.yaml` (buf 1.73.0, the pinned plugins) from the v0.0.94 proto with the +wire contract added to `module_capabilities.proto` in a scratch checkout, not +by hand. They are a stopgap: the `gen-drift` job fails until the contract is in +a published `module-saas-starter` release and `gen/` is regenerated from that +tag. Do not release this until it is. + +### Added (moduleauthority, read the journal) + +- `Client.ReadJournal(ctx, afterSeq, limit, types) (JournalPage, error)` calls + `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the + module, through the same credentials and refresh-once rule as every authority + call. The host serves it only to a cross-tenant module principal; any other + caller gets the host's `PermissionDenied`, preserved. `JournalPage.NextAfterSeq` + is the resume point the host guarantees misses no committed entry, so a + reader resumes from it rather than from the last `Seq` it saw. A negative + position, a limit outside 0..500 (0 is the host's default of 100), or no type + (or an empty one) is `ErrInvalidJournalRead` before any call. +- Generated: `ReadJournal` on `ModuleCapabilitiesService`, with + `ModuleReadJournalRequest`, `ModuleJournalEntry` and + `ModuleReadJournalResponse`. ## v0.1.1 — the broker contract, held against the host's real gateway diff --git a/moduleauthority/host_fake_test.go b/moduleauthority/host_fake_test.go index 98cbd55..75aa6f9 100644 --- a/moduleauthority/host_fake_test.go +++ b/moduleauthority/host_fake_test.go @@ -341,6 +341,7 @@ type authorityCall struct { // the capability travelled with it and that the identifiers are the // capability's own. authorityQuestion *v1.ModuleResolveWorkContextAuthorityRequest + journal *v1.ModuleReadJournalRequest } // servedOnAuthority is the slice of business.ModuleAuthorityProcedures this @@ -357,6 +358,10 @@ var servedOnAuthority = map[string]bool{ accountsv1connect.ModuleCapabilitiesServiceNotifyOrgAdminsProcedure: true, accountsv1connect.ModuleCapabilitiesServiceNotifyUserProcedure: true, accountsv1connect.ModuleCapabilitiesServiceResolveWorkContextAuthorityProcedure: true, + // ReadJournal is not in the v0.0.94 host: it is the wire contract the next + // host release is to serve (see SOURCE.txt), faked here as that contract + // states it. + accountsv1connect.ModuleCapabilitiesServiceReadJournalProcedure: true, } type fakeAuthority struct { @@ -371,6 +376,15 @@ type fakeAuthority struct { authorityRefusal error // authority is the live state this host answers, by question kind. authority authorityState + // journal is what the fake host has committed, in seq order. A test sets it + // before a call; the fake serves it the way the contract states. + journal []*v1.ModuleJournalEntry + // journalNext, when set, overrides next_after_seq, to model a host that + // holds the resume point back of the last entry it returned. + journalNext *int64 + // crossTenant is whether the fake host treats this caller as a cross-tenant + // module principal. ReadJournal is refused to any other. + crossTenant bool mu sync.Mutex calls []authorityCall @@ -378,7 +392,7 @@ type fakeAuthority struct { func newFakeAuthority(t *testing.T, gateway *fakeGateway, creds credentials.TransportCredentials) *fakeAuthority { t.Helper() - a := &fakeAuthority{gateway: gateway} + a := &fakeAuthority{gateway: gateway, crossTenant: true} options := []grpc.ServerOption{grpc.UnknownServiceHandler(a.handle)} if creds != nil { options = append(options, grpc.Creds(creds)) @@ -427,6 +441,9 @@ func (a *fakeAuthority) handle(_ any, stream grpc.ServerStream) error { case accountsv1connect.ModuleCapabilitiesServiceResolveWorkContextAuthorityProcedure: call.authorityQuestion = &v1.ModuleResolveWorkContextAuthorityRequest{} recvErr = stream.RecvMsg(call.authorityQuestion) + case accountsv1connect.ModuleCapabilitiesServiceReadJournalProcedure: + call.journal = &v1.ModuleReadJournalRequest{} + recvErr = stream.RecvMsg(call.journal) default: recvErr = stream.RecvMsg(request) call.request = request @@ -451,6 +468,9 @@ func (a *fakeAuthority) handle(_ any, stream grpc.ServerStream) error { if !a.gateway.accepts(call.workContext[0]) { return status.Error(codes.Unauthenticated, "module work context is not a valid capability") } + if j := call.journal; j != nil { + return a.serveJournal(stream, j) + } if call.declaration != nil { if a.declarationError != nil { return a.declarationError @@ -555,6 +575,40 @@ func (a *fakeAuthority) handle(_ any, stream grpc.ServerStream) error { return stream.SendMsg(&v1.IssuedWorkContext{Token: sealedToken("child.token")}) } +// serveJournal answers ReadJournal as the wire contract states it: refused to a +// caller that is not a cross-tenant module principal, entries with a seq +// greater than after_seq of the requested types, at most limit of them (zero +// meaning 100), and a resume point. +func (a *fakeAuthority) serveJournal(stream grpc.ServerStream, req *v1.ModuleReadJournalRequest) error { + a.mu.Lock() + defer a.mu.Unlock() + if !a.crossTenant { + return status.Error(codes.PermissionDenied, "journal is served to cross-tenant module principals only") + } + if req.GetAfterSeq() < 0 || req.GetLimit() < 0 || req.GetLimit() > 500 || len(req.GetTypes()) == 0 { + return status.Error(codes.InvalidArgument, "after_seq >= 0, limit 0..500 and at least one type required") + } + limit := int(req.GetLimit()) + if limit == 0 { + limit = 100 + } + out := &v1.ModuleReadJournalResponse{NextAfterSeq: req.GetAfterSeq()} + for _, e := range a.journal { + if e.GetSeq() <= req.GetAfterSeq() || !slices.Contains(req.GetTypes(), e.GetType()) { + continue + } + if len(out.Entries) == limit { + break + } + out.Entries = append(out.Entries, e) + out.NextAfterSeq = e.GetSeq() + } + if a.journalNext != nil { + out.NextAfterSeq = *a.journalNext + } + return stream.SendMsg(out) +} + func (a *fakeAuthority) snapshot() []authorityCall { a.mu.Lock() defer a.mu.Unlock() diff --git a/moduleauthority/journal.go b/moduleauthority/journal.go new file mode 100644 index 0000000..c8ede19 --- /dev/null +++ b/moduleauthority/journal.go @@ -0,0 +1,88 @@ +package moduleauthority + +import ( + "context" + "errors" + "fmt" + "time" + + v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" + "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1/accountsv1connect" +) + +// ErrInvalidJournalRead is returned before any call for a journal read the host +// would refuse as malformed: a negative position, a page size outside 0..500, or +// no registered type to read. +var ErrInvalidJournalRead = errors.New("module authority: invalid journal read") + +// MaxJournalPage is the largest page the host serves. A limit of zero asks for +// the host's default page. +const MaxJournalPage = 500 + +// JournalEntry is one entry of the host's journal, as the host committed it. +type JournalEntry struct { + // Seq is the entry's position in the journal. It orders entries; it is not + // a promise that every lower position is already visible, which is why a + // reader resumes from JournalPage.NextAfterSeq and never from the last Seq it + // happened to see. + Seq int64 + EventID string + Type string + // OrganizationID is empty for an entry that belongs to no organization. + OrganizationID string + OccurredAt time.Time + // Data is the entry's payload, decoded from the host's JSON object. + Data map[string]any +} + +// JournalPage is one page of the journal. +type JournalPage struct { + Entries []JournalEntry + // NextAfterSeq is where the next read resumes. The host guarantees that a + // reader resuming here misses no committed entry; resuming from anywhere + // else carries no such promise. It equals the position the page was asked + // from when the page is empty. + NextAfterSeq int64 +} + +// ReadJournal reads the host's journal forward from afterSeq, for the given +// registered event types, as this module. The host serves it only to a +// cross-tenant module principal; any other caller is refused with the host's +// own code, which is preserved. +// +// afterSeq is exclusive, and zero reads from the oldest retained entry. limit is +// 1..500, and zero means the host's default. types must name at least one +// registered type. A read changes nothing on the host: it is safe to repeat, and +// a reader that resumes from an earlier NextAfterSeq is served the same entries +// again. A rejected module capability is refreshed and retried once, like every +// authority call. +func (c *Client) ReadJournal(ctx context.Context, afterSeq int64, limit int32, types []string) (JournalPage, error) { + if afterSeq < 0 || limit < 0 || limit > MaxJournalPage || len(types) == 0 { + return JournalPage{}, ErrInvalidJournalRead + } + for _, t := range types { + if t == "" { + return JournalPage{}, ErrInvalidJournalRead + } + } + res, err := callAsModule(ctx, c, accountsv1connect.ModuleCapabilitiesServiceClient.ReadJournal, &v1.ModuleReadJournalRequest{ + AfterSeq: afterSeq, + Limit: limit, + Types: append([]string(nil), types...), + }) + if err != nil { + return JournalPage{}, fmt.Errorf("module authority: read journal: %w", err) + } + page := JournalPage{NextAfterSeq: res.GetNextAfterSeq()} + for _, e := range res.GetEntries() { + entry := JournalEntry{ + Seq: e.GetSeq(), EventID: e.GetEventId(), Type: e.GetType(), + OrganizationID: e.GetOrganizationId(), Data: e.GetData().AsMap(), + } + if e.GetOccurredAt() != nil { + entry.OccurredAt = e.GetOccurredAt().AsTime() + } + page.Entries = append(page.Entries, entry) + } + return page, nil +} diff --git a/moduleauthority/journal_test.go b/moduleauthority/journal_test.go new file mode 100644 index 0000000..851bc8c --- /dev/null +++ b/moduleauthority/journal_test.go @@ -0,0 +1,255 @@ +package moduleauthority + +import ( + "errors" + "fmt" + "reflect" + "testing" + "time" + + "connectrpc.com/connect" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" +) + +const journalType = "example.recorded.v1" + +var journalEpoch = time.Date(2026, 10, 12, 9, 30, 0, 0, time.UTC) + +// journalEntry is a committed entry of the fake host's journal. +func journalEntry(t *testing.T, seq int64, organization, kind string) *v1.ModuleJournalEntry { + t.Helper() + data, err := structpb.NewStruct(map[string]any{"actor": "person-" + fmt.Sprint(seq), "count": float64(seq)}) + if err != nil { + t.Fatal(err) + } + return &v1.ModuleJournalEntry{ + Seq: seq, EventId: fmt.Sprintf("event-%d", seq), Type: kind, OrganizationId: organization, + OccurredAt: timestamppb.New(journalEpoch.Add(time.Duration(seq) * time.Second)), Data: data, + } +} + +func (a *fakeAuthority) commit(entries ...*v1.ModuleJournalEntry) { + a.mu.Lock() + defer a.mu.Unlock() + a.journal = append(a.journal, entries...) +} + +func (a *fakeAuthority) setCrossTenant(v bool) { + a.mu.Lock() + defer a.mu.Unlock() + a.crossTenant = v +} + +func (a *fakeAuthority) holdResumeAt(seq int64) { + a.mu.Lock() + defer a.mu.Unlock() + a.journalNext = &seq +} + +func TestReadJournalReturnsAPageAndItsResumePoint(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + h.authority.commit( + journalEntry(t, 1, testTenant, journalType), + journalEntry(t, 2, "", journalType), // an entry with no organization + journalEntry(t, 3, testTenant, "example.other.v1"), // a type the reader did not ask for + journalEntry(t, 4, testOwner, journalType), + ) + + page, err := client.ReadJournal(t.Context(), 0, 10, []string{journalType}) + if err != nil { + t.Fatal(err) + } + if page.NextAfterSeq != 4 { + t.Fatalf("NextAfterSeq = %d, want 4", page.NextAfterSeq) + } + want := []JournalEntry{ + {Seq: 1, EventID: "event-1", Type: journalType, OrganizationID: testTenant, OccurredAt: journalEpoch.Add(time.Second), Data: map[string]any{"actor": "person-1", "count": float64(1)}}, + {Seq: 2, EventID: "event-2", Type: journalType, OrganizationID: "", OccurredAt: journalEpoch.Add(2 * time.Second), Data: map[string]any{"actor": "person-2", "count": float64(2)}}, + {Seq: 4, EventID: "event-4", Type: journalType, OrganizationID: testOwner, OccurredAt: journalEpoch.Add(4 * time.Second), Data: map[string]any{"actor": "person-4", "count": float64(4)}}, + } + if !reflect.DeepEqual(page.Entries, want) { + t.Fatalf("entries = %+v\nwant %+v", page.Entries, want) + } + + // The request on the wire, and the authority it travelled under. + calls := h.authority.snapshot() + if len(calls) != 1 { + t.Fatalf("authority calls = %d, want 1", len(calls)) + } + call := calls[0] + if call.method != "/saas.accounts.v1.ModuleCapabilitiesService/ReadJournal" { + t.Fatalf("method = %q", call.method) + } + if len(call.internalToken) != 1 || call.internalToken[0] != testInternalToken || + len(call.workContext) != 1 || call.workContext[0] != "module.token1" || len(call.authorization) != 0 { + t.Fatal("wrong authority carriers") + } + if call.journal.GetAfterSeq() != 0 || call.journal.GetLimit() != 10 || !reflect.DeepEqual(call.journal.GetTypes(), []string{journalType}) { + t.Fatalf("request = %+v", call.journal) + } + h.assertNoStrayPaths(t) +} + +func TestReadJournalResumesFromTheReturnedPointAndHonoursTheLimit(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + for seq := int64(1); seq <= 5; seq++ { + h.authority.commit(journalEntry(t, seq, testTenant, journalType)) + } + var seen []int64 + after := int64(0) + for pages := 0; ; pages++ { + page, err := client.ReadJournal(t.Context(), after, 2, []string{journalType}) + if err != nil { + t.Fatal(err) + } + if len(page.Entries) == 0 { + if page.NextAfterSeq != after { + t.Fatalf("an empty page moved the resume point from %d to %d", after, page.NextAfterSeq) + } + break + } + if len(page.Entries) > 2 { + t.Fatalf("page of %d entries for a limit of 2", len(page.Entries)) + } + for _, e := range page.Entries { + seen = append(seen, e.Seq) + } + after = page.NextAfterSeq + if pages > 5 { + t.Fatal("the reader does not reach the end of the journal") + } + } + if !reflect.DeepEqual(seen, []int64{1, 2, 3, 4, 5}) { + t.Fatalf("seen = %v", seen) + } +} + +// The resume point is the host's to name. A reader must use it as given, even +// when it is behind the last entry the page carried. +func TestReadJournalReportsTheHostsResumePointNotTheLastSeq(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + h.authority.commit(journalEntry(t, 1, testTenant, journalType), journalEntry(t, 3, testTenant, journalType)) + h.authority.holdResumeAt(1) + page, err := client.ReadJournal(t.Context(), 0, 0, []string{journalType}) + if err != nil { + t.Fatal(err) + } + if len(page.Entries) != 2 || page.NextAfterSeq != 1 { + t.Fatalf("entries=%d next=%d, want 2 entries and next=1", len(page.Entries), page.NextAfterSeq) + } +} + +func TestReadJournalAnEntryWithoutATimestampOrDataDecodes(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + h.authority.commit(&v1.ModuleJournalEntry{Seq: 7, EventId: "event-7", Type: journalType}) + page, err := client.ReadJournal(t.Context(), 0, 1, []string{journalType}) + if err != nil { + t.Fatal(err) + } + if len(page.Entries) != 1 || !page.Entries[0].OccurredAt.IsZero() || len(page.Entries[0].Data) != 0 { + t.Fatalf("entries = %+v", page.Entries) + } +} + +// A refusal keeps the host's own code, like every other authority call: a +// caller that is not a cross-tenant module principal is PermissionDenied, and +// nothing is returned that could be mistaken for an empty journal. +func TestReadJournalRefusalKeepsTheHostsCode(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + h.authority.commit(journalEntry(t, 1, testTenant, journalType)) + h.authority.setCrossTenant(false) + page, err := client.ReadJournal(t.Context(), 0, 10, []string{journalType}) + if connect.CodeOf(err) != connect.CodePermissionDenied { + t.Fatalf("err = %v, want PermissionDenied", err) + } + if len(page.Entries) != 0 || page.NextAfterSeq != 0 { + t.Fatalf("a refused read returned %+v", page) + } + if calls := h.authority.snapshot(); len(calls) != 1 { + t.Fatalf("a refusal that is not a rejected capability was retried: %d calls", len(calls)) + } +} + +func TestReadJournalRefreshesOnlyARejectedModuleCapability(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + h.authority.commit(journalEntry(t, 1, testTenant, journalType)) + if _, err := client.ModuleWorkContext(t.Context()); err != nil { + t.Fatal(err) + } + h.gateway.revoke("module.token1") + page, err := client.ReadJournal(t.Context(), 0, 10, []string{journalType}) + if err != nil || len(page.Entries) != 1 { + t.Fatalf("page = %+v, err = %v", page, err) + } + calls := h.authority.snapshot() + if len(calls) != 2 || calls[0].workContext[0] != "module.token1" || calls[1].workContext[0] != "module.token2" { + t.Fatalf("calls = %+v", calls) + } +} + +func TestReadJournalRefusesAMalformedReadBeforeAnyCall(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + for name, read := range map[string]struct { + after int64 + limit int32 + types []string + }{ + "negative position": {after: -1, limit: 10, types: []string{journalType}}, + "negative limit": {limit: -1, types: []string{journalType}}, + "limit over 500": {limit: 501, types: []string{journalType}}, + "no types": {limit: 10}, + "empty type": {limit: 10, types: []string{journalType, ""}}, + } { + t.Run(name, func(t *testing.T) { + if _, err := client.ReadJournal(t.Context(), read.after, read.limit, read.types); !errors.Is(err, ErrInvalidJournalRead) { + t.Fatalf("err = %v, want ErrInvalidJournalRead", err) + } + }) + } + if calls := h.authority.snapshot(); len(calls) != 0 { + t.Fatalf("a malformed read reached the host %d times", len(calls)) + } + // The bounds themselves are valid. + for _, limit := range []int32{0, 1, MaxJournalPage} { + if _, err := client.ReadJournal(t.Context(), 0, limit, []string{journalType}); err != nil { + t.Fatalf("limit %d: %v", limit, err) + } + } +} + +func TestReadJournalDoesNotAliasTheCallersTypes(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + types := []string{journalType} + if _, err := client.ReadJournal(t.Context(), 0, 1, types); err != nil { + t.Fatal(err) + } + types[0] = "changed" + if got := h.authority.snapshot()[0].journal.GetTypes(); got[0] != journalType { + t.Fatalf("request types followed the caller's slice: %v", got) + } +} + +func TestReadJournalNeedsTheAuthoritySeam(t *testing.T) { + h := newHost(t) + seams := h.seams() + seams.Authority = Authority{} + client, err := New(seams, moduleCredentials) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = client.Close() }) + if _, err := client.ReadJournal(t.Context(), 0, 1, []string{journalType}); !errors.Is(err, ErrNoAuthority) { + t.Fatalf("err = %v, want ErrNoAuthority", err) + } +} From 2f999456c70272106d250b4e3f1e5e6e2bec454a Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 13 Oct 2026 02:45:37 +0000 Subject: [PATCH 03/18] feat(moduleauthority): ReadJournal reads by page with opaque positions ReadJournal(ctx, after, limit, types, deliveryBindings) takes and returns opaque positions: a reader stores and compares them, never parses them. JournalEntry gains Position and DeliveryMachinery (the host's mark for a mint or exchange of one of the named delivery bindings, or the caller's own module mint), and JournalPage.NextAfter replaces NextAfterSeq. An empty type or delivery binding id is ErrInvalidJournalRead before any call. The host fake serves the new contract and the tests follow it. STOPGAP, labelled, as before: the regenerated gen/ carries the contract (ModuleReadJournalRequest with after and delivery_bindings, ModuleJournalEntry with position and delivery_machinery, ModuleReadJournalResponse with next_after) from a scratch saas-starter v0.0.94 checkout plus the contract, by this repo's buf.gen.yaml (buf 1.73.0). It replaces the earlier contract, which was never published. The gen-drift job fails until gen/ is regenerated from the saas-starter release that publishes it; SOURCE.txt and CHANGELOG.md say so. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 22 +++-- moduleauthority/host_fake_test.go | 57 ++++++++--- moduleauthority/journal.go | 72 ++++++++------ moduleauthority/journal_test.go | 158 +++++++++++++++++++----------- 4 files changed, 201 insertions(+), 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 042371c..bade6fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -247,15 +247,23 @@ tag. Do not release this until it is. ### Added (moduleauthority, read the journal) -- `Client.ReadJournal(ctx, afterSeq, limit, types) (JournalPage, error)` calls - `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the +- `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` + calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the module, through the same credentials and refresh-once rule as every authority call. The host serves it only to a cross-tenant module principal; any other - caller gets the host's `PermissionDenied`, preserved. `JournalPage.NextAfterSeq` - is the resume point the host guarantees misses no committed entry, so a - reader resumes from it rather than from the last `Seq` it saw. A negative - position, a limit outside 0..500 (0 is the host's default of 100), or no type - (or an empty one) is `ErrInvalidJournalRead` before any call. + caller gets the host's `PermissionDenied`, preserved. Positions are opaque + strings: `after` is exclusive, `""` reads from the oldest retained entry, and + `JournalPage.NextAfter` is the resume point the host guarantees misses no + committed entry and never passes an unfinished transaction, so a reader + resumes from it rather than from the last `Position` it saw, and only stores + and compares positions for equality. +- `deliveryBindings` are the caller's binding ids whose mints and exchanges count + as delivery machinery; the host sets `JournalEntry.DeliveryMachinery` on the + entries that record one of them, or the caller's own module work-context mint, + so a reader that delivers journal entries can tell a page of its own delivery + from a page of real activity. A limit outside 0..500 (0 is the host's default of + 100), no type, or an empty type or binding id is `ErrInvalidJournalRead` before + any call. - Generated: `ReadJournal` on `ModuleCapabilitiesService`, with `ModuleReadJournalRequest`, `ModuleJournalEntry` and `ModuleReadJournalResponse`. diff --git a/moduleauthority/host_fake_test.go b/moduleauthority/host_fake_test.go index 75aa6f9..c9eb46a 100644 --- a/moduleauthority/host_fake_test.go +++ b/moduleauthority/host_fake_test.go @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" @@ -376,12 +377,18 @@ type fakeAuthority struct { authorityRefusal error // authority is the live state this host answers, by question kind. authority authorityState - // journal is what the fake host has committed, in seq order. A test sets it + // journal is what the fake host has committed, in order. A test sets it // before a call; the fake serves it the way the contract states. journal []*v1.ModuleJournalEntry - // journalNext, when set, overrides next_after_seq, to model a host that - // holds the resume point back of the last entry it returned. - journalNext *int64 + // journalNext, when set, overrides next_after, to model a host that holds + // the resume point back of the last entry it returned. + journalNext *string + // machinery records, by event id, which entries are the host's delivery + // machinery: the value is the binding id the mint or exchange was for, or + // moduleMintMarker for the caller's own module work-context mint. The fake + // sets delivery_machinery on an entry only when the request names that + // binding, as the contract states. + machinery map[string]string // crossTenant is whether the fake host treats this caller as a cross-tenant // module principal. ReadJournal is refused to any other. crossTenant bool @@ -575,36 +582,56 @@ func (a *fakeAuthority) handle(_ any, stream grpc.ServerStream) error { return stream.SendMsg(&v1.IssuedWorkContext{Token: sealedToken("child.token")}) } +// moduleMintMarker marks, in fakeAuthority.machinery, the caller's own module +// work-context mint: delivery machinery whatever bindings the read names. +const moduleMintMarker = "" + // serveJournal answers ReadJournal as the wire contract states it: refused to a -// caller that is not a cross-tenant module principal, entries with a seq -// greater than after_seq of the requested types, at most limit of them (zero -// meaning 100), and a resume point. +// caller that is not a cross-tenant module principal, entries after the opaque +// position `after` of the requested types, at most limit of them (zero meaning +// 100), the host's delivery_machinery mark, and a resume point. The fake knows +// what its positions mean; a client must not. func (a *fakeAuthority) serveJournal(stream grpc.ServerStream, req *v1.ModuleReadJournalRequest) error { a.mu.Lock() defer a.mu.Unlock() if !a.crossTenant { return status.Error(codes.PermissionDenied, "journal is served to cross-tenant module principals only") } - if req.GetAfterSeq() < 0 || req.GetLimit() < 0 || req.GetLimit() > 500 || len(req.GetTypes()) == 0 { - return status.Error(codes.InvalidArgument, "after_seq >= 0, limit 0..500 and at least one type required") + if req.GetLimit() < 0 || req.GetLimit() > 500 || len(req.GetTypes()) == 0 { + return status.Error(codes.InvalidArgument, "limit 0..500 and at least one type required") + } + start := 0 + if after := req.GetAfter(); after != "" { + start = -1 + for i, e := range a.journal { + if e.GetPosition() == after { + start = i + 1 + } + } + if start < 0 { + return status.Error(codes.InvalidArgument, "after is not a position of this journal") + } } limit := int(req.GetLimit()) if limit == 0 { limit = 100 } - out := &v1.ModuleReadJournalResponse{NextAfterSeq: req.GetAfterSeq()} - for _, e := range a.journal { - if e.GetSeq() <= req.GetAfterSeq() || !slices.Contains(req.GetTypes(), e.GetType()) { + out := &v1.ModuleReadJournalResponse{NextAfter: req.GetAfter()} + for _, e := range a.journal[start:] { + if !slices.Contains(req.GetTypes(), e.GetType()) { continue } if len(out.Entries) == limit { break } - out.Entries = append(out.Entries, e) - out.NextAfterSeq = e.GetSeq() + served := proto.Clone(e).(*v1.ModuleJournalEntry) + binding, recorded := a.machinery[e.GetEventId()] + served.DeliveryMachinery = recorded && (binding == moduleMintMarker || slices.Contains(req.GetDeliveryBindings(), binding)) + out.Entries = append(out.Entries, served) + out.NextAfter = e.GetPosition() } if a.journalNext != nil { - out.NextAfterSeq = *a.journalNext + out.NextAfter = *a.journalNext } return stream.SendMsg(out) } diff --git a/moduleauthority/journal.go b/moduleauthority/journal.go index c8ede19..f192fe4 100644 --- a/moduleauthority/journal.go +++ b/moduleauthority/journal.go @@ -11,8 +11,8 @@ import ( ) // ErrInvalidJournalRead is returned before any call for a journal read the host -// would refuse as malformed: a negative position, a page size outside 0..500, or -// no registered type to read. +// would refuse as malformed: a page size outside 0..500, no registered type to +// read, or an empty type or delivery binding id. var ErrInvalidJournalRead = errors.New("module authority: invalid journal read") // MaxJournalPage is the largest page the host serves. A limit of zero asks for @@ -21,43 +21,52 @@ const MaxJournalPage = 500 // JournalEntry is one entry of the host's journal, as the host committed it. type JournalEntry struct { - // Seq is the entry's position in the journal. It orders entries; it is not - // a promise that every lower position is already visible, which is why a - // reader resumes from JournalPage.NextAfterSeq and never from the last Seq it - // happened to see. - Seq int64 - EventID string - Type string + // Position is the entry's place in the journal. It is opaque: a reader + // stores it and compares it for equality, and never parses it or orders by + // it. The host orders entries; a reader resumes from JournalPage.NextAfter, + // not from the last Position it happened to see. + Position string + EventID string + Type string // OrganizationID is empty for an entry that belongs to no organization. OrganizationID string OccurredAt time.Time // Data is the entry's payload, decoded from the host's JSON object. Data map[string]any + // DeliveryMachinery is set by the host: the entry records a mint or an + // exchange for one of the delivery bindings the read named, or the caller's + // own module work-context mint. A reader that delivers journal entries + // uses it to tell a page of its own delivery from a page of real activity. + DeliveryMachinery bool } // JournalPage is one page of the journal. type JournalPage struct { Entries []JournalEntry - // NextAfterSeq is where the next read resumes. The host guarantees that a - // reader resuming here misses no committed entry; resuming from anywhere - // else carries no such promise. It equals the position the page was asked - // from when the page is empty. - NextAfterSeq int64 + // NextAfter is where the next read resumes. It is opaque, like Position. The + // host guarantees that a reader resuming here misses no committed entry, and + // never passes an unfinished transaction; resuming from anywhere else + // carries no such promise. It equals the position the page was asked from + // when the page is empty. + NextAfter string } -// ReadJournal reads the host's journal forward from afterSeq, for the given +// ReadJournal reads the host's journal forward from after, for the given // registered event types, as this module. The host serves it only to a // cross-tenant module principal; any other caller is refused with the host's // own code, which is preserved. // -// afterSeq is exclusive, and zero reads from the oldest retained entry. limit is -// 1..500, and zero means the host's default. types must name at least one -// registered type. A read changes nothing on the host: it is safe to repeat, and -// a reader that resumes from an earlier NextAfterSeq is served the same entries -// again. A rejected module capability is refreshed and retried once, like every -// authority call. -func (c *Client) ReadJournal(ctx context.Context, afterSeq int64, limit int32, types []string) (JournalPage, error) { - if afterSeq < 0 || limit < 0 || limit > MaxJournalPage || len(types) == 0 { +// after is an opaque position the host gave, exclusive; the empty string reads +// from the oldest retained entry. limit is 1..500, and zero means the host's +// default. types must name at least one registered type. deliveryBindings are +// the caller's binding ids whose mints and exchanges count as delivery +// machinery: the host sets JournalEntry.DeliveryMachinery on the entries that +// record one of them, and may be empty for a reader that has none. A read +// changes nothing on the host: it is safe to repeat, and a reader that resumes +// from an earlier NextAfter is served the same entries again. A rejected module +// capability is refreshed and retried once, like every authority call. +func (c *Client) ReadJournal(ctx context.Context, after string, limit int32, types, deliveryBindings []string) (JournalPage, error) { + if limit < 0 || limit > MaxJournalPage || len(types) == 0 { return JournalPage{}, ErrInvalidJournalRead } for _, t := range types { @@ -65,19 +74,26 @@ func (c *Client) ReadJournal(ctx context.Context, afterSeq int64, limit int32, t return JournalPage{}, ErrInvalidJournalRead } } + for _, b := range deliveryBindings { + if b == "" { + return JournalPage{}, ErrInvalidJournalRead + } + } res, err := callAsModule(ctx, c, accountsv1connect.ModuleCapabilitiesServiceClient.ReadJournal, &v1.ModuleReadJournalRequest{ - AfterSeq: afterSeq, - Limit: limit, - Types: append([]string(nil), types...), + After: after, + Limit: limit, + Types: append([]string(nil), types...), + DeliveryBindings: append([]string(nil), deliveryBindings...), }) if err != nil { return JournalPage{}, fmt.Errorf("module authority: read journal: %w", err) } - page := JournalPage{NextAfterSeq: res.GetNextAfterSeq()} + page := JournalPage{NextAfter: res.GetNextAfter()} for _, e := range res.GetEntries() { entry := JournalEntry{ - Seq: e.GetSeq(), EventID: e.GetEventId(), Type: e.GetType(), + Position: e.GetPosition(), EventID: e.GetEventId(), Type: e.GetType(), OrganizationID: e.GetOrganizationId(), Data: e.GetData().AsMap(), + DeliveryMachinery: e.GetDeliveryMachinery(), } if e.GetOccurredAt() != nil { entry.OccurredAt = e.GetOccurredAt().AsTime() diff --git a/moduleauthority/journal_test.go b/moduleauthority/journal_test.go index 851bc8c..328d59b 100644 --- a/moduleauthority/journal_test.go +++ b/moduleauthority/journal_test.go @@ -18,16 +18,17 @@ const journalType = "example.recorded.v1" var journalEpoch = time.Date(2026, 10, 12, 9, 30, 0, 0, time.UTC) -// journalEntry is a committed entry of the fake host's journal. -func journalEntry(t *testing.T, seq int64, organization, kind string) *v1.ModuleJournalEntry { +// journalEntry is a committed entry of the fake host's journal. Its position is +// an opaque string to the client; the fake host reads it back for equality. +func journalEntry(t *testing.T, n int, organization, kind string) *v1.ModuleJournalEntry { t.Helper() - data, err := structpb.NewStruct(map[string]any{"actor": "person-" + fmt.Sprint(seq), "count": float64(seq)}) + data, err := structpb.NewStruct(map[string]any{"actor": "person-" + fmt.Sprint(n), "count": float64(n)}) if err != nil { t.Fatal(err) } return &v1.ModuleJournalEntry{ - Seq: seq, EventId: fmt.Sprintf("event-%d", seq), Type: kind, OrganizationId: organization, - OccurredAt: timestamppb.New(journalEpoch.Add(time.Duration(seq) * time.Second)), Data: data, + Position: fmt.Sprintf("tx%d.%d", 100+n/2, n), EventId: fmt.Sprintf("event-%d", n), Type: kind, OrganizationId: organization, + OccurredAt: timestamppb.New(journalEpoch.Add(time.Duration(n) * time.Second)), Data: data, } } @@ -37,39 +38,48 @@ func (a *fakeAuthority) commit(entries ...*v1.ModuleJournalEntry) { a.journal = append(a.journal, entries...) } +// markMachinery records that the entry is the host's delivery machinery for the +// binding (or, with moduleMintMarker, the caller's own module mint). +func (a *fakeAuthority) markMachinery(e *v1.ModuleJournalEntry, binding string) { + a.mu.Lock() + defer a.mu.Unlock() + if a.machinery == nil { + a.machinery = map[string]string{} + } + a.machinery[e.GetEventId()] = binding +} + func (a *fakeAuthority) setCrossTenant(v bool) { a.mu.Lock() defer a.mu.Unlock() a.crossTenant = v } -func (a *fakeAuthority) holdResumeAt(seq int64) { +func (a *fakeAuthority) holdResumeAt(position string) { a.mu.Lock() defer a.mu.Unlock() - a.journalNext = &seq + a.journalNext = &position } func TestReadJournalReturnsAPageAndItsResumePoint(t *testing.T) { h := newHost(t) client := h.client(t, moduleCredentials) - h.authority.commit( - journalEntry(t, 1, testTenant, journalType), - journalEntry(t, 2, "", journalType), // an entry with no organization - journalEntry(t, 3, testTenant, "example.other.v1"), // a type the reader did not ask for - journalEntry(t, 4, testOwner, journalType), - ) - - page, err := client.ReadJournal(t.Context(), 0, 10, []string{journalType}) + e1, e2 := journalEntry(t, 1, testTenant, journalType), journalEntry(t, 2, "", journalType) // e2 has no organization + e3 := journalEntry(t, 3, testTenant, "example.other.v1") // a type the reader did not ask for + e4 := journalEntry(t, 4, testOwner, journalType) + h.authority.commit(e1, e2, e3, e4) + + page, err := client.ReadJournal(t.Context(), "", 10, []string{journalType}, nil) if err != nil { t.Fatal(err) } - if page.NextAfterSeq != 4 { - t.Fatalf("NextAfterSeq = %d, want 4", page.NextAfterSeq) + if page.NextAfter != e4.GetPosition() { + t.Fatalf("NextAfter = %q, want %q", page.NextAfter, e4.GetPosition()) } want := []JournalEntry{ - {Seq: 1, EventID: "event-1", Type: journalType, OrganizationID: testTenant, OccurredAt: journalEpoch.Add(time.Second), Data: map[string]any{"actor": "person-1", "count": float64(1)}}, - {Seq: 2, EventID: "event-2", Type: journalType, OrganizationID: "", OccurredAt: journalEpoch.Add(2 * time.Second), Data: map[string]any{"actor": "person-2", "count": float64(2)}}, - {Seq: 4, EventID: "event-4", Type: journalType, OrganizationID: testOwner, OccurredAt: journalEpoch.Add(4 * time.Second), Data: map[string]any{"actor": "person-4", "count": float64(4)}}, + {Position: e1.GetPosition(), EventID: "event-1", Type: journalType, OrganizationID: testTenant, OccurredAt: journalEpoch.Add(time.Second), Data: map[string]any{"actor": "person-1", "count": float64(1)}}, + {Position: e2.GetPosition(), EventID: "event-2", Type: journalType, OrganizationID: "", OccurredAt: journalEpoch.Add(2 * time.Second), Data: map[string]any{"actor": "person-2", "count": float64(2)}}, + {Position: e4.GetPosition(), EventID: "event-4", Type: journalType, OrganizationID: testOwner, OccurredAt: journalEpoch.Add(4 * time.Second), Data: map[string]any{"actor": "person-4", "count": float64(4)}}, } if !reflect.DeepEqual(page.Entries, want) { t.Fatalf("entries = %+v\nwant %+v", page.Entries, want) @@ -88,7 +98,8 @@ func TestReadJournalReturnsAPageAndItsResumePoint(t *testing.T) { len(call.workContext) != 1 || call.workContext[0] != "module.token1" || len(call.authorization) != 0 { t.Fatal("wrong authority carriers") } - if call.journal.GetAfterSeq() != 0 || call.journal.GetLimit() != 10 || !reflect.DeepEqual(call.journal.GetTypes(), []string{journalType}) { + if call.journal.GetAfter() != "" || call.journal.GetLimit() != 10 || !reflect.DeepEqual(call.journal.GetTypes(), []string{journalType}) || + len(call.journal.GetDeliveryBindings()) != 0 { t.Fatalf("request = %+v", call.journal) } h.assertNoStrayPaths(t) @@ -97,19 +108,19 @@ func TestReadJournalReturnsAPageAndItsResumePoint(t *testing.T) { func TestReadJournalResumesFromTheReturnedPointAndHonoursTheLimit(t *testing.T) { h := newHost(t) client := h.client(t, moduleCredentials) - for seq := int64(1); seq <= 5; seq++ { - h.authority.commit(journalEntry(t, seq, testTenant, journalType)) + for n := 1; n <= 5; n++ { + h.authority.commit(journalEntry(t, n, testTenant, journalType)) } - var seen []int64 - after := int64(0) + var seen []string + after := "" for pages := 0; ; pages++ { - page, err := client.ReadJournal(t.Context(), after, 2, []string{journalType}) + page, err := client.ReadJournal(t.Context(), after, 2, []string{journalType}, nil) if err != nil { t.Fatal(err) } if len(page.Entries) == 0 { - if page.NextAfterSeq != after { - t.Fatalf("an empty page moved the resume point from %d to %d", after, page.NextAfterSeq) + if page.NextAfter != after { + t.Fatalf("an empty page moved the resume point from %q to %q", after, page.NextAfter) } break } @@ -117,39 +128,69 @@ func TestReadJournalResumesFromTheReturnedPointAndHonoursTheLimit(t *testing.T) t.Fatalf("page of %d entries for a limit of 2", len(page.Entries)) } for _, e := range page.Entries { - seen = append(seen, e.Seq) + seen = append(seen, e.EventID) } - after = page.NextAfterSeq + after = page.NextAfter if pages > 5 { t.Fatal("the reader does not reach the end of the journal") } } - if !reflect.DeepEqual(seen, []int64{1, 2, 3, 4, 5}) { + if !reflect.DeepEqual(seen, []string{"event-1", "event-2", "event-3", "event-4", "event-5"}) { t.Fatalf("seen = %v", seen) } } // The resume point is the host's to name. A reader must use it as given, even // when it is behind the last entry the page carried. -func TestReadJournalReportsTheHostsResumePointNotTheLastSeq(t *testing.T) { +func TestReadJournalReportsTheHostsResumePointNotTheLastPosition(t *testing.T) { h := newHost(t) client := h.client(t, moduleCredentials) - h.authority.commit(journalEntry(t, 1, testTenant, journalType), journalEntry(t, 3, testTenant, journalType)) - h.authority.holdResumeAt(1) - page, err := client.ReadJournal(t.Context(), 0, 0, []string{journalType}) + e1, e3 := journalEntry(t, 1, testTenant, journalType), journalEntry(t, 3, testTenant, journalType) + h.authority.commit(e1, e3) + h.authority.holdResumeAt(e1.GetPosition()) + page, err := client.ReadJournal(t.Context(), "", 0, []string{journalType}, nil) if err != nil { t.Fatal(err) } - if len(page.Entries) != 2 || page.NextAfterSeq != 1 { - t.Fatalf("entries=%d next=%d, want 2 entries and next=1", len(page.Entries), page.NextAfterSeq) + if len(page.Entries) != 2 || page.NextAfter != e1.GetPosition() { + t.Fatalf("entries=%d next=%q, want 2 entries and next=%q", len(page.Entries), page.NextAfter, e1.GetPosition()) + } +} + +// The host marks delivery machinery, for the bindings the read names and for +// the caller's own module mint; the client carries the mark and the bindings +// exactly, and a mark for a binding the read did not name is not set. +func TestReadJournalCarriesTheDeliveryBindingsAndTheHostsMachineryMark(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + real, mint, named, unnamed := journalEntry(t, 1, testTenant, journalType), journalEntry(t, 2, "", journalType), + journalEntry(t, 3, testTenant, journalType), journalEntry(t, 4, testTenant, journalType) + h.authority.commit(real, mint, named, unnamed) + h.authority.markMachinery(mint, moduleMintMarker) + h.authority.markMachinery(named, "binding-a") + h.authority.markMachinery(unnamed, "binding-b") + + page, err := client.ReadJournal(t.Context(), "", 0, []string{journalType}, []string{"binding-a", "binding-c"}) + if err != nil { + t.Fatal(err) + } + var got []bool + for _, e := range page.Entries { + got = append(got, e.DeliveryMachinery) + } + if !reflect.DeepEqual(got, []bool{false, true, true, false}) { + t.Fatalf("delivery_machinery = %v", got) + } + if b := h.authority.snapshot()[0].journal.GetDeliveryBindings(); !reflect.DeepEqual(b, []string{"binding-a", "binding-c"}) { + t.Fatalf("delivery_bindings on the wire = %v", b) } } func TestReadJournalAnEntryWithoutATimestampOrDataDecodes(t *testing.T) { h := newHost(t) client := h.client(t, moduleCredentials) - h.authority.commit(&v1.ModuleJournalEntry{Seq: 7, EventId: "event-7", Type: journalType}) - page, err := client.ReadJournal(t.Context(), 0, 1, []string{journalType}) + h.authority.commit(&v1.ModuleJournalEntry{Position: "p7", EventId: "event-7", Type: journalType}) + page, err := client.ReadJournal(t.Context(), "", 1, []string{journalType}, nil) if err != nil { t.Fatal(err) } @@ -166,11 +207,11 @@ func TestReadJournalRefusalKeepsTheHostsCode(t *testing.T) { client := h.client(t, moduleCredentials) h.authority.commit(journalEntry(t, 1, testTenant, journalType)) h.authority.setCrossTenant(false) - page, err := client.ReadJournal(t.Context(), 0, 10, []string{journalType}) + page, err := client.ReadJournal(t.Context(), "", 10, []string{journalType}, nil) if connect.CodeOf(err) != connect.CodePermissionDenied { t.Fatalf("err = %v, want PermissionDenied", err) } - if len(page.Entries) != 0 || page.NextAfterSeq != 0 { + if len(page.Entries) != 0 || page.NextAfter != "" { t.Fatalf("a refused read returned %+v", page) } if calls := h.authority.snapshot(); len(calls) != 1 { @@ -186,7 +227,7 @@ func TestReadJournalRefreshesOnlyARejectedModuleCapability(t *testing.T) { t.Fatal(err) } h.gateway.revoke("module.token1") - page, err := client.ReadJournal(t.Context(), 0, 10, []string{journalType}) + page, err := client.ReadJournal(t.Context(), "", 10, []string{journalType}, nil) if err != nil || len(page.Entries) != 1 { t.Fatalf("page = %+v, err = %v", page, err) } @@ -200,18 +241,18 @@ func TestReadJournalRefusesAMalformedReadBeforeAnyCall(t *testing.T) { h := newHost(t) client := h.client(t, moduleCredentials) for name, read := range map[string]struct { - after int64 - limit int32 - types []string + limit int32 + types []string + bindings []string }{ - "negative position": {after: -1, limit: 10, types: []string{journalType}}, - "negative limit": {limit: -1, types: []string{journalType}}, - "limit over 500": {limit: 501, types: []string{journalType}}, - "no types": {limit: 10}, - "empty type": {limit: 10, types: []string{journalType, ""}}, + "negative limit": {limit: -1, types: []string{journalType}}, + "limit over 500": {limit: 501, types: []string{journalType}}, + "no types": {limit: 10}, + "empty type": {limit: 10, types: []string{journalType, ""}}, + "empty delivery bound": {limit: 10, types: []string{journalType}, bindings: []string{"binding-a", ""}}, } { t.Run(name, func(t *testing.T) { - if _, err := client.ReadJournal(t.Context(), read.after, read.limit, read.types); !errors.Is(err, ErrInvalidJournalRead) { + if _, err := client.ReadJournal(t.Context(), "", read.limit, read.types, read.bindings); !errors.Is(err, ErrInvalidJournalRead) { t.Fatalf("err = %v, want ErrInvalidJournalRead", err) } }) @@ -221,7 +262,7 @@ func TestReadJournalRefusesAMalformedReadBeforeAnyCall(t *testing.T) { } // The bounds themselves are valid. for _, limit := range []int32{0, 1, MaxJournalPage} { - if _, err := client.ReadJournal(t.Context(), 0, limit, []string{journalType}); err != nil { + if _, err := client.ReadJournal(t.Context(), "", limit, []string{journalType}, nil); err != nil { t.Fatalf("limit %d: %v", limit, err) } } @@ -230,13 +271,14 @@ func TestReadJournalRefusesAMalformedReadBeforeAnyCall(t *testing.T) { func TestReadJournalDoesNotAliasTheCallersTypes(t *testing.T) { h := newHost(t) client := h.client(t, moduleCredentials) - types := []string{journalType} - if _, err := client.ReadJournal(t.Context(), 0, 1, types); err != nil { + types, bindings := []string{journalType}, []string{"binding-a"} + if _, err := client.ReadJournal(t.Context(), "", 1, types, bindings); err != nil { t.Fatal(err) } - types[0] = "changed" - if got := h.authority.snapshot()[0].journal.GetTypes(); got[0] != journalType { - t.Fatalf("request types followed the caller's slice: %v", got) + types[0], bindings[0] = "changed", "changed" + call := h.authority.snapshot()[0].journal + if call.GetTypes()[0] != journalType || call.GetDeliveryBindings()[0] != "binding-a" { + t.Fatalf("request followed the caller's slices: %v %v", call.GetTypes(), call.GetDeliveryBindings()) } } @@ -249,7 +291,7 @@ func TestReadJournalNeedsTheAuthoritySeam(t *testing.T) { t.Fatal(err) } t.Cleanup(func() { _ = client.Close() }) - if _, err := client.ReadJournal(t.Context(), 0, 1, []string{journalType}); !errors.Is(err, ErrNoAuthority) { + if _, err := client.ReadJournal(t.Context(), "", 1, []string{journalType}, nil); !errors.Is(err, ErrNoAuthority) { t.Fatalf("err = %v, want ErrNoAuthority", err) } } From 381afff605bc8546750f9de0d791ae1c817cf74c Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 13 Oct 2026 03:08:47 +0000 Subject: [PATCH 04/18] feat(moduleauthority): export JournalStart, the position before the oldest entry The host defines the literal "start": it accepts it as ReadJournal's after and as the from of its delivery operation, and never returns it as an entry's position. A reader with no position of its own starts a range from it. The host fake accepts it and a test reads from it. The regenerated proto comment says so (comment-only change in gen/, still waiting for the saas-starter release). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++++ moduleauthority/host_fake_test.go | 2 +- moduleauthority/journal.go | 12 ++++++++++-- moduleauthority/journal_test.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bade6fc..6a009c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -257,6 +257,10 @@ tag. Do not release this until it is. committed entry and never passes an unfinished transaction, so a reader resumes from it rather than from the last `Position` it saw, and only stores and compares positions for equality. +- `JournalStart` (`"start"`) is the position that means "before the oldest + retained entry": the host accepts it as `after`, and as the `from` of its + delivery operation, and never returns it as an entry's position. A reader with + no position of its own starts from it. - `deliveryBindings` are the caller's binding ids whose mints and exchanges count as delivery machinery; the host sets `JournalEntry.DeliveryMachinery` on the entries that record one of them, or the caller's own module work-context mint, diff --git a/moduleauthority/host_fake_test.go b/moduleauthority/host_fake_test.go index c9eb46a..ef4705a 100644 --- a/moduleauthority/host_fake_test.go +++ b/moduleauthority/host_fake_test.go @@ -601,7 +601,7 @@ func (a *fakeAuthority) serveJournal(stream grpc.ServerStream, req *v1.ModuleRea return status.Error(codes.InvalidArgument, "limit 0..500 and at least one type required") } start := 0 - if after := req.GetAfter(); after != "" { + if after := req.GetAfter(); after != "" && after != JournalStart { start = -1 for i, e := range a.journal { if e.GetPosition() == after { diff --git a/moduleauthority/journal.go b/moduleauthority/journal.go index f192fe4..5fcb619 100644 --- a/moduleauthority/journal.go +++ b/moduleauthority/journal.go @@ -15,6 +15,14 @@ import ( // read, or an empty type or delivery binding id. var ErrInvalidJournalRead = errors.New("module authority: invalid journal read") +// JournalStart is the position that means "before the oldest retained entry". +// The host accepts it wherever it accepts a position (ReadJournal's after, and +// the `from` of the delivery operation) and never returns it as an entry's +// position, so a reader with no position of its own starts a range from it and +// the range still has a position to be identified by. The empty string also +// reads from the oldest retained entry, but it names nothing a caller can keep. +const JournalStart = "start" + // MaxJournalPage is the largest page the host serves. A limit of zero asks for // the host's default page. const MaxJournalPage = 500 @@ -56,8 +64,8 @@ type JournalPage struct { // cross-tenant module principal; any other caller is refused with the host's // own code, which is preserved. // -// after is an opaque position the host gave, exclusive; the empty string reads -// from the oldest retained entry. limit is 1..500, and zero means the host's +// after is an opaque position the host gave, exclusive; JournalStart or the empty +// string reads from the oldest retained entry. limit is 1..500, and zero means the host's // default. types must name at least one registered type. deliveryBindings are // the caller's binding ids whose mints and exchanges count as delivery // machinery: the host sets JournalEntry.DeliveryMachinery on the entries that diff --git a/moduleauthority/journal_test.go b/moduleauthority/journal_test.go index 328d59b..03e5404 100644 --- a/moduleauthority/journal_test.go +++ b/moduleauthority/journal_test.go @@ -157,6 +157,35 @@ func TestReadJournalReportsTheHostsResumePointNotTheLastPosition(t *testing.T) { } } +// JournalStart reads from the oldest retained entry, as the empty position does, +// and is not any entry's position. +func TestReadJournalAcceptsTheStartPosition(t *testing.T) { + h := newHost(t) + client := h.client(t, moduleCredentials) + e1, e2 := journalEntry(t, 1, testTenant, journalType), journalEntry(t, 2, testTenant, journalType) + h.authority.commit(e1, e2) + if JournalStart != "start" { + t.Fatalf("JournalStart = %q, the literal the host defines is %q", JournalStart, "start") + } + page, err := client.ReadJournal(t.Context(), JournalStart, 1, []string{journalType}, nil) + if err != nil { + t.Fatal(err) + } + if len(page.Entries) != 1 || page.Entries[0].Position != e1.GetPosition() || page.NextAfter != e1.GetPosition() { + t.Fatalf("page = %+v", page) + } + if page.Entries[0].Position == JournalStart { + t.Fatal("an entry carries the start position") + } + if got := h.authority.snapshot()[0].journal.GetAfter(); got != JournalStart { + t.Fatalf("after on the wire = %q", got) + } + // And the read resumes from what it returned. + if page, err = client.ReadJournal(t.Context(), page.NextAfter, 1, []string{journalType}, nil); err != nil || len(page.Entries) != 1 || page.Entries[0].EventID != "event-2" { + t.Fatalf("page = %+v, err = %v", page, err) + } +} + // The host marks delivery machinery, for the bindings the read names and for // the caller's own module mint; the client carries the mark and the bindings // exactly, and a mark for a binding the read did not name is not set. From fd1472d9111adbf9232ea695a4a066753faeb679 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 13 Oct 2026 14:58:21 +0000 Subject: [PATCH 05/18] fix(sdk): reproduce journal API from the exact host contract --- CHANGELOG.md | 79 ++++++++++++++++++++++++++++------------------------ SOURCE.txt | 6 ---- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a009c3..4706e52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -175,6 +175,48 @@ shipped. assignment. These are the only exported symbols the regeneration removed: nothing else in `gen/` lost a type, a field or an RPC. +- `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` + calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the + module, through the same credentials and refresh-once rule as every authority + call. The host serves it only to a cross-tenant module principal; any other + caller gets the host's `PermissionDenied`, preserved. Positions are opaque + strings: `after` is exclusive, `""` reads from the oldest retained entry, and + `JournalPage.NextAfter` is the resume point the host guarantees misses no + committed entry and never passes an unfinished transaction, so a reader + resumes from it rather than from the last `Position` it saw, and only stores + and compares positions for equality. +- `JournalStart` (`"start"`) is the position that means "before the oldest + retained entry": the host accepts it as `after`, and as the `from` of its + delivery operation, and never returns it as an entry's position. A reader with + no position of its own starts from it. +- `deliveryBindings` are the caller's binding ids whose mints and exchanges count + as delivery machinery; the host sets `JournalEntry.DeliveryMachinery` on the + entries that record one of them, or the caller's own module work-context mint, + so a reader that delivers journal entries can tell a page of its own delivery + from a page of real activity. A limit outside 0..500 (0 is the host's default of + 100), no type, or an empty type or binding id is `ErrInvalidJournalRead` before + any call. +- Generated: `ReadJournal` on `ModuleCapabilitiesService`, with + `ModuleReadJournalRequest`, `ModuleJournalEntry` and + `ModuleReadJournalResponse`. + +### Conformance + +- The real host gateway in module-saas-starter commit `4217037f` (#1104) + now returns HTTP 412 for a Work Context mint while installation is pending. + The conformance expectation and fake documentation follow that contract; + the client already preserves the status as a BrokerError. + +### Generated contract + +- Regenerated the complete host-owned schema from + `module-saas-starter@b4008ddd8e38789bf96c1af3ce84c26f0b434ef7` (PR #1106, module package + 0.1.1) with the pinned buf 1.73.0 and plugins. `SOURCE.txt` records that + exact development source; the generated tree no longer mixes a release + snapshot with an unrecorded contract. This also carries the host's current + delivery/recovery, installation and datasource contracts. The SDK release + remains ordered after the host release. + ## v0.1.2 — core v0.19.1; Work Contexts are core's, carried opaque Closes #38. The SDK requires `github.com/codefly-dev/core v0.19.1` and no @@ -234,43 +276,6 @@ compiling. `gen/` and `SOURCE.txt` are unchanged (module-saas-starter v0.0.94). v0.0.94 gateway relays a pre-cutover token on all three mints, and the SDK refuses each. The guarded `sdk-go/workcontext` now stays at the host's own pin in the conformance build, since this SDK no longer requires it. -## Unreleased — read the host's journal (waiting for the host release) - -**`gen/` here is ahead of the ref `SOURCE.txt` records.** `ReadJournal` and its -three messages are not in `module-saas-starter` v0.0.94, and no release carries -them yet. The two generated files below were produced by this repo's own -`buf.gen.yaml` (buf 1.73.0, the pinned plugins) from the v0.0.94 proto with the -wire contract added to `module_capabilities.proto` in a scratch checkout, not -by hand. They are a stopgap: the `gen-drift` job fails until the contract is in -a published `module-saas-starter` release and `gen/` is regenerated from that -tag. Do not release this until it is. - -### Added (moduleauthority, read the journal) - -- `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` - calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the - module, through the same credentials and refresh-once rule as every authority - call. The host serves it only to a cross-tenant module principal; any other - caller gets the host's `PermissionDenied`, preserved. Positions are opaque - strings: `after` is exclusive, `""` reads from the oldest retained entry, and - `JournalPage.NextAfter` is the resume point the host guarantees misses no - committed entry and never passes an unfinished transaction, so a reader - resumes from it rather than from the last `Position` it saw, and only stores - and compares positions for equality. -- `JournalStart` (`"start"`) is the position that means "before the oldest - retained entry": the host accepts it as `after`, and as the `from` of its - delivery operation, and never returns it as an entry's position. A reader with - no position of its own starts from it. -- `deliveryBindings` are the caller's binding ids whose mints and exchanges count - as delivery machinery; the host sets `JournalEntry.DeliveryMachinery` on the - entries that record one of them, or the caller's own module work-context mint, - so a reader that delivers journal entries can tell a page of its own delivery - from a page of real activity. A limit outside 0..500 (0 is the host's default of - 100), no type, or an empty type or binding id is `ErrInvalidJournalRead` before - any call. -- Generated: `ReadJournal` on `ModuleCapabilitiesService`, with - `ModuleReadJournalRequest`, `ModuleJournalEntry` and - `ModuleReadJournalResponse`. ## v0.1.1 — the broker contract, held against the host's real gateway diff --git a/SOURCE.txt b/SOURCE.txt index 8564b05..1e2661d 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -9,9 +9,3 @@ Released upstream tag: v0.0.103 (2cb00737ed56c79c823bc82f4055681a51919e7c). Its auth gateway requires codefly-dev/core at b81d92e6feda, the floor this SDK's go.mod records. Regeneration does not publish an SDK release. - -PENDING (not part of the recorded ref): gen/ also carries -ModuleCapabilitiesService.ReadJournal and its messages, which v0.0.94 does not -have. They were generated by this repo's buf.gen.yaml from the v0.0.94 proto -plus the contract, in a scratch checkout. Regenerate from the saas-starter -release that publishes ReadJournal and delete this paragraph. From 413ed1f980df29e85b46cf15d0630220d52e0c5d Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:03:33 +0000 Subject: [PATCH 06/18] test(moduleauthority): use sealed journal credential fixtures --- moduleauthority/journal_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/moduleauthority/journal_test.go b/moduleauthority/journal_test.go index 03e5404..26b26ad 100644 --- a/moduleauthority/journal_test.go +++ b/moduleauthority/journal_test.go @@ -95,7 +95,7 @@ func TestReadJournalReturnsAPageAndItsResumePoint(t *testing.T) { t.Fatalf("method = %q", call.method) } if len(call.internalToken) != 1 || call.internalToken[0] != testInternalToken || - len(call.workContext) != 1 || call.workContext[0] != "module.token1" || len(call.authorization) != 0 { + len(call.workContext) != 1 || call.workContext[0] != sealedToken("module.token1") || len(call.authorization) != 0 { t.Fatal("wrong authority carriers") } if call.journal.GetAfter() != "" || call.journal.GetLimit() != 10 || !reflect.DeepEqual(call.journal.GetTypes(), []string{journalType}) || @@ -255,13 +255,13 @@ func TestReadJournalRefreshesOnlyARejectedModuleCapability(t *testing.T) { if _, err := client.ModuleWorkContext(t.Context()); err != nil { t.Fatal(err) } - h.gateway.revoke("module.token1") + h.gateway.revoke(sealedToken("module.token1")) page, err := client.ReadJournal(t.Context(), "", 10, []string{journalType}, nil) if err != nil || len(page.Entries) != 1 { t.Fatalf("page = %+v, err = %v", page, err) } calls := h.authority.snapshot() - if len(calls) != 2 || calls[0].workContext[0] != "module.token1" || calls[1].workContext[0] != "module.token2" { + if len(calls) != 2 || calls[0].workContext[0] != sealedToken("module.token1") || calls[1].workContext[0] != sealedToken("module.token2") { t.Fatalf("calls = %+v", calls) } } From c11c47f4cb9adfe0b3711f6f7f05ae913ba8920e Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:23:29 +0000 Subject: [PATCH 07/18] fix(sdk): reconcile journal schema and workload gateway conformance --- CHANGELOG.md | 87 +- README.md | 6 +- SOURCE.txt | 12 +- conformance/hostgateway/run.sh | 1 + .../testdata/sdk_conformance_test.go | 110 +- .../v1/accountsv1connect/audit.connect.go | 84 +- .../module_capabilities.connect.go | 37 + gen/saas/accounts/v1/audit.pb.go | 231 +++- .../accounts/v1/module_capabilities.pb.go | 1055 +++++++++++------ moduleauthority/broker_test.go | 10 +- moduleauthority/host_fake_test.go | 32 +- 11 files changed, 1150 insertions(+), 515 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4706e52..19d1999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,37 +144,6 @@ shipped. `TOKEN_FILE` in its `workload-identity` configuration and passes `ProjectedTokenFile(path)` itself. -### Changed - -- **`gen/` moves from module-saas-starter v0.0.94 to `9ab36e7e`** (the merge - commit of #1119 on `main`), which is where `ResolveWorkContextAuthority` - first exists. It carries every accounts contract change made since v0.0.94 — - `datasource`, `installations`, `api_keys`, `module_registration` and - `work_contexts` all move — and those changes are the host's, not this SDK's. - -- **`moduleauthority.ErrNoHostStateToMintFor`**, for HTTP **412** on the module - Work Context and operation-context mints. The gateway at this ref relays - accounts' `FailedPrecondition` as 412, where v0.0.94 relayed it as a 502 — so - a workload the host admits that is installed in no organization it may mint - for used to read as "the host is broken" and now reads as "the host works and - there is nothing to mint for you yet". Nothing is wrong with the credential - and nothing is denied; a mint succeeds once the module is installed. Found by - `conformance/hostgateway` against the new ref, which is what that suite is - for. The source mint's own 412 keeps its own meaning - (`ErrDelegationMissing`). - -### Removed (breaking) - -- **`SessionId` is gone from three Work Context mint requests**, with their - `GetSessionId()` accessors: `StartTaskWorkContextRequest`, - `StartChildSessionWorkContextRequest` and `StartInstallationTaskRequest`. The - host stopped accepting a caller-named session — every session a capability - carries is now the one its caller authenticated with, or a fresh one the - issuer assigns and returns — so there is no replacement field and nothing to - set. A consumer that set one was naming a session it did not own; it drops the - assignment. These are the only exported symbols the regeneration removed: - nothing else in `gen/` lost a type, a field or an RPC. - - `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the module, through the same credentials and refresh-once rule as every authority @@ -198,19 +167,63 @@ shipped. any call. - Generated: `ReadJournal` on `ModuleCapabilitiesService`, with `ModuleReadJournalRequest`, `ModuleJournalEntry` and - `ModuleReadJournalResponse`. + `ModuleReadJournalResponse`; `DeliverAuditEventsResponse.From` and `To` + carry the exact committed receipt interval for retention coverage checks. + +### Changed + +- **`gen/` extends the module-saas-starter `9ab36e7e` snapshot in SDK v0.1.3** + with the complete host-owned journal and delivery contract. The generated + schema carries `ResolveWorkContextAuthority` alongside `ReadJournal`; + `SOURCE.txt` identifies the exact development source and the registry + removals are listed below. + +- **`moduleauthority.ErrNoHostStateToMintFor`**, for HTTP **412** on the module + Work Context and operation-context mints. The gateway at this ref relays + accounts' `FailedPrecondition` as 412, where v0.0.94 relayed it as a 502 — so + a workload the host admits that is installed in no organization it may mint + for used to read as "the host is broken" and now reads as "the host works and + there is nothing to mint for you yet". Nothing is wrong with the credential + and nothing is denied; a mint succeeds once the module is installed. Found by + `conformance/hostgateway` against the new ref, which is what that suite is + for. The source mint's own 412 keeps its own meaning + (`ErrDelegationMissing`). + +### Removed (breaking) + +- **`SessionId` is gone from three Work Context mint requests**, with their + `GetSessionId()` accessors: `StartTaskWorkContextRequest`, + `StartChildSessionWorkContextRequest` and `StartInstallationTaskRequest`. The + host stopped accepting a caller-named session — every session a capability + carries is now the one its caller authenticated with, or a fresh one the + issuer assigns and returns — so there is no replacement field and nothing to + set. A consumer that set one was naming a session it did not own; it drops the + assignment. + +- The current host-owned schema also removes + `ModuleMintWorkContextRequest.Upstream` and `Manifest` (and their getters), + `SolutionFrontendBinding.ContractVersion` (and `GetContractVersion`), + `SolutionRegistration.Backend` (and `GetBackend`), the entire + `SolutionBackendBinding` message, and + `SolutionRegistrationStatus_SOLUTION_REGISTRATION_STATUS_INCOMPATIBLE`. + [Host main #1120](https://github.com/codefly-dev/module-saas-starter/pull/1120) + resolves addresses and frontend metadata from applied presence instead of + accepting process-reported backend/frontend halves. + The SDK's hand-written facades consume none of these removed symbols. ### Conformance -- The real host gateway in module-saas-starter commit `4217037f` (#1104) - now returns HTTP 412 for a Work Context mint while installation is pending. - The conformance expectation and fake documentation follow that contract; - the client already preserves the status as a BrokerError. +- Follow [host #1120](https://github.com/codefly-dev/module-saas-starter/pull/1120) + by exercising module mints on the workload listener. The work mint uses the + projected credential; operation/source mints retain their internal-token + checks. Conformance also checks the protected presenter assertion, rejection + of missing/forged assertions, and refusal on the public listener. Consumers + must resolve the workload endpoint as the `Gateway` seam. ### Generated contract - Regenerated the complete host-owned schema from - `module-saas-starter@b4008ddd8e38789bf96c1af3ce84c26f0b434ef7` (PR #1106, module package + `module-saas-starter@b640a5fc51ab1f4fc83d144bbf1eeddd7637c0dc` (PR #1106, module package 0.1.1) with the pinned buf 1.73.0 and plugins. `SOURCE.txt` records that exact development source; the generated tree no longer mixes a release snapshot with an unrecorded contract. This also carries the host's current diff --git a/README.md b/README.md index 7b46a94..915e1a4 100644 --- a/README.md +++ b/README.md @@ -305,8 +305,8 @@ The SDK surfaces are: It talks to the two seams the host serves a composed module, and takes both explicitly — the consumer passes what Codefly resolved, nothing is defaulted: - - the **gateway's module broker** (REST on the auth-gateway's `rest` - endpoint): `POST /modules/_work-context`, `/modules/_operation-context` + - the **gateway's module broker** (HTTP on the auth-gateway's `workload` + endpoint, API `http`): `POST /modules/_work-context`, `/modules/_operation-context` and `/modules/_source-operation-context`, where the module presents its pod's projected ServiceAccount token (audience `accounts`) as the bearer; the host reviews it and mints only for the principal's declared workload @@ -318,7 +318,7 @@ The SDK surfaces are: ```go authority, err := moduleauthority.New(moduleauthority.Seams{ - Gateway: gw, // BaseURL() + HTTPClient() + Gateway: workloadGateway, // resolved workload endpoint + HTTPClient() Authority: moduleauthority.Authority{Address: authorityHostPort}, // TLS: nil = h2c InternalToken: internalToken, // Only from the consumer's own configured assertion that every diff --git a/SOURCE.txt b/SOURCE.txt index 1e2661d..613e899 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,11 +1,9 @@ -gen/ generated from codefly-dev/module-saas-starter@v0.0.103 +gen/ generated from codefly-dev/module-saas-starter@b640a5fc51ab1f4fc83d144bbf1eeddd7637c0dc (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) -module version: 0.1.0 (module/module.package.codefly.yaml) -This SDK's release is matched to the saas-starter module version above. - -Released upstream tag: v0.0.103 (2cb00737ed56c79c823bc82f4055681a51919e7c). -Its auth gateway requires codefly-dev/core at b81d92e6feda, the floor this -SDK's go.mod records. +module version: 0.1.1 (module/module.package.codefly.yaml) +Development source: exact PR-head commit of module-saas-starter#1106. +The entire host-owned saas/ tree is generated with this repository's buf.gen.yaml. +Replace this development ref with the host release tag before publishing the SDK. Regeneration does not publish an SDK release. diff --git a/conformance/hostgateway/run.sh b/conformance/hostgateway/run.sh index ad9a0c0..63cf748 100755 --- a/conformance/hostgateway/run.sh +++ b/conformance/hostgateway/run.sh @@ -26,6 +26,7 @@ expected=( TestSDKConformanceThreeMints TestSDKConformanceTokenBound TestSDKConformanceWrongInternalToken + TestSDKConformanceWorkloadPresenter ) # The modules whose version changes what the gateway under test does. Any diff --git a/conformance/hostgateway/testdata/sdk_conformance_test.go b/conformance/hostgateway/testdata/sdk_conformance_test.go index 576d614..791f6e1 100644 --- a/conformance/hostgateway/testdata/sdk_conformance_test.go +++ b/conformance/hostgateway/testdata/sdk_conformance_test.go @@ -7,7 +7,7 @@ package main // module/services/auth-gateway/code at the ref SOURCE.txt records) with // `go test -overlay`, so the host checkout stays byte-for-byte unmodified, and // drives the SDK's real moduleauthority client against the host's real -// Gateway.ServeHTTP over httptest. +// workloadListener(Gateway) over httptest, as the native host does. // // Only accounts is faked, on a real gRPC listener and exactly the way the // host's own gateway tests fake it (gateway_modules_test.go, @@ -18,14 +18,12 @@ package main // unexported names of the host package, and to nothing else of it: // // - newGatewayHarness (gateway_test.go): the host's own gateway under test; -// - workloadListener (workload_listener.go): the mark the gateway's workload -// endpoint puts on every request it serves, the only endpoint the three -// mints are served on (module-saas-starter#1120). The harness serves the -// gateway through it as a native run's workload server does; a cell's -// waypoint-authenticated wrapper around it is not under test here; // - (*Gateway).authz, (*ExtAuthz).backendConn: where the harness's accounts // connection is set, as the host's own tests set it; // - (*ExtAuthz).internalToken: the perimeter credential the harness chose; +// - workloadListener, authenticatedWorkloadListener and the host +// testWaypoint/testPresenter fixtures (workload_listener.go and +// workload_presenter_test.go); // - the accounts types in auth-gateway/pkg/gen/saas/accounts/v1. // // The faked accounts answers Work Contexts in core's encoding @@ -134,9 +132,10 @@ func sdkSealed(name string) string { } ctx := context.Background() _, key := sdkcorework.FixtureKeyPair() + seals := sdkcorework.FixtureSeals() authority := &sdkcorework.Authority{ Issuer: sdkcorework.FixtureIssuer, KeyID: sdkcorework.FixtureKeyID, Key: key, - Revisions: sdkcorework.FixtureRevisions(), Seals: sdkcorework.FixtureSeals(), + Revisions: sdkcorework.FixtureRevisions(), Seals: seals, } token, _, err := authority.Start(ctx, sdkcorework.StartInput{ TenantID: sdkcorework.FixtureTenant, @@ -181,6 +180,7 @@ type edgeRequest struct { // the edge and what it forwarded to accounts. type realGateway struct { server *httptest.Server + gateway *Gateway internalToken string accounts *fakeAccounts @@ -269,13 +269,13 @@ func newRealGateway(t *testing.T) *realGateway { t.Cleanup(func() { _ = conn.Close() }) gw.authz.backendConn = conn - g := &realGateway{internalToken: gw.authz.internalToken, accounts: accounts} - workload := workloadListener(gw) + g := &realGateway{gateway: gw, internalToken: gw.authz.internalToken, accounts: accounts} + handler := workloadListener(gw) g.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { g.mu.Lock() g.edge = append(g.edge, edgeRequest{path: r.URL.Path, header: r.Header.Clone()}) g.mu.Unlock() - workload.ServeHTTP(w, r) + handler.ServeHTTP(w, r) })) t.Cleanup(g.server.Close) if g.internalToken == "" { @@ -548,8 +548,6 @@ func TestSDKConformanceRefusals(t *testing.T) { {"work unproven or wrong organization", mintWork, refusal(codes.PermissionDenied, ""), 401, moduleauthority.ErrInvalidCredentials}, {"work execution unestablished", mintWork, refusal(codes.Unavailable, ""), 503, moduleauthority.ErrExecutionUnestablished}, {"work invalid request", mintWork, refusal(codes.InvalidArgument, ""), 400, nil}, - // Host af5cef9a (module-saas-starter#1120, #1118): a refused token is - // a 401 on the work-context exchange, as PermissionDenied is. {"work unauthenticated", mintWork, refusal(codes.Unauthenticated, ""), 401, moduleauthority.ErrInvalidCredentials}, {"work failed precondition", mintWork, refusal(codes.FailedPrecondition, ""), 412, moduleauthority.ErrNoHostStateToMintFor}, @@ -590,8 +588,8 @@ func TestSDKConformanceRefusals(t *testing.T) { } } -// A wrong perimeter credential is refused by the gateway itself, before -// accounts, and the SDK reports it as invalid credentials. +// Operation/source mints retain the perimeter gate. The work mint on the +// workload listener authenticates the projected credential instead (#1120). func TestSDKConformanceWrongInternalToken(t *testing.T) { g := newRealGateway(t) client, err := moduleauthority.New( @@ -601,22 +599,88 @@ func TestSDKConformanceWrongInternalToken(t *testing.T) { if err != nil { t.Fatal(err) } - // The operation and source mints still hold the internal token. Host - // af5cef9a (module-saas-starter#1120) serves the three mints on the - // workload endpoint alone and dropped the token from the work-context - // mint: that endpoint is its perimeter, and the projected token its one - // credential. + if err := mint(t.Context(), client, mintWork); err != nil { + t.Fatalf("workload work mint with a valid projected credential: %v", err) + } for _, method := range []string{mintOperation, mintSource} { var be *moduleauthority.BrokerError if err := mint(t.Context(), client, method); !errors.Is(err, moduleauthority.ErrInvalidCredentials) || !errors.As(err, &be) || be.StatusCode != 401 { t.Fatalf("%s: %v, want ErrInvalidCredentials with HTTP 401", method, err) } } - if calls := g.accounts.snapshot(); len(calls) != 0 { - t.Fatalf("accounts was called %d time(s) behind a wrong internal token", len(calls)) + calls := g.accounts.snapshot() + if len(calls) != 1 || calls[0].method != mintWork { + t.Fatalf("accounts calls = %v, want only the workload work mint", calls) } - if err := mint(t.Context(), client, mintWork); err != nil { - t.Fatalf("%s behind a wrong internal token: %v, want it minted on the projected token alone", mintWork, err) + assertForwarded(t, g, calls[0], mintWork) +} + +// The protected workload listener authenticates the waypoint's assertion, +// strips caller-supplied presenter headers and forwards its own gateway proof. +// These identities are the host's public test fixtures, never production values. +func TestSDKConformanceWorkloadPresenter(t *testing.T) { + g := newRealGateway(t) + handler, err := authenticatedWorkloadListener(g.gateway, testWaypoint) + if err != nil { + t.Fatal(err) + } + protected := httptest.NewServer(handler) + t.Cleanup(protected.Close) + for name, values := range map[string][]string{ + "missing": nil, + "forged": {"By=" + testPresenter + ";URI=" + testPresenter}, + "duplicate": {"By=" + testWaypoint + ";URI=" + testPresenter, "By=" + testWaypoint + ";URI=" + testPresenter}, + "valid": {"By=" + testWaypoint + ";URI=" + testPresenter}, + } { + t.Run(name, func(t *testing.T) { + before := len(g.accounts.snapshot()) + r, err := http.NewRequestWithContext(t.Context(), http.MethodPost, protected.URL+mintPaths[mintWork], strings.NewReader(mintBodies[mintWork])) + if err != nil { + t.Fatal(err) + } + r.Header.Set("Authorization", "Bearer projected-1") + r.Header["X-Forwarded-Client-Cert"] = values + r.Header.Set(workloadPresenterHeader, "forged") + r.Header.Set("X-Codefly-Gateway-Token", "forged") + response, err := protected.Client().Do(r) + if err != nil { + t.Fatal(err) + } + _ = response.Body.Close() + calls := g.accounts.snapshot() + if name != "valid" { + if response.StatusCode != http.StatusUnauthorized || len(calls) != before { + t.Fatalf("invalid presenter: status=%d accounts=%d", response.StatusCode, len(calls)-before) + } + return + } + if response.StatusCode != http.StatusOK || len(calls) != before+1 { + t.Fatalf("valid presenter: status=%d accounts=%d", response.StatusCode, len(calls)-before) + } + call := calls[len(calls)-1] + assertForwarded(t, g, call, mintWork) + if !slices.Equal(call.md.Get(workloadPresenterHeader), []string{testPresenter}) || !slices.Equal(call.md.Get("x-codefly-gateway-token"), []string{g.gateway.authz.gatewayToken}) { + t.Fatal("gateway did not forward its authenticated presenter and proof") + } + }) + } + public := httptest.NewServer(g.gateway) + t.Cleanup(public.Close) + before := len(g.accounts.snapshot()) + r, err := http.NewRequestWithContext(t.Context(), http.MethodPost, public.URL+mintPaths[mintWork], strings.NewReader(mintBodies[mintWork])) + if err != nil { + t.Fatal(err) + } + r.Header.Set("Authorization", "Bearer projected-1") + r.Header.Set("X-Codefly-Internal-Token", g.internalToken) + r.Header.Set("X-Forwarded-Client-Cert", "By="+testWaypoint+";URI="+testPresenter) + response, err := public.Client().Do(r) + if err != nil { + t.Fatal(err) + } + _ = response.Body.Close() + if response.StatusCode != http.StatusNotFound || len(g.accounts.snapshot()) != before { + t.Fatalf("public listener served a workload mint: status=%d", response.StatusCode) } } diff --git a/gen/saas/accounts/v1/accountsv1connect/audit.connect.go b/gen/saas/accounts/v1/accountsv1connect/audit.connect.go index ca8c2dc..bf1a302 100644 --- a/gen/saas/accounts/v1/accountsv1connect/audit.connect.go +++ b/gen/saas/accounts/v1/accountsv1connect/audit.connect.go @@ -45,6 +45,12 @@ const ( // AuditServiceExportAuditLogProcedure is the fully-qualified name of the AuditService's // ExportAuditLog RPC. AuditServiceExportAuditLogProcedure = "/saas.accounts.v1.AuditService/ExportAuditLog" + // AuditServiceDeliverAuditEventsProcedure is the fully-qualified name of the AuditService's + // DeliverAuditEvents RPC. + AuditServiceDeliverAuditEventsProcedure = "/saas.accounts.v1.AuditService/DeliverAuditEvents" + // AuditServiceLookupDeliverAuditEventsProcedure is the fully-qualified name of the AuditService's + // LookupDeliverAuditEvents RPC. + AuditServiceLookupDeliverAuditEventsProcedure = "/saas.accounts.v1.AuditService/LookupDeliverAuditEvents" ) // AuditServiceClient is a client for the saas.accounts.v1.AuditService service. @@ -53,6 +59,17 @@ type AuditServiceClient interface { AggregateAuditLog(context.Context, *connect.Request[v1.AggregateAuditLogRequest]) (*connect.Response[v1.AggregateAuditLogResponse], error) ListAuditEventTypes(context.Context, *connect.Request[v1.ListAuditEventTypesRequest]) (*connect.Response[v1.ListAuditEventTypesResponse], error) ExportAuditLog(context.Context, *connect.Request[v1.ExportAuditLogRequest]) (*connect.Response[v1.ExportAuditLogResponse], error) + // DeliverAuditEvents appends one range of the journal's audit records to the + // warehouse audit storage: the operation module-runtime calls after reading the + // range through ReadJournal. It is an operation, not a sweep. It takes each + // record's organization from the journal row and never from the caller, emits no + // audit of its own, and commits one receipt per range under the installation's + // tenant; delivery is at least once, and every read of the storage returns each + // entry once by id. Its receipt is read back through LookupDeliverAuditEvents. + DeliverAuditEvents(context.Context, *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) + // LookupDeliverAuditEvents takes the invocation's same {from, to} body and + // derives the range receipt identity from from. It never delivers. + LookupDeliverAuditEvents(context.Context, *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) } // NewAuditServiceClient constructs a client for the saas.accounts.v1.AuditService service. By @@ -90,15 +107,29 @@ func NewAuditServiceClient(httpClient connect.HTTPClient, baseURL string, opts . connect.WithSchema(auditServiceMethods.ByName("ExportAuditLog")), connect.WithClientOptions(opts...), ), + deliverAuditEvents: connect.NewClient[v1.DeliverAuditEventsRequest, v1.DeliverAuditEventsResponse]( + httpClient, + baseURL+AuditServiceDeliverAuditEventsProcedure, + connect.WithSchema(auditServiceMethods.ByName("DeliverAuditEvents")), + connect.WithClientOptions(opts...), + ), + lookupDeliverAuditEvents: connect.NewClient[v1.DeliverAuditEventsRequest, v1.DeliverAuditEventsResponse]( + httpClient, + baseURL+AuditServiceLookupDeliverAuditEventsProcedure, + connect.WithSchema(auditServiceMethods.ByName("LookupDeliverAuditEvents")), + connect.WithClientOptions(opts...), + ), } } // auditServiceClient implements AuditServiceClient. type auditServiceClient struct { - queryAuditLog *connect.Client[v1.QueryAuditLogRequest, v1.QueryAuditLogResponse] - aggregateAuditLog *connect.Client[v1.AggregateAuditLogRequest, v1.AggregateAuditLogResponse] - listAuditEventTypes *connect.Client[v1.ListAuditEventTypesRequest, v1.ListAuditEventTypesResponse] - exportAuditLog *connect.Client[v1.ExportAuditLogRequest, v1.ExportAuditLogResponse] + queryAuditLog *connect.Client[v1.QueryAuditLogRequest, v1.QueryAuditLogResponse] + aggregateAuditLog *connect.Client[v1.AggregateAuditLogRequest, v1.AggregateAuditLogResponse] + listAuditEventTypes *connect.Client[v1.ListAuditEventTypesRequest, v1.ListAuditEventTypesResponse] + exportAuditLog *connect.Client[v1.ExportAuditLogRequest, v1.ExportAuditLogResponse] + deliverAuditEvents *connect.Client[v1.DeliverAuditEventsRequest, v1.DeliverAuditEventsResponse] + lookupDeliverAuditEvents *connect.Client[v1.DeliverAuditEventsRequest, v1.DeliverAuditEventsResponse] } // QueryAuditLog calls saas.accounts.v1.AuditService.QueryAuditLog. @@ -121,12 +152,33 @@ func (c *auditServiceClient) ExportAuditLog(ctx context.Context, req *connect.Re return c.exportAuditLog.CallUnary(ctx, req) } +// DeliverAuditEvents calls saas.accounts.v1.AuditService.DeliverAuditEvents. +func (c *auditServiceClient) DeliverAuditEvents(ctx context.Context, req *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) { + return c.deliverAuditEvents.CallUnary(ctx, req) +} + +// LookupDeliverAuditEvents calls saas.accounts.v1.AuditService.LookupDeliverAuditEvents. +func (c *auditServiceClient) LookupDeliverAuditEvents(ctx context.Context, req *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) { + return c.lookupDeliverAuditEvents.CallUnary(ctx, req) +} + // AuditServiceHandler is an implementation of the saas.accounts.v1.AuditService service. type AuditServiceHandler interface { QueryAuditLog(context.Context, *connect.Request[v1.QueryAuditLogRequest]) (*connect.Response[v1.QueryAuditLogResponse], error) AggregateAuditLog(context.Context, *connect.Request[v1.AggregateAuditLogRequest]) (*connect.Response[v1.AggregateAuditLogResponse], error) ListAuditEventTypes(context.Context, *connect.Request[v1.ListAuditEventTypesRequest]) (*connect.Response[v1.ListAuditEventTypesResponse], error) ExportAuditLog(context.Context, *connect.Request[v1.ExportAuditLogRequest]) (*connect.Response[v1.ExportAuditLogResponse], error) + // DeliverAuditEvents appends one range of the journal's audit records to the + // warehouse audit storage: the operation module-runtime calls after reading the + // range through ReadJournal. It is an operation, not a sweep. It takes each + // record's organization from the journal row and never from the caller, emits no + // audit of its own, and commits one receipt per range under the installation's + // tenant; delivery is at least once, and every read of the storage returns each + // entry once by id. Its receipt is read back through LookupDeliverAuditEvents. + DeliverAuditEvents(context.Context, *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) + // LookupDeliverAuditEvents takes the invocation's same {from, to} body and + // derives the range receipt identity from from. It never delivers. + LookupDeliverAuditEvents(context.Context, *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) } // NewAuditServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -160,6 +212,18 @@ func NewAuditServiceHandler(svc AuditServiceHandler, opts ...connect.HandlerOpti connect.WithSchema(auditServiceMethods.ByName("ExportAuditLog")), connect.WithHandlerOptions(opts...), ) + auditServiceDeliverAuditEventsHandler := connect.NewUnaryHandler( + AuditServiceDeliverAuditEventsProcedure, + svc.DeliverAuditEvents, + connect.WithSchema(auditServiceMethods.ByName("DeliverAuditEvents")), + connect.WithHandlerOptions(opts...), + ) + auditServiceLookupDeliverAuditEventsHandler := connect.NewUnaryHandler( + AuditServiceLookupDeliverAuditEventsProcedure, + svc.LookupDeliverAuditEvents, + connect.WithSchema(auditServiceMethods.ByName("LookupDeliverAuditEvents")), + connect.WithHandlerOptions(opts...), + ) return "/saas.accounts.v1.AuditService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case AuditServiceQueryAuditLogProcedure: @@ -170,6 +234,10 @@ func NewAuditServiceHandler(svc AuditServiceHandler, opts ...connect.HandlerOpti auditServiceListAuditEventTypesHandler.ServeHTTP(w, r) case AuditServiceExportAuditLogProcedure: auditServiceExportAuditLogHandler.ServeHTTP(w, r) + case AuditServiceDeliverAuditEventsProcedure: + auditServiceDeliverAuditEventsHandler.ServeHTTP(w, r) + case AuditServiceLookupDeliverAuditEventsProcedure: + auditServiceLookupDeliverAuditEventsHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -194,3 +262,11 @@ func (UnimplementedAuditServiceHandler) ListAuditEventTypes(context.Context, *co func (UnimplementedAuditServiceHandler) ExportAuditLog(context.Context, *connect.Request[v1.ExportAuditLogRequest]) (*connect.Response[v1.ExportAuditLogResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.AuditService.ExportAuditLog is not implemented")) } + +func (UnimplementedAuditServiceHandler) DeliverAuditEvents(context.Context, *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.AuditService.DeliverAuditEvents is not implemented")) +} + +func (UnimplementedAuditServiceHandler) LookupDeliverAuditEvents(context.Context, *connect.Request[v1.DeliverAuditEventsRequest]) (*connect.Response[v1.DeliverAuditEventsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.AuditService.LookupDeliverAuditEvents is not implemented")) +} diff --git a/gen/saas/accounts/v1/accountsv1connect/module_capabilities.connect.go b/gen/saas/accounts/v1/accountsv1connect/module_capabilities.connect.go index 6717eef..afc01d1 100644 --- a/gen/saas/accounts/v1/accountsv1connect/module_capabilities.connect.go +++ b/gen/saas/accounts/v1/accountsv1connect/module_capabilities.connect.go @@ -95,6 +95,9 @@ const ( // ModuleCapabilitiesServiceEmitAuditEventProcedure is the fully-qualified name of the // ModuleCapabilitiesService's EmitAuditEvent RPC. ModuleCapabilitiesServiceEmitAuditEventProcedure = "/saas.accounts.v1.ModuleCapabilitiesService/EmitAuditEvent" + // ModuleCapabilitiesServiceReadJournalProcedure is the fully-qualified name of the + // ModuleCapabilitiesService's ReadJournal RPC. + ModuleCapabilitiesServiceReadJournalProcedure = "/saas.accounts.v1.ModuleCapabilitiesService/ReadJournal" // ModuleCapabilitiesServiceDeclareAuditEventTypesProcedure is the fully-qualified name of the // ModuleCapabilitiesService's DeclareAuditEventTypes RPC. ModuleCapabilitiesServiceDeclareAuditEventTypesProcedure = "/saas.accounts.v1.ModuleCapabilitiesService/DeclareAuditEventTypes" @@ -200,6 +203,11 @@ type ModuleCapabilitiesServiceClient interface { CancelApproval(context.Context, *connect.Request[v1.ModuleCancelApprovalRequest]) (*connect.Response[emptypb.Empty], error) // EmitAuditEvent records a registered audit event on the tenant's spine. EmitAuditEvent(context.Context, *connect.Request[v1.ModuleEmitAuditEventRequest]) (*connect.Response[emptypb.Empty], error) + // ReadJournal serves the platform journal across every tenant, and the rows with + // no tenant, to a module principal whose grant is cross_tenant. Rows are served + // in the order their writing transactions are final, so a reader that resumes + // from next_after skips nothing; it enqueues nothing and is not a replay. + ReadJournal(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) // DeclareAuditEventTypes admits the audit event types a composed module owns, // into the namespaces the operator bound to it; see the request. DeclareAuditEventTypes(context.Context, *connect.Request[v1.ModuleDeclareAuditEventTypesRequest]) (*connect.Response[v1.ModuleDeclareAuditEventTypesResponse], error) @@ -401,6 +409,12 @@ func NewModuleCapabilitiesServiceClient(httpClient connect.HTTPClient, baseURL s connect.WithSchema(moduleCapabilitiesServiceMethods.ByName("EmitAuditEvent")), connect.WithClientOptions(opts...), ), + readJournal: connect.NewClient[v1.ModuleReadJournalRequest, v1.ModuleReadJournalResponse]( + httpClient, + baseURL+ModuleCapabilitiesServiceReadJournalProcedure, + connect.WithSchema(moduleCapabilitiesServiceMethods.ByName("ReadJournal")), + connect.WithClientOptions(opts...), + ), declareAuditEventTypes: connect.NewClient[v1.ModuleDeclareAuditEventTypesRequest, v1.ModuleDeclareAuditEventTypesResponse]( httpClient, baseURL+ModuleCapabilitiesServiceDeclareAuditEventTypesProcedure, @@ -504,6 +518,7 @@ type moduleCapabilitiesServiceClient struct { getApproval *connect.Client[v1.ModuleGetApprovalRequest, v1.ModuleApproval] cancelApproval *connect.Client[v1.ModuleCancelApprovalRequest, emptypb.Empty] emitAuditEvent *connect.Client[v1.ModuleEmitAuditEventRequest, emptypb.Empty] + readJournal *connect.Client[v1.ModuleReadJournalRequest, v1.ModuleReadJournalResponse] declareAuditEventTypes *connect.Client[v1.ModuleDeclareAuditEventTypesRequest, v1.ModuleDeclareAuditEventTypesResponse] listSubjectVisibility *connect.Client[v1.ModuleListSubjectVisibilityRequest, v1.ModuleListSubjectVisibilityResponse] fetchDatasourceBlob *connect.Client[v1.FetchDatasourceBlobRequest, v1.FetchDatasourceBlobChunk] @@ -626,6 +641,11 @@ func (c *moduleCapabilitiesServiceClient) EmitAuditEvent(ctx context.Context, re return c.emitAuditEvent.CallUnary(ctx, req) } +// ReadJournal calls saas.accounts.v1.ModuleCapabilitiesService.ReadJournal. +func (c *moduleCapabilitiesServiceClient) ReadJournal(ctx context.Context, req *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + return c.readJournal.CallUnary(ctx, req) +} + // DeclareAuditEventTypes calls saas.accounts.v1.ModuleCapabilitiesService.DeclareAuditEventTypes. func (c *moduleCapabilitiesServiceClient) DeclareAuditEventTypes(ctx context.Context, req *connect.Request[v1.ModuleDeclareAuditEventTypesRequest]) (*connect.Response[v1.ModuleDeclareAuditEventTypesResponse], error) { return c.declareAuditEventTypes.CallUnary(ctx, req) @@ -760,6 +780,11 @@ type ModuleCapabilitiesServiceHandler interface { CancelApproval(context.Context, *connect.Request[v1.ModuleCancelApprovalRequest]) (*connect.Response[emptypb.Empty], error) // EmitAuditEvent records a registered audit event on the tenant's spine. EmitAuditEvent(context.Context, *connect.Request[v1.ModuleEmitAuditEventRequest]) (*connect.Response[emptypb.Empty], error) + // ReadJournal serves the platform journal across every tenant, and the rows with + // no tenant, to a module principal whose grant is cross_tenant. Rows are served + // in the order their writing transactions are final, so a reader that resumes + // from next_after skips nothing; it enqueues nothing and is not a replay. + ReadJournal(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) // DeclareAuditEventTypes admits the audit event types a composed module owns, // into the namespaces the operator bound to it; see the request. DeclareAuditEventTypes(context.Context, *connect.Request[v1.ModuleDeclareAuditEventTypesRequest]) (*connect.Response[v1.ModuleDeclareAuditEventTypesResponse], error) @@ -957,6 +982,12 @@ func NewModuleCapabilitiesServiceHandler(svc ModuleCapabilitiesServiceHandler, o connect.WithSchema(moduleCapabilitiesServiceMethods.ByName("EmitAuditEvent")), connect.WithHandlerOptions(opts...), ) + moduleCapabilitiesServiceReadJournalHandler := connect.NewUnaryHandler( + ModuleCapabilitiesServiceReadJournalProcedure, + svc.ReadJournal, + connect.WithSchema(moduleCapabilitiesServiceMethods.ByName("ReadJournal")), + connect.WithHandlerOptions(opts...), + ) moduleCapabilitiesServiceDeclareAuditEventTypesHandler := connect.NewUnaryHandler( ModuleCapabilitiesServiceDeclareAuditEventTypesProcedure, svc.DeclareAuditEventTypes, @@ -1077,6 +1108,8 @@ func NewModuleCapabilitiesServiceHandler(svc ModuleCapabilitiesServiceHandler, o moduleCapabilitiesServiceCancelApprovalHandler.ServeHTTP(w, r) case ModuleCapabilitiesServiceEmitAuditEventProcedure: moduleCapabilitiesServiceEmitAuditEventHandler.ServeHTTP(w, r) + case ModuleCapabilitiesServiceReadJournalProcedure: + moduleCapabilitiesServiceReadJournalHandler.ServeHTTP(w, r) case ModuleCapabilitiesServiceDeclareAuditEventTypesProcedure: moduleCapabilitiesServiceDeclareAuditEventTypesHandler.ServeHTTP(w, r) case ModuleCapabilitiesServiceListSubjectVisibilityProcedure: @@ -1192,6 +1225,10 @@ func (UnimplementedModuleCapabilitiesServiceHandler) EmitAuditEvent(context.Cont return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.ModuleCapabilitiesService.EmitAuditEvent is not implemented")) } +func (UnimplementedModuleCapabilitiesServiceHandler) ReadJournal(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.ModuleCapabilitiesService.ReadJournal is not implemented")) +} + func (UnimplementedModuleCapabilitiesServiceHandler) DeclareAuditEventTypes(context.Context, *connect.Request[v1.ModuleDeclareAuditEventTypesRequest]) (*connect.Response[v1.ModuleDeclareAuditEventTypesResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.ModuleCapabilitiesService.DeclareAuditEventTypes is not implemented")) } diff --git a/gen/saas/accounts/v1/audit.pb.go b/gen/saas/accounts/v1/audit.pb.go index dbea0c7..8649b05 100644 --- a/gen/saas/accounts/v1/audit.pb.go +++ b/gen/saas/accounts/v1/audit.pb.go @@ -8,6 +8,7 @@ package accountsv1 import ( _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/codefly-dev/core/generated/go/codefly/runnable/v0" _ "github.com/codefly-dev/saas-sdk-go/gen/saas/policy/v1" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -588,7 +589,11 @@ type AuditMetric struct { // percentile) require a "payload:" field; non-numeric payload values are // ignored. Field string `protobuf:"bytes,2,opt,name=field,proto3" json:"field,omitempty"` - // percentile ∈ (0,1], used only when op == percentile (0.95 → p95). + // percentile ∈ (0,1], used only when op == percentile (0.95 → p95). Only the + // Postgres audit backend computes it: the warehouse backend keeps entries + // at-least-once and deduplicates them on read, and a percentile over that is not + // an exact answer, so a deployment on the warehouse backend refuses a query that + // asks for one with InvalidArgument. Every other op is served by both backends. Percentile float64 `protobuf:"fixed64,3,opt,name=percentile,proto3" json:"percentile,omitempty"` // alias names this metric in the response map. Defaults to "count" for count, // else "_". @@ -1266,11 +1271,157 @@ func (x *ListAuditEventTypesResponse) GetTypes() []*AuditEventType { return nil } +// DeliverAuditEventsRequest names one range of the platform journal, (from, to], +// whose audit records the operation appends to the warehouse audit storage. Both +// ends are positions a ModuleReadJournal page returned; from may instead be the +// literal "start", meaning before the oldest retained entry, and is never empty so +// that the first range of a fresh journal has a non-empty value to correlate on. +// The range ends at a position that is final (below the oldest transaction still +// running) and holds at most 500 audit records, or it is refused before anything +// is appended. The effect id is "saas.audit.recorded.v1/", derived by the +// host: one range, one receipt. A runtime task may carry its own transport +// effect identity; the range receipt is independent of that task identity. +type DeliverAuditEventsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeliverAuditEventsRequest) Reset() { + *x = DeliverAuditEventsRequest{} + mi := &file_saas_accounts_v1_audit_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeliverAuditEventsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeliverAuditEventsRequest) ProtoMessage() {} + +func (x *DeliverAuditEventsRequest) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_audit_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeliverAuditEventsRequest.ProtoReflect.Descriptor instead. +func (*DeliverAuditEventsRequest) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_audit_proto_rawDescGZIP(), []int{13} +} + +func (x *DeliverAuditEventsRequest) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *DeliverAuditEventsRequest) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +type DeliverAuditEventsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + EffectId string `protobuf:"bytes,1,opt,name=effect_id,json=effectId,proto3" json:"effect_id,omitempty"` + // status is "committed" when the range is recorded. An absent lookup + // returns NOT_FOUND: absence is inconclusive, never a committed response. + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // delivered is the number of audit records of the range appended to the + // storage; a range with none still commits a receipt. + Delivered int32 `protobuf:"varint,3,opt,name=delivered,proto3" json:"delivered,omitempty"` + // The exact committed interval is (from, to]. Retention verifies these + // positions against the receipt's effect identity and request digest before + // retiring journal rows. Legacy receipts without both positions prove no + // coverage; delivered alone never proves an interval. + From string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeliverAuditEventsResponse) Reset() { + *x = DeliverAuditEventsResponse{} + mi := &file_saas_accounts_v1_audit_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeliverAuditEventsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeliverAuditEventsResponse) ProtoMessage() {} + +func (x *DeliverAuditEventsResponse) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_audit_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeliverAuditEventsResponse.ProtoReflect.Descriptor instead. +func (*DeliverAuditEventsResponse) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_audit_proto_rawDescGZIP(), []int{14} +} + +func (x *DeliverAuditEventsResponse) GetEffectId() string { + if x != nil { + return x.EffectId + } + return "" +} + +func (x *DeliverAuditEventsResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DeliverAuditEventsResponse) GetDelivered() int32 { + if x != nil { + return x.Delivered + } + return 0 +} + +func (x *DeliverAuditEventsResponse) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *DeliverAuditEventsResponse) GetTo() string { + if x != nil { + return x.To + } + return "" +} + var File_saas_accounts_v1_audit_proto protoreflect.FileDescriptor const file_saas_accounts_v1_audit_proto_rawDesc = "" + "\n" + - "\x1csaas/accounts/v1/audit.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1csaas/policy/v1/options.proto\"\xdb\x04\n" + + "\x1csaas/accounts/v1/audit.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!codefly/runnable/v0/options.proto\x1a\x1csaas/policy/v1/options.proto\"\xdb\x04\n" + "\n" + "AuditEvent\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x19\n" + @@ -1405,7 +1556,16 @@ const file_saas_accounts_v1_audit_proto_rawDesc = "" + "\x11marks_user_joined\x18\b \x01(\bR\x0fmarksUserJoined\x12)\n" + "\x10webhook_eligible\x18\t \x01(\bR\x0fwebhookEligible\"U\n" + "\x1bListAuditEventTypesResponse\x126\n" + - "\x05types\x18\x01 \x03(\v2 .saas.accounts.v1.AuditEventTypeR\x05types2\x9d\x06\n" + + "\x05types\x18\x01 \x03(\v2 .saas.accounts.v1.AuditEventTypeR\x05types\"U\n" + + "\x19DeliverAuditEventsRequest\x12\x1d\n" + + "\x04from\x18\x01 \x01(\tB\t\xbaH\x06r\x04\x10\x01\x18@R\x04from\x12\x19\n" + + "\x02to\x18\x02 \x01(\tB\t\xbaH\x06r\x04\x10\x01\x18@R\x02to\"\x93\x01\n" + + "\x1aDeliverAuditEventsResponse\x12\x1b\n" + + "\teffect_id\x18\x01 \x01(\tR\beffectId\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x1c\n" + + "\tdelivered\x18\x03 \x01(\x05R\tdelivered\x12\x12\n" + + "\x04from\x18\x04 \x01(\tR\x04from\x12\x0e\n" + + "\x02to\x18\x05 \x01(\tR\x02to2\xe9\t\n" + "\fAuditService\x12\xb5\x01\n" + "\rQueryAuditLog\x12&.saas.accounts.v1.QueryAuditLogRequest\x1a'.saas.accounts.v1.QueryAuditLogResponse\"S\xc2\xf3\x18:\b\x02\x10\x03\x1a\n" + "audit:read\"\n" + @@ -1421,7 +1581,12 @@ const file_saas_accounts_v1_audit_proto_rawDesc = "" + "\x0eExportAuditLog\x12'.saas.accounts.v1.ExportAuditLogRequest\x1a(.saas.accounts.v1.ExportAuditLogResponse\"]\xc2\xf3\x18:\b\x02\x10\x04\x1a\n" + "audit:read\"\n" + "audit:read*\f\n" + - "\x06org_id\x10\x02\x18\x010\x01:\x02\x10\x01@\x01H\x03P\x03X\x03`\x01\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v1/audit-log:exportB\xc8\x01\n" + + "\x06org_id\x10\x02\x18\x010\x01:\x02\x10\x01@\x01H\x03P\x03X\x03`\x01\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v1/audit-log:export\x12\xb7\x02\n" + + "\x12DeliverAuditEvents\x12+.saas.accounts.v1.DeliverAuditEventsRequest\x1a,.saas.accounts.v1.DeliverAuditEventsResponse\"\xc5\x01\xc2\xf3\x18\x14\b\x02\x10\x010\x01:\x02\x10\x01@\x03H\x04P\x02X\x02`\x01\xda\xf3\x18\xa8\x01\n" + + "\x02\b<\x12\x03\b\xd8\x04\x18\x03\"\x02\b\x05*\vUNAVAILABLE*\x12RESOURCE_EXHAUSTED*\aABORTED2\n" + + "saas-auditJ7/saas.accounts.v1.AuditService/LookupDeliverAuditEventsP\x80\x02X\x80\b`\x01r \n" + + "\x0eaudit_delivery\x12\x06invoke\x12\x04read\x18\x01\x12\x8f\x01\n" + + "\x18LookupDeliverAuditEvents\x12+.saas.accounts.v1.DeliverAuditEventsRequest\x1a,.saas.accounts.v1.DeliverAuditEventsResponse\"\x18\xc2\xf3\x18\x14\b\x02\x10\x010\x01:\x02\x10\x01@\x01H\x03P\x02X\x02`\x01B\xc8\x01\n" + "\x14com.saas.accounts.v1B\n" + "AuditProtoP\x01ZBgithub.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1;accountsv1\xa2\x02\x03SAX\xaa\x02\x10Saas.Accounts.V1\xca\x02\x10Saas\\Accounts\\V1\xe2\x02\x1cSaas\\Accounts\\V1\\GPBMetadata\xea\x02\x12Saas::Accounts::V1b\x06proto3" @@ -1437,7 +1602,7 @@ func file_saas_accounts_v1_audit_proto_rawDescGZIP() []byte { return file_saas_accounts_v1_audit_proto_rawDescData } -var file_saas_accounts_v1_audit_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_saas_accounts_v1_audit_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_saas_accounts_v1_audit_proto_goTypes = []any{ (*AuditEvent)(nil), // 0: saas.accounts.v1.AuditEvent (*QueryAuditLogRequest)(nil), // 1: saas.accounts.v1.QueryAuditLogRequest @@ -1452,41 +1617,47 @@ var file_saas_accounts_v1_audit_proto_goTypes = []any{ (*ListAuditEventTypesRequest)(nil), // 10: saas.accounts.v1.ListAuditEventTypesRequest (*AuditEventType)(nil), // 11: saas.accounts.v1.AuditEventType (*ListAuditEventTypesResponse)(nil), // 12: saas.accounts.v1.ListAuditEventTypesResponse - nil, // 13: saas.accounts.v1.AuditEvent.MetadataEntry - nil, // 14: saas.accounts.v1.QueryAuditLogRequest.PayloadContainsEntry - nil, // 15: saas.accounts.v1.AggregateAuditLogRequest.PayloadContainsEntry - nil, // 16: saas.accounts.v1.AuditAggregateBucket.MetricsEntry - nil, // 17: saas.accounts.v1.AuditAggregateBucket.SamplesEntry - (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 19: google.protobuf.Struct + (*DeliverAuditEventsRequest)(nil), // 13: saas.accounts.v1.DeliverAuditEventsRequest + (*DeliverAuditEventsResponse)(nil), // 14: saas.accounts.v1.DeliverAuditEventsResponse + nil, // 15: saas.accounts.v1.AuditEvent.MetadataEntry + nil, // 16: saas.accounts.v1.QueryAuditLogRequest.PayloadContainsEntry + nil, // 17: saas.accounts.v1.AggregateAuditLogRequest.PayloadContainsEntry + nil, // 18: saas.accounts.v1.AuditAggregateBucket.MetricsEntry + nil, // 19: saas.accounts.v1.AuditAggregateBucket.SamplesEntry + (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 21: google.protobuf.Struct } var file_saas_accounts_v1_audit_proto_depIdxs = []int32{ - 13, // 0: saas.accounts.v1.AuditEvent.metadata:type_name -> saas.accounts.v1.AuditEvent.MetadataEntry - 18, // 1: saas.accounts.v1.AuditEvent.created_at:type_name -> google.protobuf.Timestamp - 19, // 2: saas.accounts.v1.AuditEvent.payload:type_name -> google.protobuf.Struct - 18, // 3: saas.accounts.v1.QueryAuditLogRequest.from:type_name -> google.protobuf.Timestamp - 18, // 4: saas.accounts.v1.QueryAuditLogRequest.to:type_name -> google.protobuf.Timestamp - 14, // 5: saas.accounts.v1.QueryAuditLogRequest.payload_contains:type_name -> saas.accounts.v1.QueryAuditLogRequest.PayloadContainsEntry + 15, // 0: saas.accounts.v1.AuditEvent.metadata:type_name -> saas.accounts.v1.AuditEvent.MetadataEntry + 20, // 1: saas.accounts.v1.AuditEvent.created_at:type_name -> google.protobuf.Timestamp + 21, // 2: saas.accounts.v1.AuditEvent.payload:type_name -> google.protobuf.Struct + 20, // 3: saas.accounts.v1.QueryAuditLogRequest.from:type_name -> google.protobuf.Timestamp + 20, // 4: saas.accounts.v1.QueryAuditLogRequest.to:type_name -> google.protobuf.Timestamp + 16, // 5: saas.accounts.v1.QueryAuditLogRequest.payload_contains:type_name -> saas.accounts.v1.QueryAuditLogRequest.PayloadContainsEntry 0, // 6: saas.accounts.v1.QueryAuditLogResponse.events:type_name -> saas.accounts.v1.AuditEvent - 18, // 7: saas.accounts.v1.AggregateAuditLogRequest.from:type_name -> google.protobuf.Timestamp - 18, // 8: saas.accounts.v1.AggregateAuditLogRequest.to:type_name -> google.protobuf.Timestamp + 20, // 7: saas.accounts.v1.AggregateAuditLogRequest.from:type_name -> google.protobuf.Timestamp + 20, // 8: saas.accounts.v1.AggregateAuditLogRequest.to:type_name -> google.protobuf.Timestamp 5, // 9: saas.accounts.v1.AggregateAuditLogRequest.metrics:type_name -> saas.accounts.v1.AuditMetric 6, // 10: saas.accounts.v1.AggregateAuditLogRequest.derived:type_name -> saas.accounts.v1.AuditDerivedMetric - 15, // 11: saas.accounts.v1.AggregateAuditLogRequest.payload_contains:type_name -> saas.accounts.v1.AggregateAuditLogRequest.PayloadContainsEntry - 16, // 12: saas.accounts.v1.AuditAggregateBucket.metrics:type_name -> saas.accounts.v1.AuditAggregateBucket.MetricsEntry - 17, // 13: saas.accounts.v1.AuditAggregateBucket.samples:type_name -> saas.accounts.v1.AuditAggregateBucket.SamplesEntry + 17, // 11: saas.accounts.v1.AggregateAuditLogRequest.payload_contains:type_name -> saas.accounts.v1.AggregateAuditLogRequest.PayloadContainsEntry + 18, // 12: saas.accounts.v1.AuditAggregateBucket.metrics:type_name -> saas.accounts.v1.AuditAggregateBucket.MetricsEntry + 19, // 13: saas.accounts.v1.AuditAggregateBucket.samples:type_name -> saas.accounts.v1.AuditAggregateBucket.SamplesEntry 8, // 14: saas.accounts.v1.AggregateAuditLogResponse.buckets:type_name -> saas.accounts.v1.AuditAggregateBucket 11, // 15: saas.accounts.v1.ListAuditEventTypesResponse.types:type_name -> saas.accounts.v1.AuditEventType 1, // 16: saas.accounts.v1.AuditService.QueryAuditLog:input_type -> saas.accounts.v1.QueryAuditLogRequest 7, // 17: saas.accounts.v1.AuditService.AggregateAuditLog:input_type -> saas.accounts.v1.AggregateAuditLogRequest 10, // 18: saas.accounts.v1.AuditService.ListAuditEventTypes:input_type -> saas.accounts.v1.ListAuditEventTypesRequest 3, // 19: saas.accounts.v1.AuditService.ExportAuditLog:input_type -> saas.accounts.v1.ExportAuditLogRequest - 2, // 20: saas.accounts.v1.AuditService.QueryAuditLog:output_type -> saas.accounts.v1.QueryAuditLogResponse - 9, // 21: saas.accounts.v1.AuditService.AggregateAuditLog:output_type -> saas.accounts.v1.AggregateAuditLogResponse - 12, // 22: saas.accounts.v1.AuditService.ListAuditEventTypes:output_type -> saas.accounts.v1.ListAuditEventTypesResponse - 4, // 23: saas.accounts.v1.AuditService.ExportAuditLog:output_type -> saas.accounts.v1.ExportAuditLogResponse - 20, // [20:24] is the sub-list for method output_type - 16, // [16:20] is the sub-list for method input_type + 13, // 20: saas.accounts.v1.AuditService.DeliverAuditEvents:input_type -> saas.accounts.v1.DeliverAuditEventsRequest + 13, // 21: saas.accounts.v1.AuditService.LookupDeliverAuditEvents:input_type -> saas.accounts.v1.DeliverAuditEventsRequest + 2, // 22: saas.accounts.v1.AuditService.QueryAuditLog:output_type -> saas.accounts.v1.QueryAuditLogResponse + 9, // 23: saas.accounts.v1.AuditService.AggregateAuditLog:output_type -> saas.accounts.v1.AggregateAuditLogResponse + 12, // 24: saas.accounts.v1.AuditService.ListAuditEventTypes:output_type -> saas.accounts.v1.ListAuditEventTypesResponse + 4, // 25: saas.accounts.v1.AuditService.ExportAuditLog:output_type -> saas.accounts.v1.ExportAuditLogResponse + 14, // 26: saas.accounts.v1.AuditService.DeliverAuditEvents:output_type -> saas.accounts.v1.DeliverAuditEventsResponse + 14, // 27: saas.accounts.v1.AuditService.LookupDeliverAuditEvents:output_type -> saas.accounts.v1.DeliverAuditEventsResponse + 22, // [22:28] is the sub-list for method output_type + 16, // [16:22] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name 16, // [16:16] is the sub-list for extension extendee 0, // [0:16] is the sub-list for field type_name @@ -1503,7 +1674,7 @@ func file_saas_accounts_v1_audit_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_saas_accounts_v1_audit_proto_rawDesc), len(file_saas_accounts_v1_audit_proto_rawDesc)), NumEnums: 0, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/saas/accounts/v1/module_capabilities.pb.go b/gen/saas/accounts/v1/module_capabilities.pb.go index dd7c172..bb3db6e 100644 --- a/gen/saas/accounts/v1/module_capabilities.pb.go +++ b/gen/saas/accounts/v1/module_capabilities.pb.go @@ -2038,6 +2038,252 @@ func (x *ModuleEmitAuditEventRequest) GetIdempotencyKey() string { return "" } +// ModuleReadJournalRequest reads the platform journal across every tenant. Only a +// module principal whose grant is cross_tenant may call it, the gate a +// system-scoped EmitAuditEvent has; any other caller is PermissionDenied. +type ModuleReadJournalRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // after is the position the previous page ended at (a page's next_after). The + // literal "start" is a valid position meaning "before the oldest retained + // entry", and the empty string is accepted as an alias of it; so a reader with + // nothing read yet has a non-empty position to correlate its first page on. + // Positions are opaque: store one and hand it back unread. No entry's position + // is ever "start". A value this host did not issue is InvalidArgument. + After string `protobuf:"bytes,1,opt,name=after,proto3" json:"after,omitempty"` + // limit is the page size: 0 is 100, and more than 500 is served as 500. A + // page is also bounded to 2 MiB of event data, though the first entry is always + // served. + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // types are the event types to read, exactly (no pattern). At least one is + // required, so no caller reads every tenant's every event by leaving the + // filter out. + Types []string `protobuf:"bytes,3,rep,name=types,proto3" json:"types,omitempty"` + // delivery_bindings are the binding ids the caller's own delivery of the journal + // runs under. They decide which entries are marked delivery_machinery. + DeliveryBindings []string `protobuf:"bytes,4,rep,name=delivery_bindings,json=deliveryBindings,proto3" json:"delivery_bindings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModuleReadJournalRequest) Reset() { + *x = ModuleReadJournalRequest{} + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModuleReadJournalRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModuleReadJournalRequest) ProtoMessage() {} + +func (x *ModuleReadJournalRequest) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModuleReadJournalRequest.ProtoReflect.Descriptor instead. +func (*ModuleReadJournalRequest) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{20} +} + +func (x *ModuleReadJournalRequest) GetAfter() string { + if x != nil { + return x.After + } + return "" +} + +func (x *ModuleReadJournalRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ModuleReadJournalRequest) GetTypes() []string { + if x != nil { + return x.Types + } + return nil +} + +func (x *ModuleReadJournalRequest) GetDeliveryBindings() []string { + if x != nil { + return x.DeliveryBindings + } + return nil +} + +// ModuleJournalEntry is one journal row. Rows are served in the order their +// writing transactions are final (see ModuleReadJournalRequest), so a reader that +// resumes from the last position it was given skips nothing. +type ModuleJournalEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + // position is where the row sits in the read order: opaque, and the value a + // reader passes as after to resume past it. + Position string `protobuf:"bytes,1,opt,name=position,proto3" json:"position,omitempty"` + EventId string `protobuf:"bytes,2,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + // organization_id is the row's tenant, empty for a row that has none. + OrganizationId string `protobuf:"bytes,4,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + OccurredAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=occurred_at,json=occurredAt,proto3" json:"occurred_at,omitempty"` + // data is the event's JSON object; empty when the event's data is not one. + Data *structpb.Struct `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` + // delivery_machinery is true when the entry the row carries is the footprint of + // the caller's own delivery of the journal: the host computes it, so a caller + // never parses this module's payloads. It is true for a + // saas.module.operation_context_minted or + // saas.module.delegated_audience_exchange entry whose binding_id is one of the + // request's delivery_bindings, and for a saas.module.work_context_minted entry + // of the caller's own module principal. + DeliveryMachinery bool `protobuf:"varint,7,opt,name=delivery_machinery,json=deliveryMachinery,proto3" json:"delivery_machinery,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModuleJournalEntry) Reset() { + *x = ModuleJournalEntry{} + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModuleJournalEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModuleJournalEntry) ProtoMessage() {} + +func (x *ModuleJournalEntry) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModuleJournalEntry.ProtoReflect.Descriptor instead. +func (*ModuleJournalEntry) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{21} +} + +func (x *ModuleJournalEntry) GetPosition() string { + if x != nil { + return x.Position + } + return "" +} + +func (x *ModuleJournalEntry) GetEventId() string { + if x != nil { + return x.EventId + } + return "" +} + +func (x *ModuleJournalEntry) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ModuleJournalEntry) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +func (x *ModuleJournalEntry) GetOccurredAt() *timestamppb.Timestamp { + if x != nil { + return x.OccurredAt + } + return nil +} + +func (x *ModuleJournalEntry) GetData() *structpb.Struct { + if x != nil { + return x.Data + } + return nil +} + +func (x *ModuleJournalEntry) GetDeliveryMachinery() bool { + if x != nil { + return x.DeliveryMachinery + } + return false +} + +// ModuleReadJournalResponse is one page and where to resume. +type ModuleReadJournalResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Entries []*ModuleJournalEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` + // next_after is the last entry's position, or the request's after when the page + // is empty. + NextAfter string `protobuf:"bytes,2,opt,name=next_after,json=nextAfter,proto3" json:"next_after,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModuleReadJournalResponse) Reset() { + *x = ModuleReadJournalResponse{} + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModuleReadJournalResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModuleReadJournalResponse) ProtoMessage() {} + +func (x *ModuleReadJournalResponse) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModuleReadJournalResponse.ProtoReflect.Descriptor instead. +func (*ModuleReadJournalResponse) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{22} +} + +func (x *ModuleReadJournalResponse) GetEntries() []*ModuleJournalEntry { + if x != nil { + return x.Entries + } + return nil +} + +func (x *ModuleReadJournalResponse) GetNextAfter() string { + if x != nil { + return x.NextAfter + } + return "" +} + // ModuleAuditFieldDeclaration is one payload field of a declared audit event // type. `solution` is stamped by the host from the emitting scope and may not // be declared. @@ -2057,7 +2303,7 @@ type ModuleAuditFieldDeclaration struct { func (x *ModuleAuditFieldDeclaration) Reset() { *x = ModuleAuditFieldDeclaration{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[20] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2069,7 +2315,7 @@ func (x *ModuleAuditFieldDeclaration) String() string { func (*ModuleAuditFieldDeclaration) ProtoMessage() {} func (x *ModuleAuditFieldDeclaration) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[20] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2082,7 +2328,7 @@ func (x *ModuleAuditFieldDeclaration) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleAuditFieldDeclaration.ProtoReflect.Descriptor instead. func (*ModuleAuditFieldDeclaration) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{20} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{23} } func (x *ModuleAuditFieldDeclaration) GetName() string { @@ -2135,7 +2381,7 @@ type ModuleAuditEventTypeDeclaration struct { func (x *ModuleAuditEventTypeDeclaration) Reset() { *x = ModuleAuditEventTypeDeclaration{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[21] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2147,7 +2393,7 @@ func (x *ModuleAuditEventTypeDeclaration) String() string { func (*ModuleAuditEventTypeDeclaration) ProtoMessage() {} func (x *ModuleAuditEventTypeDeclaration) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[21] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2160,7 +2406,7 @@ func (x *ModuleAuditEventTypeDeclaration) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleAuditEventTypeDeclaration.ProtoReflect.Descriptor instead. func (*ModuleAuditEventTypeDeclaration) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{21} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{24} } func (x *ModuleAuditEventTypeDeclaration) GetType() string { @@ -2210,7 +2456,7 @@ type ModuleDeclareAuditEventTypesRequest struct { func (x *ModuleDeclareAuditEventTypesRequest) Reset() { *x = ModuleDeclareAuditEventTypesRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[22] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2222,7 +2468,7 @@ func (x *ModuleDeclareAuditEventTypesRequest) String() string { func (*ModuleDeclareAuditEventTypesRequest) ProtoMessage() {} func (x *ModuleDeclareAuditEventTypesRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[22] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2235,7 +2481,7 @@ func (x *ModuleDeclareAuditEventTypesRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ModuleDeclareAuditEventTypesRequest.ProtoReflect.Descriptor instead. func (*ModuleDeclareAuditEventTypesRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{22} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{25} } func (x *ModuleDeclareAuditEventTypesRequest) GetPrefix() string { @@ -2265,7 +2511,7 @@ type ModuleDeclareAuditEventTypesResponse struct { func (x *ModuleDeclareAuditEventTypesResponse) Reset() { *x = ModuleDeclareAuditEventTypesResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[23] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2277,7 +2523,7 @@ func (x *ModuleDeclareAuditEventTypesResponse) String() string { func (*ModuleDeclareAuditEventTypesResponse) ProtoMessage() {} func (x *ModuleDeclareAuditEventTypesResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[23] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2290,7 +2536,7 @@ func (x *ModuleDeclareAuditEventTypesResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ModuleDeclareAuditEventTypesResponse.ProtoReflect.Descriptor instead. func (*ModuleDeclareAuditEventTypesResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{23} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{26} } func (x *ModuleDeclareAuditEventTypesResponse) GetAdmittedTypes() []string { @@ -2324,7 +2570,7 @@ type SubjectVisibilityGrant struct { func (x *SubjectVisibilityGrant) Reset() { *x = SubjectVisibilityGrant{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[24] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2336,7 +2582,7 @@ func (x *SubjectVisibilityGrant) String() string { func (*SubjectVisibilityGrant) ProtoMessage() {} func (x *SubjectVisibilityGrant) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[24] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2349,7 +2595,7 @@ func (x *SubjectVisibilityGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use SubjectVisibilityGrant.ProtoReflect.Descriptor instead. func (*SubjectVisibilityGrant) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{24} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{27} } func (x *SubjectVisibilityGrant) GetVisibleSubjectId() string { @@ -2387,7 +2633,7 @@ type ModuleListSubjectVisibilityRequest struct { func (x *ModuleListSubjectVisibilityRequest) Reset() { *x = ModuleListSubjectVisibilityRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[25] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2399,7 +2645,7 @@ func (x *ModuleListSubjectVisibilityRequest) String() string { func (*ModuleListSubjectVisibilityRequest) ProtoMessage() {} func (x *ModuleListSubjectVisibilityRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[25] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2412,7 +2658,7 @@ func (x *ModuleListSubjectVisibilityRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ModuleListSubjectVisibilityRequest.ProtoReflect.Descriptor instead. func (*ModuleListSubjectVisibilityRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{25} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{28} } func (x *ModuleListSubjectVisibilityRequest) GetTenant() string { @@ -2451,7 +2697,7 @@ type ModuleListSubjectVisibilityResponse struct { func (x *ModuleListSubjectVisibilityResponse) Reset() { *x = ModuleListSubjectVisibilityResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[26] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2463,7 +2709,7 @@ func (x *ModuleListSubjectVisibilityResponse) String() string { func (*ModuleListSubjectVisibilityResponse) ProtoMessage() {} func (x *ModuleListSubjectVisibilityResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[26] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2476,7 +2722,7 @@ func (x *ModuleListSubjectVisibilityResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use ModuleListSubjectVisibilityResponse.ProtoReflect.Descriptor instead. func (*ModuleListSubjectVisibilityResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{26} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{29} } func (x *ModuleListSubjectVisibilityResponse) GetGrants() []*SubjectVisibilityGrant { @@ -2504,7 +2750,7 @@ type FetchDatasourceBlobRequest struct { func (x *FetchDatasourceBlobRequest) Reset() { *x = FetchDatasourceBlobRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[27] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2516,7 +2762,7 @@ func (x *FetchDatasourceBlobRequest) String() string { func (*FetchDatasourceBlobRequest) ProtoMessage() {} func (x *FetchDatasourceBlobRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[27] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2529,7 +2775,7 @@ func (x *FetchDatasourceBlobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchDatasourceBlobRequest.ProtoReflect.Descriptor instead. func (*FetchDatasourceBlobRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{27} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{30} } func (x *FetchDatasourceBlobRequest) GetSourceId() string { @@ -2561,7 +2807,7 @@ type FetchDatasourceBlobChunk struct { func (x *FetchDatasourceBlobChunk) Reset() { *x = FetchDatasourceBlobChunk{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[28] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2573,7 +2819,7 @@ func (x *FetchDatasourceBlobChunk) String() string { func (*FetchDatasourceBlobChunk) ProtoMessage() {} func (x *FetchDatasourceBlobChunk) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[28] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2586,7 +2832,7 @@ func (x *FetchDatasourceBlobChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchDatasourceBlobChunk.ProtoReflect.Descriptor instead. func (*FetchDatasourceBlobChunk) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{28} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{31} } func (x *FetchDatasourceBlobChunk) GetData() []byte { @@ -2624,7 +2870,7 @@ type DatasourceFileRef struct { func (x *DatasourceFileRef) Reset() { *x = DatasourceFileRef{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[29] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2636,7 +2882,7 @@ func (x *DatasourceFileRef) String() string { func (*DatasourceFileRef) ProtoMessage() {} func (x *DatasourceFileRef) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[29] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2649,7 +2895,7 @@ func (x *DatasourceFileRef) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceFileRef.ProtoReflect.Descriptor instead. func (*DatasourceFileRef) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{29} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{32} } func (x *DatasourceFileRef) GetItemId() string { @@ -2685,7 +2931,7 @@ type FetchDatasourceFilesRequest struct { func (x *FetchDatasourceFilesRequest) Reset() { *x = FetchDatasourceFilesRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[30] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2697,7 +2943,7 @@ func (x *FetchDatasourceFilesRequest) String() string { func (*FetchDatasourceFilesRequest) ProtoMessage() {} func (x *FetchDatasourceFilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[30] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2710,7 +2956,7 @@ func (x *FetchDatasourceFilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchDatasourceFilesRequest.ProtoReflect.Descriptor instead. func (*FetchDatasourceFilesRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{30} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{33} } func (x *FetchDatasourceFilesRequest) GetSourceId() string { @@ -2752,7 +2998,7 @@ type DatasourceProvenance struct { func (x *DatasourceProvenance) Reset() { *x = DatasourceProvenance{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[31] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2764,7 +3010,7 @@ func (x *DatasourceProvenance) String() string { func (*DatasourceProvenance) ProtoMessage() {} func (x *DatasourceProvenance) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[31] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2777,7 +3023,7 @@ func (x *DatasourceProvenance) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceProvenance.ProtoReflect.Descriptor instead. func (*DatasourceProvenance) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{31} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{34} } func (x *DatasourceProvenance) GetSourceId() string { @@ -2843,7 +3089,7 @@ type DatasourceItemReaders struct { func (x *DatasourceItemReaders) Reset() { *x = DatasourceItemReaders{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[32] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2855,7 +3101,7 @@ func (x *DatasourceItemReaders) String() string { func (*DatasourceItemReaders) ProtoMessage() {} func (x *DatasourceItemReaders) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[32] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2868,7 +3114,7 @@ func (x *DatasourceItemReaders) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceItemReaders.ProtoReflect.Descriptor instead. func (*DatasourceItemReaders) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{32} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{35} } func (x *DatasourceItemReaders) GetBasis() DatasourceItemReadersBasis { @@ -2929,7 +3175,7 @@ type DatasourceFileHeader struct { func (x *DatasourceFileHeader) Reset() { *x = DatasourceFileHeader{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[33] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2941,7 +3187,7 @@ func (x *DatasourceFileHeader) String() string { func (*DatasourceFileHeader) ProtoMessage() {} func (x *DatasourceFileHeader) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[33] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2954,7 +3200,7 @@ func (x *DatasourceFileHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceFileHeader.ProtoReflect.Descriptor instead. func (*DatasourceFileHeader) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{33} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{36} } func (x *DatasourceFileHeader) GetProvenance() *DatasourceProvenance { @@ -3008,7 +3254,7 @@ type FetchDatasourceFilesFrame struct { func (x *FetchDatasourceFilesFrame) Reset() { *x = FetchDatasourceFilesFrame{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[34] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3020,7 +3266,7 @@ func (x *FetchDatasourceFilesFrame) String() string { func (*FetchDatasourceFilesFrame) ProtoMessage() {} func (x *FetchDatasourceFilesFrame) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[34] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3033,7 +3279,7 @@ func (x *FetchDatasourceFilesFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchDatasourceFilesFrame.ProtoReflect.Descriptor instead. func (*FetchDatasourceFilesFrame) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{34} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{37} } func (x *FetchDatasourceFilesFrame) GetFrame() isFetchDatasourceFilesFrame_Frame { @@ -3092,7 +3338,7 @@ type ModulePublishEventRequest struct { func (x *ModulePublishEventRequest) Reset() { *x = ModulePublishEventRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[35] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3104,7 +3350,7 @@ func (x *ModulePublishEventRequest) String() string { func (*ModulePublishEventRequest) ProtoMessage() {} func (x *ModulePublishEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[35] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3117,7 +3363,7 @@ func (x *ModulePublishEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModulePublishEventRequest.ProtoReflect.Descriptor instead. func (*ModulePublishEventRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{35} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{38} } func (x *ModulePublishEventRequest) GetTenant() string { @@ -3147,7 +3393,7 @@ type ModulePublishEventResponse struct { func (x *ModulePublishEventResponse) Reset() { *x = ModulePublishEventResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[36] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3159,7 +3405,7 @@ func (x *ModulePublishEventResponse) String() string { func (*ModulePublishEventResponse) ProtoMessage() {} func (x *ModulePublishEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[36] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3172,7 +3418,7 @@ func (x *ModulePublishEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ModulePublishEventResponse.ProtoReflect.Descriptor instead. func (*ModulePublishEventResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{36} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{39} } func (x *ModulePublishEventResponse) GetEventId() string { @@ -3198,7 +3444,7 @@ type ModuleSubscribeRequest struct { func (x *ModuleSubscribeRequest) Reset() { *x = ModuleSubscribeRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[37] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3210,7 +3456,7 @@ func (x *ModuleSubscribeRequest) String() string { func (*ModuleSubscribeRequest) ProtoMessage() {} func (x *ModuleSubscribeRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[37] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3223,7 +3469,7 @@ func (x *ModuleSubscribeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleSubscribeRequest.ProtoReflect.Descriptor instead. func (*ModuleSubscribeRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{37} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{40} } func (x *ModuleSubscribeRequest) GetTypePattern() string { @@ -3262,7 +3508,7 @@ type ModuleSubscription struct { func (x *ModuleSubscription) Reset() { *x = ModuleSubscription{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[38] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3274,7 +3520,7 @@ func (x *ModuleSubscription) String() string { func (*ModuleSubscription) ProtoMessage() {} func (x *ModuleSubscription) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[38] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3287,7 +3533,7 @@ func (x *ModuleSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleSubscription.ProtoReflect.Descriptor instead. func (*ModuleSubscription) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{38} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{41} } func (x *ModuleSubscription) GetId() string { @@ -3341,7 +3587,7 @@ type ModuleSubscribeResponse struct { func (x *ModuleSubscribeResponse) Reset() { *x = ModuleSubscribeResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[39] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3353,7 +3599,7 @@ func (x *ModuleSubscribeResponse) String() string { func (*ModuleSubscribeResponse) ProtoMessage() {} func (x *ModuleSubscribeResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[39] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3366,7 +3612,7 @@ func (x *ModuleSubscribeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleSubscribeResponse.ProtoReflect.Descriptor instead. func (*ModuleSubscribeResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{39} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{42} } func (x *ModuleSubscribeResponse) GetSubscription() *ModuleSubscription { @@ -3386,7 +3632,7 @@ type ModuleUnsubscribeRequest struct { func (x *ModuleUnsubscribeRequest) Reset() { *x = ModuleUnsubscribeRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[40] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3398,7 +3644,7 @@ func (x *ModuleUnsubscribeRequest) String() string { func (*ModuleUnsubscribeRequest) ProtoMessage() {} func (x *ModuleUnsubscribeRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[40] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3411,7 +3657,7 @@ func (x *ModuleUnsubscribeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleUnsubscribeRequest.ProtoReflect.Descriptor instead. func (*ModuleUnsubscribeRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{40} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{43} } func (x *ModuleUnsubscribeRequest) GetSubscriptionId() string { @@ -3431,7 +3677,7 @@ type ModuleListSubscriptionsRequest struct { func (x *ModuleListSubscriptionsRequest) Reset() { *x = ModuleListSubscriptionsRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[41] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3443,7 +3689,7 @@ func (x *ModuleListSubscriptionsRequest) String() string { func (*ModuleListSubscriptionsRequest) ProtoMessage() {} func (x *ModuleListSubscriptionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[41] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3456,7 +3702,7 @@ func (x *ModuleListSubscriptionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleListSubscriptionsRequest.ProtoReflect.Descriptor instead. func (*ModuleListSubscriptionsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{41} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{44} } type ModuleListSubscriptionsResponse struct { @@ -3468,7 +3714,7 @@ type ModuleListSubscriptionsResponse struct { func (x *ModuleListSubscriptionsResponse) Reset() { *x = ModuleListSubscriptionsResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[42] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3480,7 +3726,7 @@ func (x *ModuleListSubscriptionsResponse) String() string { func (*ModuleListSubscriptionsResponse) ProtoMessage() {} func (x *ModuleListSubscriptionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[42] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3493,7 +3739,7 @@ func (x *ModuleListSubscriptionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleListSubscriptionsResponse.ProtoReflect.Descriptor instead. func (*ModuleListSubscriptionsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{42} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{45} } func (x *ModuleListSubscriptionsResponse) GetSubscriptions() []*ModuleSubscription { @@ -3518,7 +3764,7 @@ type ModuleReplayEventsRequest struct { func (x *ModuleReplayEventsRequest) Reset() { *x = ModuleReplayEventsRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[43] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3530,7 +3776,7 @@ func (x *ModuleReplayEventsRequest) String() string { func (*ModuleReplayEventsRequest) ProtoMessage() {} func (x *ModuleReplayEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[43] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3543,7 +3789,7 @@ func (x *ModuleReplayEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleReplayEventsRequest.ProtoReflect.Descriptor instead. func (*ModuleReplayEventsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{43} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{46} } func (x *ModuleReplayEventsRequest) GetTenant() string { @@ -3578,7 +3824,7 @@ type ModuleReplayEventsResponse struct { func (x *ModuleReplayEventsResponse) Reset() { *x = ModuleReplayEventsResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[44] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3590,7 +3836,7 @@ func (x *ModuleReplayEventsResponse) String() string { func (*ModuleReplayEventsResponse) ProtoMessage() {} func (x *ModuleReplayEventsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[44] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3603,7 +3849,7 @@ func (x *ModuleReplayEventsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleReplayEventsResponse.ProtoReflect.Descriptor instead. func (*ModuleReplayEventsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{44} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{47} } func (x *ModuleReplayEventsResponse) GetRedelivered() int32 { @@ -3648,7 +3894,7 @@ type ReadableSourceCollection struct { func (x *ReadableSourceCollection) Reset() { *x = ReadableSourceCollection{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[45] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3660,7 +3906,7 @@ func (x *ReadableSourceCollection) String() string { func (*ReadableSourceCollection) ProtoMessage() {} func (x *ReadableSourceCollection) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[45] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3673,7 +3919,7 @@ func (x *ReadableSourceCollection) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadableSourceCollection.ProtoReflect.Descriptor instead. func (*ReadableSourceCollection) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{45} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{48} } func (x *ReadableSourceCollection) GetSourceId() string { @@ -3767,7 +4013,7 @@ type ReadableCollectionGrant struct { func (x *ReadableCollectionGrant) Reset() { *x = ReadableCollectionGrant{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[46] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3779,7 +4025,7 @@ func (x *ReadableCollectionGrant) String() string { func (*ReadableCollectionGrant) ProtoMessage() {} func (x *ReadableCollectionGrant) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[46] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3792,7 +4038,7 @@ func (x *ReadableCollectionGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadableCollectionGrant.ProtoReflect.Descriptor instead. func (*ReadableCollectionGrant) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{46} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{49} } func (x *ReadableCollectionGrant) GetSubjectLabel() string { @@ -3865,7 +4111,7 @@ type CollectionSyncProvenance struct { func (x *CollectionSyncProvenance) Reset() { *x = CollectionSyncProvenance{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[47] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3877,7 +4123,7 @@ func (x *CollectionSyncProvenance) String() string { func (*CollectionSyncProvenance) ProtoMessage() {} func (x *CollectionSyncProvenance) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[47] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3890,7 +4136,7 @@ func (x *CollectionSyncProvenance) ProtoReflect() protoreflect.Message { // Deprecated: Use CollectionSyncProvenance.ProtoReflect.Descriptor instead. func (*CollectionSyncProvenance) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{47} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{50} } func (x *CollectionSyncProvenance) GetStage() SourceSyncStage { @@ -3953,7 +4199,7 @@ type ListReadableSourceCollectionsRequest struct { func (x *ListReadableSourceCollectionsRequest) Reset() { *x = ListReadableSourceCollectionsRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[48] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3965,7 +4211,7 @@ func (x *ListReadableSourceCollectionsRequest) String() string { func (*ListReadableSourceCollectionsRequest) ProtoMessage() {} func (x *ListReadableSourceCollectionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[48] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3978,7 +4224,7 @@ func (x *ListReadableSourceCollectionsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListReadableSourceCollectionsRequest.ProtoReflect.Descriptor instead. func (*ListReadableSourceCollectionsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{48} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{51} } func (x *ListReadableSourceCollectionsRequest) GetPageSize() int32 { @@ -4005,7 +4251,7 @@ type ListReadableSourceCollectionsResponse struct { func (x *ListReadableSourceCollectionsResponse) Reset() { *x = ListReadableSourceCollectionsResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[49] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4017,7 +4263,7 @@ func (x *ListReadableSourceCollectionsResponse) String() string { func (*ListReadableSourceCollectionsResponse) ProtoMessage() {} func (x *ListReadableSourceCollectionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[49] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4030,7 +4276,7 @@ func (x *ListReadableSourceCollectionsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListReadableSourceCollectionsResponse.ProtoReflect.Descriptor instead. func (*ListReadableSourceCollectionsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{49} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{52} } func (x *ListReadableSourceCollectionsResponse) GetCollections() []*ReadableSourceCollection { @@ -4074,7 +4320,7 @@ type ModulePlaceRecordRequest struct { func (x *ModulePlaceRecordRequest) Reset() { *x = ModulePlaceRecordRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[50] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4086,7 +4332,7 @@ func (x *ModulePlaceRecordRequest) String() string { func (*ModulePlaceRecordRequest) ProtoMessage() {} func (x *ModulePlaceRecordRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[50] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4099,7 +4345,7 @@ func (x *ModulePlaceRecordRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModulePlaceRecordRequest.ProtoReflect.Descriptor instead. func (*ModulePlaceRecordRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{50} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{53} } func (x *ModulePlaceRecordRequest) GetTenant() string { @@ -4158,7 +4404,7 @@ type ModulePlaceRecordResponse struct { func (x *ModulePlaceRecordResponse) Reset() { *x = ModulePlaceRecordResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[51] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4170,7 +4416,7 @@ func (x *ModulePlaceRecordResponse) String() string { func (*ModulePlaceRecordResponse) ProtoMessage() {} func (x *ModulePlaceRecordResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[51] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4183,7 +4429,7 @@ func (x *ModulePlaceRecordResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ModulePlaceRecordResponse.ProtoReflect.Descriptor instead. func (*ModulePlaceRecordResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{51} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{54} } func (x *ModulePlaceRecordResponse) GetNodeId() string { @@ -4206,7 +4452,7 @@ type CheckWorkContextRecordAccessRequest struct { func (x *CheckWorkContextRecordAccessRequest) Reset() { *x = CheckWorkContextRecordAccessRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[52] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4218,7 +4464,7 @@ func (x *CheckWorkContextRecordAccessRequest) String() string { func (*CheckWorkContextRecordAccessRequest) ProtoMessage() {} func (x *CheckWorkContextRecordAccessRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[52] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4231,7 +4477,7 @@ func (x *CheckWorkContextRecordAccessRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use CheckWorkContextRecordAccessRequest.ProtoReflect.Descriptor instead. func (*CheckWorkContextRecordAccessRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{52} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{55} } func (x *CheckWorkContextRecordAccessRequest) GetResourceType() string { @@ -4267,7 +4513,7 @@ type CheckWorkContextRecordAccessResponse struct { func (x *CheckWorkContextRecordAccessResponse) Reset() { *x = CheckWorkContextRecordAccessResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[53] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4279,7 +4525,7 @@ func (x *CheckWorkContextRecordAccessResponse) String() string { func (*CheckWorkContextRecordAccessResponse) ProtoMessage() {} func (x *CheckWorkContextRecordAccessResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[53] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4292,7 +4538,7 @@ func (x *CheckWorkContextRecordAccessResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use CheckWorkContextRecordAccessResponse.ProtoReflect.Descriptor instead. func (*CheckWorkContextRecordAccessResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{53} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{56} } func (x *CheckWorkContextRecordAccessResponse) GetAllowed() bool { @@ -4321,7 +4567,7 @@ type ModuleExchangeDelegatedReadAudienceRequest struct { func (x *ModuleExchangeDelegatedReadAudienceRequest) Reset() { *x = ModuleExchangeDelegatedReadAudienceRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[54] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4333,7 +4579,7 @@ func (x *ModuleExchangeDelegatedReadAudienceRequest) String() string { func (*ModuleExchangeDelegatedReadAudienceRequest) ProtoMessage() {} func (x *ModuleExchangeDelegatedReadAudienceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[54] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4346,7 +4592,7 @@ func (x *ModuleExchangeDelegatedReadAudienceRequest) ProtoReflect() protoreflect // Deprecated: Use ModuleExchangeDelegatedReadAudienceRequest.ProtoReflect.Descriptor instead. func (*ModuleExchangeDelegatedReadAudienceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{54} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{57} } func (x *ModuleExchangeDelegatedReadAudienceRequest) GetBindingId() string { @@ -4402,7 +4648,7 @@ type ModuleExchangeDelegatedOperationAudienceRequest struct { func (x *ModuleExchangeDelegatedOperationAudienceRequest) Reset() { *x = ModuleExchangeDelegatedOperationAudienceRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[55] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4414,7 +4660,7 @@ func (x *ModuleExchangeDelegatedOperationAudienceRequest) String() string { func (*ModuleExchangeDelegatedOperationAudienceRequest) ProtoMessage() {} func (x *ModuleExchangeDelegatedOperationAudienceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[55] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4427,7 +4673,7 @@ func (x *ModuleExchangeDelegatedOperationAudienceRequest) ProtoReflect() protore // Deprecated: Use ModuleExchangeDelegatedOperationAudienceRequest.ProtoReflect.Descriptor instead. func (*ModuleExchangeDelegatedOperationAudienceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{55} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{58} } func (x *ModuleExchangeDelegatedOperationAudienceRequest) GetBindingId() string { @@ -4470,7 +4716,7 @@ type ExecutableArtifactContract struct { func (x *ExecutableArtifactContract) Reset() { *x = ExecutableArtifactContract{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[56] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4482,7 +4728,7 @@ func (x *ExecutableArtifactContract) String() string { func (*ExecutableArtifactContract) ProtoMessage() {} func (x *ExecutableArtifactContract) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[56] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4495,7 +4741,7 @@ func (x *ExecutableArtifactContract) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecutableArtifactContract.ProtoReflect.Descriptor instead. func (*ExecutableArtifactContract) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{56} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{59} } func (x *ExecutableArtifactContract) GetKind() string { @@ -4534,7 +4780,7 @@ type ExecutableArtifactIdentity struct { func (x *ExecutableArtifactIdentity) Reset() { *x = ExecutableArtifactIdentity{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[57] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4546,7 +4792,7 @@ func (x *ExecutableArtifactIdentity) String() string { func (*ExecutableArtifactIdentity) ProtoMessage() {} func (x *ExecutableArtifactIdentity) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[57] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4559,7 +4805,7 @@ func (x *ExecutableArtifactIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecutableArtifactIdentity.ProtoReflect.Descriptor instead. func (*ExecutableArtifactIdentity) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{57} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{60} } func (x *ExecutableArtifactIdentity) GetSchema() string { @@ -4609,7 +4855,7 @@ type ModuleExecutableArtifactRequest struct { func (x *ModuleExecutableArtifactRequest) Reset() { *x = ModuleExecutableArtifactRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[58] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4621,7 +4867,7 @@ func (x *ModuleExecutableArtifactRequest) String() string { func (*ModuleExecutableArtifactRequest) ProtoMessage() {} func (x *ModuleExecutableArtifactRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[58] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4634,7 +4880,7 @@ func (x *ModuleExecutableArtifactRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleExecutableArtifactRequest.ProtoReflect.Descriptor instead. func (*ModuleExecutableArtifactRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{58} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{61} } func (x *ModuleExecutableArtifactRequest) GetParentWorkContextToken() string { @@ -4676,7 +4922,7 @@ type ModuleRevokeExecutableArtifactRequest struct { func (x *ModuleRevokeExecutableArtifactRequest) Reset() { *x = ModuleRevokeExecutableArtifactRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[59] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4688,7 +4934,7 @@ func (x *ModuleRevokeExecutableArtifactRequest) String() string { func (*ModuleRevokeExecutableArtifactRequest) ProtoMessage() {} func (x *ModuleRevokeExecutableArtifactRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[59] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4701,7 +4947,7 @@ func (x *ModuleRevokeExecutableArtifactRequest) ProtoReflect() protoreflect.Mess // Deprecated: Use ModuleRevokeExecutableArtifactRequest.ProtoReflect.Descriptor instead. func (*ModuleRevokeExecutableArtifactRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{59} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{62} } func (x *ModuleRevokeExecutableArtifactRequest) GetParentWorkContextToken() string { @@ -4737,7 +4983,7 @@ type ModuleExecutableArtifactResponse struct { func (x *ModuleExecutableArtifactResponse) Reset() { *x = ModuleExecutableArtifactResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[60] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4749,7 +4995,7 @@ func (x *ModuleExecutableArtifactResponse) String() string { func (*ModuleExecutableArtifactResponse) ProtoMessage() {} func (x *ModuleExecutableArtifactResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[60] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4762,7 +5008,7 @@ func (x *ModuleExecutableArtifactResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleExecutableArtifactResponse.ProtoReflect.Descriptor instead. func (*ModuleExecutableArtifactResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{60} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{63} } func (x *ModuleExecutableArtifactResponse) GetSchemaVersion() string { @@ -4805,7 +5051,7 @@ type ModuleCurrentInstallationRequest struct { func (x *ModuleCurrentInstallationRequest) Reset() { *x = ModuleCurrentInstallationRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[61] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4817,7 +5063,7 @@ func (x *ModuleCurrentInstallationRequest) String() string { func (*ModuleCurrentInstallationRequest) ProtoMessage() {} func (x *ModuleCurrentInstallationRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[61] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4830,7 +5076,7 @@ func (x *ModuleCurrentInstallationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ModuleCurrentInstallationRequest.ProtoReflect.Descriptor instead. func (*ModuleCurrentInstallationRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{61} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{64} } func (x *ModuleCurrentInstallationRequest) GetParentWorkContextToken() string { @@ -4863,7 +5109,7 @@ type ModuleCurrentInstallationResponse struct { func (x *ModuleCurrentInstallationResponse) Reset() { *x = ModuleCurrentInstallationResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[62] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4875,7 +5121,7 @@ func (x *ModuleCurrentInstallationResponse) String() string { func (*ModuleCurrentInstallationResponse) ProtoMessage() {} func (x *ModuleCurrentInstallationResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[62] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4888,7 +5134,7 @@ func (x *ModuleCurrentInstallationResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ModuleCurrentInstallationResponse.ProtoReflect.Descriptor instead. func (*ModuleCurrentInstallationResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{62} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{65} } func (x *ModuleCurrentInstallationResponse) GetInstallationId() string { @@ -4937,7 +5183,7 @@ type WorkContextSealQuestion struct { func (x *WorkContextSealQuestion) Reset() { *x = WorkContextSealQuestion{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[63] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4949,7 +5195,7 @@ func (x *WorkContextSealQuestion) String() string { func (*WorkContextSealQuestion) ProtoMessage() {} func (x *WorkContextSealQuestion) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[63] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4962,7 +5208,7 @@ func (x *WorkContextSealQuestion) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextSealQuestion.ProtoReflect.Descriptor instead. func (*WorkContextSealQuestion) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{63} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{66} } func (x *WorkContextSealQuestion) GetPrincipalId() string { @@ -4993,7 +5239,7 @@ type WorkContextPrincipalEpochQuestion struct { func (x *WorkContextPrincipalEpochQuestion) Reset() { *x = WorkContextPrincipalEpochQuestion{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[64] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5005,7 +5251,7 @@ func (x *WorkContextPrincipalEpochQuestion) String() string { func (*WorkContextPrincipalEpochQuestion) ProtoMessage() {} func (x *WorkContextPrincipalEpochQuestion) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[64] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5018,7 +5264,7 @@ func (x *WorkContextPrincipalEpochQuestion) ProtoReflect() protoreflect.Message // Deprecated: Use WorkContextPrincipalEpochQuestion.ProtoReflect.Descriptor instead. func (*WorkContextPrincipalEpochQuestion) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{64} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{67} } func (x *WorkContextPrincipalEpochQuestion) GetPrincipalId() string { @@ -5042,7 +5288,7 @@ type WorkContextApprovedBuildQuestion struct { func (x *WorkContextApprovedBuildQuestion) Reset() { *x = WorkContextApprovedBuildQuestion{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[65] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5054,7 +5300,7 @@ func (x *WorkContextApprovedBuildQuestion) String() string { func (*WorkContextApprovedBuildQuestion) ProtoMessage() {} func (x *WorkContextApprovedBuildQuestion) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[65] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5067,7 +5313,7 @@ func (x *WorkContextApprovedBuildQuestion) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextApprovedBuildQuestion.ProtoReflect.Descriptor instead. func (*WorkContextApprovedBuildQuestion) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{65} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{68} } func (x *WorkContextApprovedBuildQuestion) GetPrincipalId() string { @@ -5093,7 +5339,7 @@ type WorkContextOperationBindingQuestion struct { func (x *WorkContextOperationBindingQuestion) Reset() { *x = WorkContextOperationBindingQuestion{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[66] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5105,7 +5351,7 @@ func (x *WorkContextOperationBindingQuestion) String() string { func (*WorkContextOperationBindingQuestion) ProtoMessage() {} func (x *WorkContextOperationBindingQuestion) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[66] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5118,7 +5364,7 @@ func (x *WorkContextOperationBindingQuestion) ProtoReflect() protoreflect.Messag // Deprecated: Use WorkContextOperationBindingQuestion.ProtoReflect.Descriptor instead. func (*WorkContextOperationBindingQuestion) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{66} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{69} } func (x *WorkContextOperationBindingQuestion) GetBindingId() string { @@ -5146,7 +5392,7 @@ type WorkContextAuthorizationRevisionQuestion struct { func (x *WorkContextAuthorizationRevisionQuestion) Reset() { *x = WorkContextAuthorizationRevisionQuestion{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[67] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5158,7 +5404,7 @@ func (x *WorkContextAuthorizationRevisionQuestion) String() string { func (*WorkContextAuthorizationRevisionQuestion) ProtoMessage() {} func (x *WorkContextAuthorizationRevisionQuestion) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[67] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5171,7 +5417,7 @@ func (x *WorkContextAuthorizationRevisionQuestion) ProtoReflect() protoreflect.M // Deprecated: Use WorkContextAuthorizationRevisionQuestion.ProtoReflect.Descriptor instead. func (*WorkContextAuthorizationRevisionQuestion) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{67} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{70} } func (x *WorkContextAuthorizationRevisionQuestion) GetOwnerPrincipalId() string { @@ -5193,7 +5439,7 @@ type WorkContextApprovalGrantQuestion struct { func (x *WorkContextApprovalGrantQuestion) Reset() { *x = WorkContextApprovalGrantQuestion{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[68] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5205,7 +5451,7 @@ func (x *WorkContextApprovalGrantQuestion) String() string { func (*WorkContextApprovalGrantQuestion) ProtoMessage() {} func (x *WorkContextApprovalGrantQuestion) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[68] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5218,7 +5464,7 @@ func (x *WorkContextApprovalGrantQuestion) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextApprovalGrantQuestion.ProtoReflect.Descriptor instead. func (*WorkContextApprovalGrantQuestion) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{68} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{71} } func (x *WorkContextApprovalGrantQuestion) GetGrantId() string { @@ -5241,7 +5487,7 @@ type WorkContextSealAnswer struct { func (x *WorkContextSealAnswer) Reset() { *x = WorkContextSealAnswer{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[69] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5253,7 +5499,7 @@ func (x *WorkContextSealAnswer) String() string { func (*WorkContextSealAnswer) ProtoMessage() {} func (x *WorkContextSealAnswer) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[69] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5266,7 +5512,7 @@ func (x *WorkContextSealAnswer) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextSealAnswer.ProtoReflect.Descriptor instead. func (*WorkContextSealAnswer) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{69} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{72} } func (x *WorkContextSealAnswer) GetState() WorkContextSealState { @@ -5301,7 +5547,7 @@ type WorkContextPrincipalEpochAnswer struct { func (x *WorkContextPrincipalEpochAnswer) Reset() { *x = WorkContextPrincipalEpochAnswer{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[70] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5313,7 +5559,7 @@ func (x *WorkContextPrincipalEpochAnswer) String() string { func (*WorkContextPrincipalEpochAnswer) ProtoMessage() {} func (x *WorkContextPrincipalEpochAnswer) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[70] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5326,7 +5572,7 @@ func (x *WorkContextPrincipalEpochAnswer) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextPrincipalEpochAnswer.ProtoReflect.Descriptor instead. func (*WorkContextPrincipalEpochAnswer) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{70} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{73} } func (x *WorkContextPrincipalEpochAnswer) GetState() WorkContextPrincipalState { @@ -5364,7 +5610,7 @@ type WorkContextApprovedBuildAnswer struct { func (x *WorkContextApprovedBuildAnswer) Reset() { *x = WorkContextApprovedBuildAnswer{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[71] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5376,7 +5622,7 @@ func (x *WorkContextApprovedBuildAnswer) String() string { func (*WorkContextApprovedBuildAnswer) ProtoMessage() {} func (x *WorkContextApprovedBuildAnswer) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[71] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5389,7 +5635,7 @@ func (x *WorkContextApprovedBuildAnswer) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextApprovedBuildAnswer.ProtoReflect.Descriptor instead. func (*WorkContextApprovedBuildAnswer) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{71} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{74} } func (x *WorkContextApprovedBuildAnswer) GetState() WorkContextApprovedBuildState { @@ -5440,7 +5686,7 @@ type WorkContextOperationBindingAnswer struct { func (x *WorkContextOperationBindingAnswer) Reset() { *x = WorkContextOperationBindingAnswer{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[72] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5452,7 +5698,7 @@ func (x *WorkContextOperationBindingAnswer) String() string { func (*WorkContextOperationBindingAnswer) ProtoMessage() {} func (x *WorkContextOperationBindingAnswer) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[72] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5465,7 +5711,7 @@ func (x *WorkContextOperationBindingAnswer) ProtoReflect() protoreflect.Message // Deprecated: Use WorkContextOperationBindingAnswer.ProtoReflect.Descriptor instead. func (*WorkContextOperationBindingAnswer) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{72} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{75} } func (x *WorkContextOperationBindingAnswer) GetState() WorkContextOperationBindingState { @@ -5527,7 +5773,7 @@ type WorkContextAuthorizationRevisionAnswer struct { func (x *WorkContextAuthorizationRevisionAnswer) Reset() { *x = WorkContextAuthorizationRevisionAnswer{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[73] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5539,7 +5785,7 @@ func (x *WorkContextAuthorizationRevisionAnswer) String() string { func (*WorkContextAuthorizationRevisionAnswer) ProtoMessage() {} func (x *WorkContextAuthorizationRevisionAnswer) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[73] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5552,7 +5798,7 @@ func (x *WorkContextAuthorizationRevisionAnswer) ProtoReflect() protoreflect.Mes // Deprecated: Use WorkContextAuthorizationRevisionAnswer.ProtoReflect.Descriptor instead. func (*WorkContextAuthorizationRevisionAnswer) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{73} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{76} } func (x *WorkContextAuthorizationRevisionAnswer) GetOwnerPrincipalId() string { @@ -5579,7 +5825,7 @@ type WorkContextApprovalGrantAnswer struct { func (x *WorkContextApprovalGrantAnswer) Reset() { *x = WorkContextApprovalGrantAnswer{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[74] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5591,7 +5837,7 @@ func (x *WorkContextApprovalGrantAnswer) String() string { func (*WorkContextApprovalGrantAnswer) ProtoMessage() {} func (x *WorkContextApprovalGrantAnswer) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[74] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5604,7 +5850,7 @@ func (x *WorkContextApprovalGrantAnswer) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkContextApprovalGrantAnswer.ProtoReflect.Descriptor instead. func (*WorkContextApprovalGrantAnswer) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{74} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{77} } func (x *WorkContextApprovalGrantAnswer) GetState() WorkContextApprovalGrantState { @@ -5647,7 +5893,7 @@ type ModuleResolveWorkContextAuthorityRequest struct { func (x *ModuleResolveWorkContextAuthorityRequest) Reset() { *x = ModuleResolveWorkContextAuthorityRequest{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[75] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5659,7 +5905,7 @@ func (x *ModuleResolveWorkContextAuthorityRequest) String() string { func (*ModuleResolveWorkContextAuthorityRequest) ProtoMessage() {} func (x *ModuleResolveWorkContextAuthorityRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[75] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5672,7 +5918,7 @@ func (x *ModuleResolveWorkContextAuthorityRequest) ProtoReflect() protoreflect.M // Deprecated: Use ModuleResolveWorkContextAuthorityRequest.ProtoReflect.Descriptor instead. func (*ModuleResolveWorkContextAuthorityRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{75} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{78} } func (x *ModuleResolveWorkContextAuthorityRequest) GetWorkContext() string { @@ -5810,7 +6056,7 @@ type ModuleResolveWorkContextAuthorityResponse struct { func (x *ModuleResolveWorkContextAuthorityResponse) Reset() { *x = ModuleResolveWorkContextAuthorityResponse{} - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[76] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5822,7 +6068,7 @@ func (x *ModuleResolveWorkContextAuthorityResponse) String() string { func (*ModuleResolveWorkContextAuthorityResponse) ProtoMessage() {} func (x *ModuleResolveWorkContextAuthorityResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[76] + mi := &file_saas_accounts_v1_module_capabilities_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5835,7 +6081,7 @@ func (x *ModuleResolveWorkContextAuthorityResponse) ProtoReflect() protoreflect. // Deprecated: Use ModuleResolveWorkContextAuthorityResponse.ProtoReflect.Descriptor instead. func (*ModuleResolveWorkContextAuthorityResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{76} + return file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP(), []int{79} } func (x *ModuleResolveWorkContextAuthorityResponse) GetAnswer() isModuleResolveWorkContextAuthorityResponse_Answer { @@ -6070,7 +6316,25 @@ const file_saas_accounts_v1_module_capabilities_proto_rawDesc = "" + "\xbaH\ar\x05\x10\x01\x18\x80\x01R\bsolution\x12#\n" + "\bentry_id\x18\x05 \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\aentryId\x12/\n" + "\x06fields\x18\x06 \x01(\v2\x17.google.protobuf.StructR\x06fields\x121\n" + - "\x0fidempotency_key\x18\a \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\x0eidempotencyKey\"\xb8\x01\n" + + "\x0fidempotency_key\x18\a \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\x0eidempotencyKey\"\xc3\x01\n" + + "\x18ModuleReadJournalRequest\x12\x1d\n" + + "\x05after\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x18@R\x05after\x12\x1d\n" + + "\x05limit\x18\x02 \x01(\x05B\a\xbaH\x04\x1a\x02(\x00R\x05limit\x12)\n" + + "\x05types\x18\x03 \x03(\tB\x13\xbaH\x10\x92\x01\r\b\x01\x10\x10\"\ar\x05\x10\x01\x18\x80\x02R\x05types\x12>\n" + + "\x11delivery_bindings\x18\x04 \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10@\"\ar\x05\x10\x01\x18\x80\x02R\x10deliveryBindings\"\xa1\x02\n" + + "\x12ModuleJournalEntry\x12\x1a\n" + + "\bposition\x18\x01 \x01(\tR\bposition\x12\x19\n" + + "\bevent_id\x18\x02 \x01(\tR\aeventId\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12'\n" + + "\x0forganization_id\x18\x04 \x01(\tR\x0eorganizationId\x12;\n" + + "\voccurred_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "occurredAt\x12+\n" + + "\x04data\x18\x06 \x01(\v2\x17.google.protobuf.StructR\x04data\x12-\n" + + "\x12delivery_machinery\x18\a \x01(\bR\x11deliveryMachinery\"z\n" + + "\x19ModuleReadJournalResponse\x12>\n" + + "\aentries\x18\x01 \x03(\v2$.saas.accounts.v1.ModuleJournalEntryR\aentries\x12\x1d\n" + + "\n" + + "next_after\x18\x02 \x01(\tR\tnextAfter\"\xb8\x01\n" + "\x1bModuleAuditFieldDeclaration\x12\x1d\n" + "\x04name\x18\x01 \x01(\tB\t\xbaH\x06r\x04\x10\x01\x18@R\x04name\x12F\n" + "\x04kind\x18\x02 \x01(\x0e2&.saas.accounts.v1.ModuleAuditFieldKindB\n" + @@ -6405,7 +6669,7 @@ const file_saas_accounts_v1_module_capabilities_proto_rawDesc = "" + "+WORK_CONTEXT_OPERATION_BINDING_STATE_ABSENT\x10\x02*\x86\x01\n" + "\x1dWorkContextApprovalGrantState\x121\n" + "-WORK_CONTEXT_APPROVAL_GRANT_STATE_UNSPECIFIED\x10\x00\x122\n" + - ".WORK_CONTEXT_APPROVAL_GRANT_STATE_NOT_RECORDED\x10\x012\xe0'\n" + + ".WORK_CONTEXT_APPROVAL_GRANT_STATE_NOT_RECORDED\x10\x012\xe3(\n" + "\x19ModuleCapabilitiesService\x12\x9b\x01\n" + "\x16GetCurrentInstallation\x122.saas.accounts.v1.ModuleCurrentInstallationRequest\x1a3.saas.accounts.v1.ModuleCurrentInstallationResponse\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x04X\x03`\x01\x12\xc6\x01\n" + "\x19ApproveExecutableArtifact\x121.saas.accounts.v1.ModuleExecutableArtifactRequest\x1a2.saas.accounts.v1.ModuleExecutableArtifactResponse\"B\xc2\xf3\x18>\b\x03\x10\x010\x01:,\n" + @@ -6433,7 +6697,8 @@ const file_saas_accounts_v1_module_capabilities_proto_rawDesc = "" + "\x0fRequestApproval\x12..saas.accounts.v1.ModuleRequestApprovalRequest\x1a/.saas.accounts.v1.ModuleRequestApprovalResponse\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12u\n" + "\vGetApproval\x12*.saas.accounts.v1.ModuleGetApprovalRequest\x1a .saas.accounts.v1.ModuleApproval\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12q\n" + "\x0eCancelApproval\x12-.saas.accounts.v1.ModuleCancelApprovalRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12q\n" + - "\x0eEmitAuditEvent\x12-.saas.accounts.v1.ModuleEmitAuditEventRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\xc3\x01\n" + + "\x0eEmitAuditEvent\x12-.saas.accounts.v1.ModuleEmitAuditEventRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\x80\x01\n" + + "\vReadJournal\x12*.saas.accounts.v1.ModuleReadJournalRequest\x1a+.saas.accounts.v1.ModuleReadJournalResponse\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\xc3\x01\n" + "\x16DeclareAuditEventTypes\x125.saas.accounts.v1.ModuleDeclareAuditEventTypesRequest\x1a6.saas.accounts.v1.ModuleDeclareAuditEventTypesResponse\":\xc2\xf3\x186\b\x03\x10\x010\x01:$\n" + " saas.module.audit_types_declared\x10\x02@\x01H\aP\x03X\x03`\x01\x12\x9e\x01\n" + "\x15ListSubjectVisibility\x124.saas.accounts.v1.ModuleListSubjectVisibilityRequest\x1a5.saas.accounts.v1.ModuleListSubjectVisibilityResponse\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\x8e\x01\n" + @@ -6466,7 +6731,7 @@ func file_saas_accounts_v1_module_capabilities_proto_rawDescGZIP() []byte { } var file_saas_accounts_v1_module_capabilities_proto_enumTypes = make([]protoimpl.EnumInfo, 11) -var file_saas_accounts_v1_module_capabilities_proto_msgTypes = make([]protoimpl.MessageInfo, 77) +var file_saas_accounts_v1_module_capabilities_proto_msgTypes = make([]protoimpl.MessageInfo, 80) var file_saas_accounts_v1_module_capabilities_proto_goTypes = []any{ (ModuleAuditFieldKind)(0), // 0: saas.accounts.v1.ModuleAuditFieldKind (ModuleAuditEventVisibility)(0), // 1: saas.accounts.v1.ModuleAuditEventVisibility @@ -6499,223 +6764,231 @@ var file_saas_accounts_v1_module_capabilities_proto_goTypes = []any{ (*ModuleApproval)(nil), // 28: saas.accounts.v1.ModuleApproval (*ModuleCancelApprovalRequest)(nil), // 29: saas.accounts.v1.ModuleCancelApprovalRequest (*ModuleEmitAuditEventRequest)(nil), // 30: saas.accounts.v1.ModuleEmitAuditEventRequest - (*ModuleAuditFieldDeclaration)(nil), // 31: saas.accounts.v1.ModuleAuditFieldDeclaration - (*ModuleAuditEventTypeDeclaration)(nil), // 32: saas.accounts.v1.ModuleAuditEventTypeDeclaration - (*ModuleDeclareAuditEventTypesRequest)(nil), // 33: saas.accounts.v1.ModuleDeclareAuditEventTypesRequest - (*ModuleDeclareAuditEventTypesResponse)(nil), // 34: saas.accounts.v1.ModuleDeclareAuditEventTypesResponse - (*SubjectVisibilityGrant)(nil), // 35: saas.accounts.v1.SubjectVisibilityGrant - (*ModuleListSubjectVisibilityRequest)(nil), // 36: saas.accounts.v1.ModuleListSubjectVisibilityRequest - (*ModuleListSubjectVisibilityResponse)(nil), // 37: saas.accounts.v1.ModuleListSubjectVisibilityResponse - (*FetchDatasourceBlobRequest)(nil), // 38: saas.accounts.v1.FetchDatasourceBlobRequest - (*FetchDatasourceBlobChunk)(nil), // 39: saas.accounts.v1.FetchDatasourceBlobChunk - (*DatasourceFileRef)(nil), // 40: saas.accounts.v1.DatasourceFileRef - (*FetchDatasourceFilesRequest)(nil), // 41: saas.accounts.v1.FetchDatasourceFilesRequest - (*DatasourceProvenance)(nil), // 42: saas.accounts.v1.DatasourceProvenance - (*DatasourceItemReaders)(nil), // 43: saas.accounts.v1.DatasourceItemReaders - (*DatasourceFileHeader)(nil), // 44: saas.accounts.v1.DatasourceFileHeader - (*FetchDatasourceFilesFrame)(nil), // 45: saas.accounts.v1.FetchDatasourceFilesFrame - (*ModulePublishEventRequest)(nil), // 46: saas.accounts.v1.ModulePublishEventRequest - (*ModulePublishEventResponse)(nil), // 47: saas.accounts.v1.ModulePublishEventResponse - (*ModuleSubscribeRequest)(nil), // 48: saas.accounts.v1.ModuleSubscribeRequest - (*ModuleSubscription)(nil), // 49: saas.accounts.v1.ModuleSubscription - (*ModuleSubscribeResponse)(nil), // 50: saas.accounts.v1.ModuleSubscribeResponse - (*ModuleUnsubscribeRequest)(nil), // 51: saas.accounts.v1.ModuleUnsubscribeRequest - (*ModuleListSubscriptionsRequest)(nil), // 52: saas.accounts.v1.ModuleListSubscriptionsRequest - (*ModuleListSubscriptionsResponse)(nil), // 53: saas.accounts.v1.ModuleListSubscriptionsResponse - (*ModuleReplayEventsRequest)(nil), // 54: saas.accounts.v1.ModuleReplayEventsRequest - (*ModuleReplayEventsResponse)(nil), // 55: saas.accounts.v1.ModuleReplayEventsResponse - (*ReadableSourceCollection)(nil), // 56: saas.accounts.v1.ReadableSourceCollection - (*ReadableCollectionGrant)(nil), // 57: saas.accounts.v1.ReadableCollectionGrant - (*CollectionSyncProvenance)(nil), // 58: saas.accounts.v1.CollectionSyncProvenance - (*ListReadableSourceCollectionsRequest)(nil), // 59: saas.accounts.v1.ListReadableSourceCollectionsRequest - (*ListReadableSourceCollectionsResponse)(nil), // 60: saas.accounts.v1.ListReadableSourceCollectionsResponse - (*ModulePlaceRecordRequest)(nil), // 61: saas.accounts.v1.ModulePlaceRecordRequest - (*ModulePlaceRecordResponse)(nil), // 62: saas.accounts.v1.ModulePlaceRecordResponse - (*CheckWorkContextRecordAccessRequest)(nil), // 63: saas.accounts.v1.CheckWorkContextRecordAccessRequest - (*CheckWorkContextRecordAccessResponse)(nil), // 64: saas.accounts.v1.CheckWorkContextRecordAccessResponse - (*ModuleExchangeDelegatedReadAudienceRequest)(nil), // 65: saas.accounts.v1.ModuleExchangeDelegatedReadAudienceRequest - (*ModuleExchangeDelegatedOperationAudienceRequest)(nil), // 66: saas.accounts.v1.ModuleExchangeDelegatedOperationAudienceRequest - (*ExecutableArtifactContract)(nil), // 67: saas.accounts.v1.ExecutableArtifactContract - (*ExecutableArtifactIdentity)(nil), // 68: saas.accounts.v1.ExecutableArtifactIdentity - (*ModuleExecutableArtifactRequest)(nil), // 69: saas.accounts.v1.ModuleExecutableArtifactRequest - (*ModuleRevokeExecutableArtifactRequest)(nil), // 70: saas.accounts.v1.ModuleRevokeExecutableArtifactRequest - (*ModuleExecutableArtifactResponse)(nil), // 71: saas.accounts.v1.ModuleExecutableArtifactResponse - (*ModuleCurrentInstallationRequest)(nil), // 72: saas.accounts.v1.ModuleCurrentInstallationRequest - (*ModuleCurrentInstallationResponse)(nil), // 73: saas.accounts.v1.ModuleCurrentInstallationResponse - (*WorkContextSealQuestion)(nil), // 74: saas.accounts.v1.WorkContextSealQuestion - (*WorkContextPrincipalEpochQuestion)(nil), // 75: saas.accounts.v1.WorkContextPrincipalEpochQuestion - (*WorkContextApprovedBuildQuestion)(nil), // 76: saas.accounts.v1.WorkContextApprovedBuildQuestion - (*WorkContextOperationBindingQuestion)(nil), // 77: saas.accounts.v1.WorkContextOperationBindingQuestion - (*WorkContextAuthorizationRevisionQuestion)(nil), // 78: saas.accounts.v1.WorkContextAuthorizationRevisionQuestion - (*WorkContextApprovalGrantQuestion)(nil), // 79: saas.accounts.v1.WorkContextApprovalGrantQuestion - (*WorkContextSealAnswer)(nil), // 80: saas.accounts.v1.WorkContextSealAnswer - (*WorkContextPrincipalEpochAnswer)(nil), // 81: saas.accounts.v1.WorkContextPrincipalEpochAnswer - (*WorkContextApprovedBuildAnswer)(nil), // 82: saas.accounts.v1.WorkContextApprovedBuildAnswer - (*WorkContextOperationBindingAnswer)(nil), // 83: saas.accounts.v1.WorkContextOperationBindingAnswer - (*WorkContextAuthorizationRevisionAnswer)(nil), // 84: saas.accounts.v1.WorkContextAuthorizationRevisionAnswer - (*WorkContextApprovalGrantAnswer)(nil), // 85: saas.accounts.v1.WorkContextApprovalGrantAnswer - (*ModuleResolveWorkContextAuthorityRequest)(nil), // 86: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest - (*ModuleResolveWorkContextAuthorityResponse)(nil), // 87: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse - (*v1.NewJob)(nil), // 88: saas.jobs.v1.NewJob - (v1.JobEnqueueDisposition)(0), // 89: saas.jobs.v1.JobEnqueueDisposition - (*durationpb.Duration)(nil), // 90: google.protobuf.Duration - (*v1.JobEnvelope)(nil), // 91: saas.jobs.v1.JobEnvelope - (*v1.JobLeaseReference)(nil), // 92: saas.jobs.v1.JobLeaseReference - (*v1.JobLease)(nil), // 93: saas.jobs.v1.JobLease - (*v1.JobFailure)(nil), // 94: saas.jobs.v1.JobFailure - (*timestamppb.Timestamp)(nil), // 95: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 96: google.protobuf.Struct - (*v11.EventEnvelope)(nil), // 97: saas.events.v1.EventEnvelope - (*ModuleMintWorkContextRequest)(nil), // 98: saas.accounts.v1.ModuleMintWorkContextRequest - (*ModuleMintOperationContextRequest)(nil), // 99: saas.accounts.v1.ModuleMintOperationContextRequest - (*ModuleMintSourceOperationContextRequest)(nil), // 100: saas.accounts.v1.ModuleMintSourceOperationContextRequest - (*IssuedWorkContext)(nil), // 101: saas.accounts.v1.IssuedWorkContext - (*emptypb.Empty)(nil), // 102: google.protobuf.Empty - (*ModuleMintWorkContextResponse)(nil), // 103: saas.accounts.v1.ModuleMintWorkContextResponse - (*ModuleMintOperationContextResponse)(nil), // 104: saas.accounts.v1.ModuleMintOperationContextResponse - (*ModuleMintSourceOperationContextResponse)(nil), // 105: saas.accounts.v1.ModuleMintSourceOperationContextResponse + (*ModuleReadJournalRequest)(nil), // 31: saas.accounts.v1.ModuleReadJournalRequest + (*ModuleJournalEntry)(nil), // 32: saas.accounts.v1.ModuleJournalEntry + (*ModuleReadJournalResponse)(nil), // 33: saas.accounts.v1.ModuleReadJournalResponse + (*ModuleAuditFieldDeclaration)(nil), // 34: saas.accounts.v1.ModuleAuditFieldDeclaration + (*ModuleAuditEventTypeDeclaration)(nil), // 35: saas.accounts.v1.ModuleAuditEventTypeDeclaration + (*ModuleDeclareAuditEventTypesRequest)(nil), // 36: saas.accounts.v1.ModuleDeclareAuditEventTypesRequest + (*ModuleDeclareAuditEventTypesResponse)(nil), // 37: saas.accounts.v1.ModuleDeclareAuditEventTypesResponse + (*SubjectVisibilityGrant)(nil), // 38: saas.accounts.v1.SubjectVisibilityGrant + (*ModuleListSubjectVisibilityRequest)(nil), // 39: saas.accounts.v1.ModuleListSubjectVisibilityRequest + (*ModuleListSubjectVisibilityResponse)(nil), // 40: saas.accounts.v1.ModuleListSubjectVisibilityResponse + (*FetchDatasourceBlobRequest)(nil), // 41: saas.accounts.v1.FetchDatasourceBlobRequest + (*FetchDatasourceBlobChunk)(nil), // 42: saas.accounts.v1.FetchDatasourceBlobChunk + (*DatasourceFileRef)(nil), // 43: saas.accounts.v1.DatasourceFileRef + (*FetchDatasourceFilesRequest)(nil), // 44: saas.accounts.v1.FetchDatasourceFilesRequest + (*DatasourceProvenance)(nil), // 45: saas.accounts.v1.DatasourceProvenance + (*DatasourceItemReaders)(nil), // 46: saas.accounts.v1.DatasourceItemReaders + (*DatasourceFileHeader)(nil), // 47: saas.accounts.v1.DatasourceFileHeader + (*FetchDatasourceFilesFrame)(nil), // 48: saas.accounts.v1.FetchDatasourceFilesFrame + (*ModulePublishEventRequest)(nil), // 49: saas.accounts.v1.ModulePublishEventRequest + (*ModulePublishEventResponse)(nil), // 50: saas.accounts.v1.ModulePublishEventResponse + (*ModuleSubscribeRequest)(nil), // 51: saas.accounts.v1.ModuleSubscribeRequest + (*ModuleSubscription)(nil), // 52: saas.accounts.v1.ModuleSubscription + (*ModuleSubscribeResponse)(nil), // 53: saas.accounts.v1.ModuleSubscribeResponse + (*ModuleUnsubscribeRequest)(nil), // 54: saas.accounts.v1.ModuleUnsubscribeRequest + (*ModuleListSubscriptionsRequest)(nil), // 55: saas.accounts.v1.ModuleListSubscriptionsRequest + (*ModuleListSubscriptionsResponse)(nil), // 56: saas.accounts.v1.ModuleListSubscriptionsResponse + (*ModuleReplayEventsRequest)(nil), // 57: saas.accounts.v1.ModuleReplayEventsRequest + (*ModuleReplayEventsResponse)(nil), // 58: saas.accounts.v1.ModuleReplayEventsResponse + (*ReadableSourceCollection)(nil), // 59: saas.accounts.v1.ReadableSourceCollection + (*ReadableCollectionGrant)(nil), // 60: saas.accounts.v1.ReadableCollectionGrant + (*CollectionSyncProvenance)(nil), // 61: saas.accounts.v1.CollectionSyncProvenance + (*ListReadableSourceCollectionsRequest)(nil), // 62: saas.accounts.v1.ListReadableSourceCollectionsRequest + (*ListReadableSourceCollectionsResponse)(nil), // 63: saas.accounts.v1.ListReadableSourceCollectionsResponse + (*ModulePlaceRecordRequest)(nil), // 64: saas.accounts.v1.ModulePlaceRecordRequest + (*ModulePlaceRecordResponse)(nil), // 65: saas.accounts.v1.ModulePlaceRecordResponse + (*CheckWorkContextRecordAccessRequest)(nil), // 66: saas.accounts.v1.CheckWorkContextRecordAccessRequest + (*CheckWorkContextRecordAccessResponse)(nil), // 67: saas.accounts.v1.CheckWorkContextRecordAccessResponse + (*ModuleExchangeDelegatedReadAudienceRequest)(nil), // 68: saas.accounts.v1.ModuleExchangeDelegatedReadAudienceRequest + (*ModuleExchangeDelegatedOperationAudienceRequest)(nil), // 69: saas.accounts.v1.ModuleExchangeDelegatedOperationAudienceRequest + (*ExecutableArtifactContract)(nil), // 70: saas.accounts.v1.ExecutableArtifactContract + (*ExecutableArtifactIdentity)(nil), // 71: saas.accounts.v1.ExecutableArtifactIdentity + (*ModuleExecutableArtifactRequest)(nil), // 72: saas.accounts.v1.ModuleExecutableArtifactRequest + (*ModuleRevokeExecutableArtifactRequest)(nil), // 73: saas.accounts.v1.ModuleRevokeExecutableArtifactRequest + (*ModuleExecutableArtifactResponse)(nil), // 74: saas.accounts.v1.ModuleExecutableArtifactResponse + (*ModuleCurrentInstallationRequest)(nil), // 75: saas.accounts.v1.ModuleCurrentInstallationRequest + (*ModuleCurrentInstallationResponse)(nil), // 76: saas.accounts.v1.ModuleCurrentInstallationResponse + (*WorkContextSealQuestion)(nil), // 77: saas.accounts.v1.WorkContextSealQuestion + (*WorkContextPrincipalEpochQuestion)(nil), // 78: saas.accounts.v1.WorkContextPrincipalEpochQuestion + (*WorkContextApprovedBuildQuestion)(nil), // 79: saas.accounts.v1.WorkContextApprovedBuildQuestion + (*WorkContextOperationBindingQuestion)(nil), // 80: saas.accounts.v1.WorkContextOperationBindingQuestion + (*WorkContextAuthorizationRevisionQuestion)(nil), // 81: saas.accounts.v1.WorkContextAuthorizationRevisionQuestion + (*WorkContextApprovalGrantQuestion)(nil), // 82: saas.accounts.v1.WorkContextApprovalGrantQuestion + (*WorkContextSealAnswer)(nil), // 83: saas.accounts.v1.WorkContextSealAnswer + (*WorkContextPrincipalEpochAnswer)(nil), // 84: saas.accounts.v1.WorkContextPrincipalEpochAnswer + (*WorkContextApprovedBuildAnswer)(nil), // 85: saas.accounts.v1.WorkContextApprovedBuildAnswer + (*WorkContextOperationBindingAnswer)(nil), // 86: saas.accounts.v1.WorkContextOperationBindingAnswer + (*WorkContextAuthorizationRevisionAnswer)(nil), // 87: saas.accounts.v1.WorkContextAuthorizationRevisionAnswer + (*WorkContextApprovalGrantAnswer)(nil), // 88: saas.accounts.v1.WorkContextApprovalGrantAnswer + (*ModuleResolveWorkContextAuthorityRequest)(nil), // 89: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest + (*ModuleResolveWorkContextAuthorityResponse)(nil), // 90: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse + (*v1.NewJob)(nil), // 91: saas.jobs.v1.NewJob + (v1.JobEnqueueDisposition)(0), // 92: saas.jobs.v1.JobEnqueueDisposition + (*durationpb.Duration)(nil), // 93: google.protobuf.Duration + (*v1.JobEnvelope)(nil), // 94: saas.jobs.v1.JobEnvelope + (*v1.JobLeaseReference)(nil), // 95: saas.jobs.v1.JobLeaseReference + (*v1.JobLease)(nil), // 96: saas.jobs.v1.JobLease + (*v1.JobFailure)(nil), // 97: saas.jobs.v1.JobFailure + (*timestamppb.Timestamp)(nil), // 98: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 99: google.protobuf.Struct + (*v11.EventEnvelope)(nil), // 100: saas.events.v1.EventEnvelope + (*ModuleMintWorkContextRequest)(nil), // 101: saas.accounts.v1.ModuleMintWorkContextRequest + (*ModuleMintOperationContextRequest)(nil), // 102: saas.accounts.v1.ModuleMintOperationContextRequest + (*ModuleMintSourceOperationContextRequest)(nil), // 103: saas.accounts.v1.ModuleMintSourceOperationContextRequest + (*IssuedWorkContext)(nil), // 104: saas.accounts.v1.IssuedWorkContext + (*emptypb.Empty)(nil), // 105: google.protobuf.Empty + (*ModuleMintWorkContextResponse)(nil), // 106: saas.accounts.v1.ModuleMintWorkContextResponse + (*ModuleMintOperationContextResponse)(nil), // 107: saas.accounts.v1.ModuleMintOperationContextResponse + (*ModuleMintSourceOperationContextResponse)(nil), // 108: saas.accounts.v1.ModuleMintSourceOperationContextResponse } var file_saas_accounts_v1_module_capabilities_proto_depIdxs = []int32{ - 88, // 0: saas.accounts.v1.ModuleEnqueueJobRequest.job:type_name -> saas.jobs.v1.NewJob - 89, // 1: saas.accounts.v1.ModuleEnqueueJobResponse.disposition:type_name -> saas.jobs.v1.JobEnqueueDisposition - 90, // 2: saas.accounts.v1.ModuleClaimJobsRequest.lease_duration:type_name -> google.protobuf.Duration - 91, // 3: saas.accounts.v1.ModuleClaimJobsResponse.jobs:type_name -> saas.jobs.v1.JobEnvelope - 92, // 4: saas.accounts.v1.ModuleHeartbeatJobRequest.lease:type_name -> saas.jobs.v1.JobLeaseReference - 90, // 5: saas.accounts.v1.ModuleHeartbeatJobRequest.extension:type_name -> google.protobuf.Duration - 93, // 6: saas.accounts.v1.ModuleHeartbeatJobResponse.lease:type_name -> saas.jobs.v1.JobLease - 92, // 7: saas.accounts.v1.ModuleAckJobRequest.lease:type_name -> saas.jobs.v1.JobLeaseReference - 92, // 8: saas.accounts.v1.ModuleNackJobRequest.lease:type_name -> saas.jobs.v1.JobLeaseReference - 94, // 9: saas.accounts.v1.ModuleNackJobRequest.failure:type_name -> saas.jobs.v1.JobFailure - 95, // 10: saas.accounts.v1.ModuleNackJobRequest.retry_at:type_name -> google.protobuf.Timestamp - 96, // 11: saas.accounts.v1.ModuleResumeRef.payload:type_name -> google.protobuf.Struct - 96, // 12: saas.accounts.v1.ModuleRequestApprovalRequest.subject:type_name -> google.protobuf.Struct + 91, // 0: saas.accounts.v1.ModuleEnqueueJobRequest.job:type_name -> saas.jobs.v1.NewJob + 92, // 1: saas.accounts.v1.ModuleEnqueueJobResponse.disposition:type_name -> saas.jobs.v1.JobEnqueueDisposition + 93, // 2: saas.accounts.v1.ModuleClaimJobsRequest.lease_duration:type_name -> google.protobuf.Duration + 94, // 3: saas.accounts.v1.ModuleClaimJobsResponse.jobs:type_name -> saas.jobs.v1.JobEnvelope + 95, // 4: saas.accounts.v1.ModuleHeartbeatJobRequest.lease:type_name -> saas.jobs.v1.JobLeaseReference + 93, // 5: saas.accounts.v1.ModuleHeartbeatJobRequest.extension:type_name -> google.protobuf.Duration + 96, // 6: saas.accounts.v1.ModuleHeartbeatJobResponse.lease:type_name -> saas.jobs.v1.JobLease + 95, // 7: saas.accounts.v1.ModuleAckJobRequest.lease:type_name -> saas.jobs.v1.JobLeaseReference + 95, // 8: saas.accounts.v1.ModuleNackJobRequest.lease:type_name -> saas.jobs.v1.JobLeaseReference + 97, // 9: saas.accounts.v1.ModuleNackJobRequest.failure:type_name -> saas.jobs.v1.JobFailure + 98, // 10: saas.accounts.v1.ModuleNackJobRequest.retry_at:type_name -> google.protobuf.Timestamp + 99, // 11: saas.accounts.v1.ModuleResumeRef.payload:type_name -> google.protobuf.Struct + 99, // 12: saas.accounts.v1.ModuleRequestApprovalRequest.subject:type_name -> google.protobuf.Struct 23, // 13: saas.accounts.v1.ModuleRequestApprovalRequest.policy:type_name -> saas.accounts.v1.ModuleApprovalPolicy 24, // 14: saas.accounts.v1.ModuleRequestApprovalRequest.resume_ref:type_name -> saas.accounts.v1.ModuleResumeRef - 95, // 15: saas.accounts.v1.ModuleRequestApprovalRequest.expires_at:type_name -> google.protobuf.Timestamp - 95, // 16: saas.accounts.v1.ModuleRequestApprovalRequest.escalate_at:type_name -> google.protobuf.Timestamp - 96, // 17: saas.accounts.v1.ModuleApproval.subject:type_name -> google.protobuf.Struct + 98, // 15: saas.accounts.v1.ModuleRequestApprovalRequest.expires_at:type_name -> google.protobuf.Timestamp + 98, // 16: saas.accounts.v1.ModuleRequestApprovalRequest.escalate_at:type_name -> google.protobuf.Timestamp + 99, // 17: saas.accounts.v1.ModuleApproval.subject:type_name -> google.protobuf.Struct 24, // 18: saas.accounts.v1.ModuleApproval.resume_ref:type_name -> saas.accounts.v1.ModuleResumeRef - 95, // 19: saas.accounts.v1.ModuleApproval.expires_at:type_name -> google.protobuf.Timestamp - 95, // 20: saas.accounts.v1.ModuleApproval.escalate_at:type_name -> google.protobuf.Timestamp - 95, // 21: saas.accounts.v1.ModuleApproval.created_at:type_name -> google.protobuf.Timestamp - 96, // 22: saas.accounts.v1.ModuleEmitAuditEventRequest.fields:type_name -> google.protobuf.Struct - 0, // 23: saas.accounts.v1.ModuleAuditFieldDeclaration.kind:type_name -> saas.accounts.v1.ModuleAuditFieldKind - 31, // 24: saas.accounts.v1.ModuleAuditEventTypeDeclaration.fields:type_name -> saas.accounts.v1.ModuleAuditFieldDeclaration - 1, // 25: saas.accounts.v1.ModuleAuditEventTypeDeclaration.visibility:type_name -> saas.accounts.v1.ModuleAuditEventVisibility - 32, // 26: saas.accounts.v1.ModuleDeclareAuditEventTypesRequest.types:type_name -> saas.accounts.v1.ModuleAuditEventTypeDeclaration - 95, // 27: saas.accounts.v1.SubjectVisibilityGrant.expires_at:type_name -> google.protobuf.Timestamp - 35, // 28: saas.accounts.v1.ModuleListSubjectVisibilityResponse.grants:type_name -> saas.accounts.v1.SubjectVisibilityGrant - 40, // 29: saas.accounts.v1.FetchDatasourceFilesRequest.files:type_name -> saas.accounts.v1.DatasourceFileRef - 2, // 30: saas.accounts.v1.DatasourceItemReaders.basis:type_name -> saas.accounts.v1.DatasourceItemReadersBasis - 42, // 31: saas.accounts.v1.DatasourceFileHeader.provenance:type_name -> saas.accounts.v1.DatasourceProvenance - 43, // 32: saas.accounts.v1.DatasourceFileHeader.readers:type_name -> saas.accounts.v1.DatasourceItemReaders - 44, // 33: saas.accounts.v1.FetchDatasourceFilesFrame.header:type_name -> saas.accounts.v1.DatasourceFileHeader - 97, // 34: saas.accounts.v1.ModulePublishEventRequest.envelope:type_name -> saas.events.v1.EventEnvelope - 3, // 35: saas.accounts.v1.ModuleSubscribeRequest.delivery:type_name -> saas.accounts.v1.EventDelivery - 3, // 36: saas.accounts.v1.ModuleSubscription.delivery:type_name -> saas.accounts.v1.EventDelivery - 95, // 37: saas.accounts.v1.ModuleSubscription.created_at:type_name -> google.protobuf.Timestamp - 49, // 38: saas.accounts.v1.ModuleSubscribeResponse.subscription:type_name -> saas.accounts.v1.ModuleSubscription - 49, // 39: saas.accounts.v1.ModuleListSubscriptionsResponse.subscriptions:type_name -> saas.accounts.v1.ModuleSubscription - 95, // 40: saas.accounts.v1.ModuleReplayEventsRequest.since:type_name -> google.protobuf.Timestamp - 4, // 41: saas.accounts.v1.ReadableSourceCollection.grant_disclosure:type_name -> saas.accounts.v1.MetadataDisclosure - 57, // 42: saas.accounts.v1.ReadableSourceCollection.read_grants:type_name -> saas.accounts.v1.ReadableCollectionGrant - 58, // 43: saas.accounts.v1.ReadableSourceCollection.sync:type_name -> saas.accounts.v1.CollectionSyncProvenance - 95, // 44: saas.accounts.v1.ReadableCollectionGrant.expires_at:type_name -> google.protobuf.Timestamp - 5, // 45: saas.accounts.v1.CollectionSyncProvenance.stage:type_name -> saas.accounts.v1.SourceSyncStage - 95, // 46: saas.accounts.v1.CollectionSyncProvenance.at:type_name -> google.protobuf.Timestamp - 4, // 47: saas.accounts.v1.CollectionSyncProvenance.requester_disclosure:type_name -> saas.accounts.v1.MetadataDisclosure - 95, // 48: saas.accounts.v1.CollectionSyncProvenance.requested_at:type_name -> google.protobuf.Timestamp - 56, // 49: saas.accounts.v1.ListReadableSourceCollectionsResponse.collections:type_name -> saas.accounts.v1.ReadableSourceCollection - 67, // 50: saas.accounts.v1.ExecutableArtifactIdentity.contracts:type_name -> saas.accounts.v1.ExecutableArtifactContract - 68, // 51: saas.accounts.v1.ModuleExecutableArtifactRequest.identity:type_name -> saas.accounts.v1.ExecutableArtifactIdentity - 6, // 52: saas.accounts.v1.WorkContextSealAnswer.state:type_name -> saas.accounts.v1.WorkContextSealState - 7, // 53: saas.accounts.v1.WorkContextPrincipalEpochAnswer.state:type_name -> saas.accounts.v1.WorkContextPrincipalState - 8, // 54: saas.accounts.v1.WorkContextApprovedBuildAnswer.state:type_name -> saas.accounts.v1.WorkContextApprovedBuildState - 9, // 55: saas.accounts.v1.WorkContextOperationBindingAnswer.state:type_name -> saas.accounts.v1.WorkContextOperationBindingState - 10, // 56: saas.accounts.v1.WorkContextApprovalGrantAnswer.state:type_name -> saas.accounts.v1.WorkContextApprovalGrantState - 74, // 57: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.seal:type_name -> saas.accounts.v1.WorkContextSealQuestion - 75, // 58: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.principal_epoch:type_name -> saas.accounts.v1.WorkContextPrincipalEpochQuestion - 76, // 59: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.approved_build:type_name -> saas.accounts.v1.WorkContextApprovedBuildQuestion - 77, // 60: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.operation_binding:type_name -> saas.accounts.v1.WorkContextOperationBindingQuestion - 78, // 61: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.authorization_revision:type_name -> saas.accounts.v1.WorkContextAuthorizationRevisionQuestion - 79, // 62: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.approval_grant:type_name -> saas.accounts.v1.WorkContextApprovalGrantQuestion - 80, // 63: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.seal:type_name -> saas.accounts.v1.WorkContextSealAnswer - 81, // 64: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.principal_epoch:type_name -> saas.accounts.v1.WorkContextPrincipalEpochAnswer - 82, // 65: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.approved_build:type_name -> saas.accounts.v1.WorkContextApprovedBuildAnswer - 83, // 66: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.operation_binding:type_name -> saas.accounts.v1.WorkContextOperationBindingAnswer - 84, // 67: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.authorization_revision:type_name -> saas.accounts.v1.WorkContextAuthorizationRevisionAnswer - 85, // 68: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.approval_grant:type_name -> saas.accounts.v1.WorkContextApprovalGrantAnswer - 72, // 69: saas.accounts.v1.ModuleCapabilitiesService.GetCurrentInstallation:input_type -> saas.accounts.v1.ModuleCurrentInstallationRequest - 69, // 70: saas.accounts.v1.ModuleCapabilitiesService.ApproveExecutableArtifact:input_type -> saas.accounts.v1.ModuleExecutableArtifactRequest - 69, // 71: saas.accounts.v1.ModuleCapabilitiesService.AuthorizeExecutableArtifact:input_type -> saas.accounts.v1.ModuleExecutableArtifactRequest - 70, // 72: saas.accounts.v1.ModuleCapabilitiesService.RevokeExecutableArtifact:input_type -> saas.accounts.v1.ModuleRevokeExecutableArtifactRequest - 65, // 73: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedReadAudience:input_type -> saas.accounts.v1.ModuleExchangeDelegatedReadAudienceRequest - 66, // 74: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedOperationAudience:input_type -> saas.accounts.v1.ModuleExchangeDelegatedOperationAudienceRequest - 63, // 75: saas.accounts.v1.ModuleCapabilitiesService.CheckWorkContextRecordAccess:input_type -> saas.accounts.v1.CheckWorkContextRecordAccessRequest - 59, // 76: saas.accounts.v1.ModuleCapabilitiesService.ListReadableSourceCollections:input_type -> saas.accounts.v1.ListReadableSourceCollectionsRequest - 61, // 77: saas.accounts.v1.ModuleCapabilitiesService.PlaceRecord:input_type -> saas.accounts.v1.ModulePlaceRecordRequest - 11, // 78: saas.accounts.v1.ModuleCapabilitiesService.EnqueueJob:input_type -> saas.accounts.v1.ModuleEnqueueJobRequest - 13, // 79: saas.accounts.v1.ModuleCapabilitiesService.ClaimJobs:input_type -> saas.accounts.v1.ModuleClaimJobsRequest - 15, // 80: saas.accounts.v1.ModuleCapabilitiesService.HeartbeatJob:input_type -> saas.accounts.v1.ModuleHeartbeatJobRequest - 17, // 81: saas.accounts.v1.ModuleCapabilitiesService.AckJob:input_type -> saas.accounts.v1.ModuleAckJobRequest - 18, // 82: saas.accounts.v1.ModuleCapabilitiesService.NackJob:input_type -> saas.accounts.v1.ModuleNackJobRequest - 19, // 83: saas.accounts.v1.ModuleCapabilitiesService.NotifyUser:input_type -> saas.accounts.v1.ModuleNotifyUserRequest - 21, // 84: saas.accounts.v1.ModuleCapabilitiesService.NotifyOrgAdmins:input_type -> saas.accounts.v1.ModuleNotifyOrgAdminsRequest - 25, // 85: saas.accounts.v1.ModuleCapabilitiesService.RequestApproval:input_type -> saas.accounts.v1.ModuleRequestApprovalRequest - 27, // 86: saas.accounts.v1.ModuleCapabilitiesService.GetApproval:input_type -> saas.accounts.v1.ModuleGetApprovalRequest - 29, // 87: saas.accounts.v1.ModuleCapabilitiesService.CancelApproval:input_type -> saas.accounts.v1.ModuleCancelApprovalRequest - 30, // 88: saas.accounts.v1.ModuleCapabilitiesService.EmitAuditEvent:input_type -> saas.accounts.v1.ModuleEmitAuditEventRequest - 33, // 89: saas.accounts.v1.ModuleCapabilitiesService.DeclareAuditEventTypes:input_type -> saas.accounts.v1.ModuleDeclareAuditEventTypesRequest - 36, // 90: saas.accounts.v1.ModuleCapabilitiesService.ListSubjectVisibility:input_type -> saas.accounts.v1.ModuleListSubjectVisibilityRequest - 38, // 91: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceBlob:input_type -> saas.accounts.v1.FetchDatasourceBlobRequest - 41, // 92: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceFiles:input_type -> saas.accounts.v1.FetchDatasourceFilesRequest - 98, // 93: saas.accounts.v1.ModuleCapabilitiesService.MintModuleWorkContext:input_type -> saas.accounts.v1.ModuleMintWorkContextRequest - 99, // 94: saas.accounts.v1.ModuleCapabilitiesService.MintModuleOperationContext:input_type -> saas.accounts.v1.ModuleMintOperationContextRequest - 100, // 95: saas.accounts.v1.ModuleCapabilitiesService.MintSourceOperationContext:input_type -> saas.accounts.v1.ModuleMintSourceOperationContextRequest - 46, // 96: saas.accounts.v1.ModuleCapabilitiesService.PublishEvent:input_type -> saas.accounts.v1.ModulePublishEventRequest - 48, // 97: saas.accounts.v1.ModuleCapabilitiesService.Subscribe:input_type -> saas.accounts.v1.ModuleSubscribeRequest - 51, // 98: saas.accounts.v1.ModuleCapabilitiesService.Unsubscribe:input_type -> saas.accounts.v1.ModuleUnsubscribeRequest - 52, // 99: saas.accounts.v1.ModuleCapabilitiesService.ListSubscriptions:input_type -> saas.accounts.v1.ModuleListSubscriptionsRequest - 54, // 100: saas.accounts.v1.ModuleCapabilitiesService.ReplayEvents:input_type -> saas.accounts.v1.ModuleReplayEventsRequest - 86, // 101: saas.accounts.v1.ModuleCapabilitiesService.ResolveWorkContextAuthority:input_type -> saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest - 73, // 102: saas.accounts.v1.ModuleCapabilitiesService.GetCurrentInstallation:output_type -> saas.accounts.v1.ModuleCurrentInstallationResponse - 71, // 103: saas.accounts.v1.ModuleCapabilitiesService.ApproveExecutableArtifact:output_type -> saas.accounts.v1.ModuleExecutableArtifactResponse - 71, // 104: saas.accounts.v1.ModuleCapabilitiesService.AuthorizeExecutableArtifact:output_type -> saas.accounts.v1.ModuleExecutableArtifactResponse - 71, // 105: saas.accounts.v1.ModuleCapabilitiesService.RevokeExecutableArtifact:output_type -> saas.accounts.v1.ModuleExecutableArtifactResponse - 101, // 106: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedReadAudience:output_type -> saas.accounts.v1.IssuedWorkContext - 101, // 107: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedOperationAudience:output_type -> saas.accounts.v1.IssuedWorkContext - 64, // 108: saas.accounts.v1.ModuleCapabilitiesService.CheckWorkContextRecordAccess:output_type -> saas.accounts.v1.CheckWorkContextRecordAccessResponse - 60, // 109: saas.accounts.v1.ModuleCapabilitiesService.ListReadableSourceCollections:output_type -> saas.accounts.v1.ListReadableSourceCollectionsResponse - 62, // 110: saas.accounts.v1.ModuleCapabilitiesService.PlaceRecord:output_type -> saas.accounts.v1.ModulePlaceRecordResponse - 12, // 111: saas.accounts.v1.ModuleCapabilitiesService.EnqueueJob:output_type -> saas.accounts.v1.ModuleEnqueueJobResponse - 14, // 112: saas.accounts.v1.ModuleCapabilitiesService.ClaimJobs:output_type -> saas.accounts.v1.ModuleClaimJobsResponse - 16, // 113: saas.accounts.v1.ModuleCapabilitiesService.HeartbeatJob:output_type -> saas.accounts.v1.ModuleHeartbeatJobResponse - 102, // 114: saas.accounts.v1.ModuleCapabilitiesService.AckJob:output_type -> google.protobuf.Empty - 102, // 115: saas.accounts.v1.ModuleCapabilitiesService.NackJob:output_type -> google.protobuf.Empty - 20, // 116: saas.accounts.v1.ModuleCapabilitiesService.NotifyUser:output_type -> saas.accounts.v1.ModuleNotifyUserResponse - 22, // 117: saas.accounts.v1.ModuleCapabilitiesService.NotifyOrgAdmins:output_type -> saas.accounts.v1.ModuleNotifyOrgAdminsResponse - 26, // 118: saas.accounts.v1.ModuleCapabilitiesService.RequestApproval:output_type -> saas.accounts.v1.ModuleRequestApprovalResponse - 28, // 119: saas.accounts.v1.ModuleCapabilitiesService.GetApproval:output_type -> saas.accounts.v1.ModuleApproval - 102, // 120: saas.accounts.v1.ModuleCapabilitiesService.CancelApproval:output_type -> google.protobuf.Empty - 102, // 121: saas.accounts.v1.ModuleCapabilitiesService.EmitAuditEvent:output_type -> google.protobuf.Empty - 34, // 122: saas.accounts.v1.ModuleCapabilitiesService.DeclareAuditEventTypes:output_type -> saas.accounts.v1.ModuleDeclareAuditEventTypesResponse - 37, // 123: saas.accounts.v1.ModuleCapabilitiesService.ListSubjectVisibility:output_type -> saas.accounts.v1.ModuleListSubjectVisibilityResponse - 39, // 124: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceBlob:output_type -> saas.accounts.v1.FetchDatasourceBlobChunk - 45, // 125: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceFiles:output_type -> saas.accounts.v1.FetchDatasourceFilesFrame - 103, // 126: saas.accounts.v1.ModuleCapabilitiesService.MintModuleWorkContext:output_type -> saas.accounts.v1.ModuleMintWorkContextResponse - 104, // 127: saas.accounts.v1.ModuleCapabilitiesService.MintModuleOperationContext:output_type -> saas.accounts.v1.ModuleMintOperationContextResponse - 105, // 128: saas.accounts.v1.ModuleCapabilitiesService.MintSourceOperationContext:output_type -> saas.accounts.v1.ModuleMintSourceOperationContextResponse - 47, // 129: saas.accounts.v1.ModuleCapabilitiesService.PublishEvent:output_type -> saas.accounts.v1.ModulePublishEventResponse - 50, // 130: saas.accounts.v1.ModuleCapabilitiesService.Subscribe:output_type -> saas.accounts.v1.ModuleSubscribeResponse - 102, // 131: saas.accounts.v1.ModuleCapabilitiesService.Unsubscribe:output_type -> google.protobuf.Empty - 53, // 132: saas.accounts.v1.ModuleCapabilitiesService.ListSubscriptions:output_type -> saas.accounts.v1.ModuleListSubscriptionsResponse - 55, // 133: saas.accounts.v1.ModuleCapabilitiesService.ReplayEvents:output_type -> saas.accounts.v1.ModuleReplayEventsResponse - 87, // 134: saas.accounts.v1.ModuleCapabilitiesService.ResolveWorkContextAuthority:output_type -> saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse - 102, // [102:135] is the sub-list for method output_type - 69, // [69:102] is the sub-list for method input_type - 69, // [69:69] is the sub-list for extension type_name - 69, // [69:69] is the sub-list for extension extendee - 0, // [0:69] is the sub-list for field type_name + 98, // 19: saas.accounts.v1.ModuleApproval.expires_at:type_name -> google.protobuf.Timestamp + 98, // 20: saas.accounts.v1.ModuleApproval.escalate_at:type_name -> google.protobuf.Timestamp + 98, // 21: saas.accounts.v1.ModuleApproval.created_at:type_name -> google.protobuf.Timestamp + 99, // 22: saas.accounts.v1.ModuleEmitAuditEventRequest.fields:type_name -> google.protobuf.Struct + 98, // 23: saas.accounts.v1.ModuleJournalEntry.occurred_at:type_name -> google.protobuf.Timestamp + 99, // 24: saas.accounts.v1.ModuleJournalEntry.data:type_name -> google.protobuf.Struct + 32, // 25: saas.accounts.v1.ModuleReadJournalResponse.entries:type_name -> saas.accounts.v1.ModuleJournalEntry + 0, // 26: saas.accounts.v1.ModuleAuditFieldDeclaration.kind:type_name -> saas.accounts.v1.ModuleAuditFieldKind + 34, // 27: saas.accounts.v1.ModuleAuditEventTypeDeclaration.fields:type_name -> saas.accounts.v1.ModuleAuditFieldDeclaration + 1, // 28: saas.accounts.v1.ModuleAuditEventTypeDeclaration.visibility:type_name -> saas.accounts.v1.ModuleAuditEventVisibility + 35, // 29: saas.accounts.v1.ModuleDeclareAuditEventTypesRequest.types:type_name -> saas.accounts.v1.ModuleAuditEventTypeDeclaration + 98, // 30: saas.accounts.v1.SubjectVisibilityGrant.expires_at:type_name -> google.protobuf.Timestamp + 38, // 31: saas.accounts.v1.ModuleListSubjectVisibilityResponse.grants:type_name -> saas.accounts.v1.SubjectVisibilityGrant + 43, // 32: saas.accounts.v1.FetchDatasourceFilesRequest.files:type_name -> saas.accounts.v1.DatasourceFileRef + 2, // 33: saas.accounts.v1.DatasourceItemReaders.basis:type_name -> saas.accounts.v1.DatasourceItemReadersBasis + 45, // 34: saas.accounts.v1.DatasourceFileHeader.provenance:type_name -> saas.accounts.v1.DatasourceProvenance + 46, // 35: saas.accounts.v1.DatasourceFileHeader.readers:type_name -> saas.accounts.v1.DatasourceItemReaders + 47, // 36: saas.accounts.v1.FetchDatasourceFilesFrame.header:type_name -> saas.accounts.v1.DatasourceFileHeader + 100, // 37: saas.accounts.v1.ModulePublishEventRequest.envelope:type_name -> saas.events.v1.EventEnvelope + 3, // 38: saas.accounts.v1.ModuleSubscribeRequest.delivery:type_name -> saas.accounts.v1.EventDelivery + 3, // 39: saas.accounts.v1.ModuleSubscription.delivery:type_name -> saas.accounts.v1.EventDelivery + 98, // 40: saas.accounts.v1.ModuleSubscription.created_at:type_name -> google.protobuf.Timestamp + 52, // 41: saas.accounts.v1.ModuleSubscribeResponse.subscription:type_name -> saas.accounts.v1.ModuleSubscription + 52, // 42: saas.accounts.v1.ModuleListSubscriptionsResponse.subscriptions:type_name -> saas.accounts.v1.ModuleSubscription + 98, // 43: saas.accounts.v1.ModuleReplayEventsRequest.since:type_name -> google.protobuf.Timestamp + 4, // 44: saas.accounts.v1.ReadableSourceCollection.grant_disclosure:type_name -> saas.accounts.v1.MetadataDisclosure + 60, // 45: saas.accounts.v1.ReadableSourceCollection.read_grants:type_name -> saas.accounts.v1.ReadableCollectionGrant + 61, // 46: saas.accounts.v1.ReadableSourceCollection.sync:type_name -> saas.accounts.v1.CollectionSyncProvenance + 98, // 47: saas.accounts.v1.ReadableCollectionGrant.expires_at:type_name -> google.protobuf.Timestamp + 5, // 48: saas.accounts.v1.CollectionSyncProvenance.stage:type_name -> saas.accounts.v1.SourceSyncStage + 98, // 49: saas.accounts.v1.CollectionSyncProvenance.at:type_name -> google.protobuf.Timestamp + 4, // 50: saas.accounts.v1.CollectionSyncProvenance.requester_disclosure:type_name -> saas.accounts.v1.MetadataDisclosure + 98, // 51: saas.accounts.v1.CollectionSyncProvenance.requested_at:type_name -> google.protobuf.Timestamp + 59, // 52: saas.accounts.v1.ListReadableSourceCollectionsResponse.collections:type_name -> saas.accounts.v1.ReadableSourceCollection + 70, // 53: saas.accounts.v1.ExecutableArtifactIdentity.contracts:type_name -> saas.accounts.v1.ExecutableArtifactContract + 71, // 54: saas.accounts.v1.ModuleExecutableArtifactRequest.identity:type_name -> saas.accounts.v1.ExecutableArtifactIdentity + 6, // 55: saas.accounts.v1.WorkContextSealAnswer.state:type_name -> saas.accounts.v1.WorkContextSealState + 7, // 56: saas.accounts.v1.WorkContextPrincipalEpochAnswer.state:type_name -> saas.accounts.v1.WorkContextPrincipalState + 8, // 57: saas.accounts.v1.WorkContextApprovedBuildAnswer.state:type_name -> saas.accounts.v1.WorkContextApprovedBuildState + 9, // 58: saas.accounts.v1.WorkContextOperationBindingAnswer.state:type_name -> saas.accounts.v1.WorkContextOperationBindingState + 10, // 59: saas.accounts.v1.WorkContextApprovalGrantAnswer.state:type_name -> saas.accounts.v1.WorkContextApprovalGrantState + 77, // 60: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.seal:type_name -> saas.accounts.v1.WorkContextSealQuestion + 78, // 61: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.principal_epoch:type_name -> saas.accounts.v1.WorkContextPrincipalEpochQuestion + 79, // 62: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.approved_build:type_name -> saas.accounts.v1.WorkContextApprovedBuildQuestion + 80, // 63: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.operation_binding:type_name -> saas.accounts.v1.WorkContextOperationBindingQuestion + 81, // 64: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.authorization_revision:type_name -> saas.accounts.v1.WorkContextAuthorizationRevisionQuestion + 82, // 65: saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest.approval_grant:type_name -> saas.accounts.v1.WorkContextApprovalGrantQuestion + 83, // 66: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.seal:type_name -> saas.accounts.v1.WorkContextSealAnswer + 84, // 67: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.principal_epoch:type_name -> saas.accounts.v1.WorkContextPrincipalEpochAnswer + 85, // 68: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.approved_build:type_name -> saas.accounts.v1.WorkContextApprovedBuildAnswer + 86, // 69: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.operation_binding:type_name -> saas.accounts.v1.WorkContextOperationBindingAnswer + 87, // 70: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.authorization_revision:type_name -> saas.accounts.v1.WorkContextAuthorizationRevisionAnswer + 88, // 71: saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse.approval_grant:type_name -> saas.accounts.v1.WorkContextApprovalGrantAnswer + 75, // 72: saas.accounts.v1.ModuleCapabilitiesService.GetCurrentInstallation:input_type -> saas.accounts.v1.ModuleCurrentInstallationRequest + 72, // 73: saas.accounts.v1.ModuleCapabilitiesService.ApproveExecutableArtifact:input_type -> saas.accounts.v1.ModuleExecutableArtifactRequest + 72, // 74: saas.accounts.v1.ModuleCapabilitiesService.AuthorizeExecutableArtifact:input_type -> saas.accounts.v1.ModuleExecutableArtifactRequest + 73, // 75: saas.accounts.v1.ModuleCapabilitiesService.RevokeExecutableArtifact:input_type -> saas.accounts.v1.ModuleRevokeExecutableArtifactRequest + 68, // 76: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedReadAudience:input_type -> saas.accounts.v1.ModuleExchangeDelegatedReadAudienceRequest + 69, // 77: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedOperationAudience:input_type -> saas.accounts.v1.ModuleExchangeDelegatedOperationAudienceRequest + 66, // 78: saas.accounts.v1.ModuleCapabilitiesService.CheckWorkContextRecordAccess:input_type -> saas.accounts.v1.CheckWorkContextRecordAccessRequest + 62, // 79: saas.accounts.v1.ModuleCapabilitiesService.ListReadableSourceCollections:input_type -> saas.accounts.v1.ListReadableSourceCollectionsRequest + 64, // 80: saas.accounts.v1.ModuleCapabilitiesService.PlaceRecord:input_type -> saas.accounts.v1.ModulePlaceRecordRequest + 11, // 81: saas.accounts.v1.ModuleCapabilitiesService.EnqueueJob:input_type -> saas.accounts.v1.ModuleEnqueueJobRequest + 13, // 82: saas.accounts.v1.ModuleCapabilitiesService.ClaimJobs:input_type -> saas.accounts.v1.ModuleClaimJobsRequest + 15, // 83: saas.accounts.v1.ModuleCapabilitiesService.HeartbeatJob:input_type -> saas.accounts.v1.ModuleHeartbeatJobRequest + 17, // 84: saas.accounts.v1.ModuleCapabilitiesService.AckJob:input_type -> saas.accounts.v1.ModuleAckJobRequest + 18, // 85: saas.accounts.v1.ModuleCapabilitiesService.NackJob:input_type -> saas.accounts.v1.ModuleNackJobRequest + 19, // 86: saas.accounts.v1.ModuleCapabilitiesService.NotifyUser:input_type -> saas.accounts.v1.ModuleNotifyUserRequest + 21, // 87: saas.accounts.v1.ModuleCapabilitiesService.NotifyOrgAdmins:input_type -> saas.accounts.v1.ModuleNotifyOrgAdminsRequest + 25, // 88: saas.accounts.v1.ModuleCapabilitiesService.RequestApproval:input_type -> saas.accounts.v1.ModuleRequestApprovalRequest + 27, // 89: saas.accounts.v1.ModuleCapabilitiesService.GetApproval:input_type -> saas.accounts.v1.ModuleGetApprovalRequest + 29, // 90: saas.accounts.v1.ModuleCapabilitiesService.CancelApproval:input_type -> saas.accounts.v1.ModuleCancelApprovalRequest + 30, // 91: saas.accounts.v1.ModuleCapabilitiesService.EmitAuditEvent:input_type -> saas.accounts.v1.ModuleEmitAuditEventRequest + 31, // 92: saas.accounts.v1.ModuleCapabilitiesService.ReadJournal:input_type -> saas.accounts.v1.ModuleReadJournalRequest + 36, // 93: saas.accounts.v1.ModuleCapabilitiesService.DeclareAuditEventTypes:input_type -> saas.accounts.v1.ModuleDeclareAuditEventTypesRequest + 39, // 94: saas.accounts.v1.ModuleCapabilitiesService.ListSubjectVisibility:input_type -> saas.accounts.v1.ModuleListSubjectVisibilityRequest + 41, // 95: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceBlob:input_type -> saas.accounts.v1.FetchDatasourceBlobRequest + 44, // 96: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceFiles:input_type -> saas.accounts.v1.FetchDatasourceFilesRequest + 101, // 97: saas.accounts.v1.ModuleCapabilitiesService.MintModuleWorkContext:input_type -> saas.accounts.v1.ModuleMintWorkContextRequest + 102, // 98: saas.accounts.v1.ModuleCapabilitiesService.MintModuleOperationContext:input_type -> saas.accounts.v1.ModuleMintOperationContextRequest + 103, // 99: saas.accounts.v1.ModuleCapabilitiesService.MintSourceOperationContext:input_type -> saas.accounts.v1.ModuleMintSourceOperationContextRequest + 49, // 100: saas.accounts.v1.ModuleCapabilitiesService.PublishEvent:input_type -> saas.accounts.v1.ModulePublishEventRequest + 51, // 101: saas.accounts.v1.ModuleCapabilitiesService.Subscribe:input_type -> saas.accounts.v1.ModuleSubscribeRequest + 54, // 102: saas.accounts.v1.ModuleCapabilitiesService.Unsubscribe:input_type -> saas.accounts.v1.ModuleUnsubscribeRequest + 55, // 103: saas.accounts.v1.ModuleCapabilitiesService.ListSubscriptions:input_type -> saas.accounts.v1.ModuleListSubscriptionsRequest + 57, // 104: saas.accounts.v1.ModuleCapabilitiesService.ReplayEvents:input_type -> saas.accounts.v1.ModuleReplayEventsRequest + 89, // 105: saas.accounts.v1.ModuleCapabilitiesService.ResolveWorkContextAuthority:input_type -> saas.accounts.v1.ModuleResolveWorkContextAuthorityRequest + 76, // 106: saas.accounts.v1.ModuleCapabilitiesService.GetCurrentInstallation:output_type -> saas.accounts.v1.ModuleCurrentInstallationResponse + 74, // 107: saas.accounts.v1.ModuleCapabilitiesService.ApproveExecutableArtifact:output_type -> saas.accounts.v1.ModuleExecutableArtifactResponse + 74, // 108: saas.accounts.v1.ModuleCapabilitiesService.AuthorizeExecutableArtifact:output_type -> saas.accounts.v1.ModuleExecutableArtifactResponse + 74, // 109: saas.accounts.v1.ModuleCapabilitiesService.RevokeExecutableArtifact:output_type -> saas.accounts.v1.ModuleExecutableArtifactResponse + 104, // 110: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedReadAudience:output_type -> saas.accounts.v1.IssuedWorkContext + 104, // 111: saas.accounts.v1.ModuleCapabilitiesService.ExchangeDelegatedOperationAudience:output_type -> saas.accounts.v1.IssuedWorkContext + 67, // 112: saas.accounts.v1.ModuleCapabilitiesService.CheckWorkContextRecordAccess:output_type -> saas.accounts.v1.CheckWorkContextRecordAccessResponse + 63, // 113: saas.accounts.v1.ModuleCapabilitiesService.ListReadableSourceCollections:output_type -> saas.accounts.v1.ListReadableSourceCollectionsResponse + 65, // 114: saas.accounts.v1.ModuleCapabilitiesService.PlaceRecord:output_type -> saas.accounts.v1.ModulePlaceRecordResponse + 12, // 115: saas.accounts.v1.ModuleCapabilitiesService.EnqueueJob:output_type -> saas.accounts.v1.ModuleEnqueueJobResponse + 14, // 116: saas.accounts.v1.ModuleCapabilitiesService.ClaimJobs:output_type -> saas.accounts.v1.ModuleClaimJobsResponse + 16, // 117: saas.accounts.v1.ModuleCapabilitiesService.HeartbeatJob:output_type -> saas.accounts.v1.ModuleHeartbeatJobResponse + 105, // 118: saas.accounts.v1.ModuleCapabilitiesService.AckJob:output_type -> google.protobuf.Empty + 105, // 119: saas.accounts.v1.ModuleCapabilitiesService.NackJob:output_type -> google.protobuf.Empty + 20, // 120: saas.accounts.v1.ModuleCapabilitiesService.NotifyUser:output_type -> saas.accounts.v1.ModuleNotifyUserResponse + 22, // 121: saas.accounts.v1.ModuleCapabilitiesService.NotifyOrgAdmins:output_type -> saas.accounts.v1.ModuleNotifyOrgAdminsResponse + 26, // 122: saas.accounts.v1.ModuleCapabilitiesService.RequestApproval:output_type -> saas.accounts.v1.ModuleRequestApprovalResponse + 28, // 123: saas.accounts.v1.ModuleCapabilitiesService.GetApproval:output_type -> saas.accounts.v1.ModuleApproval + 105, // 124: saas.accounts.v1.ModuleCapabilitiesService.CancelApproval:output_type -> google.protobuf.Empty + 105, // 125: saas.accounts.v1.ModuleCapabilitiesService.EmitAuditEvent:output_type -> google.protobuf.Empty + 33, // 126: saas.accounts.v1.ModuleCapabilitiesService.ReadJournal:output_type -> saas.accounts.v1.ModuleReadJournalResponse + 37, // 127: saas.accounts.v1.ModuleCapabilitiesService.DeclareAuditEventTypes:output_type -> saas.accounts.v1.ModuleDeclareAuditEventTypesResponse + 40, // 128: saas.accounts.v1.ModuleCapabilitiesService.ListSubjectVisibility:output_type -> saas.accounts.v1.ModuleListSubjectVisibilityResponse + 42, // 129: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceBlob:output_type -> saas.accounts.v1.FetchDatasourceBlobChunk + 48, // 130: saas.accounts.v1.ModuleCapabilitiesService.FetchDatasourceFiles:output_type -> saas.accounts.v1.FetchDatasourceFilesFrame + 106, // 131: saas.accounts.v1.ModuleCapabilitiesService.MintModuleWorkContext:output_type -> saas.accounts.v1.ModuleMintWorkContextResponse + 107, // 132: saas.accounts.v1.ModuleCapabilitiesService.MintModuleOperationContext:output_type -> saas.accounts.v1.ModuleMintOperationContextResponse + 108, // 133: saas.accounts.v1.ModuleCapabilitiesService.MintSourceOperationContext:output_type -> saas.accounts.v1.ModuleMintSourceOperationContextResponse + 50, // 134: saas.accounts.v1.ModuleCapabilitiesService.PublishEvent:output_type -> saas.accounts.v1.ModulePublishEventResponse + 53, // 135: saas.accounts.v1.ModuleCapabilitiesService.Subscribe:output_type -> saas.accounts.v1.ModuleSubscribeResponse + 105, // 136: saas.accounts.v1.ModuleCapabilitiesService.Unsubscribe:output_type -> google.protobuf.Empty + 56, // 137: saas.accounts.v1.ModuleCapabilitiesService.ListSubscriptions:output_type -> saas.accounts.v1.ModuleListSubscriptionsResponse + 58, // 138: saas.accounts.v1.ModuleCapabilitiesService.ReplayEvents:output_type -> saas.accounts.v1.ModuleReplayEventsResponse + 90, // 139: saas.accounts.v1.ModuleCapabilitiesService.ResolveWorkContextAuthority:output_type -> saas.accounts.v1.ModuleResolveWorkContextAuthorityResponse + 106, // [106:140] is the sub-list for method output_type + 72, // [72:106] is the sub-list for method input_type + 72, // [72:72] is the sub-list for extension type_name + 72, // [72:72] is the sub-list for extension extendee + 0, // [0:72] is the sub-list for field type_name } func init() { file_saas_accounts_v1_module_capabilities_proto_init() } @@ -6725,11 +6998,11 @@ func file_saas_accounts_v1_module_capabilities_proto_init() { } file_saas_accounts_v1_work_contexts_proto_init() file_saas_accounts_v1_module_registration_proto_init() - file_saas_accounts_v1_module_capabilities_proto_msgTypes[34].OneofWrappers = []any{ + file_saas_accounts_v1_module_capabilities_proto_msgTypes[37].OneofWrappers = []any{ (*FetchDatasourceFilesFrame_Header)(nil), (*FetchDatasourceFilesFrame_Data)(nil), } - file_saas_accounts_v1_module_capabilities_proto_msgTypes[75].OneofWrappers = []any{ + file_saas_accounts_v1_module_capabilities_proto_msgTypes[78].OneofWrappers = []any{ (*ModuleResolveWorkContextAuthorityRequest_Seal)(nil), (*ModuleResolveWorkContextAuthorityRequest_PrincipalEpoch)(nil), (*ModuleResolveWorkContextAuthorityRequest_ApprovedBuild)(nil), @@ -6737,7 +7010,7 @@ func file_saas_accounts_v1_module_capabilities_proto_init() { (*ModuleResolveWorkContextAuthorityRequest_AuthorizationRevision)(nil), (*ModuleResolveWorkContextAuthorityRequest_ApprovalGrant)(nil), } - file_saas_accounts_v1_module_capabilities_proto_msgTypes[76].OneofWrappers = []any{ + file_saas_accounts_v1_module_capabilities_proto_msgTypes[79].OneofWrappers = []any{ (*ModuleResolveWorkContextAuthorityResponse_Seal)(nil), (*ModuleResolveWorkContextAuthorityResponse_PrincipalEpoch)(nil), (*ModuleResolveWorkContextAuthorityResponse_ApprovedBuild)(nil), @@ -6751,7 +7024,7 @@ func file_saas_accounts_v1_module_capabilities_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_saas_accounts_v1_module_capabilities_proto_rawDesc), len(file_saas_accounts_v1_module_capabilities_proto_rawDesc)), NumEnums: 11, - NumMessages: 77, + NumMessages: 80, NumExtensions: 0, NumServices: 1, }, diff --git a/moduleauthority/broker_test.go b/moduleauthority/broker_test.go index 9009cdb..21d31d9 100644 --- a/moduleauthority/broker_test.go +++ b/moduleauthority/broker_test.go @@ -149,8 +149,14 @@ func TestTheBrokerRefusesAWrongPerimeterCredential(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := client.MintModuleOperationContext(t.Context(), "model"); !errors.Is(err, ErrInvalidCredentials) { - t.Fatalf("wrong internal token: %v", err) + if _, err := client.ModuleWorkContext(t.Context()); err != nil { + t.Fatalf("workload work mint with valid projected credential: %v", err) + } + if _, err := client.MintModuleOperationContext(t.Context(), testBindingID); !errors.Is(err, ErrInvalidCredentials) { + t.Fatalf("wrong internal token on operation mint: %v", err) + } + if _, err := client.MintSourceOperationContext(t.Context(), exampleSource); !errors.Is(err, ErrInvalidCredentials) { + t.Fatalf("wrong internal token on source mint: %v", err) } wrongToken := h.client(t, Credentials{Prefix: testPrefix, Token: staticToken("another-pods-token")}) if _, err := wrongToken.ModuleWorkContext(t.Context()); !errors.Is(err, ErrInvalidCredentials) { diff --git a/moduleauthority/host_fake_test.go b/moduleauthority/host_fake_test.go index ef4705a..192f548 100644 --- a/moduleauthority/host_fake_test.go +++ b/moduleauthority/host_fake_test.go @@ -28,14 +28,14 @@ import ( "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1/accountsv1connect" ) -// This file fakes the two host seams as module-saas-starter v0.0.103 serves +// This file fakes the two host seams as module-saas-starter v0.0.94 serves // them, so the client is tested against the host's contract rather than // against itself: // // - fakeGateway is the auth-gateway's module broker // (module/services/auth-gateway/code/gateway_modules.go): the three -// POST /modules/_* paths, their perimeter (projected ServiceAccount bearer, -// and the internal token on the two operation mints), body validation, status mapping, plain-text refusals and +// POST /modules/_* paths on its workload listener, their credentials +// (projected ServiceAccount bearer; internal token for operation/source), body validation, status mapping, plain-text refusals and // JSON answers. Every other path answers 404 "endpoint not exposed", as the // real gateway does for every EXPOSURE_INTERNAL procedure — the old bug was // a Connect call to exactly such a path. @@ -46,20 +46,21 @@ import ( // // fakeGateway is the edge-case double; the contract itself is held by // conformance/hostgateway, which mints through the host's REAL gateway -// handlers at the tag in SOURCE.txt. The fake follows the real gateway's -// perimeter, prefix rule and check order (the internal token on the operation -// mints, then exactly one Bearer, then body, prefix and binding/source validation, then accounts' -// verdict). Where it still differs, it differs on purpose: +// handlers at the ref in SOURCE.txt. The fake follows the real gateway's +// prefix rule and check order (internal token for operation/source, then +// exactly one Bearer, body, prefix and binding/source validation, then +// accounts' verdict). The real protected listener additionally authenticates +// its waypoint presenter; conformance exercises that listener directly. Where it still differs, it differs on purpose: // // - D2: it decodes the body into map[string]string and records every key, so // an extra or misnamed field is visible to an exact-body assertion; the // real gateway decodes into a struct and drops unknown keys silently. // - D5: a test may inject any refusal on any path. The real gateway never -// answers 403 on the SDK's /modules/_work-context path — accounts' +// answers 403 on /modules/_work-context — accounts' // PermissionDenied is a 401 there — so no test here injects one. -// - D6: accounts' Unauthenticated and FailedPrecondition on the work-context -// mint (401 and 412 respectively from the real gateway) are not modelled; -// only injected statuses are. Conformance holds both mappings at SOURCE.txt. +// - D6: accounts' Unauthenticated (401) and installation-pending +// FailedPrecondition (412) on the work-context mint are not modelled; +// only injected statuses are. // - D7: it accepts one internal token; the real gateway also accepts the // previous one during rotation. // - D8: refusal bodies are whatever a test injects; the real gateway sends @@ -200,9 +201,6 @@ func (g *fakeGateway) serve(w http.ResponseWriter, r *http.Request) { plainError(w, http.StatusMethodNotAllowed, "method not allowed") return } - // Host af5cef9a (module-saas-starter#1120) serves the mints on the workload - // endpoint alone, which is the work-context mint's whole perimeter: only - // the operation mints still hold the internal token. if r.URL.Path != hostWorkContextPath && r.Header.Get("X-Codefly-Internal-Token") != testInternalToken { plainError(w, http.StatusUnauthorized, "unauthorized") return @@ -342,7 +340,7 @@ type authorityCall struct { // the capability travelled with it and that the identifiers are the // capability's own. authorityQuestion *v1.ModuleResolveWorkContextAuthorityRequest - journal *v1.ModuleReadJournalRequest + journal *v1.ModuleReadJournalRequest } // servedOnAuthority is the slice of business.ModuleAuthorityProcedures this @@ -359,9 +357,7 @@ var servedOnAuthority = map[string]bool{ accountsv1connect.ModuleCapabilitiesServiceNotifyOrgAdminsProcedure: true, accountsv1connect.ModuleCapabilitiesServiceNotifyUserProcedure: true, accountsv1connect.ModuleCapabilitiesServiceResolveWorkContextAuthorityProcedure: true, - // ReadJournal is not in the v0.0.94 host: it is the wire contract the next - // host release is to serve (see SOURCE.txt), faked here as that contract - // states it. + // ReadJournal follows the exact host ref recorded in SOURCE.txt. accountsv1connect.ModuleCapabilitiesServiceReadJournalProcedure: true, } From bdb8717aab476c337e5e19bfbd4a8519c919efc8 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss Date: Wed, 21 Oct 2026 03:05:06 +0000 Subject: [PATCH 08/18] chore: mark generated SDK bindings for review --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..efc7352 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Host-owned protocol bindings are generated. Keep facades and locks visible. +gen/** linguist-generated=true From 10b87f2e7f06ae5d8106f0a6576ad46b3e96771b Mon Sep 17 00:00:00 2001 From: crisfiorini-oss Date: Wed, 21 Oct 2026 17:26:09 +0000 Subject: [PATCH 09/18] fix(sdk): regenerate journal API against current host authority Generate the complete host-owned schema from module-saas-starter d5ea5dc75790dfeb55a5492eaa7c7b759df7a84d with the pinned SDK plugins. Consume the exact host Core contract and remove ApprovedBuild from WorkContextSources, matching the current live verifier API. Keep Work Contexts opaque and retain the real workload-listener conformance assertions. --- CHANGELOG.md | 19 ++++++++++++++++--- SOURCE.txt | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d1999..2273453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -191,6 +191,13 @@ shipped. ### Removed (breaking) +- **`WorkContextSources.ApprovedBuild` is removed.** The host's Core contract + no longer includes approved builds in `SealSource` or execution fields in + capability minting. The SDK now requires the host's exact Core + `v0.19.6-0.20261018051341-b81d92e6feda` dependency, reuses Core's sealed token + fixtures without legacy execution claims, and keeps workload identity checks + in the host. There is no replacement SDK verifier or compatibility parser. + - **`SessionId` is gone from three Work Context mint requests**, with their `GetSessionId()` accessors: `StartTaskWorkContextRequest`, `StartChildSessionWorkContextRequest` and `StartInstallationTaskRequest`. The @@ -222,11 +229,17 @@ shipped. ### Generated contract +- The same host snapshot adds source reconfiguration (`UpdateSource` and + `SourceFetchScope`), installation readiness conditions and module authority + requests, grant lineage, and the explicit `SealsNoBinding` revision assertion. + These additive generated contracts require no new facade or verifier. + - Regenerated the complete host-owned schema from - `module-saas-starter@b640a5fc51ab1f4fc83d144bbf1eeddd7637c0dc` (PR #1106, module package + `module-saas-starter@d5ea5dc75790dfeb55a5492eaa7c7b759df7a84d` (PR #1106, module package 0.1.1) with the pinned buf 1.73.0 and plugins. `SOURCE.txt` records that - exact development source; the generated tree no longer mixes a release - snapshot with an unrecorded contract. This also carries the host's current + exact development source, including the current delivered module authority + registry and workload gateway contract. The generated tree comes from that + single committed snapshot. This also carries the host's current delivery/recovery, installation and datasource contracts. The SDK release remains ordered after the host release. diff --git a/SOURCE.txt b/SOURCE.txt index 613e899..96c364d 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,4 +1,4 @@ -gen/ generated from codefly-dev/module-saas-starter@b640a5fc51ab1f4fc83d144bbf1eeddd7637c0dc +gen/ generated from codefly-dev/module-saas-starter@d5ea5dc75790dfeb55a5492eaa7c7b759df7a84d (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) From 4a785ffa97251d295acd01796c5188253020d4fc Mon Sep 17 00:00:00 2001 From: crisfiorini-oss Date: Wed, 21 Oct 2026 20:33:02 +0000 Subject: [PATCH 10/18] docs(sdk): align journal provenance with rebased host source Record the exact final host source after its rebase onto current main. Preserve current SDK facade and mint APIs, keep journal changes under Unreleased, and document the protected workload-listener conformance coverage. The full owner generation reproduces from the unchanged accounts proto tree. --- CHANGELOG.md | 130 +++++++++++------------------- SOURCE.txt | 2 +- conformance/hostgateway/AGENTS.md | 8 +- moduleauthority/host_fake_test.go | 2 +- 4 files changed, 55 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2273453..108af7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## Unreleased -Core main and module-saas-starter v0.0.103. No Core tag carries the Core this -needs, so the floor is a pseudo-version; it moves to the first tag that does. +The journal contract uses the exact module-saas-starter development source in +`SOURCE.txt`. The Core floor remains the current host's immutable pseudo-version; +the floor moves to the first Core tag that carries the required API. ### Changed @@ -14,11 +15,12 @@ needs, so the floor is a pseudo-version; it moves to the first tag that does. for any consumer that requires that Core. It is also the Core the v0.0.103 gateway requires. Core v0.19.5 still requires `SealSource.ApprovedBuild` and cannot build this release; MVS never selects it beside this floor. -- **`gen/` moves from `9ab36e7e` to released module-saas-starter v0.0.103** - (`2cb00737`), with the existing plugin pins and buf v1.73.0. The tree - reproduces byte-for-byte from that tag, and `SOURCE.txt` records it for both - `gen-drift` and `conformance/hostgateway`. The module package version stays - 0.1.0; regeneration cuts no SDK release. +- **`gen/` adds the journal and delivery contract to the current host schema.** + The complete host-owned `saas/` tree is generated from the exact development + source recorded in `SOURCE.txt`, with buf 1.73.0 and the existing plugin pins. + The generated tree retains current main's solution registry and datasource + contracts. Regeneration cuts no SDK release; publishing follows the host + release and replacement of the development source with its release tag. - **The module broker is the gateway's workload endpoint.** Host `af5cef9a` (module-saas-starter#1120) serves the three mints there alone — the public endpoint answers 404 — and `Seams.Gateway`'s documentation now says so. The @@ -33,6 +35,12 @@ needs, so the floor is a pseudo-version; it moves to the first tag that does. ### Removed (breaking) +- The journal generation adds methods to generated interfaces: + `AuditServiceClient`/`Handler.DeliverAuditEvents` and + `.LookupDeliverAuditEvents`, and `ModuleCapabilitiesServiceClient`/`Handler.ReadJournal`. + Implementers must provide the methods or embed the generated + `Unimplemented…Handler`. No existing generated symbol is removed relative + to current main. - **`(*moduleauthority.WorkContextSources).ApprovedBuild`**. Core's verifier no longer asks an issuer for an approved build — a change of approved builds revokes through the principal's epoch and the installation's revision — and @@ -56,6 +64,32 @@ needs, so the floor is a pseudo-version; it moves to the first tag that does. ### Added +- `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` + calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the + module, through the same credentials and refresh-once rule as every authority + call. The host serves it only to a cross-tenant module principal; any other + caller gets the host's `PermissionDenied`, preserved. Positions are opaque + strings: `after` is exclusive, `""` reads from the oldest retained entry, and + `JournalPage.NextAfter` is the resume point the host guarantees misses no + committed entry and never passes an unfinished transaction, so a reader + resumes from it rather than from the last `Position` it saw, and only stores + and compares positions for equality. +- `JournalStart` (`"start"`) is the position that means "before the oldest + retained entry": the host accepts it as `after`, and as the `from` of its + delivery operation, and never returns it as an entry's position. A reader with + no position of its own starts from it. +- `deliveryBindings` are the caller's binding ids whose mints and exchanges count + as delivery machinery; the host sets `JournalEntry.DeliveryMachinery` on the + entries that record one of them, or the caller's own module work-context mint, + so a reader that delivers journal entries can tell a page of its own delivery + from a page of real activity. A limit outside 0..500 (0 is the host's default of + 100), no type, or an empty type or binding id is `ErrInvalidJournalRead` before + any call. +- Generated: `ReadJournal` on `ModuleCapabilitiesService`, with + `ModuleReadJournalRequest`, `ModuleJournalEntry` and + `ModuleReadJournalResponse`; `DeliverAuditEventsResponse.From` and `To` + carry the exact committed receipt interval for retention coverage checks. + - **`accounts.Client.Datasources()`**, the `DatasourceService` facade beside `Audit()`: `ListSources`, `GetSource`, `AddSource`, `ListSourceOperations`, `DeclareSourceOperations`, `InvokeSourceOperation`, @@ -144,39 +178,13 @@ shipped. `TOKEN_FILE` in its `workload-identity` configuration and passes `ProjectedTokenFile(path)` itself. -- `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` - calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the - module, through the same credentials and refresh-once rule as every authority - call. The host serves it only to a cross-tenant module principal; any other - caller gets the host's `PermissionDenied`, preserved. Positions are opaque - strings: `after` is exclusive, `""` reads from the oldest retained entry, and - `JournalPage.NextAfter` is the resume point the host guarantees misses no - committed entry and never passes an unfinished transaction, so a reader - resumes from it rather than from the last `Position` it saw, and only stores - and compares positions for equality. -- `JournalStart` (`"start"`) is the position that means "before the oldest - retained entry": the host accepts it as `after`, and as the `from` of its - delivery operation, and never returns it as an entry's position. A reader with - no position of its own starts from it. -- `deliveryBindings` are the caller's binding ids whose mints and exchanges count - as delivery machinery; the host sets `JournalEntry.DeliveryMachinery` on the - entries that record one of them, or the caller's own module work-context mint, - so a reader that delivers journal entries can tell a page of its own delivery - from a page of real activity. A limit outside 0..500 (0 is the host's default of - 100), no type, or an empty type or binding id is `ErrInvalidJournalRead` before - any call. -- Generated: `ReadJournal` on `ModuleCapabilitiesService`, with - `ModuleReadJournalRequest`, `ModuleJournalEntry` and - `ModuleReadJournalResponse`; `DeliverAuditEventsResponse.From` and `To` - carry the exact committed receipt interval for retention coverage checks. - ### Changed -- **`gen/` extends the module-saas-starter `9ab36e7e` snapshot in SDK v0.1.3** - with the complete host-owned journal and delivery contract. The generated - schema carries `ResolveWorkContextAuthority` alongside `ReadJournal`; - `SOURCE.txt` identifies the exact development source and the registry - removals are listed below. +- **`gen/` moves from module-saas-starter v0.0.94 to `9ab36e7e`** (the merge + commit of #1119 on `main`), which is where `ResolveWorkContextAuthority` + first exists. It carries every accounts contract change made since v0.0.94 — + `datasource`, `installations`, `api_keys`, `module_registration` and + `work_contexts` all move — and those changes are the host's, not this SDK's. - **`moduleauthority.ErrNoHostStateToMintFor`**, for HTTP **412** on the module Work Context and operation-context mints. The gateway at this ref relays @@ -191,13 +199,6 @@ shipped. ### Removed (breaking) -- **`WorkContextSources.ApprovedBuild` is removed.** The host's Core contract - no longer includes approved builds in `SealSource` or execution fields in - capability minting. The SDK now requires the host's exact Core - `v0.19.6-0.20261018051341-b81d92e6feda` dependency, reuses Core's sealed token - fixtures without legacy execution claims, and keeps workload identity checks - in the host. There is no replacement SDK verifier or compatibility parser. - - **`SessionId` is gone from three Work Context mint requests**, with their `GetSessionId()` accessors: `StartTaskWorkContextRequest`, `StartChildSessionWorkContextRequest` and `StartInstallationTaskRequest`. The @@ -205,43 +206,8 @@ shipped. carries is now the one its caller authenticated with, or a fresh one the issuer assigns and returns — so there is no replacement field and nothing to set. A consumer that set one was naming a session it did not own; it drops the - assignment. - -- The current host-owned schema also removes - `ModuleMintWorkContextRequest.Upstream` and `Manifest` (and their getters), - `SolutionFrontendBinding.ContractVersion` (and `GetContractVersion`), - `SolutionRegistration.Backend` (and `GetBackend`), the entire - `SolutionBackendBinding` message, and - `SolutionRegistrationStatus_SOLUTION_REGISTRATION_STATUS_INCOMPATIBLE`. - [Host main #1120](https://github.com/codefly-dev/module-saas-starter/pull/1120) - resolves addresses and frontend metadata from applied presence instead of - accepting process-reported backend/frontend halves. - The SDK's hand-written facades consume none of these removed symbols. - -### Conformance - -- Follow [host #1120](https://github.com/codefly-dev/module-saas-starter/pull/1120) - by exercising module mints on the workload listener. The work mint uses the - projected credential; operation/source mints retain their internal-token - checks. Conformance also checks the protected presenter assertion, rejection - of missing/forged assertions, and refusal on the public listener. Consumers - must resolve the workload endpoint as the `Gateway` seam. - -### Generated contract - -- The same host snapshot adds source reconfiguration (`UpdateSource` and - `SourceFetchScope`), installation readiness conditions and module authority - requests, grant lineage, and the explicit `SealsNoBinding` revision assertion. - These additive generated contracts require no new facade or verifier. - -- Regenerated the complete host-owned schema from - `module-saas-starter@d5ea5dc75790dfeb55a5492eaa7c7b759df7a84d` (PR #1106, module package - 0.1.1) with the pinned buf 1.73.0 and plugins. `SOURCE.txt` records that - exact development source, including the current delivered module authority - registry and workload gateway contract. The generated tree comes from that - single committed snapshot. This also carries the host's current - delivery/recovery, installation and datasource contracts. The SDK release - remains ordered after the host release. + assignment. These are the only exported symbols the regeneration removed: + nothing else in `gen/` lost a type, a field or an RPC. ## v0.1.2 — core v0.19.1; Work Contexts are core's, carried opaque diff --git a/SOURCE.txt b/SOURCE.txt index 96c364d..c847eb5 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,4 +1,4 @@ -gen/ generated from codefly-dev/module-saas-starter@d5ea5dc75790dfeb55a5492eaa7c7b759df7a84d +gen/ generated from codefly-dev/module-saas-starter@f025ffe2c050739becb3d43feedf699c4913e77d (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) diff --git a/conformance/hostgateway/AGENTS.md b/conformance/hostgateway/AGENTS.md index e63924b..2f99cdf 100644 --- a/conformance/hostgateway/AGENTS.md +++ b/conformance/hostgateway/AGENTS.md @@ -85,9 +85,11 @@ Since host `af5cef9a` (module-saas-starter#1120, in v0.0.103) the three mints are served on the gateway's workload endpoint alone and answer 404 on the public one, so the harness serves the gateway through the host's own `workloadListener`, as a native run's workload server does. A cell wraps that -listener in a waypoint-authenticated presenter check the SDK never sees, and -which is not under test here. The same commit dropped the internal token from -the work-context mint — that endpoint is its perimeter — and relays accounts' +listener in a waypoint-authenticated presenter check. The conformance suite +uses the host's own authenticated listener and presenter fixtures to check +missing, forged and duplicate assertions, forwarding of the trusted presenter, +and refusal on the public listener. The same commit dropped the internal token +from the work-context mint — that endpoint is its perimeter — and relays accounts' `Unauthenticated` there as 401 (`ErrInvalidCredentials`), as it already did `PermissionDenied`. The two operation mints still hold the internal token. diff --git a/moduleauthority/host_fake_test.go b/moduleauthority/host_fake_test.go index 192f548..1e76a4f 100644 --- a/moduleauthority/host_fake_test.go +++ b/moduleauthority/host_fake_test.go @@ -28,7 +28,7 @@ import ( "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1/accountsv1connect" ) -// This file fakes the two host seams as module-saas-starter v0.0.94 serves +// This file fakes the two host seams as the module-saas-starter source in SOURCE.txt serves // them, so the client is tested against the host's contract rather than // against itself: // From 1fcb2fc30464b0870aec40f06d1f0460f237f4bf Mon Sep 17 00:00:00 2001 From: crisfiorini-oss Date: Fri, 23 Oct 2026 23:11:20 +0000 Subject: [PATCH 11/18] fix(journal): align host provenance and use released Core --- CHANGELOG.md | 16 ++++++++++------ README.md | 12 +++++------- SOURCE.txt | 4 ++-- go.mod | 4 ++-- go.sum | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 108af7c..ab586c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,21 @@ ## Unreleased The journal contract uses the exact module-saas-starter development source in -`SOURCE.txt`. The Core floor remains the current host's immutable pseudo-version; -the floor moves to the first Core tag that carries the required API. +`SOURCE.txt`. The Core floor is the compatible published v0.19.6 release. ### Changed -- **Core floor: `v0.19.6-0.20261018051341-b81d92e6feda`** (Core main, - 2026-10-18), from v0.19.4 on this branch and v0.19.1 in v0.1.3. Core +- Refresh development provenance to the rebased module-saas-starter journal + source recorded in `SOURCE.txt`, with module package version 0.2.0. The full + generated API is byte-identical; no SDK release is published. + +- **Core floor: `v0.19.6`**, replacing the development pseudo-version + `v0.19.6-0.20261018051341-b81d92e6feda`. The dependency requires Go 1.27.3, + which is now this SDK's minimum too. Core `22ddde98` removed the approved build from a Work Context: `SealSource` lost `ApprovedBuild` and `ErrNoApprovedBuild` is gone, so v0.1.3 does not compile - for any consumer that requires that Core. It is also the Core the v0.0.103 - gateway requires. Core v0.19.5 still requires `SealSource.ApprovedBuild` and + for any consumer that requires that Core. Core v0.19.6 contains the required + APIs. Core v0.19.5 still requires `SealSource.ApprovedBuild` and cannot build this release; MVS never selects it beside this floor. - **`gen/` adds the journal and delivery contract to the current host schema.** The complete host-owned `saas/` tree is generated from the exact development diff --git a/README.md b/README.md index 915e1a4..3df20ca 100644 --- a/README.md +++ b/README.md @@ -526,13 +526,11 @@ The template generates the complete host-owned `saas/` schema tree. Its vendored `codefly/` import closure is compiled for Runnable annotations, with imports resolved to `github.com/codefly-dev/core/generated/go`; Core owns those Go descriptors. Emitting a second copy in this SDK would conflict at -protobuf initialization. The SDK requires Core at `b81d92e6feda` -(`v0.19.6-0.20261018051341-b81d92e6feda`), the Core the v0.0.103 gateway -requires: it supplies the Runnable package, and its `SealSource` has no -approved build. No tagged Core carries that yet; v0.19.5 still requires -`SealSource.ApprovedBuild`, so it cannot build this SDK, and the floor moves to -the first Core tag that contains `b81d92e6feda`. The SDK need not adopt the -host's entire dependency generation. Verify the compatible dependency floor +protobuf initialization. The SDK requires published Core `v0.19.6`, which contains +`b81d92e6feda`: it supplies the Runnable package, and its `SealSource` has no +approved build. The dependency requires Go 1.27.3. Core v0.19.5 still requires +`SealSource.ApprovedBuild`, so it cannot build this SDK. The SDK need not adopt +the host's entire dependency generation. Verify the compatible dependency floor with SDK and consumer builds when refreshing it. The generation plugin versions remain pinned. Record the release tag in `SOURCE.txt`; CI checks out that tag through diff --git a/SOURCE.txt b/SOURCE.txt index c847eb5..97cfbcb 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,8 +1,8 @@ -gen/ generated from codefly-dev/module-saas-starter@f025ffe2c050739becb3d43feedf699c4913e77d +gen/ generated from codefly-dev/module-saas-starter@757fa74d5cdf181234500a1788f2a42a67e8a601 (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) -module version: 0.1.1 (module/module.package.codefly.yaml) +module version: 0.2.0 (module/module.package.codefly.yaml) Development source: exact PR-head commit of module-saas-starter#1106. The entire host-owned saas/ tree is generated with this repository's buf.gen.yaml. Replace this development ref with the host release tag before publishing the SDK. diff --git a/go.mod b/go.mod index fa2f9ee..36dc005 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/codefly-dev/saas-sdk-go -go 1.27.0 +go 1.27.3 require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 connectrpc.com/connect v1.21.0 - github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda + github.com/codefly-dev/core v0.19.6 github.com/google/uuid v1.6.0 golang.org/x/tools v0.52.0 google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc diff --git a/go.sum b/go.sum index e7fbc60..57791e9 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= -github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda h1:ytTihtIEfVhQ7llQl+ls5swIL8JgJu89X0wlDMeqM+o= -github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda/go.mod h1:0uX/7lTBVaGV0RI2Mb3mpEr/PPdx/jZqXHym/U8FVDI= +github.com/codefly-dev/core v0.19.6 h1:ZkCChK/BioK+B7zZ0fp+/rXDCy0k+lsdFqHjyc5xFIg= +github.com/codefly-dev/core v0.19.6/go.mod h1:1ina3kP+YSAL4tlZvnEYd4P8bFGnJECfkP86ojeeF1A= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= From 509693197119234b0aa5d68c765360c89d9d2568 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:13:14 +0000 Subject: [PATCH 12/18] fix(sdk): refresh journal contract and reuse shared approval types --- .claude/skills/refresh-generated-api/SKILL.md | 3 +- AGENTS.md | 3 +- CHANGELOG.md | 33 +- README.md | 15 +- SOURCE.txt | 2 +- buf.gen.yaml | 5 + .../work_contexts.connect.go | 53 +++ gen/saas/accounts/v1/datasource.pb.go | 34 +- gen/saas/accounts/v1/installations.pb.go | 137 ++++--- .../accounts/v1/module_capabilities.pb.go | 60 +++- gen/saas/accounts/v1/work_contexts.pb.go | 79 ++-- .../v1/workload_operation_admission.pb.go | 340 ++++++++++++++++++ gen/saas/catalog/v1/catalog.pb.go | 22 +- go.mod | 7 +- go.sum | 14 +- 15 files changed, 658 insertions(+), 149 deletions(-) create mode 100644 gen/saas/accounts/v1/workload_operation_admission.pb.go diff --git a/.claude/skills/refresh-generated-api/SKILL.md b/.claude/skills/refresh-generated-api/SKILL.md index 533447b..3568daa 100644 --- a/.claude/skills/refresh-generated-api/SKILL.md +++ b/.claude/skills/refresh-generated-api/SKILL.md @@ -52,7 +52,8 @@ Three properties of that template matter: version that provides those packages; validate the dependency floor against SDK and consumer builds instead of copying the host's dependency generation. Generating another copy of Core's descriptors here creates duplicate protobuf - registrations at initialization. + registrations at initialization. Shared `host/` installation imports likewise + resolve to the canonical Authority client rather than another descriptor copy. - **`clean: true` wipes `gen/` first.** So a proto deleted upstream disappears here rather than lingering — which is how `AuditExportService` left in 0.1.0. diff --git a/AGENTS.md b/AGENTS.md index 7e66932..c9d3e11 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,7 +133,8 @@ examples. Read it rather than re-deriving the API from the tree. file descriptors embed length-prefixed package strings; a text rewrite corrupts them and panics at `init()`. Regenerate (skill below). Generate the host-owned `saas/` tree; Core's vendored Runnable imports resolve - to Core's Go dependency, never a second descriptor copy in this SDK. + to Core's Go dependency; `host/` installation imports resolve to the canonical + Authority client. Neither shared descriptor tree is copied into this SDK. - **Every generated type reachable from a facade needs an alias in that package's `types.go`** — and a generated enum needs its constants re-exported too, because an alias carries the type and not the constants. diff --git a/CHANGELOG.md b/CHANGELOG.md index ab586c1..7e4fac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,22 +3,24 @@ ## Unreleased The journal contract uses the exact module-saas-starter development source in -`SOURCE.txt`. The Core floor is the compatible published v0.19.6 release. +`SOURCE.txt`. Shared installation approval types come from the canonical +Authority client; its declared Core dependency sets the development floor. ### Changed - Refresh development provenance to the rebased module-saas-starter journal source recorded in `SOURCE.txt`, with module package version 0.2.0. The full - generated API is byte-identical; no SDK release is published. - -- **Core floor: `v0.19.6`**, replacing the development pseudo-version - `v0.19.6-0.20261018051341-b81d92e6feda`. The dependency requires Go 1.27.3, - which is now this SDK's minimum too. Core - `22ddde98` removed the approved build from a Work Context: `SealSource` lost - `ApprovedBuild` and `ErrNoApprovedBuild` is gone, so v0.1.3 does not compile - for any consumer that requires that Core. Core v0.19.6 contains the required - APIs. Core v0.19.5 still requires `SealSource.ApprovedBuild` and - cannot build this release; MVS never selects it beside this floor. + generated API carries the latest host fields, including installation approval + and workload operation admission. No SDK release is published. + +- **Shared installation approval bindings** resolve to the existing Authority + client at `v0.0.0-20261026143932-67b85b14a942`, rather than generating a second + `host.*` descriptor tree. That client requires Core + `v0.19.7-0.20261026092055-b23dc13543b2`, the SDK's development floor. Core + `v0.19.6` supplies the earlier Work Context APIs but does not satisfy the + canonical client's declared floor. These are development source pins; + publication remains gated on actual compatible owner releases. Go 1.27.3 + remains the minimum toolchain. - **`gen/` adds the journal and delivery contract to the current host schema.** The complete host-owned `saas/` tree is generated from the exact development source recorded in `SOURCE.txt`, with buf 1.73.0 and the existing plugin pins. @@ -41,7 +43,8 @@ The journal contract uses the exact module-saas-starter development source in - The journal generation adds methods to generated interfaces: `AuditServiceClient`/`Handler.DeliverAuditEvents` and - `.LookupDeliverAuditEvents`, and `ModuleCapabilitiesServiceClient`/`Handler.ReadJournal`. + `.LookupDeliverAuditEvents`, `ModuleCapabilitiesServiceClient`/`Handler.ReadJournal`, + and `WorkContextServiceClient`/`Handler.AdmitWorkloadOperation`. Implementers must provide the methods or embed the generated `Unimplemented…Handler`. No existing generated symbol is removed relative to current main. @@ -68,6 +71,12 @@ The journal contract uses the exact module-saas-starter development source in ### Added +- The refreshed host schema includes installation revision and explicit + `InstallSolutionRequest.Approval` (`ConsentApproval` from Authority), workload + operation admission, task operation bindings, declaration-digest checks, + viewer Work Context fields and service interface-package inventory. These + are the host's contracts; the SDK adds no alternate admission or approval path. + - `Client.ReadJournal(ctx, after, limit, types, deliveryBindings) (JournalPage, error)` calls `ModuleCapabilitiesService.ReadJournal` on the authority endpoint as the module, through the same credentials and refresh-once rule as every authority diff --git a/README.md b/README.md index 3df20ca..a81e5f6 100644 --- a/README.md +++ b/README.md @@ -526,12 +526,15 @@ The template generates the complete host-owned `saas/` schema tree. Its vendored `codefly/` import closure is compiled for Runnable annotations, with imports resolved to `github.com/codefly-dev/core/generated/go`; Core owns those Go descriptors. Emitting a second copy in this SDK would conflict at -protobuf initialization. The SDK requires published Core `v0.19.6`, which contains -`b81d92e6feda`: it supplies the Runnable package, and its `SealSource` has no -approved build. The dependency requires Go 1.27.3. Core v0.19.5 still requires -`SealSource.ApprovedBuild`, so it cannot build this SDK. The SDK need not adopt -the host's entire dependency generation. Verify the compatible dependency floor -with SDK and consumer builds when refreshing it. +protobuf initialization. The `host/` installation import similarly resolves to +`github.com/codefly-dev/authority/libraries/authority-client/go/gen`, the +existing owner of shared `ConsentApproval` bindings. The canonical Authority +client at `v0.0.0-20261026143932-67b85b14a942` requires Core +`v0.19.7-0.20261026092055-b23dc13543b2`; these development source pins are not +published release tags. Publishing the SDK requires compatible owner releases. +The dependency requires Go 1.27.3. Core v0.19.5 still requires +`SealSource.ApprovedBuild`, so it cannot build this SDK. Verify the compatible +dependency floor with SDK and consumer builds when refreshing it. The generation plugin versions remain pinned. Record the release tag in `SOURCE.txt`; CI checks out that tag through `refs/tags/` and verifies that regeneration leaves no diff. Before a host diff --git a/SOURCE.txt b/SOURCE.txt index 97cfbcb..d71b8e7 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,4 +1,4 @@ -gen/ generated from codefly-dev/module-saas-starter@757fa74d5cdf181234500a1788f2a42a67e8a601 +gen/ generated from codefly-dev/module-saas-starter@80e238cec651847d51ff7d5b23a102d56cf06bb4 (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) diff --git a/buf.gen.yaml b/buf.gen.yaml index 3663906..278e63d 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -19,6 +19,11 @@ managed: - file_option: go_package_prefix path: codefly value: github.com/codefly-dev/core/generated/go + # Shared installation approvals belong to the canonical Authority schema. + # Import those bindings rather than registering another host.* descriptor. + - file_option: go_package_prefix + path: host + value: github.com/codefly-dev/authority/libraries/authority-client/go/gen plugins: - local: [go, run, google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11] out: gen diff --git a/gen/saas/accounts/v1/accountsv1connect/work_contexts.connect.go b/gen/saas/accounts/v1/accountsv1connect/work_contexts.connect.go index b1cb716..6130c60 100644 --- a/gen/saas/accounts/v1/accountsv1connect/work_contexts.connect.go +++ b/gen/saas/accounts/v1/accountsv1connect/work_contexts.connect.go @@ -43,6 +43,9 @@ const ( // WorkContextServiceConsumeSingleUseProcedure is the fully-qualified name of the // WorkContextService's ConsumeSingleUse RPC. WorkContextServiceConsumeSingleUseProcedure = "/saas.accounts.v1.WorkContextService/ConsumeSingleUse" + // WorkContextServiceAdmitWorkloadOperationProcedure is the fully-qualified name of the + // WorkContextService's AdmitWorkloadOperation RPC. + WorkContextServiceAdmitWorkloadOperationProcedure = "/saas.accounts.v1.WorkContextService/AdmitWorkloadOperation" // WorkContextServiceStartTaskProcedure is the fully-qualified name of the WorkContextService's // StartTask RPC. WorkContextServiceStartTaskProcedure = "/saas.accounts.v1.WorkContextService/StartTask" @@ -82,6 +85,19 @@ type WorkContextServiceClient interface { // a replay and returns ALREADY_EXISTS. A caller must treat that as the // capability being spent and fail the operation closed, not retry it. ConsumeSingleUse(context.Context, *connect.Request[v1.ConsumeSingleUseWorkContextRequest]) (*connect.Response[emptypb.Empty], error) + // AdmitWorkloadOperation answers whether a capability presented with no + // bearer at the declared module route /v1//* is a module operation + // context (StartModuleOperationTask) for that alias, and admits nothing else; + // and whether the route's target is installed in the capability's tenant. + // The auth-gateway's bearer-less arm is its caller. Unlike the revision + // checks above, the signed capability itself crosses this boundary: the + // answer depends on its owner, owner kind and chain, which only the issuer + // reads. It reads the capability back (core's Authority.ReadBack: signature + // under this issuer, window, audience, revision, seal, epochs) and never + // verifies it as presented, so nothing is consumed and the callee still + // verifies at full strength. Internal-tier: the tenant it answers about is + // the one the capability names. + AdmitWorkloadOperation(context.Context, *connect.Request[v1.AdmitWorkloadOperationRequest]) (*connect.Response[v1.AdmitWorkloadOperationResponse], error) StartTask(context.Context, *connect.Request[v1.StartTaskWorkContextRequest]) (*connect.Response[v1.IssuedWorkContext], error) // StartInstallationTask is the headless mint. Unlike StartTask it opens with a // service credential rather than a bearer: it resolves the installation's owner @@ -135,6 +151,12 @@ func NewWorkContextServiceClient(httpClient connect.HTTPClient, baseURL string, connect.WithSchema(workContextServiceMethods.ByName("ConsumeSingleUse")), connect.WithClientOptions(opts...), ), + admitWorkloadOperation: connect.NewClient[v1.AdmitWorkloadOperationRequest, v1.AdmitWorkloadOperationResponse]( + httpClient, + baseURL+WorkContextServiceAdmitWorkloadOperationProcedure, + connect.WithSchema(workContextServiceMethods.ByName("AdmitWorkloadOperation")), + connect.WithClientOptions(opts...), + ), startTask: connect.NewClient[v1.StartTaskWorkContextRequest, v1.IssuedWorkContext]( httpClient, baseURL+WorkContextServiceStartTaskProcedure, @@ -179,6 +201,7 @@ type workContextServiceClient struct { checkAuthorizationRevision *connect.Client[v1.CheckAuthorizationRevisionRequest, emptypb.Empty] authorizeEvidenceRead *connect.Client[v1.AuthorizeEvidenceReadRequest, emptypb.Empty] consumeSingleUse *connect.Client[v1.ConsumeSingleUseWorkContextRequest, emptypb.Empty] + admitWorkloadOperation *connect.Client[v1.AdmitWorkloadOperationRequest, v1.AdmitWorkloadOperationResponse] startTask *connect.Client[v1.StartTaskWorkContextRequest, v1.IssuedWorkContext] startInstallationTask *connect.Client[v1.StartInstallationTaskRequest, v1.IssuedWorkContext] startRootSession *connect.Client[v1.StartRootSessionWorkContextRequest, v1.IssuedWorkContext] @@ -202,6 +225,11 @@ func (c *workContextServiceClient) ConsumeSingleUse(ctx context.Context, req *co return c.consumeSingleUse.CallUnary(ctx, req) } +// AdmitWorkloadOperation calls saas.accounts.v1.WorkContextService.AdmitWorkloadOperation. +func (c *workContextServiceClient) AdmitWorkloadOperation(ctx context.Context, req *connect.Request[v1.AdmitWorkloadOperationRequest]) (*connect.Response[v1.AdmitWorkloadOperationResponse], error) { + return c.admitWorkloadOperation.CallUnary(ctx, req) +} + // StartTask calls saas.accounts.v1.WorkContextService.StartTask. func (c *workContextServiceClient) StartTask(ctx context.Context, req *connect.Request[v1.StartTaskWorkContextRequest]) (*connect.Response[v1.IssuedWorkContext], error) { return c.startTask.CallUnary(ctx, req) @@ -252,6 +280,19 @@ type WorkContextServiceHandler interface { // a replay and returns ALREADY_EXISTS. A caller must treat that as the // capability being spent and fail the operation closed, not retry it. ConsumeSingleUse(context.Context, *connect.Request[v1.ConsumeSingleUseWorkContextRequest]) (*connect.Response[emptypb.Empty], error) + // AdmitWorkloadOperation answers whether a capability presented with no + // bearer at the declared module route /v1//* is a module operation + // context (StartModuleOperationTask) for that alias, and admits nothing else; + // and whether the route's target is installed in the capability's tenant. + // The auth-gateway's bearer-less arm is its caller. Unlike the revision + // checks above, the signed capability itself crosses this boundary: the + // answer depends on its owner, owner kind and chain, which only the issuer + // reads. It reads the capability back (core's Authority.ReadBack: signature + // under this issuer, window, audience, revision, seal, epochs) and never + // verifies it as presented, so nothing is consumed and the callee still + // verifies at full strength. Internal-tier: the tenant it answers about is + // the one the capability names. + AdmitWorkloadOperation(context.Context, *connect.Request[v1.AdmitWorkloadOperationRequest]) (*connect.Response[v1.AdmitWorkloadOperationResponse], error) StartTask(context.Context, *connect.Request[v1.StartTaskWorkContextRequest]) (*connect.Response[v1.IssuedWorkContext], error) // StartInstallationTask is the headless mint. Unlike StartTask it opens with a // service credential rather than a bearer: it resolves the installation's owner @@ -301,6 +342,12 @@ func NewWorkContextServiceHandler(svc WorkContextServiceHandler, opts ...connect connect.WithSchema(workContextServiceMethods.ByName("ConsumeSingleUse")), connect.WithHandlerOptions(opts...), ) + workContextServiceAdmitWorkloadOperationHandler := connect.NewUnaryHandler( + WorkContextServiceAdmitWorkloadOperationProcedure, + svc.AdmitWorkloadOperation, + connect.WithSchema(workContextServiceMethods.ByName("AdmitWorkloadOperation")), + connect.WithHandlerOptions(opts...), + ) workContextServiceStartTaskHandler := connect.NewUnaryHandler( WorkContextServiceStartTaskProcedure, svc.StartTask, @@ -345,6 +392,8 @@ func NewWorkContextServiceHandler(svc WorkContextServiceHandler, opts ...connect workContextServiceAuthorizeEvidenceReadHandler.ServeHTTP(w, r) case WorkContextServiceConsumeSingleUseProcedure: workContextServiceConsumeSingleUseHandler.ServeHTTP(w, r) + case WorkContextServiceAdmitWorkloadOperationProcedure: + workContextServiceAdmitWorkloadOperationHandler.ServeHTTP(w, r) case WorkContextServiceStartTaskProcedure: workContextServiceStartTaskHandler.ServeHTTP(w, r) case WorkContextServiceStartInstallationTaskProcedure: @@ -378,6 +427,10 @@ func (UnimplementedWorkContextServiceHandler) ConsumeSingleUse(context.Context, return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.WorkContextService.ConsumeSingleUse is not implemented")) } +func (UnimplementedWorkContextServiceHandler) AdmitWorkloadOperation(context.Context, *connect.Request[v1.AdmitWorkloadOperationRequest]) (*connect.Response[v1.AdmitWorkloadOperationResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.WorkContextService.AdmitWorkloadOperation is not implemented")) +} + func (UnimplementedWorkContextServiceHandler) StartTask(context.Context, *connect.Request[v1.StartTaskWorkContextRequest]) (*connect.Response[v1.IssuedWorkContext], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.WorkContextService.StartTask is not implemented")) } diff --git a/gen/saas/accounts/v1/datasource.pb.go b/gen/saas/accounts/v1/datasource.pb.go index ba4782c..9633707 100644 --- a/gen/saas/accounts/v1/datasource.pb.go +++ b/gen/saas/accounts/v1/datasource.pb.go @@ -7017,14 +7017,18 @@ func (x *ListSourceOperationsResponse) GetOperations() []*SourceOperation { // InvokeSourceOperationRequest carries exactly one JSON object in input_json. // effect_id must match x-codefly-effect-id; either location may supply it. type InvokeSourceOperationRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - OrgId string `protobuf:"bytes,1,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"` - SourceId string `protobuf:"bytes,2,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` - Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"` - InputJson string `protobuf:"bytes,4,opt,name=input_json,json=inputJson,proto3" json:"input_json,omitempty"` - EffectId string `protobuf:"bytes,5,opt,name=effect_id,json=effectId,proto3" json:"effect_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + OrgId string `protobuf:"bytes,1,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"` + SourceId string `protobuf:"bytes,2,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` + Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"` + InputJson string `protobuf:"bytes,4,opt,name=input_json,json=inputJson,proto3" json:"input_json,omitempty"` + EffectId string `protobuf:"bytes,5,opt,name=effect_id,json=effectId,proto3" json:"effect_id,omitempty"` + // Optional for legacy callers. A caller that admitted a declaration supplies + // its canonical digest; a replacement is refused before provider dispatch. + // This precondition participates in the receipt request fingerprint. + ExpectedDeclarationDigest *string `protobuf:"bytes,6,opt,name=expected_declaration_digest,json=expectedDeclarationDigest,proto3,oneof" json:"expected_declaration_digest,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *InvokeSourceOperationRequest) Reset() { @@ -7092,6 +7096,13 @@ func (x *InvokeSourceOperationRequest) GetEffectId() string { return "" } +func (x *InvokeSourceOperationRequest) GetExpectedDeclarationDigest() string { + if x != nil && x.ExpectedDeclarationDigest != nil { + return *x.ExpectedDeclarationDigest + } + return "" +} + // SourceOperationReceipt contains only the committed result and safe metadata. type SourceOperationReceipt struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -7919,14 +7930,16 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x1cListSourceOperationsResponse\x12A\n" + "\n" + "operations\x18\x01 \x03(\v2!.saas.accounts.v1.SourceOperationR\n" + - "operations\"\xf4\x01\n" + + "operations\"\xfa\x02\n" + "\x1cInvokeSourceOperationRequest\x12\x1f\n" + "\x06org_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x12%\n" + "\tsource_id\x18\x02 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\bsourceId\x12;\n" + "\toperation\x18\x03 \x01(\tB\x1d\xbaH\x1ar\x182\x16^[a-z][a-z0-9_]{0,63}$R\toperation\x12(\n" + "\n" + "input_json\x18\x04 \x01(\tB\t\xbaH\x06r\x04(\x80\x80\x04R\tinputJson\x12%\n" + - "\teffect_id\x18\x05 \x01(\tB\b\xbaH\x05r\x03(\x80\x01R\beffectId\"\xba\x01\n" + + "\teffect_id\x18\x05 \x01(\tB\b\xbaH\x05r\x03(\x80\x01R\beffectId\x12d\n" + + "\x1bexpected_declaration_digest\x18\x06 \x01(\tB\x1f\xbaH\x1cr\x1a2\x18^(sha256:[0-9a-f]{64})?$H\x00R\x19expectedDeclarationDigest\x88\x01\x01B\x1e\n" + + "\x1c_expected_declaration_digest\"\xba\x01\n" + "\x16SourceOperationReceipt\x12\x1b\n" + "\teffect_id\x18\x01 \x01(\tR\beffectId\x12!\n" + "\fcommitted_at\x18\x02 \x01(\tR\vcommittedAt\x12\x16\n" + @@ -8442,6 +8455,7 @@ func file_saas_accounts_v1_datasource_proto_init() { (*AddSourceRequest_CollectionLabel)(nil), } file_saas_accounts_v1_datasource_proto_msgTypes[64].OneofWrappers = []any{} + file_saas_accounts_v1_datasource_proto_msgTypes[85].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/gen/saas/accounts/v1/installations.pb.go b/gen/saas/accounts/v1/installations.pb.go index 20eaeab..9aa331f 100644 --- a/gen/saas/accounts/v1/installations.pb.go +++ b/gen/saas/accounts/v1/installations.pb.go @@ -8,6 +8,7 @@ package accountsv1 import ( _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + v1 "github.com/codefly-dev/authority/libraries/authority-client/go/gen/host/installation/v1" _ "github.com/codefly-dev/saas-sdk-go/gen/saas/policy/v1" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -404,6 +405,8 @@ type Installation struct { // Why the install step failed: the solution's bounded refusal reason, or a // fixed reason when it never answered. Empty unless status is FAILED. FailureReason string `protobuf:"bytes,13,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + // Current consent/lifecycle revision, for an explicit re-install approval. + Revision uint64 `protobuf:"varint,14,opt,name=revision,proto3" json:"revision,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -522,6 +525,13 @@ func (x *Installation) GetFailureReason() string { return "" } +func (x *Installation) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + type InstallSolutionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` OrgId string `protobuf:"bytes,1,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"` @@ -572,7 +582,9 @@ type InstallSolutionRequest struct { // artifact_policies, with the same shapes as the module envelope grant. // Must be a subset of the current ceiling. Omitted selects that ceiling; // an explicit {} grants nothing. Tenant and identity are never scope fields. - Scope *string `protobuf:"bytes,13,opt,name=scope,proto3,oneof" json:"scope,omitempty"` + Scope *string `protobuf:"bytes,13,opt,name=scope,proto3,oneof" json:"scope,omitempty"` + // Required: the exact offer and installation state reviewed by the admin. + Approval *v1.ConsentApproval `protobuf:"bytes,14,opt,name=approval,proto3" json:"approval,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -684,6 +696,13 @@ func (x *InstallSolutionRequest) GetScope() string { return "" } +func (x *InstallSolutionRequest) GetApproval() *v1.ConsentApproval { + if x != nil { + return x.Approval + } + return nil +} + // An org administrator may only remove installed authority. The host reads // the current revision and serializes the narrowing with other lifecycle acts. type NarrowInstallationRequest struct { @@ -2238,7 +2257,7 @@ var File_saas_accounts_v1_installations_proto protoreflect.FileDescriptor const file_saas_accounts_v1_installations_proto_rawDesc = "" + "\n" + - "$saas/accounts/v1/installations.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$saas/accounts/v1/authorization.proto\x1a\x1csaas/policy/v1/options.proto\"\x94\x05\n" + + "$saas/accounts/v1/installations.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a'host/installation/v1/installation.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$saas/accounts/v1/authorization.proto\x1a\x1csaas/policy/v1/options.proto\"\xb0\x05\n" + "\fInstallation\x12\x18\n" + "\x02id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x02id\x12\x1f\n" + "\x06org_id\x18\x02 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x126\n" + @@ -2254,8 +2273,9 @@ const file_saas_accounts_v1_installations_proto_rawDesc = "" + " \x01(\v2\x1a.google.protobuf.TimestampH\x00R\trevokedAt\x88\x01\x01\x12(\n" + "\ttarget_id\x18\v \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\btargetId\x12/\n" + "\x0erevoked_reason\x18\f \x01(\tB\b\xbaH\x05r\x03\x18\x80\x04R\rrevokedReason\x12/\n" + - "\x0efailure_reason\x18\r \x01(\tB\b\xbaH\x05r\x03\x18\x80\x04R\rfailureReasonB\r\n" + - "\v_revoked_atJ\x04\b\x04\x10\x05R\x13solution_identifier\"\xf6\x04\n" + + "\x0efailure_reason\x18\r \x01(\tB\b\xbaH\x05r\x03\x18\x80\x04R\rfailureReason\x12\x1a\n" + + "\brevision\x18\x0e \x01(\x04R\brevisionB\r\n" + + "\v_revoked_atJ\x04\b\x04\x10\x05R\x13solution_identifier\"\xb9\x05\n" + "\x16InstallSolutionRequest\x12\x1f\n" + "\x06org_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x125\n" + "\x10agent_identifier\x18\x02 \x01(\tB\n" + @@ -2269,7 +2289,8 @@ const file_saas_accounts_v1_installations_proto_rawDesc = "" + " \x03(\tB\x0f\xbaH\f\x92\x01\t\x10@\"\x05r\x03\x18\x80\x04R\x10allowedAudiences\x126\n" + "\x0eallowed_scopes\x18\v \x03(\tB\x0f\xbaH\f\x92\x01\t\x10@\"\x05r\x03\x18\x80\x01R\rallowedScopes\x12%\n" + "\ttarget_id\x18\f \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\btargetId\x12&\n" + - "\x05scope\x18\r \x01(\tB\v\xbaH\br\x06\x10\x02(\x80\x80@H\x00R\x05scope\x88\x01\x01B\b\n" + + "\x05scope\x18\r \x01(\tB\v\xbaH\br\x06\x10\x02(\x80\x80@H\x00R\x05scope\x88\x01\x01\x12A\n" + + "\bapproval\x18\x0e \x01(\v2%.host.installation.v1.ConsentApprovalR\bapprovalB\b\n" + "\x06_scopeJ\x04\b\x03\x10\x04J\x04\b\x05\x10\x06R\x13solution_identifierR\x0froot_scope_path\"\x92\x01\n" + "\x19NarrowInstallationRequest\x12\x1f\n" + "\x06org_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x121\n" + @@ -2496,63 +2517,65 @@ var file_saas_accounts_v1_installations_proto_goTypes = []any{ (*ListAvailableSolutionsRequest)(nil), // 27: saas.accounts.v1.ListAvailableSolutionsRequest (*ListAvailableSolutionsResponse)(nil), // 28: saas.accounts.v1.ListAvailableSolutionsResponse (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp - (*CollectionReadGrant)(nil), // 30: saas.accounts.v1.CollectionReadGrant - (*ScopeGrant)(nil), // 31: saas.accounts.v1.ScopeGrant - (*emptypb.Empty)(nil), // 32: google.protobuf.Empty + (*v1.ConsentApproval)(nil), // 30: host.installation.v1.ConsentApproval + (*CollectionReadGrant)(nil), // 31: saas.accounts.v1.CollectionReadGrant + (*ScopeGrant)(nil), // 32: saas.accounts.v1.ScopeGrant + (*emptypb.Empty)(nil), // 33: google.protobuf.Empty } var file_saas_accounts_v1_installations_proto_depIdxs = []int32{ 0, // 0: saas.accounts.v1.Installation.status:type_name -> saas.accounts.v1.InstallationStatus 29, // 1: saas.accounts.v1.Installation.created_at:type_name -> google.protobuf.Timestamp 29, // 2: saas.accounts.v1.Installation.revoked_at:type_name -> google.protobuf.Timestamp - 5, // 3: saas.accounts.v1.GetInstallationResponse.installation:type_name -> saas.accounts.v1.Installation - 1, // 4: saas.accounts.v1.GetInstallationResponse.health:type_name -> saas.accounts.v1.InstallationHealth - 0, // 5: saas.accounts.v1.ListInstallationsRequest.status:type_name -> saas.accounts.v1.InstallationStatus - 5, // 6: saas.accounts.v1.InstallationSummary.installation:type_name -> saas.accounts.v1.Installation - 1, // 7: saas.accounts.v1.InstallationSummary.health:type_name -> saas.accounts.v1.InstallationHealth - 14, // 8: saas.accounts.v1.ListInstallationsResponse.installations:type_name -> saas.accounts.v1.InstallationSummary - 5, // 9: saas.accounts.v1.OrganizationInstallation.installation:type_name -> saas.accounts.v1.Installation - 1, // 10: saas.accounts.v1.OrganizationInstallation.health:type_name -> saas.accounts.v1.InstallationHealth - 26, // 11: saas.accounts.v1.OrganizationInstallation.target:type_name -> saas.accounts.v1.AvailableSolution - 30, // 12: saas.accounts.v1.OrganizationInstallation.granted_teams:type_name -> saas.accounts.v1.CollectionReadGrant - 30, // 13: saas.accounts.v1.OrganizationInstallation.inherited_teams:type_name -> saas.accounts.v1.CollectionReadGrant - 19, // 14: saas.accounts.v1.OrganizationInstallation.readiness:type_name -> saas.accounts.v1.InstallationReadiness - 20, // 15: saas.accounts.v1.OrganizationInstallation.reached_modules:type_name -> saas.accounts.v1.ReachedModule - 2, // 16: saas.accounts.v1.ReadinessCondition.kind:type_name -> saas.accounts.v1.ReadinessConditionKind - 3, // 17: saas.accounts.v1.ReadinessCondition.cleared_by:type_name -> saas.accounts.v1.ReadinessClearedBy - 4, // 18: saas.accounts.v1.ReadinessCondition.action:type_name -> saas.accounts.v1.ReadinessAction - 18, // 19: saas.accounts.v1.InstallationReadiness.unmet:type_name -> saas.accounts.v1.ReadinessCondition - 17, // 20: saas.accounts.v1.ListOrganizationInstallationsResponse.installations:type_name -> saas.accounts.v1.OrganizationInstallation - 31, // 21: saas.accounts.v1.GrantInstallationToTeamResponse.grant:type_name -> saas.accounts.v1.ScopeGrant - 23, // 22: saas.accounts.v1.GrantInstallationToTeamResponse.carried:type_name -> saas.accounts.v1.CarriedModuleAccess - 18, // 23: saas.accounts.v1.GrantInstallationToTeamResponse.unmet:type_name -> saas.accounts.v1.ReadinessCondition - 26, // 24: saas.accounts.v1.ListAvailableSolutionsResponse.solutions:type_name -> saas.accounts.v1.AvailableSolution - 6, // 25: saas.accounts.v1.InstallationService.InstallSolution:input_type -> saas.accounts.v1.InstallSolutionRequest - 9, // 26: saas.accounts.v1.InstallationService.UninstallSolution:input_type -> saas.accounts.v1.UninstallSolutionRequest - 7, // 27: saas.accounts.v1.InstallationService.NarrowInstallation:input_type -> saas.accounts.v1.NarrowInstallationRequest - 8, // 28: saas.accounts.v1.InstallationService.RetryInstallation:input_type -> saas.accounts.v1.RetryInstallationRequest - 10, // 29: saas.accounts.v1.InstallationService.TransferInstallationOwnership:input_type -> saas.accounts.v1.TransferInstallationOwnershipRequest - 11, // 30: saas.accounts.v1.InstallationService.GetInstallation:input_type -> saas.accounts.v1.GetInstallationRequest - 13, // 31: saas.accounts.v1.InstallationService.ListInstallations:input_type -> saas.accounts.v1.ListInstallationsRequest - 27, // 32: saas.accounts.v1.InstallationService.ListAvailableSolutions:input_type -> saas.accounts.v1.ListAvailableSolutionsRequest - 16, // 33: saas.accounts.v1.InstallationService.ListOrganizationInstallations:input_type -> saas.accounts.v1.ListOrganizationInstallationsRequest - 22, // 34: saas.accounts.v1.InstallationService.GrantInstallationToTeam:input_type -> saas.accounts.v1.GrantInstallationToTeamRequest - 25, // 35: saas.accounts.v1.InstallationService.RevokeInstallationFromTeam:input_type -> saas.accounts.v1.RevokeInstallationFromTeamRequest - 5, // 36: saas.accounts.v1.InstallationService.InstallSolution:output_type -> saas.accounts.v1.Installation - 32, // 37: saas.accounts.v1.InstallationService.UninstallSolution:output_type -> google.protobuf.Empty - 32, // 38: saas.accounts.v1.InstallationService.NarrowInstallation:output_type -> google.protobuf.Empty - 5, // 39: saas.accounts.v1.InstallationService.RetryInstallation:output_type -> saas.accounts.v1.Installation - 5, // 40: saas.accounts.v1.InstallationService.TransferInstallationOwnership:output_type -> saas.accounts.v1.Installation - 12, // 41: saas.accounts.v1.InstallationService.GetInstallation:output_type -> saas.accounts.v1.GetInstallationResponse - 15, // 42: saas.accounts.v1.InstallationService.ListInstallations:output_type -> saas.accounts.v1.ListInstallationsResponse - 28, // 43: saas.accounts.v1.InstallationService.ListAvailableSolutions:output_type -> saas.accounts.v1.ListAvailableSolutionsResponse - 21, // 44: saas.accounts.v1.InstallationService.ListOrganizationInstallations:output_type -> saas.accounts.v1.ListOrganizationInstallationsResponse - 24, // 45: saas.accounts.v1.InstallationService.GrantInstallationToTeam:output_type -> saas.accounts.v1.GrantInstallationToTeamResponse - 32, // 46: saas.accounts.v1.InstallationService.RevokeInstallationFromTeam:output_type -> google.protobuf.Empty - 36, // [36:47] is the sub-list for method output_type - 25, // [25:36] is the sub-list for method input_type - 25, // [25:25] is the sub-list for extension type_name - 25, // [25:25] is the sub-list for extension extendee - 0, // [0:25] is the sub-list for field type_name + 30, // 3: saas.accounts.v1.InstallSolutionRequest.approval:type_name -> host.installation.v1.ConsentApproval + 5, // 4: saas.accounts.v1.GetInstallationResponse.installation:type_name -> saas.accounts.v1.Installation + 1, // 5: saas.accounts.v1.GetInstallationResponse.health:type_name -> saas.accounts.v1.InstallationHealth + 0, // 6: saas.accounts.v1.ListInstallationsRequest.status:type_name -> saas.accounts.v1.InstallationStatus + 5, // 7: saas.accounts.v1.InstallationSummary.installation:type_name -> saas.accounts.v1.Installation + 1, // 8: saas.accounts.v1.InstallationSummary.health:type_name -> saas.accounts.v1.InstallationHealth + 14, // 9: saas.accounts.v1.ListInstallationsResponse.installations:type_name -> saas.accounts.v1.InstallationSummary + 5, // 10: saas.accounts.v1.OrganizationInstallation.installation:type_name -> saas.accounts.v1.Installation + 1, // 11: saas.accounts.v1.OrganizationInstallation.health:type_name -> saas.accounts.v1.InstallationHealth + 26, // 12: saas.accounts.v1.OrganizationInstallation.target:type_name -> saas.accounts.v1.AvailableSolution + 31, // 13: saas.accounts.v1.OrganizationInstallation.granted_teams:type_name -> saas.accounts.v1.CollectionReadGrant + 31, // 14: saas.accounts.v1.OrganizationInstallation.inherited_teams:type_name -> saas.accounts.v1.CollectionReadGrant + 19, // 15: saas.accounts.v1.OrganizationInstallation.readiness:type_name -> saas.accounts.v1.InstallationReadiness + 20, // 16: saas.accounts.v1.OrganizationInstallation.reached_modules:type_name -> saas.accounts.v1.ReachedModule + 2, // 17: saas.accounts.v1.ReadinessCondition.kind:type_name -> saas.accounts.v1.ReadinessConditionKind + 3, // 18: saas.accounts.v1.ReadinessCondition.cleared_by:type_name -> saas.accounts.v1.ReadinessClearedBy + 4, // 19: saas.accounts.v1.ReadinessCondition.action:type_name -> saas.accounts.v1.ReadinessAction + 18, // 20: saas.accounts.v1.InstallationReadiness.unmet:type_name -> saas.accounts.v1.ReadinessCondition + 17, // 21: saas.accounts.v1.ListOrganizationInstallationsResponse.installations:type_name -> saas.accounts.v1.OrganizationInstallation + 32, // 22: saas.accounts.v1.GrantInstallationToTeamResponse.grant:type_name -> saas.accounts.v1.ScopeGrant + 23, // 23: saas.accounts.v1.GrantInstallationToTeamResponse.carried:type_name -> saas.accounts.v1.CarriedModuleAccess + 18, // 24: saas.accounts.v1.GrantInstallationToTeamResponse.unmet:type_name -> saas.accounts.v1.ReadinessCondition + 26, // 25: saas.accounts.v1.ListAvailableSolutionsResponse.solutions:type_name -> saas.accounts.v1.AvailableSolution + 6, // 26: saas.accounts.v1.InstallationService.InstallSolution:input_type -> saas.accounts.v1.InstallSolutionRequest + 9, // 27: saas.accounts.v1.InstallationService.UninstallSolution:input_type -> saas.accounts.v1.UninstallSolutionRequest + 7, // 28: saas.accounts.v1.InstallationService.NarrowInstallation:input_type -> saas.accounts.v1.NarrowInstallationRequest + 8, // 29: saas.accounts.v1.InstallationService.RetryInstallation:input_type -> saas.accounts.v1.RetryInstallationRequest + 10, // 30: saas.accounts.v1.InstallationService.TransferInstallationOwnership:input_type -> saas.accounts.v1.TransferInstallationOwnershipRequest + 11, // 31: saas.accounts.v1.InstallationService.GetInstallation:input_type -> saas.accounts.v1.GetInstallationRequest + 13, // 32: saas.accounts.v1.InstallationService.ListInstallations:input_type -> saas.accounts.v1.ListInstallationsRequest + 27, // 33: saas.accounts.v1.InstallationService.ListAvailableSolutions:input_type -> saas.accounts.v1.ListAvailableSolutionsRequest + 16, // 34: saas.accounts.v1.InstallationService.ListOrganizationInstallations:input_type -> saas.accounts.v1.ListOrganizationInstallationsRequest + 22, // 35: saas.accounts.v1.InstallationService.GrantInstallationToTeam:input_type -> saas.accounts.v1.GrantInstallationToTeamRequest + 25, // 36: saas.accounts.v1.InstallationService.RevokeInstallationFromTeam:input_type -> saas.accounts.v1.RevokeInstallationFromTeamRequest + 5, // 37: saas.accounts.v1.InstallationService.InstallSolution:output_type -> saas.accounts.v1.Installation + 33, // 38: saas.accounts.v1.InstallationService.UninstallSolution:output_type -> google.protobuf.Empty + 33, // 39: saas.accounts.v1.InstallationService.NarrowInstallation:output_type -> google.protobuf.Empty + 5, // 40: saas.accounts.v1.InstallationService.RetryInstallation:output_type -> saas.accounts.v1.Installation + 5, // 41: saas.accounts.v1.InstallationService.TransferInstallationOwnership:output_type -> saas.accounts.v1.Installation + 12, // 42: saas.accounts.v1.InstallationService.GetInstallation:output_type -> saas.accounts.v1.GetInstallationResponse + 15, // 43: saas.accounts.v1.InstallationService.ListInstallations:output_type -> saas.accounts.v1.ListInstallationsResponse + 28, // 44: saas.accounts.v1.InstallationService.ListAvailableSolutions:output_type -> saas.accounts.v1.ListAvailableSolutionsResponse + 21, // 45: saas.accounts.v1.InstallationService.ListOrganizationInstallations:output_type -> saas.accounts.v1.ListOrganizationInstallationsResponse + 24, // 46: saas.accounts.v1.InstallationService.GrantInstallationToTeam:output_type -> saas.accounts.v1.GrantInstallationToTeamResponse + 33, // 47: saas.accounts.v1.InstallationService.RevokeInstallationFromTeam:output_type -> google.protobuf.Empty + 37, // [37:48] is the sub-list for method output_type + 26, // [26:37] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name } func init() { file_saas_accounts_v1_installations_proto_init() } diff --git a/gen/saas/accounts/v1/module_capabilities.pb.go b/gen/saas/accounts/v1/module_capabilities.pb.go index bb3db6e..49e356e 100644 --- a/gen/saas/accounts/v1/module_capabilities.pb.go +++ b/gen/saas/accounts/v1/module_capabilities.pb.go @@ -4188,13 +4188,17 @@ func (x *CollectionSyncProvenance) GetRequestedByLabel() string { return "" } -// Tenant and viewer come exclusively from the verified Work Context. +// The calling module authenticates with its own module Work Context in the +// x-codefly-work-context header; the viewer it reads for travels here, and +// must be addressed to that module. Tenant and viewer come exclusively from +// the verified viewer Work Context. type ListReadableSourceCollectionsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + ViewerWorkContextToken string `protobuf:"bytes,3,opt,name=viewer_work_context_token,json=viewerWorkContextToken,proto3" json:"viewer_work_context_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListReadableSourceCollectionsRequest) Reset() { @@ -4241,6 +4245,13 @@ func (x *ListReadableSourceCollectionsRequest) GetPageToken() string { return "" } +func (x *ListReadableSourceCollectionsRequest) GetViewerWorkContextToken() string { + if x != nil { + return x.ViewerWorkContextToken + } + return "" +} + type ListReadableSourceCollectionsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Collections []*ReadableSourceCollection `protobuf:"bytes,1,rep,name=collections,proto3" json:"collections,omitempty"` @@ -4439,15 +4450,19 @@ func (x *ModulePlaceRecordResponse) GetNodeId() string { return "" } -// Exact-record delegated oracle. Tenant and subjects come only from the signed -// forwarded Work Context; a caller cannot provide a scope path or principal. +// Exact-record delegated oracle. The calling module authenticates with its +// own module Work Context in the x-codefly-work-context header; the viewer it +// asks for travels here, and must be addressed to that module. Tenant and +// subjects come only from the signed viewer Work Context; a caller cannot +// provide a scope path or principal. type CheckWorkContextRecordAccessRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ResourceType string `protobuf:"bytes,1,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"` - ResourceId string `protobuf:"bytes,2,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` - Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ResourceType string `protobuf:"bytes,1,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"` + ResourceId string `protobuf:"bytes,2,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` + ViewerWorkContextToken string `protobuf:"bytes,4,opt,name=viewer_work_context_token,json=viewerWorkContextToken,proto3" json:"viewer_work_context_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CheckWorkContextRecordAccessRequest) Reset() { @@ -4501,6 +4516,13 @@ func (x *CheckWorkContextRecordAccessRequest) GetAction() string { return "" } +func (x *CheckWorkContextRecordAccessRequest) GetViewerWorkContextToken() string { + if x != nil { + return x.ViewerWorkContextToken + } + return "" +} + type CheckWorkContextRecordAccessResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // False also covers an unplaced or inaccessible record; no scope is disclosed. @@ -6472,12 +6494,13 @@ const file_saas_accounts_v1_module_capabilities_proto_rawDesc = "" + "\atrigger\x18\x04 \x01(\tR\atrigger\x12W\n" + "\x14requester_disclosure\x18\x05 \x01(\x0e2$.saas.accounts.v1.MetadataDisclosureR\x13requesterDisclosure\x12=\n" + "\frequested_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\vrequestedAt\x12,\n" + - "\x12requested_by_label\x18\a \x01(\tR\x10requestedByLabel\"x\n" + + "\x12requested_by_label\x18\a \x01(\tR\x10requestedByLabel\"\xc0\x01\n" + "$ListReadableSourceCollectionsRequest\x12'\n" + "\tpage_size\x18\x01 \x01(\x05B\n" + "\xbaH\a\x1a\x05\x18\xe8\a(\x00R\bpageSize\x12'\n" + "\n" + - "page_token\x18\x02 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x10R\tpageToken\"\x9d\x01\n" + + "page_token\x18\x02 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x10R\tpageToken\x12F\n" + + "\x19viewer_work_context_token\x18\x03 \x01(\tB\v\xbaH\br\x06\x10\x01\x18\x80\x80\x02R\x16viewerWorkContextToken\"\x9d\x01\n" + "%ListReadableSourceCollectionsResponse\x12L\n" + "\vcollections\x18\x01 \x03(\v2*.saas.accounts.v1.ReadableSourceCollectionR\vcollections\x12&\n" + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa3\x02\n" + @@ -6493,7 +6516,7 @@ const file_saas_accounts_v1_module_capabilities_proto_rawDesc = "" + "\xbaH\ar\x05\x10\x01\x18\x80\x04R\n" + "resourceId\">\n" + "\x19ModulePlaceRecordResponse\x12!\n" + - "\anode_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x06nodeId\"\xa7\x01\n" + + "\anode_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x06nodeId\"\xef\x01\n" + "#CheckWorkContextRecordAccessRequest\x12/\n" + "\rresource_type\x18\x01 \x01(\tB\n" + "\xbaH\ar\x05\x10\x01\x18\x80\x02R\fresourceType\x12+\n" + @@ -6501,7 +6524,8 @@ const file_saas_accounts_v1_module_capabilities_proto_rawDesc = "" + "\xbaH\ar\x05\x10\x01\x18\x80\x04R\n" + "resourceId\x12\"\n" + "\x06action\x18\x03 \x01(\tB\n" + - "\xbaH\ar\x05\x10\x01\x18\x80\x02R\x06action\"d\n" + + "\xbaH\ar\x05\x10\x01\x18\x80\x02R\x06action\x12F\n" + + "\x19viewer_work_context_token\x18\x04 \x01(\tB\v\xbaH\br\x06\x10\x01\x18\x80\x80\x02R\x16viewerWorkContextToken\"d\n" + "$CheckWorkContextRecordAccessResponse\x12\x18\n" + "\aallowed\x18\x01 \x01(\bR\aallowed\x12\"\n" + "\rscope_node_id\x18\x02 \x01(\tR\vscopeNodeId\"\x9f\x01\n" + diff --git a/gen/saas/accounts/v1/work_contexts.pb.go b/gen/saas/accounts/v1/work_contexts.pb.go index a44e74d..d110c08 100644 --- a/gen/saas/accounts/v1/work_contexts.pb.go +++ b/gen/saas/accounts/v1/work_contexts.pb.go @@ -172,9 +172,16 @@ type StartTaskWorkContextRequest struct { // capability to this exact workspace and reject cross-workspace reuse. WorkspaceId *string `protobuf:"bytes,9,opt,name=workspace_id,json=workspaceId,proto3,oneof" json:"workspace_id,omitempty"` // Optional product/project attribution inside workspace_id. - ProjectId *string `protobuf:"bytes,10,opt,name=project_id,json=projectId,proto3,oneof" json:"project_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ProjectId *string `protobuf:"bytes,10,opt,name=project_id,json=projectId,proto3,oneof" json:"project_id,omitempty"` + // Operation bindings of the audience module, by name, whose invoke scopes + // this capability carries beside authority_scopes. The host resolves each + // name to the binding as installed at org_id, narrowed by that installation + // exactly as the module's delegated exchange redeems it, and keeps each + // resource only if the person holds it. The caller names a binding and never + // its resource ids. A direct human-owned mint only. + OperationBindings []string `protobuf:"bytes,11,rep,name=operation_bindings,json=operationBindings,proto3" json:"operation_bindings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *StartTaskWorkContextRequest) Reset() { @@ -270,6 +277,13 @@ func (x *StartTaskWorkContextRequest) GetProjectId() string { return "" } +func (x *StartTaskWorkContextRequest) GetOperationBindings() []string { + if x != nil { + return x.OperationBindings + } + return nil +} + // StartInstallationTaskRequest is the headless (no-user-present) mint path. The // caller is a trusted internal service (a job consumer leasing an ingest job), // not a bearer: owner := the installation's owner of record; actor := the agent @@ -1195,18 +1209,17 @@ var File_saas_accounts_v1_work_contexts_proto protoreflect.FileDescriptor const file_saas_accounts_v1_work_contexts_proto_rawDesc = "" + "\n" + - "$saas/accounts/v1/work_contexts.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1csaas/policy/v1/options.proto\"\xad\x01\n" + + "$saas/accounts/v1/work_contexts.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a3saas/accounts/v1/workload_operation_admission.proto\x1a\x1csaas/policy/v1/options.proto\"\xad\x01\n" + "\x10WorkContextScope\x12/\n" + "\rresource_kind\x18\x01 \x01(\tB\n" + "\xbaH\ar\x05\x10\x01\x18\x80\x01R\fresourceKind\x12/\n" + "\aactions\x18\x02 \x03(\tB\x15\xbaH\x12\x92\x01\x0f\b\x01\x10@\x18\x01\"\ar\x05\x10\x01\x18\x80\x01R\aactions\x127\n" + - "\fresource_ids\x18\x03 \x03(\tB\x14\xbaH\x11\x92\x01\x0e\x10\x80\x02\x18\x01\"\ar\x05\x10\x01\x18\x80\x04R\vresourceIds\"\xa8\x04\n" + + "\fresource_ids\x18\x03 \x03(\tB\x14\xbaH\x11\x92\x01\x0e\x10\x80\x02\x18\x01\"\ar\x05\x10\x01\x18\x80\x04R\vresourceIds\"\x88\x06\n" + "\x1bStartTaskWorkContextRequest\x12\x1f\n" + "\x06org_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x12$\n" + "\atask_id\x18\x02 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\x06taskId\x12,\n" + - "\x12actor_principal_id\x18\x04 \x01(\tR\x10actorPrincipalId\x12Y\n" + - "\x10authority_scopes\x18\x05 \x03(\v2\".saas.accounts.v1.WorkContextScopeB\n" + - "\xbaH\a\x92\x01\x04\b\x01\x10@R\x0fauthorityScopes\x12&\n" + + "\x12actor_principal_id\x18\x04 \x01(\tR\x10actorPrincipalId\x12W\n" + + "\x10authority_scopes\x18\x05 \x03(\v2\".saas.accounts.v1.WorkContextScopeB\b\xbaH\x05\x92\x01\x02\x10@R\x0fauthorityScopes\x12&\n" + "\baudience\x18\x06 \x01(\tB\n" + "\xbaH\ar\x05\x10\x01\x18\x80\x04R\baudience\x12N\n" + "\rreplay_policy\x18\a \x01(\x0e2).saas.accounts.v1.WorkContextReplayPolicyR\freplayPolicy\x12+\n" + @@ -1218,7 +1231,9 @@ const file_saas_accounts_v1_work_contexts_proto_rawDesc = "" + "\n" + "project_id\x18\n" + " \x01(\tB\n" + - "\xbaH\ar\x05\x10\x01\x18\x80\x04H\x01R\tprojectId\x88\x01\x01B\x0f\n" + + "\xbaH\ar\x05\x10\x01\x18\x80\x04H\x01R\tprojectId\x88\x01\x01\x12B\n" + + "\x12operation_bindings\x18\v \x03(\tB\x13\xbaH\x10\x92\x01\r\x10@\x18\x01\"\ar\x05\x10\x01\x18\x80\x02R\x11operationBindings:\x9b\x01\xbaH\x97\x01\x1a\x94\x01\n" + + "\x18start_task.authority_ask\x122authority_scopes or operation_bindings is required\x1aDsize(this.authority_scopes) > 0 || size(this.operation_bindings) > 0B\x0f\n" + "\r_workspace_idB\r\n" + "\v_project_idJ\x04\b\x03\x10\x04R\n" + "session_id\"\xab\x04\n" + @@ -1345,11 +1360,12 @@ const file_saas_accounts_v1_work_contexts_proto_rawDesc = "" + "\x17WorkContextReplayPolicy\x12*\n" + "&WORK_CONTEXT_REPLAY_POLICY_UNSPECIFIED\x10\x00\x12)\n" + "%WORK_CONTEXT_REPLAY_POLICY_IDEMPOTENT\x10\x01\x12)\n" + - "%WORK_CONTEXT_REPLAY_POLICY_SINGLE_USE\x10\x022\xfb\f\n" + + "%WORK_CONTEXT_REPLAY_POLICY_SINGLE_USE\x10\x022\x93\x0e\n" + "\x12WorkContextService\x12\x83\x01\n" + "\x1aCheckAuthorizationRevision\x123.saas.accounts.v1.CheckAuthorizationRevisionRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12y\n" + "\x15AuthorizeEvidenceRead\x12..saas.accounts.v1.AuthorizeEvidenceReadRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12z\n" + - "\x10ConsumeSingleUse\x124.saas.accounts.v1.ConsumeSingleUseWorkContextRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\xca\x01\n" + + "\x10ConsumeSingleUse\x124.saas.accounts.v1.ConsumeSingleUseWorkContextRequest\x1a\x16.google.protobuf.Empty\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\x95\x01\n" + + "\x16AdmitWorkloadOperation\x12/.saas.accounts.v1.AdmitWorkloadOperationRequest\x1a0.saas.accounts.v1.AdmitWorkloadOperationResponse\"\x18\xc2\xf3\x18\x14\b\x03\x10\x010\x01:\x02\x10\x01@\x01H\aP\x03X\x03`\x01\x12\xca\x01\n" + "\tStartTask\x12-.saas.accounts.v1.StartTaskWorkContextRequest\x1a#.saas.accounts.v1.IssuedWorkContext\"i\xc2\xf3\x18D\b\x02\x10\x03*\f\n" + "\x06org_id\x10\x02\x18\x010\x01:\"\n" + "\x1esaas.work_context.task_started\x10\x02@\x01H\x05P\x03X\x04`\x01x\x02\x82\xd3\xe4\x93\x02\x1b:\x01*\"\x16/v1/work-contexts:task\x12\x86\x01\n" + @@ -1397,7 +1413,9 @@ var file_saas_accounts_v1_work_contexts_proto_goTypes = []any{ (*AuthorizeEvidenceReadRequest)(nil), // 11: saas.accounts.v1.AuthorizeEvidenceReadRequest (*ConsumeSingleUseWorkContextRequest)(nil), // 12: saas.accounts.v1.ConsumeSingleUseWorkContextRequest (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 14: google.protobuf.Empty + (*AdmitWorkloadOperationRequest)(nil), // 14: saas.accounts.v1.AdmitWorkloadOperationRequest + (*emptypb.Empty)(nil), // 15: google.protobuf.Empty + (*AdmitWorkloadOperationResponse)(nil), // 16: saas.accounts.v1.AdmitWorkloadOperationResponse } var file_saas_accounts_v1_work_contexts_proto_depIdxs = []int32{ 1, // 0: saas.accounts.v1.StartTaskWorkContextRequest.authority_scopes:type_name -> saas.accounts.v1.WorkContextScope @@ -1418,23 +1436,25 @@ var file_saas_accounts_v1_work_contexts_proto_depIdxs = []int32{ 10, // 15: saas.accounts.v1.WorkContextService.CheckAuthorizationRevision:input_type -> saas.accounts.v1.CheckAuthorizationRevisionRequest 11, // 16: saas.accounts.v1.WorkContextService.AuthorizeEvidenceRead:input_type -> saas.accounts.v1.AuthorizeEvidenceReadRequest 12, // 17: saas.accounts.v1.WorkContextService.ConsumeSingleUse:input_type -> saas.accounts.v1.ConsumeSingleUseWorkContextRequest - 2, // 18: saas.accounts.v1.WorkContextService.StartTask:input_type -> saas.accounts.v1.StartTaskWorkContextRequest - 3, // 19: saas.accounts.v1.WorkContextService.StartInstallationTask:input_type -> saas.accounts.v1.StartInstallationTaskRequest - 4, // 20: saas.accounts.v1.WorkContextService.StartRootSession:input_type -> saas.accounts.v1.StartRootSessionWorkContextRequest - 5, // 21: saas.accounts.v1.WorkContextService.ExchangeAudience:input_type -> saas.accounts.v1.ExchangeWorkContextAudienceRequest - 6, // 22: saas.accounts.v1.WorkContextService.StartChildSession:input_type -> saas.accounts.v1.StartChildSessionWorkContextRequest - 7, // 23: saas.accounts.v1.WorkContextService.RenewWorkContext:input_type -> saas.accounts.v1.RenewWorkContextRequest - 14, // 24: saas.accounts.v1.WorkContextService.CheckAuthorizationRevision:output_type -> google.protobuf.Empty - 14, // 25: saas.accounts.v1.WorkContextService.AuthorizeEvidenceRead:output_type -> google.protobuf.Empty - 14, // 26: saas.accounts.v1.WorkContextService.ConsumeSingleUse:output_type -> google.protobuf.Empty - 8, // 27: saas.accounts.v1.WorkContextService.StartTask:output_type -> saas.accounts.v1.IssuedWorkContext - 8, // 28: saas.accounts.v1.WorkContextService.StartInstallationTask:output_type -> saas.accounts.v1.IssuedWorkContext - 8, // 29: saas.accounts.v1.WorkContextService.StartRootSession:output_type -> saas.accounts.v1.IssuedWorkContext - 8, // 30: saas.accounts.v1.WorkContextService.ExchangeAudience:output_type -> saas.accounts.v1.IssuedWorkContext - 8, // 31: saas.accounts.v1.WorkContextService.StartChildSession:output_type -> saas.accounts.v1.IssuedWorkContext - 8, // 32: saas.accounts.v1.WorkContextService.RenewWorkContext:output_type -> saas.accounts.v1.IssuedWorkContext - 24, // [24:33] is the sub-list for method output_type - 15, // [15:24] is the sub-list for method input_type + 14, // 18: saas.accounts.v1.WorkContextService.AdmitWorkloadOperation:input_type -> saas.accounts.v1.AdmitWorkloadOperationRequest + 2, // 19: saas.accounts.v1.WorkContextService.StartTask:input_type -> saas.accounts.v1.StartTaskWorkContextRequest + 3, // 20: saas.accounts.v1.WorkContextService.StartInstallationTask:input_type -> saas.accounts.v1.StartInstallationTaskRequest + 4, // 21: saas.accounts.v1.WorkContextService.StartRootSession:input_type -> saas.accounts.v1.StartRootSessionWorkContextRequest + 5, // 22: saas.accounts.v1.WorkContextService.ExchangeAudience:input_type -> saas.accounts.v1.ExchangeWorkContextAudienceRequest + 6, // 23: saas.accounts.v1.WorkContextService.StartChildSession:input_type -> saas.accounts.v1.StartChildSessionWorkContextRequest + 7, // 24: saas.accounts.v1.WorkContextService.RenewWorkContext:input_type -> saas.accounts.v1.RenewWorkContextRequest + 15, // 25: saas.accounts.v1.WorkContextService.CheckAuthorizationRevision:output_type -> google.protobuf.Empty + 15, // 26: saas.accounts.v1.WorkContextService.AuthorizeEvidenceRead:output_type -> google.protobuf.Empty + 15, // 27: saas.accounts.v1.WorkContextService.ConsumeSingleUse:output_type -> google.protobuf.Empty + 16, // 28: saas.accounts.v1.WorkContextService.AdmitWorkloadOperation:output_type -> saas.accounts.v1.AdmitWorkloadOperationResponse + 8, // 29: saas.accounts.v1.WorkContextService.StartTask:output_type -> saas.accounts.v1.IssuedWorkContext + 8, // 30: saas.accounts.v1.WorkContextService.StartInstallationTask:output_type -> saas.accounts.v1.IssuedWorkContext + 8, // 31: saas.accounts.v1.WorkContextService.StartRootSession:output_type -> saas.accounts.v1.IssuedWorkContext + 8, // 32: saas.accounts.v1.WorkContextService.ExchangeAudience:output_type -> saas.accounts.v1.IssuedWorkContext + 8, // 33: saas.accounts.v1.WorkContextService.StartChildSession:output_type -> saas.accounts.v1.IssuedWorkContext + 8, // 34: saas.accounts.v1.WorkContextService.RenewWorkContext:output_type -> saas.accounts.v1.IssuedWorkContext + 25, // [25:35] is the sub-list for method output_type + 15, // [15:25] is the sub-list for method input_type 15, // [15:15] is the sub-list for extension type_name 15, // [15:15] is the sub-list for extension extendee 0, // [0:15] is the sub-list for field type_name @@ -1445,6 +1465,7 @@ func file_saas_accounts_v1_work_contexts_proto_init() { if File_saas_accounts_v1_work_contexts_proto != nil { return } + file_saas_accounts_v1_workload_operation_admission_proto_init() file_saas_accounts_v1_work_contexts_proto_msgTypes[1].OneofWrappers = []any{} file_saas_accounts_v1_work_contexts_proto_msgTypes[2].OneofWrappers = []any{} file_saas_accounts_v1_work_contexts_proto_msgTypes[6].OneofWrappers = []any{} diff --git a/gen/saas/accounts/v1/workload_operation_admission.pb.go b/gen/saas/accounts/v1/workload_operation_admission.pb.go new file mode 100644 index 0000000..7790148 --- /dev/null +++ b/gen/saas/accounts/v1/workload_operation_admission.pb.go @@ -0,0 +1,340 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: saas/accounts/v1/workload_operation_admission.proto + +package accountsv1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// WorkloadOperationRefusal is why a capability was not admitted. The gateway +// logs it and answers every refusal alike. +type WorkloadOperationRefusal int32 + +const ( + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED WorkloadOperationRefusal = 0 + // Not a capability this issuer signed, inside its window, addressed to the + // alias; or the alias is one no operation context is minted for. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_NOT_INSPECTED WorkloadOperationRefusal = 1 + // Owned by a principal that is not a service: a person, an agent, or a kind + // this issuer does not name. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_OWNER_NOT_SERVICE WorkloadOperationRefusal = 2 + // A service principal's capability that is not shaped as a module operation + // context: it carries an actor chain, a grant hop, an operation binding, a + // parent session or an owner agent, names no authority scope, or has another + // replay policy or a longer window. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_NOT_OPERATION_CONTEXT WorkloadOperationRefusal = 3 + // Owned by the module principal the alias names: a module does not reach + // itself through this route. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_OWNER_IS_TARGET WorkloadOperationRefusal = 4 + // The owner is not a declared module principal, so no installation of its + // can stand behind the seal. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_SEAL_NOT_OWNERS WorkloadOperationRefusal = 5 + // The tenant has no ACTIVE installation of the route's target. The gateway + // answers this one 403, as it answers a person whose organization never + // installed the module. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_TARGET_NOT_INSTALLED WorkloadOperationRefusal = 6 + // The seal is one of the target's own installations: the caller would be the + // module it addresses. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_SEAL_ON_TARGET WorkloadOperationRefusal = 7 + // The issuer's live state has moved past the capability: its authorization + // revision is superseded, an epoch it carries has advanced, the seal names an + // installation the owner no longer holds, or the owner's module was + // uninstalled or now belongs to another tenant. + WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_NOT_CURRENT WorkloadOperationRefusal = 8 +) + +// Enum value maps for WorkloadOperationRefusal. +var ( + WorkloadOperationRefusal_name = map[int32]string{ + 0: "WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED", + 1: "WORKLOAD_OPERATION_REFUSAL_NOT_INSPECTED", + 2: "WORKLOAD_OPERATION_REFUSAL_OWNER_NOT_SERVICE", + 3: "WORKLOAD_OPERATION_REFUSAL_NOT_OPERATION_CONTEXT", + 4: "WORKLOAD_OPERATION_REFUSAL_OWNER_IS_TARGET", + 5: "WORKLOAD_OPERATION_REFUSAL_SEAL_NOT_OWNERS", + 6: "WORKLOAD_OPERATION_REFUSAL_TARGET_NOT_INSTALLED", + 7: "WORKLOAD_OPERATION_REFUSAL_SEAL_ON_TARGET", + 8: "WORKLOAD_OPERATION_REFUSAL_NOT_CURRENT", + } + WorkloadOperationRefusal_value = map[string]int32{ + "WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED": 0, + "WORKLOAD_OPERATION_REFUSAL_NOT_INSPECTED": 1, + "WORKLOAD_OPERATION_REFUSAL_OWNER_NOT_SERVICE": 2, + "WORKLOAD_OPERATION_REFUSAL_NOT_OPERATION_CONTEXT": 3, + "WORKLOAD_OPERATION_REFUSAL_OWNER_IS_TARGET": 4, + "WORKLOAD_OPERATION_REFUSAL_SEAL_NOT_OWNERS": 5, + "WORKLOAD_OPERATION_REFUSAL_TARGET_NOT_INSTALLED": 6, + "WORKLOAD_OPERATION_REFUSAL_SEAL_ON_TARGET": 7, + "WORKLOAD_OPERATION_REFUSAL_NOT_CURRENT": 8, + } +) + +func (x WorkloadOperationRefusal) Enum() *WorkloadOperationRefusal { + p := new(WorkloadOperationRefusal) + *p = x + return p +} + +func (x WorkloadOperationRefusal) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WorkloadOperationRefusal) Descriptor() protoreflect.EnumDescriptor { + return file_saas_accounts_v1_workload_operation_admission_proto_enumTypes[0].Descriptor() +} + +func (WorkloadOperationRefusal) Type() protoreflect.EnumType { + return &file_saas_accounts_v1_workload_operation_admission_proto_enumTypes[0] +} + +func (x WorkloadOperationRefusal) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WorkloadOperationRefusal.Descriptor instead. +func (WorkloadOperationRefusal) EnumDescriptor() ([]byte, []int) { + return file_saas_accounts_v1_workload_operation_admission_proto_rawDescGZIP(), []int{0} +} + +type AdmitWorkloadOperationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The capability as presented, encoded. It is read back by its issuer, never + // verified as presented: nothing it carries is consumed. + WorkContext string `protobuf:"bytes,1,opt,name=work_context,json=workContext,proto3" json:"work_context,omitempty"` + // The route alias the gateway resolved from the request path. The capability + // must be addressed to it. + Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"` + // The immutable solution target the route's alias resolved to, from the + // gateway's own routing. The tenant must have an ACTIVE installation of it. + TargetId string `protobuf:"bytes,3,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdmitWorkloadOperationRequest) Reset() { + *x = AdmitWorkloadOperationRequest{} + mi := &file_saas_accounts_v1_workload_operation_admission_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdmitWorkloadOperationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdmitWorkloadOperationRequest) ProtoMessage() {} + +func (x *AdmitWorkloadOperationRequest) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_workload_operation_admission_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdmitWorkloadOperationRequest.ProtoReflect.Descriptor instead. +func (*AdmitWorkloadOperationRequest) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_workload_operation_admission_proto_rawDescGZIP(), []int{0} +} + +func (x *AdmitWorkloadOperationRequest) GetWorkContext() string { + if x != nil { + return x.WorkContext + } + return "" +} + +func (x *AdmitWorkloadOperationRequest) GetAlias() string { + if x != nil { + return x.Alias + } + return "" +} + +func (x *AdmitWorkloadOperationRequest) GetTargetId() string { + if x != nil { + return x.TargetId + } + return "" +} + +type AdmitWorkloadOperationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Admitted bool `protobuf:"varint,1,opt,name=admitted,proto3" json:"admitted,omitempty"` + // Set when admitted is false. + Refusal WorkloadOperationRefusal `protobuf:"varint,2,opt,name=refusal,proto3,enum=saas.accounts.v1.WorkloadOperationRefusal" json:"refusal,omitempty"` + // The signed facts the gateway logs and forwards, set only when admitted: + // the tenant, the installation the capability is sealed to (the CALLING + // module's), and the task. + TenantId string `protobuf:"bytes,3,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` + InstallationId string `protobuf:"bytes,4,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + TaskId string `protobuf:"bytes,5,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdmitWorkloadOperationResponse) Reset() { + *x = AdmitWorkloadOperationResponse{} + mi := &file_saas_accounts_v1_workload_operation_admission_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdmitWorkloadOperationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdmitWorkloadOperationResponse) ProtoMessage() {} + +func (x *AdmitWorkloadOperationResponse) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_workload_operation_admission_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdmitWorkloadOperationResponse.ProtoReflect.Descriptor instead. +func (*AdmitWorkloadOperationResponse) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_workload_operation_admission_proto_rawDescGZIP(), []int{1} +} + +func (x *AdmitWorkloadOperationResponse) GetAdmitted() bool { + if x != nil { + return x.Admitted + } + return false +} + +func (x *AdmitWorkloadOperationResponse) GetRefusal() WorkloadOperationRefusal { + if x != nil { + return x.Refusal + } + return WorkloadOperationRefusal_WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED +} + +func (x *AdmitWorkloadOperationResponse) GetTenantId() string { + if x != nil { + return x.TenantId + } + return "" +} + +func (x *AdmitWorkloadOperationResponse) GetInstallationId() string { + if x != nil { + return x.InstallationId + } + return "" +} + +func (x *AdmitWorkloadOperationResponse) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +var File_saas_accounts_v1_workload_operation_admission_proto protoreflect.FileDescriptor + +const file_saas_accounts_v1_workload_operation_admission_proto_rawDesc = "" + + "\n" + + "3saas/accounts/v1/workload_operation_admission.proto\x12\x10saas.accounts.v1\x1a\x1bbuf/validate/validate.proto\"\x98\x01\n" + + "\x1dAdmitWorkloadOperationRequest\x12.\n" + + "\fwork_context\x18\x01 \x01(\tB\v\xbaH\br\x06\x10\x01\x18\x80\x80\x02R\vworkContext\x12 \n" + + "\x05alias\x18\x02 \x01(\tB\n" + + "\xbaH\ar\x05\x10\x01\x18\x80\x04R\x05alias\x12%\n" + + "\ttarget_id\x18\x03 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\btargetId\"\xe1\x01\n" + + "\x1eAdmitWorkloadOperationResponse\x12\x1a\n" + + "\badmitted\x18\x01 \x01(\bR\badmitted\x12D\n" + + "\arefusal\x18\x02 \x01(\x0e2*.saas.accounts.v1.WorkloadOperationRefusalR\arefusal\x12\x1b\n" + + "\ttenant_id\x18\x03 \x01(\tR\btenantId\x12'\n" + + "\x0finstallation_id\x18\x04 \x01(\tR\x0einstallationId\x12\x17\n" + + "\atask_id\x18\x05 \x01(\tR\x06taskId*\xcc\x03\n" + + "\x18WorkloadOperationRefusal\x12*\n" + + "&WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED\x10\x00\x12,\n" + + "(WORKLOAD_OPERATION_REFUSAL_NOT_INSPECTED\x10\x01\x120\n" + + ",WORKLOAD_OPERATION_REFUSAL_OWNER_NOT_SERVICE\x10\x02\x124\n" + + "0WORKLOAD_OPERATION_REFUSAL_NOT_OPERATION_CONTEXT\x10\x03\x12.\n" + + "*WORKLOAD_OPERATION_REFUSAL_OWNER_IS_TARGET\x10\x04\x12.\n" + + "*WORKLOAD_OPERATION_REFUSAL_SEAL_NOT_OWNERS\x10\x05\x123\n" + + "/WORKLOAD_OPERATION_REFUSAL_TARGET_NOT_INSTALLED\x10\x06\x12-\n" + + ")WORKLOAD_OPERATION_REFUSAL_SEAL_ON_TARGET\x10\a\x12*\n" + + "&WORKLOAD_OPERATION_REFUSAL_NOT_CURRENT\x10\bB\xdd\x01\n" + + "\x14com.saas.accounts.v1B\x1fWorkloadOperationAdmissionProtoP\x01ZBgithub.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1;accountsv1\xa2\x02\x03SAX\xaa\x02\x10Saas.Accounts.V1\xca\x02\x10Saas\\Accounts\\V1\xe2\x02\x1cSaas\\Accounts\\V1\\GPBMetadata\xea\x02\x12Saas::Accounts::V1b\x06proto3" + +var ( + file_saas_accounts_v1_workload_operation_admission_proto_rawDescOnce sync.Once + file_saas_accounts_v1_workload_operation_admission_proto_rawDescData []byte +) + +func file_saas_accounts_v1_workload_operation_admission_proto_rawDescGZIP() []byte { + file_saas_accounts_v1_workload_operation_admission_proto_rawDescOnce.Do(func() { + file_saas_accounts_v1_workload_operation_admission_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_saas_accounts_v1_workload_operation_admission_proto_rawDesc), len(file_saas_accounts_v1_workload_operation_admission_proto_rawDesc))) + }) + return file_saas_accounts_v1_workload_operation_admission_proto_rawDescData +} + +var file_saas_accounts_v1_workload_operation_admission_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_saas_accounts_v1_workload_operation_admission_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_saas_accounts_v1_workload_operation_admission_proto_goTypes = []any{ + (WorkloadOperationRefusal)(0), // 0: saas.accounts.v1.WorkloadOperationRefusal + (*AdmitWorkloadOperationRequest)(nil), // 1: saas.accounts.v1.AdmitWorkloadOperationRequest + (*AdmitWorkloadOperationResponse)(nil), // 2: saas.accounts.v1.AdmitWorkloadOperationResponse +} +var file_saas_accounts_v1_workload_operation_admission_proto_depIdxs = []int32{ + 0, // 0: saas.accounts.v1.AdmitWorkloadOperationResponse.refusal:type_name -> saas.accounts.v1.WorkloadOperationRefusal + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_saas_accounts_v1_workload_operation_admission_proto_init() } +func file_saas_accounts_v1_workload_operation_admission_proto_init() { + if File_saas_accounts_v1_workload_operation_admission_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_saas_accounts_v1_workload_operation_admission_proto_rawDesc), len(file_saas_accounts_v1_workload_operation_admission_proto_rawDesc)), + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_saas_accounts_v1_workload_operation_admission_proto_goTypes, + DependencyIndexes: file_saas_accounts_v1_workload_operation_admission_proto_depIdxs, + EnumInfos: file_saas_accounts_v1_workload_operation_admission_proto_enumTypes, + MessageInfos: file_saas_accounts_v1_workload_operation_admission_proto_msgTypes, + }.Build() + File_saas_accounts_v1_workload_operation_admission_proto = out.File + file_saas_accounts_v1_workload_operation_admission_proto_goTypes = nil + file_saas_accounts_v1_workload_operation_admission_proto_depIdxs = nil +} diff --git a/gen/saas/catalog/v1/catalog.pb.go b/gen/saas/catalog/v1/catalog.pb.go index bb3b40b..6040a38 100644 --- a/gen/saas/catalog/v1/catalog.pb.go +++ b/gen/saas/catalog/v1/catalog.pb.go @@ -659,9 +659,13 @@ type ServiceCatalog struct { Permissions []*PermissionDefinition `protobuf:"bytes,7,rep,name=permissions,proto3" json:"permissions,omitempty"` // entitlements is the sorted server-owned feature/quota vocabulary shared // by plans, runtime checks, generated clients, and plugin manifests. - Entitlements []*EntitlementDefinition `protobuf:"bytes,8,rep,name=entitlements,proto3" json:"entitlements,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Entitlements []*EntitlementDefinition `protobuf:"bytes,8,rep,name=entitlements,proto3" json:"entitlements,omitempty"` + // Protobuf packages of the published interfaces this service implements, beside + // its own api_package. A method of one of these packages is served and + // catalogued like any other; its policy is the implementation's own sidecar. + InterfacePackages []string `protobuf:"bytes,9,rep,name=interface_packages,json=interfacePackages,proto3" json:"interface_packages,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ServiceCatalog) Reset() { @@ -750,6 +754,13 @@ func (x *ServiceCatalog) GetEntitlements() []*EntitlementDefinition { return nil } +func (x *ServiceCatalog) GetInterfacePackages() []string { + if x != nil { + return x.InterfacePackages + } + return nil +} + // PermissionDefinition is one canonical resource:action capability. The // permission field is redundant by design: consumers can use it directly, // while validation proves it agrees with resource and action. @@ -2515,7 +2526,7 @@ var File_saas_catalog_v1_catalog_proto protoreflect.FileDescriptor const file_saas_catalog_v1_catalog_proto_rawDesc = "" + "\n" + - "\x1dsaas/catalog/v1/catalog.proto\x12\x0fsaas.catalog.v1\x1a\x1csaas/policy/v1/options.proto\"\xac\x03\n" + + "\x1dsaas/catalog/v1/catalog.proto\x12\x0fsaas.catalog.v1\x1a\x1csaas/policy/v1/options.proto\"\xdb\x03\n" + "\x0eServiceCatalog\x12%\n" + "\x0eschema_version\x18\x01 \x01(\tR\rschemaVersion\x123\n" + "\x05owner\x18\x02 \x01(\v2\x1d.saas.catalog.v1.ServiceOwnerR\x05owner\x12\x1f\n" + @@ -2526,7 +2537,8 @@ const file_saas_catalog_v1_catalog_proto_rawDesc = "" + "\bservices\x18\x05 \x03(\v2\x18.saas.catalog.v1.ServiceR\bservices\x121\n" + "\amethods\x18\x06 \x03(\v2\x17.saas.catalog.v1.MethodR\amethods\x12G\n" + "\vpermissions\x18\a \x03(\v2%.saas.catalog.v1.PermissionDefinitionR\vpermissions\x12J\n" + - "\fentitlements\x18\b \x03(\v2&.saas.catalog.v1.EntitlementDefinitionR\fentitlements\"\xd6\x01\n" + + "\fentitlements\x18\b \x03(\v2&.saas.catalog.v1.EntitlementDefinitionR\fentitlements\x12-\n" + + "\x12interface_packages\x18\t \x03(\tR\x11interfacePackages\"\xd6\x01\n" + "\x14PermissionDefinition\x12\x1e\n" + "\n" + "permission\x18\x01 \x01(\tR\n" + diff --git a/go.mod b/go.mod index 36dc005..5f1babc 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,12 @@ go 1.27.3 require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 connectrpc.com/connect v1.21.0 - github.com/codefly-dev/core v0.19.6 + github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261026143932-67b85b14a942 + github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 github.com/google/uuid v1.6.0 golang.org/x/tools v0.52.0 - google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc - google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc + google.golang.org/genproto/googleapis/api v0.0.0-20261019180522-ce908d29299b + google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941 google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 ) diff --git a/go.sum b/go.sum index 57791e9..36baa12 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,10 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= -github.com/codefly-dev/core v0.19.6 h1:ZkCChK/BioK+B7zZ0fp+/rXDCy0k+lsdFqHjyc5xFIg= -github.com/codefly-dev/core v0.19.6/go.mod h1:1ina3kP+YSAL4tlZvnEYd4P8bFGnJECfkP86ojeeF1A= +github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261026143932-67b85b14a942 h1:jLP7MJVHga8yOKMcS6pN2LF3b+usJcjtez8UFSXoc00= +github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261026143932-67b85b14a942/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= +github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 h1:I1wL0ANXhTdQuEAnxXF+s4+rKILlBu/QHILq4Skyi3I= +github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2/go.mod h1:1ina3kP+YSAL4tlZvnEYd4P8bFGnJECfkP86ojeeF1A= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -46,10 +48,10 @@ golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs= golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1:rZHRz0ogq4Pid1IKtA/ivHi8XONVF5tqUQXA2TtgpkE= -google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc h1:4bNTbnb44EqGVy9HaQxSY2jnafSUdgV3qHtedvNpDKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/genproto/googleapis/api v0.0.0-20261019180522-ce908d29299b h1:nB3spRIaSPtxJpxb0k41yljROvm2Jrwfs2pSNdKS5y8= +google.golang.org/genproto/googleapis/api v0.0.0-20261019180522-ce908d29299b/go.mod h1:9N4vtjEwvgQKEdlFVQO0M44ErhecQ9DCSQfGoZ51M6w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941 h1:QG6hS4zFplRZvOQedzei4t2pT/zkQc3VR1u+S9/rcAw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= From 25eddbd4a1162adaa431084608559231b1fdcebd Mon Sep 17 00:00:00 2001 From: crisfiorini-oss Date: Tue, 27 Oct 2026 16:13:34 +0000 Subject: [PATCH 13/18] feat(sdk): reuse journal transport with shared Authority capabilities --- CHANGELOG.md | 6 + README.md | 14 ++ SOURCE.txt | 2 +- .../installations.connect.go | 18 ++- go.mod | 2 +- moduleauthority/journal.go | 33 +++-- moduleauthority/journal_reader.go | 70 ++++++++++ moduleauthority/journal_reader_test.go | 122 ++++++++++++++++++ 8 files changed, 238 insertions(+), 29 deletions(-) create mode 100644 moduleauthority/journal_reader.go create mode 100644 moduleauthority/journal_reader_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4fac8..4b92970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ Authority client; its declared Core dependency sets the development floor. ### Changed +- Add `moduleauthority.NewJournalReader` for callers whose capabilities come + from shared Authority. It reuses the existing Accounts transport and journal + decoder with a fresh sealed Work Context source, without gateway broker + credentials or token caching. Both journal facades reuse the host protobuf + request constraints before obtaining credentials. + - Refresh development provenance to the rebased module-saas-starter journal source recorded in `SOURCE.txt`, with module package version 0.2.0. The full generated API carries the latest host fields, including installation approval diff --git a/README.md b/README.md index a81e5f6..1b4fd41 100644 --- a/README.md +++ b/README.md @@ -744,3 +744,17 @@ their context's `Err()` for cancellation. For example, a caller may retry a 502, denial. The SDK does not automatically retry broker failures. Malformed initial HTTP headers provide no usable status and produce a transport error; malformed bodies cannot change a successfully parsed non-200 status into a transport failure. + +### Journal reads with shared Authority + +`moduleauthority.NewJournalReader` accepts a `JournalReaderConfig` with the +resolved Accounts `Authority` endpoint and a `WorkContext(context.Context)` +source. The source requests a sealed module capability from shared Authority +for audience `module-capabilities`; the workload credential goes only to +Authority. Each read calls the source afresh, uses the existing Accounts +HTTP/2 gRPC transport and journal decoder, and carries no legacy internal token. +The caller's context bounds both issuance and the read. Call `Close` at shutdown. + +| Build or reuse | Searched and verified | Result | +| --- | --- | --- | +| Journal reads with externally issued authority | Existing journal facade, `authorityEndpoint`, Core sealed-capability reader and protobuf request validation | Extend the published SDK with a journal-only credential source. Reuse transport and decoding without a gateway broker, copied descriptors, credential cache or another publication repository. | diff --git a/SOURCE.txt b/SOURCE.txt index d71b8e7..7f3b121 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,4 +1,4 @@ -gen/ generated from codefly-dev/module-saas-starter@80e238cec651847d51ff7d5b23a102d56cf06bb4 +gen/ generated from codefly-dev/module-saas-starter@21c121350bb728ab10f64000164881e434aaab97 (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) diff --git a/gen/saas/accounts/v1/accountsv1connect/installations.connect.go b/gen/saas/accounts/v1/accountsv1connect/installations.connect.go index 57ca8be..40d1e5d 100644 --- a/gen/saas/accounts/v1/accountsv1connect/installations.connect.go +++ b/gen/saas/accounts/v1/accountsv1connect/installations.connect.go @@ -81,8 +81,8 @@ type InstallationServiceClient interface { // is left in place (inert without a grant or a live agent) so a reinstall reuses // it. Idempotent on an already-revoked installation. UninstallSolution(context.Context, *connect.Request[v1.UninstallSolutionRequest]) (*connect.Response[emptypb.Empty], error) - // Narrowing is witnessed in the policy log before commit, then atomically - // bumps the installation revision, cancels its org's work and records audit. + // A narrowing applies directly: it atomically bumps the installation + // revision, cancels its org's work and records audit. NarrowInstallation(context.Context, *connect.Request[v1.NarrowInstallationRequest]) (*connect.Response[emptypb.Empty], error) // RetryInstallation runs a failed installation's install step again, at the // same revision. The installation is pending until the solution acknowledges. @@ -149,9 +149,8 @@ type InstallationServiceClient interface { // ListOrganizationInstallations describes. GrantInstallationToTeam(context.Context, *connect.Request[v1.GrantInstallationToTeamRequest]) (*connect.Response[v1.GrantInstallationToTeamResponse], error) // RevokeInstallationFromTeam removes one team grant at an installation's - // authority-root node. It is a narrowing, so it runs under the policy log - // exactly as RevokeScope does: a host that cannot witness the narrowing refuses - // it, and says so, rather than revoking unwitnessed. It removes the one grant + // authority-root node. It is a narrowing, so it applies directly, with its + // audit record in one transaction, exactly as RevokeScope does. It removes the one grant // at the authority root that granted_teams reports; a team that also reaches // the installation from a grant above keeps that reach. Organisation-admin, // with the same platform super administrator exception. @@ -323,8 +322,8 @@ type InstallationServiceHandler interface { // is left in place (inert without a grant or a live agent) so a reinstall reuses // it. Idempotent on an already-revoked installation. UninstallSolution(context.Context, *connect.Request[v1.UninstallSolutionRequest]) (*connect.Response[emptypb.Empty], error) - // Narrowing is witnessed in the policy log before commit, then atomically - // bumps the installation revision, cancels its org's work and records audit. + // A narrowing applies directly: it atomically bumps the installation + // revision, cancels its org's work and records audit. NarrowInstallation(context.Context, *connect.Request[v1.NarrowInstallationRequest]) (*connect.Response[emptypb.Empty], error) // RetryInstallation runs a failed installation's install step again, at the // same revision. The installation is pending until the solution acknowledges. @@ -391,9 +390,8 @@ type InstallationServiceHandler interface { // ListOrganizationInstallations describes. GrantInstallationToTeam(context.Context, *connect.Request[v1.GrantInstallationToTeamRequest]) (*connect.Response[v1.GrantInstallationToTeamResponse], error) // RevokeInstallationFromTeam removes one team grant at an installation's - // authority-root node. It is a narrowing, so it runs under the policy log - // exactly as RevokeScope does: a host that cannot witness the narrowing refuses - // it, and says so, rather than revoking unwitnessed. It removes the one grant + // authority-root node. It is a narrowing, so it applies directly, with its + // audit record in one transaction, exactly as RevokeScope does. It removes the one grant // at the authority root that granted_teams reports; a team that also reaches // the installation from a grant above keeps that reach. Organisation-admin, // with the same platform super administrator exception. diff --git a/go.mod b/go.mod index 5f1babc..ac6b6a8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.27.3 require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 + buf.build/go/protovalidate v1.4.0 connectrpc.com/connect v1.21.0 github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261026143932-67b85b14a942 github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 @@ -16,7 +17,6 @@ require ( ) require ( - buf.build/go/protovalidate v1.4.0 // indirect cel.dev/cel-go v0.32.0 // indirect cel.dev/expr v0.25.3 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect diff --git a/moduleauthority/journal.go b/moduleauthority/journal.go index 5fcb619..3d05ae1 100644 --- a/moduleauthority/journal.go +++ b/moduleauthority/journal.go @@ -6,6 +6,8 @@ import ( "fmt" "time" + "buf.build/go/protovalidate" + v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1/accountsv1connect" ) @@ -15,6 +17,9 @@ import ( // read, or an empty type or delivery binding id. var ErrInvalidJournalRead = errors.New("module authority: invalid journal read") +// ErrInvalidJournalPage refuses a malformed response before exposing it. +var ErrInvalidJournalPage = errors.New("module authority: invalid journal page") + // JournalStart is the position that means "before the oldest retained entry". // The host accepts it wherever it accepts a position (ReadJournal's after, and // the `from` of the delivery operation) and never returns it as an entry's @@ -74,25 +79,19 @@ type JournalPage struct { // from an earlier NextAfter is served the same entries again. A rejected module // capability is refreshed and retried once, like every authority call. func (c *Client) ReadJournal(ctx context.Context, after string, limit int32, types, deliveryBindings []string) (JournalPage, error) { - if limit < 0 || limit > MaxJournalPage || len(types) == 0 { - return JournalPage{}, ErrInvalidJournalRead - } - for _, t := range types { - if t == "" { - return JournalPage{}, ErrInvalidJournalRead - } + return readJournal(ctx, after, limit, types, deliveryBindings, func(ctx context.Context, request *v1.ModuleReadJournalRequest) (*v1.ModuleReadJournalResponse, error) { + return callAsModule(ctx, c, accountsv1connect.ModuleCapabilitiesServiceClient.ReadJournal, request) + }) +} + +func readJournal(ctx context.Context, after string, limit int32, types, deliveryBindings []string, read func(context.Context, *v1.ModuleReadJournalRequest) (*v1.ModuleReadJournalResponse, error)) (JournalPage, error) { + request := &v1.ModuleReadJournalRequest{ + After: after, Limit: limit, Types: append([]string(nil), types...), DeliveryBindings: append([]string(nil), deliveryBindings...), } - for _, b := range deliveryBindings { - if b == "" { - return JournalPage{}, ErrInvalidJournalRead - } + if limit > MaxJournalPage || protovalidate.Validate(request) != nil { + return JournalPage{}, ErrInvalidJournalRead } - res, err := callAsModule(ctx, c, accountsv1connect.ModuleCapabilitiesServiceClient.ReadJournal, &v1.ModuleReadJournalRequest{ - After: after, - Limit: limit, - Types: append([]string(nil), types...), - DeliveryBindings: append([]string(nil), deliveryBindings...), - }) + res, err := read(ctx, request) if err != nil { return JournalPage{}, fmt.Errorf("module authority: read journal: %w", err) } diff --git a/moduleauthority/journal_reader.go b/moduleauthority/journal_reader.go new file mode 100644 index 0000000..7287970 --- /dev/null +++ b/moduleauthority/journal_reader.go @@ -0,0 +1,70 @@ +package moduleauthority + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + "github.com/codefly-dev/core/wool" + v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" +) + +// JournalReaderConfig supplies Accounts' transport and a source of module Work +// Contexts. WorkContext typically calls shared Authority Issue for audience +// module-capabilities. The workload credential stays with that authority. +type JournalReaderConfig struct { + Authority Authority + AllowInsecureHTTP bool + WorkContext func(context.Context) (string, error) +} + +// JournalReader uses the existing Accounts transport and journal decoder with +// caller-supplied authority. It never contacts the legacy gateway broker or +// caches a capability. The caller bounds the context of issuance and the read. +type JournalReader struct { + authority authorityEndpoint + context func(context.Context) (string, error) +} + +func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) { + if config.WorkContext == nil { + return nil, fmt.Errorf("%w: a journal module Work Context source is required", ErrInvalidSeams) + } + endpoint, err := newAuthorityEndpoint(Seams{Authority: config.Authority, AllowInsecureHTTP: config.AllowInsecureHTTP}) + if err != nil { + return nil, err + } + return &JournalReader{authority: endpoint, context: config.WorkContext}, nil +} + +// Close releases the existing transport's idle connections. +func (r *JournalReader) Close() error { r.authority.close(); return nil } + +// ReadJournal validates through the same facade as Client.ReadJournal, then +// obtains one fresh module capability and makes one bounded Accounts read. +func (r *JournalReader) ReadJournal(ctx context.Context, after string, limit int32, types, bindings []string) (JournalPage, error) { + return readJournal(ctx, after, limit, types, bindings, func(ctx context.Context, request *v1.ModuleReadJournalRequest) (*v1.ModuleReadJournalResponse, error) { + token, err := r.context(ctx) + if err != nil { + return nil, err + } + if !sealed(token) { + return nil, ErrInvalidCapability + } + req := connect.NewRequest(request) + req.Header().Set(wool.WorkContextHeader, token) + response, err := r.authority.capabilities.ReadJournal(ctx, req) + if err != nil { + return nil, err + } + if response == nil || response.Msg == nil { + return nil, ErrInvalidJournalPage + } + for _, entry := range response.Msg.GetEntries() { + if entry == nil || entry.GetOccurredAt().CheckValid() != nil { + return nil, ErrInvalidJournalPage + } + } + return response.Msg, nil + }) +} diff --git a/moduleauthority/journal_reader_test.go b/moduleauthority/journal_reader_test.go new file mode 100644 index 0000000..4f4c8d1 --- /dev/null +++ b/moduleauthority/journal_reader_test.go @@ -0,0 +1,122 @@ +package moduleauthority + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync/atomic" + "testing" + "time" + + "connectrpc.com/connect" + v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" + rpc "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1/accountsv1connect" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type sourceJournal struct { + rpc.UnimplementedModuleCapabilitiesServiceHandler + read func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) +} + +func (s sourceJournal) ReadJournal(ctx context.Context, r *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + return s.read(ctx, r) +} + +func journalFromSource(t *testing.T, source func(context.Context) (string, error), read func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error)) *JournalReader { + t.Helper() + _, handler := rpc.NewModuleCapabilitiesServiceHandler(sourceJournal{read: read}) + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.ProtoMajor != 2 || !strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { + t.Error("journal did not use native HTTP/2 gRPC") + } + handler.ServeHTTP(w, r) + })) + server.Config.Protocols = new(http.Protocols) + server.Config.Protocols.SetUnencryptedHTTP2(true) + server.Start() + t.Cleanup(server.Close) + endpoint, _ := url.Parse(server.URL) + reader, err := NewJournalReader(JournalReaderConfig{Authority: Authority{Address: endpoint.Host}, WorkContext: source}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = reader.Close() }) + return reader +} + +func TestJournalReaderReusesTheTransportAndObtainsFreshAuthority(t *testing.T) { + issued := 0 + var calls atomic.Int32 + reader := journalFromSource(t, func(context.Context) (string, error) { + issued++ + return sealedToken("source-module"), nil + }, func(_ context.Context, request *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + calls.Add(1) + if request.Header().Get("X-Codefly-Work-Context") != sealedToken("source-module") || request.Header().Get("X-Codefly-Internal-Token") != "" || request.Msg.GetAfter() != JournalStart { + t.Error("journal used a legacy credential or changed the request") + } + return connect.NewResponse(&v1.ModuleReadJournalResponse{NextAfter: "p1", Entries: []*v1.ModuleJournalEntry{{Position: "p1", EventId: "event", Type: journalType, OccurredAt: timestamppb.New(journalEpoch), DeliveryMachinery: true}}}), nil + }) + for range 2 { + page, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil) + if err != nil || page.NextAfter != "p1" || len(page.Entries) != 1 || !page.Entries[0].DeliveryMachinery || !page.Entries[0].OccurredAt.Equal(journalEpoch) { + t.Fatalf("journal projection: %v %v", page, err) + } + } + if issued != 2 || calls.Load() != 2 { + t.Fatal("journal reused authority or duplicated a read") + } + if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, nil, nil); !errors.Is(err, ErrInvalidJournalRead) || issued != 2 { + t.Fatal("invalid request obtained authority") + } +} + +func TestJournalReaderSourceFailuresAndMalformedPages(t *testing.T) { + for _, token := range []string{"", "not-sealed"} { + reader := journalFromSource(t, func(context.Context) (string, error) { return token, nil }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + t.Fatal("invalid capability reached Accounts") + return nil, nil + }) + if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil); !errors.Is(err, ErrInvalidCapability) { + t.Fatalf("invalid source token: %v", err) + } + } + refusal := errors.New("issuer refused") + reader := journalFromSource(t, func(context.Context) (string, error) { return "", refusal }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + t.Fatal("source refusal reached Accounts") + return nil, nil + }) + if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil); !errors.Is(err, refusal) { + t.Fatalf("source refusal lost: %v", err) + } + reader = journalFromSource(t, func(context.Context) (string, error) { return sealedToken("source-module"), nil }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + return connect.NewResponse(&v1.ModuleReadJournalResponse{Entries: []*v1.ModuleJournalEntry{{Position: "p1"}}}), nil + }) + if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil); !errors.Is(err, ErrInvalidJournalPage) { + t.Fatalf("malformed response accepted: %v", err) + } +} + +func TestJournalReaderSourceReceivesTheReadDeadline(t *testing.T) { + reader := journalFromSource(t, func(ctx context.Context) (string, error) { <-ctx.Done(); return "", ctx.Err() }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { + t.Fatal("expired source reached Accounts") + return nil, nil + }) + ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) + defer cancel() + if _, err := reader.ReadJournal(ctx, JournalStart, 100, []string{journalType}, nil); !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("read did not bound source issuance: %v", err) + } +} + +func TestJournalReaderRequiresAnExplicitAuthorityAndSource(t *testing.T) { + for _, config := range []JournalReaderConfig{{}, {WorkContext: func(context.Context) (string, error) { return "", nil }}} { + if reader, err := NewJournalReader(config); !errors.Is(err, ErrInvalidSeams) || reader != nil { + t.Fatalf("incomplete journal configuration accepted: %v %v", reader, err) + } + } +} From e73eff17031a6def3f619cd759da3d33a7301283 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss Date: Tue, 27 Oct 2026 16:23:15 +0000 Subject: [PATCH 14/18] chore(sdk): regenerate the rebased host journal contract --- SOURCE.txt | 2 +- gen/saas/accounts/v1/datasource.pb.go | 113 ++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 18 deletions(-) diff --git a/SOURCE.txt b/SOURCE.txt index 7f3b121..f63ad96 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,4 +1,4 @@ -gen/ generated from codefly-dev/module-saas-starter@21c121350bb728ab10f64000164881e434aaab97 +gen/ generated from codefly-dev/module-saas-starter@13cf75310dc52e39954ad44da1d9a8d08458ff72 (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) diff --git a/gen/saas/accounts/v1/datasource.pb.go b/gen/saas/accounts/v1/datasource.pb.go index 9633707..c4c77ec 100644 --- a/gen/saas/accounts/v1/datasource.pb.go +++ b/gen/saas/accounts/v1/datasource.pb.go @@ -1248,9 +1248,25 @@ type UploadDatasourceConfig struct { // AWS-style access key id. The matching secret access key is the credential. AccessKeyId string `protobuf:"bytes,5,opt,name=access_key_id,json=accessKeyId,proto3" json:"access_key_id,omitempty"` // Upper bound on objects fetched per sync. Zero applies the connector default. - MaxObjects uint32 `protobuf:"varint,6,opt,name=max_objects,json=maxObjects,proto3" json:"max_objects,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Only objects that pass the filters below count against it. + MaxObjects uint32 `protobuf:"varint,6,opt,name=max_objects,json=maxObjects,proto3" json:"max_objects,omitempty"` + // Which keys under the prefix are ingested. Each pattern is an RE2 regular + // expression matched against the object key with the prefix removed, + // unanchored and case-sensitive (anchor with ^ and $, fold case with (?i)). + // An empty include admits every key; an exclude match always wins. Applied + // when the prefix is listed, before anything is fetched, so an excluded + // object is never read and never counts against max_objects. An invalid + // expression is refused when the source is added or updated, never at sync: + // the call fails INVALID_ARGUMENT with a google.rpc.BadRequest violation, and + // a message that starts with the field's path, e.g. + // "upload.include_patterns[1]: …". + IncludePatterns []string `protobuf:"bytes,7,rep,name=include_patterns,json=includePatterns,proto3" json:"include_patterns,omitempty"` + ExcludePatterns []string `protobuf:"bytes,8,rep,name=exclude_patterns,json=excludePatterns,proto3" json:"exclude_patterns,omitempty"` + // Case-insensitive key suffixes, e.g. [".md", ".pdf"], intersected with the + // patterns. Empty admits every type. GitHub's file_extensions semantics. + FileExtensions []string `protobuf:"bytes,9,rep,name=file_extensions,json=fileExtensions,proto3" json:"file_extensions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UploadDatasourceConfig) Reset() { @@ -1325,6 +1341,27 @@ func (x *UploadDatasourceConfig) GetMaxObjects() uint32 { return 0 } +func (x *UploadDatasourceConfig) GetIncludePatterns() []string { + if x != nil { + return x.IncludePatterns + } + return nil +} + +func (x *UploadDatasourceConfig) GetExcludePatterns() []string { + if x != nil { + return x.ExcludePatterns + } + return nil +} + +func (x *UploadDatasourceConfig) GetFileExtensions() []string { + if x != nil { + return x.FileExtensions + } + return nil +} + // Datasource is one connected external datasource. It is org-scoped and carries // only non-secret projection fields; credential material is deliberately absent. // Exactly one provider config (github, api, crawler, or upload) is set, matching @@ -2083,9 +2120,14 @@ type UpdateSourceRequest struct { // on every successful sync, which would refuse a perfectly good edit for the // sole reason that the source had reconciled since the form was opened. What // an editor needs guarded is what they were looking at. - Expected *SourceFetchScope `protobuf:"bytes,6,opt,name=expected,proto3" json:"expected,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Expected *SourceFetchScope `protobuf:"bytes,6,opt,name=expected,proto3" json:"expected,omitempty"` + // An object-storage source's key patterns (see UploadDatasourceConfig), + // replaced whole like every field here. Its file_extensions is field 5; its + // branch and paths must be empty. Refused for a github source. + IncludePatterns []string `protobuf:"bytes,7,rep,name=include_patterns,json=includePatterns,proto3" json:"include_patterns,omitempty"` + ExcludePatterns []string `protobuf:"bytes,8,rep,name=exclude_patterns,json=excludePatterns,proto3" json:"exclude_patterns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateSourceRequest) Reset() { @@ -2160,6 +2202,20 @@ func (x *UpdateSourceRequest) GetExpected() *SourceFetchScope { return nil } +func (x *UpdateSourceRequest) GetIncludePatterns() []string { + if x != nil { + return x.IncludePatterns + } + return nil +} + +func (x *UpdateSourceRequest) GetExcludePatterns() []string { + if x != nil { + return x.ExcludePatterns + } + return nil +} + // SourceFetchScope is what a source fetches — the three values UpdateSource // replaces — carried on its own so a request can state the scope it expects to // be replacing as well as the one it wants. @@ -2170,12 +2226,14 @@ func (x *UpdateSourceRequest) GetExpected() *SourceFetchScope { // edit at all. Size is still bounded, because the comparison reads the whole // thing. type SourceFetchScope struct { - state protoimpl.MessageState `protogen:"open.v1"` - Branch string `protobuf:"bytes,1,opt,name=branch,proto3" json:"branch,omitempty"` - Paths []string `protobuf:"bytes,2,rep,name=paths,proto3" json:"paths,omitempty"` - FileExtensions []string `protobuf:"bytes,3,rep,name=file_extensions,json=fileExtensions,proto3" json:"file_extensions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Branch string `protobuf:"bytes,1,opt,name=branch,proto3" json:"branch,omitempty"` + Paths []string `protobuf:"bytes,2,rep,name=paths,proto3" json:"paths,omitempty"` + FileExtensions []string `protobuf:"bytes,3,rep,name=file_extensions,json=fileExtensions,proto3" json:"file_extensions,omitempty"` + IncludePatterns []string `protobuf:"bytes,4,rep,name=include_patterns,json=includePatterns,proto3" json:"include_patterns,omitempty"` + ExcludePatterns []string `protobuf:"bytes,5,rep,name=exclude_patterns,json=excludePatterns,proto3" json:"exclude_patterns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SourceFetchScope) Reset() { @@ -2229,6 +2287,20 @@ func (x *SourceFetchScope) GetFileExtensions() []string { return nil } +func (x *SourceFetchScope) GetIncludePatterns() []string { + if x != nil { + return x.IncludePatterns + } + return nil +} + +func (x *SourceFetchScope) GetExcludePatterns() []string { + if x != nil { + return x.ExcludePatterns + } + return nil +} + type UpdateSourceResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Datasource *Datasource `protobuf:"bytes,1,opt,name=datasource,proto3" json:"datasource,omitempty"` @@ -7473,7 +7545,7 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x17CrawlerDatasourceConfig\x12\x1f\n" + "\vsitemap_url\x18\x01 \x01(\tR\n" + "sitemapUrl\x12\x1b\n" + - "\tmax_pages\x18\x02 \x01(\rR\bmaxPages\"\xc1\x01\n" + + "\tmax_pages\x18\x02 \x01(\rR\bmaxPages\"\x98\x03\n" + "\x16UploadDatasourceConfig\x12\x1a\n" + "\bendpoint\x18\x01 \x01(\tR\bendpoint\x12\x16\n" + "\x06region\x18\x02 \x01(\tR\x06region\x12\x16\n" + @@ -7481,7 +7553,10 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x06prefix\x18\x04 \x01(\tR\x06prefix\x12\"\n" + "\raccess_key_id\x18\x05 \x01(\tR\vaccessKeyId\x12\x1f\n" + "\vmax_objects\x18\x06 \x01(\rR\n" + - "maxObjects\"\x83\t\n" + + "maxObjects\x12<\n" + + "\x10include_patterns\x18\a \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10\x10\"\ar\x05\x10\x01\x18\x80\x02R\x0fincludePatterns\x12<\n" + + "\x10exclude_patterns\x18\b \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10\x10\"\ar\x05\x10\x01\x18\x80\x02R\x0fexcludePatterns\x12Y\n" + + "\x0ffile_extensions\x18\t \x03(\tB0\xbaH-\x92\x01*\x10 \"&r$\x10\x02\x18!2\x1e^\\.[A-Za-z0-9][A-Za-z0-9._-]*$R\x0efileExtensions\"\x83\t\n" + "\n" + "Datasource\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x15\n" + @@ -7549,19 +7624,23 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x11AddSourceResponse\x12<\n" + "\n" + "datasource\x18\x01 \x01(\v2\x1c.saas.accounts.v1.DatasourceR\n" + - "datasource\"\xbe\x02\n" + + "datasource\"\xba\x03\n" + "\x13UpdateSourceRequest\x12\x1f\n" + "\x06org_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x12\x18\n" + "\x02id\x18\x02 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x02id\x12 \n" + "\x06branch\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\x06branch\x12'\n" + "\x05paths\x18\x04 \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10@\"\ar\x05\x10\x01\x18\x80\x04R\x05paths\x12Y\n" + "\x0ffile_extensions\x18\x05 \x03(\tB0\xbaH-\x92\x01*\x10 \"&r$\x10\x02\x18!2\x1e^\\.[A-Za-z0-9][A-Za-z0-9._-]*$R\x0efileExtensions\x12F\n" + - "\bexpected\x18\x06 \x01(\v2\".saas.accounts.v1.SourceFetchScopeB\x06\xbaH\x03\xc8\x01\x01R\bexpected\"\x98\x01\n" + + "\bexpected\x18\x06 \x01(\v2\".saas.accounts.v1.SourceFetchScopeB\x06\xbaH\x03\xc8\x01\x01R\bexpected\x12<\n" + + "\x10include_patterns\x18\a \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10\x10\"\ar\x05\x10\x01\x18\x80\x02R\x0fincludePatterns\x12<\n" + + "\x10exclude_patterns\x18\b \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10\x10\"\ar\x05\x10\x01\x18\x80\x02R\x0fexcludePatterns\"\x90\x02\n" + "\x10SourceFetchScope\x12 \n" + "\x06branch\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\x06branch\x12'\n" + "\x05paths\x18\x02 \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10@\"\ar\x05\x10\x01\x18\x80\x04R\x05paths\x129\n" + "\x0ffile_extensions\x18\x03 \x03(\tB\x10\xbaH\r\x92\x01\n" + - "\x10 \"\x06r\x04\x10\x01\x18!R\x0efileExtensions\"k\n" + + "\x10 \"\x06r\x04\x10\x01\x18!R\x0efileExtensions\x12:\n" + + "\x10include_patterns\x18\x04 \x03(\tB\x0f\xbaH\f\x92\x01\t\x10\x10\"\x05r\x03\x18\x80\x02R\x0fincludePatterns\x12:\n" + + "\x10exclude_patterns\x18\x05 \x03(\tB\x0f\xbaH\f\x92\x01\t\x10\x10\"\x05r\x03\x18\x80\x02R\x0fexcludePatterns\"k\n" + "\x14UpdateSourceResponse\x12<\n" + "\n" + "datasource\x18\x01 \x01(\v2\x1c.saas.accounts.v1.DatasourceR\n" + From 140f69ed51a61861b409b11cc06e0c5d77133c7d Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:48:28 +0000 Subject: [PATCH 15/18] chore(sdk): track shared installation-compatible host source --- SOURCE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOURCE.txt b/SOURCE.txt index f63ad96..2802655 100644 --- a/SOURCE.txt +++ b/SOURCE.txt @@ -1,4 +1,4 @@ -gen/ generated from codefly-dev/module-saas-starter@13cf75310dc52e39954ad44da1d9a8d08458ff72 +gen/ generated from codefly-dev/module-saas-starter@bb66a84cfff053bcccf3f0842bc6ffa6b2e0d159 (accounts public proto: module/services/accounts/proto) (auth gateway: module/services/auth-gateway/code) From ccb34ed0cf8fac4b5c57d0b74cc0f89afed69613 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:38:18 +0000 Subject: [PATCH 16/18] docs(sdk): require a qualified journal credential producer --- CHANGELOG.md | 6 ++++-- README.md | 11 ++++++----- moduleauthority/journal_reader.go | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b92970..610d35b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ Authority client; its declared Core dependency sets the development floor. ### Changed -- Add `moduleauthority.NewJournalReader` for callers whose capabilities come - from shared Authority. It reuses the existing Accounts transport and journal +- Add `moduleauthority.NewJournalReader` for a caller-supplied host-valid + module capability source. Its issuer must be qualified separately: generic + shared Authority Issue does not mint an unbound module identity. The reader + reuses the existing Accounts transport and journal decoder with a fresh sealed Work Context source, without gateway broker credentials or token caching. Both journal facades reuse the host protobuf request constraints before obtaining credentials. diff --git a/README.md b/README.md index 1b4fd41..5a56905 100644 --- a/README.md +++ b/README.md @@ -745,16 +745,17 @@ denial. The SDK does not automatically retry broker failures. Malformed initial HTTP headers provide no usable status and produce a transport error; malformed bodies cannot change a successfully parsed non-200 status into a transport failure. -### Journal reads with shared Authority +### Journal reads with a supplied module credential `moduleauthority.NewJournalReader` accepts a `JournalReaderConfig` with the resolved Accounts `Authority` endpoint and a `WorkContext(context.Context)` -source. The source requests a sealed module capability from shared Authority -for audience `module-capabilities`; the workload credential goes only to -Authority. Each read calls the source afresh, uses the existing Accounts +source. The source must return a host-valid sealed module capability for +audience `module-capabilities`. Generic shared Authority `Issue` currently +requires an operation binding and cannot mint that unbound identity. The issuer +must be composed and qualified separately. Each read calls the source afresh, uses the existing Accounts HTTP/2 gRPC transport and journal decoder, and carries no legacy internal token. The caller's context bounds both issuance and the read. Call `Close` at shutdown. | Build or reuse | Searched and verified | Result | | --- | --- | --- | -| Journal reads with externally issued authority | Existing journal facade, `authorityEndpoint`, Core sealed-capability reader and protobuf request validation | Extend the published SDK with a journal-only credential source. Reuse transport and decoding without a gateway broker, copied descriptors, credential cache or another publication repository. | +| Journal reads with externally issued authority | Existing journal facade, `authorityEndpoint`, Core sealed-capability reader and protobuf request validation | Extend the published SDK with a journal-only credential source. Reuse transport and decoding; the credential producer remains an unresolved integration prerequisite. No new issuer, copied descriptors, credential cache or publication repository. | diff --git a/moduleauthority/journal_reader.go b/moduleauthority/journal_reader.go index 7287970..f2f6b7a 100644 --- a/moduleauthority/journal_reader.go +++ b/moduleauthority/journal_reader.go @@ -10,8 +10,9 @@ import ( ) // JournalReaderConfig supplies Accounts' transport and a source of module Work -// Contexts. WorkContext typically calls shared Authority Issue for audience -// module-capabilities. The workload credential stays with that authority. +// Contexts valid for the host module-capabilities audience. The caller must +// compose a compatible issuer; generic Authority Issue does not mint this +// unbound identity. The reader receives no workload credential. type JournalReaderConfig struct { Authority Authority AllowInsecureHTTP bool From 4bbea05990efa9a9d8c777830711cb10706dcb89 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:12:58 +0000 Subject: [PATCH 17/18] refactor(sdk): reuse the existing journal client --- CHANGELOG.md | 13 ++- README.md | 21 +++-- moduleauthority/journal.go | 14 +-- moduleauthority/journal_reader.go | 71 -------------- moduleauthority/journal_reader_test.go | 122 ------------------------- moduleauthority/journal_test.go | 11 +++ 6 files changed, 35 insertions(+), 217 deletions(-) delete mode 100644 moduleauthority/journal_reader.go delete mode 100644 moduleauthority/journal_reader_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 610d35b..b013c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,12 @@ Authority client; its declared Core dependency sets the development floor. ### Changed -- Add `moduleauthority.NewJournalReader` for a caller-supplied host-valid - module capability source. Its issuer must be qualified separately: generic - shared Authority Issue does not mint an unbound module identity. The reader - reuses the existing Accounts transport and journal - decoder with a fresh sealed Work Context source, without gateway broker - credentials or token caching. Both journal facades reuse the host protobuf - request constraints before obtaining credentials. +- Reuse the host protobuf request constraints in `Client.ReadJournal` before + obtaining credentials; reject invalid supplied timestamps. Keep the existing + broker, module-credential cache, refresh-once behavior and Accounts transport. + No second reader factory or credential-producer API is added. Generic shared + Authority Issue does not mint the required unbound module identity; consuming + modules must qualify their producer route separately. - Refresh development provenance to the rebased module-saas-starter journal source recorded in `SOURCE.txt`, with module package version 0.2.0. The full diff --git a/README.md b/README.md index 5a56905..19bcc83 100644 --- a/README.md +++ b/README.md @@ -745,17 +745,18 @@ denial. The SDK does not automatically retry broker failures. Malformed initial HTTP headers provide no usable status and produce a transport error; malformed bodies cannot change a successfully parsed non-200 status into a transport failure. -### Journal reads with a supplied module credential +### Journal client reuse -`moduleauthority.NewJournalReader` accepts a `JournalReaderConfig` with the -resolved Accounts `Authority` endpoint and a `WorkContext(context.Context)` -source. The source must return a host-valid sealed module capability for -audience `module-capabilities`. Generic shared Authority `Issue` currently -requires an operation binding and cannot mint that unbound identity. The issuer -must be composed and qualified separately. Each read calls the source afresh, uses the existing Accounts -HTTP/2 gRPC transport and journal decoder, and carries no legacy internal token. -The caller's context bounds both issuance and the read. Call `Close` at shutdown. +`moduleauthority.Client.ReadJournal` reuses this client's broker, module-credential +cache and refresh-once behavior, existing Accounts HTTP/2 transport, and journal +projection. The host protobuf constraints validate requests before credentials +are obtained. Invalid nonempty timestamps are refused before a page is returned. +No additional reader factory or credential-producer API is added. + +Generic shared Authority `Issue` requires an operation binding and cannot mint +an unbound `module-capabilities` identity. A consumer must qualify its credential +route; transport tests alone do not establish producer compatibility. | Build or reuse | Searched and verified | Result | | --- | --- | --- | -| Journal reads with externally issued authority | Existing journal facade, `authorityEndpoint`, Core sealed-capability reader and protobuf request validation | Extend the published SDK with a journal-only credential source. Reuse transport and decoding; the credential producer remains an unresolved integration prerequisite. No new issuer, copied descriptors, credential cache or publication repository. | +| Journal client | Existing Client module credentials, cache, callAsModule transport, protobuf request constraints and journal tests | Extend Client.ReadJournal. Remove the unqualified second reader factory; reuse the existing broker, cache and decoder instead of adding a credential API. | diff --git a/moduleauthority/journal.go b/moduleauthority/journal.go index 3d05ae1..569b4a6 100644 --- a/moduleauthority/journal.go +++ b/moduleauthority/journal.go @@ -79,24 +79,24 @@ type JournalPage struct { // from an earlier NextAfter is served the same entries again. A rejected module // capability is refreshed and retried once, like every authority call. func (c *Client) ReadJournal(ctx context.Context, after string, limit int32, types, deliveryBindings []string) (JournalPage, error) { - return readJournal(ctx, after, limit, types, deliveryBindings, func(ctx context.Context, request *v1.ModuleReadJournalRequest) (*v1.ModuleReadJournalResponse, error) { - return callAsModule(ctx, c, accountsv1connect.ModuleCapabilitiesServiceClient.ReadJournal, request) - }) -} - -func readJournal(ctx context.Context, after string, limit int32, types, deliveryBindings []string, read func(context.Context, *v1.ModuleReadJournalRequest) (*v1.ModuleReadJournalResponse, error)) (JournalPage, error) { request := &v1.ModuleReadJournalRequest{ After: after, Limit: limit, Types: append([]string(nil), types...), DeliveryBindings: append([]string(nil), deliveryBindings...), } if limit > MaxJournalPage || protovalidate.Validate(request) != nil { return JournalPage{}, ErrInvalidJournalRead } - res, err := read(ctx, request) + res, err := callAsModule(ctx, c, accountsv1connect.ModuleCapabilitiesServiceClient.ReadJournal, request) if err != nil { return JournalPage{}, fmt.Errorf("module authority: read journal: %w", err) } + if res == nil { + return JournalPage{}, ErrInvalidJournalPage + } page := JournalPage{NextAfter: res.GetNextAfter()} for _, e := range res.GetEntries() { + if e == nil || (e.GetOccurredAt() != nil && e.GetOccurredAt().CheckValid() != nil) { + return JournalPage{}, ErrInvalidJournalPage + } entry := JournalEntry{ Position: e.GetPosition(), EventID: e.GetEventId(), Type: e.GetType(), OrganizationID: e.GetOrganizationId(), Data: e.GetData().AsMap(), diff --git a/moduleauthority/journal_reader.go b/moduleauthority/journal_reader.go deleted file mode 100644 index f2f6b7a..0000000 --- a/moduleauthority/journal_reader.go +++ /dev/null @@ -1,71 +0,0 @@ -package moduleauthority - -import ( - "context" - "fmt" - - "connectrpc.com/connect" - "github.com/codefly-dev/core/wool" - v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" -) - -// JournalReaderConfig supplies Accounts' transport and a source of module Work -// Contexts valid for the host module-capabilities audience. The caller must -// compose a compatible issuer; generic Authority Issue does not mint this -// unbound identity. The reader receives no workload credential. -type JournalReaderConfig struct { - Authority Authority - AllowInsecureHTTP bool - WorkContext func(context.Context) (string, error) -} - -// JournalReader uses the existing Accounts transport and journal decoder with -// caller-supplied authority. It never contacts the legacy gateway broker or -// caches a capability. The caller bounds the context of issuance and the read. -type JournalReader struct { - authority authorityEndpoint - context func(context.Context) (string, error) -} - -func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) { - if config.WorkContext == nil { - return nil, fmt.Errorf("%w: a journal module Work Context source is required", ErrInvalidSeams) - } - endpoint, err := newAuthorityEndpoint(Seams{Authority: config.Authority, AllowInsecureHTTP: config.AllowInsecureHTTP}) - if err != nil { - return nil, err - } - return &JournalReader{authority: endpoint, context: config.WorkContext}, nil -} - -// Close releases the existing transport's idle connections. -func (r *JournalReader) Close() error { r.authority.close(); return nil } - -// ReadJournal validates through the same facade as Client.ReadJournal, then -// obtains one fresh module capability and makes one bounded Accounts read. -func (r *JournalReader) ReadJournal(ctx context.Context, after string, limit int32, types, bindings []string) (JournalPage, error) { - return readJournal(ctx, after, limit, types, bindings, func(ctx context.Context, request *v1.ModuleReadJournalRequest) (*v1.ModuleReadJournalResponse, error) { - token, err := r.context(ctx) - if err != nil { - return nil, err - } - if !sealed(token) { - return nil, ErrInvalidCapability - } - req := connect.NewRequest(request) - req.Header().Set(wool.WorkContextHeader, token) - response, err := r.authority.capabilities.ReadJournal(ctx, req) - if err != nil { - return nil, err - } - if response == nil || response.Msg == nil { - return nil, ErrInvalidJournalPage - } - for _, entry := range response.Msg.GetEntries() { - if entry == nil || entry.GetOccurredAt().CheckValid() != nil { - return nil, ErrInvalidJournalPage - } - } - return response.Msg, nil - }) -} diff --git a/moduleauthority/journal_reader_test.go b/moduleauthority/journal_reader_test.go deleted file mode 100644 index 4f4c8d1..0000000 --- a/moduleauthority/journal_reader_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package moduleauthority - -import ( - "context" - "errors" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "sync/atomic" - "testing" - "time" - - "connectrpc.com/connect" - v1 "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1" - rpc "github.com/codefly-dev/saas-sdk-go/gen/saas/accounts/v1/accountsv1connect" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type sourceJournal struct { - rpc.UnimplementedModuleCapabilitiesServiceHandler - read func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) -} - -func (s sourceJournal) ReadJournal(ctx context.Context, r *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { - return s.read(ctx, r) -} - -func journalFromSource(t *testing.T, source func(context.Context) (string, error), read func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error)) *JournalReader { - t.Helper() - _, handler := rpc.NewModuleCapabilitiesServiceHandler(sourceJournal{read: read}) - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.ProtoMajor != 2 || !strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { - t.Error("journal did not use native HTTP/2 gRPC") - } - handler.ServeHTTP(w, r) - })) - server.Config.Protocols = new(http.Protocols) - server.Config.Protocols.SetUnencryptedHTTP2(true) - server.Start() - t.Cleanup(server.Close) - endpoint, _ := url.Parse(server.URL) - reader, err := NewJournalReader(JournalReaderConfig{Authority: Authority{Address: endpoint.Host}, WorkContext: source}) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { _ = reader.Close() }) - return reader -} - -func TestJournalReaderReusesTheTransportAndObtainsFreshAuthority(t *testing.T) { - issued := 0 - var calls atomic.Int32 - reader := journalFromSource(t, func(context.Context) (string, error) { - issued++ - return sealedToken("source-module"), nil - }, func(_ context.Context, request *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { - calls.Add(1) - if request.Header().Get("X-Codefly-Work-Context") != sealedToken("source-module") || request.Header().Get("X-Codefly-Internal-Token") != "" || request.Msg.GetAfter() != JournalStart { - t.Error("journal used a legacy credential or changed the request") - } - return connect.NewResponse(&v1.ModuleReadJournalResponse{NextAfter: "p1", Entries: []*v1.ModuleJournalEntry{{Position: "p1", EventId: "event", Type: journalType, OccurredAt: timestamppb.New(journalEpoch), DeliveryMachinery: true}}}), nil - }) - for range 2 { - page, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil) - if err != nil || page.NextAfter != "p1" || len(page.Entries) != 1 || !page.Entries[0].DeliveryMachinery || !page.Entries[0].OccurredAt.Equal(journalEpoch) { - t.Fatalf("journal projection: %v %v", page, err) - } - } - if issued != 2 || calls.Load() != 2 { - t.Fatal("journal reused authority or duplicated a read") - } - if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, nil, nil); !errors.Is(err, ErrInvalidJournalRead) || issued != 2 { - t.Fatal("invalid request obtained authority") - } -} - -func TestJournalReaderSourceFailuresAndMalformedPages(t *testing.T) { - for _, token := range []string{"", "not-sealed"} { - reader := journalFromSource(t, func(context.Context) (string, error) { return token, nil }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { - t.Fatal("invalid capability reached Accounts") - return nil, nil - }) - if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil); !errors.Is(err, ErrInvalidCapability) { - t.Fatalf("invalid source token: %v", err) - } - } - refusal := errors.New("issuer refused") - reader := journalFromSource(t, func(context.Context) (string, error) { return "", refusal }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { - t.Fatal("source refusal reached Accounts") - return nil, nil - }) - if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil); !errors.Is(err, refusal) { - t.Fatalf("source refusal lost: %v", err) - } - reader = journalFromSource(t, func(context.Context) (string, error) { return sealedToken("source-module"), nil }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { - return connect.NewResponse(&v1.ModuleReadJournalResponse{Entries: []*v1.ModuleJournalEntry{{Position: "p1"}}}), nil - }) - if _, err := reader.ReadJournal(t.Context(), JournalStart, 100, []string{journalType}, nil); !errors.Is(err, ErrInvalidJournalPage) { - t.Fatalf("malformed response accepted: %v", err) - } -} - -func TestJournalReaderSourceReceivesTheReadDeadline(t *testing.T) { - reader := journalFromSource(t, func(ctx context.Context) (string, error) { <-ctx.Done(); return "", ctx.Err() }, func(context.Context, *connect.Request[v1.ModuleReadJournalRequest]) (*connect.Response[v1.ModuleReadJournalResponse], error) { - t.Fatal("expired source reached Accounts") - return nil, nil - }) - ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) - defer cancel() - if _, err := reader.ReadJournal(ctx, JournalStart, 100, []string{journalType}, nil); !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("read did not bound source issuance: %v", err) - } -} - -func TestJournalReaderRequiresAnExplicitAuthorityAndSource(t *testing.T) { - for _, config := range []JournalReaderConfig{{}, {WorkContext: func(context.Context) (string, error) { return "", nil }}} { - if reader, err := NewJournalReader(config); !errors.Is(err, ErrInvalidSeams) || reader != nil { - t.Fatalf("incomplete journal configuration accepted: %v %v", reader, err) - } - } -} diff --git a/moduleauthority/journal_test.go b/moduleauthority/journal_test.go index 26b26ad..3bc98a2 100644 --- a/moduleauthority/journal_test.go +++ b/moduleauthority/journal_test.go @@ -228,6 +228,17 @@ func TestReadJournalAnEntryWithoutATimestampOrDataDecodes(t *testing.T) { } } +func TestReadJournalRefusesInvalidTimestamps(t *testing.T) { + for _, stamp := range []*timestamppb.Timestamp{{Seconds: 253402300800}, {Nanos: 1000000000}} { + h := newHost(t) + client := h.client(t, moduleCredentials) + h.authority.commit(&v1.ModuleJournalEntry{Position: "p7", EventId: "event-7", Type: journalType, OccurredAt: stamp}) + if _, err := client.ReadJournal(t.Context(), "", 1, []string{journalType}, nil); !errors.Is(err, ErrInvalidJournalPage) { + t.Fatalf("invalid timestamp accepted: %v", err) + } + } +} + // A refusal keeps the host's own code, like every other authority call: a // caller that is not a cross-tenant module principal is PermissionDenied, and // nothing is returned that could be mistaken for an empty journal. From f73008be0b8703dc62f9f70e600b44419dd9e411 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:59:26 +0000 Subject: [PATCH 18/18] ci(sdk): qualify source branch pushes through the existing gates --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ceb0439..92334a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,8 @@ name: ci on: push: - branches: [main] pull_request: + workflow_dispatch: permissions: contents: read